[00:00] (0.24s)
In this course, we are going to build a
[00:02] (2.48s)
dental platform which has an appointment
[00:05] (5.44s)
booking system, a complete admin
[00:07] (7.92s)
dashboard, a modern looking landing
[00:10] (10.48s)
page, and a voice agent that can talk to
[00:13] (13.68s)
your users and provide information in
[00:16] (16.64s)
real time. On top of all these, we have
[00:19] (19.28s)
features like monthly subscriptions and
[00:22] (22.08s)
sending emails. Just before we jump into
[00:24] (24.64s)
coding, let me show you the end result
[00:26] (26.80s)
so that you know what we are building in
[00:28] (28.80s)
this course. So this is a platform
[00:31] (31.04s)
called Dentwise which allows users to
[00:33] (33.84s)
book appointments and get access to a
[00:36] (36.64s)
voice AI agent in one place. We will
[00:39] (39.68s)
start by building a modern looking
[00:42] (42.00s)
landing page that has beautiful
[00:44] (44.08s)
backgrounds, gradients and really nice
[00:46] (46.72s)
looking images. Then we will implement
[00:49] (49.04s)
authentication where users can sign up
[00:51] (51.92s)
with Google, GitHub or email and
[00:54] (54.80s)
password combination. If they use email
[00:57] (57.60s)
and password, we will send them a
[00:59] (59.76s)
six-digit verification code and they
[01:02] (62.32s)
will be authenticated. Once they are
[01:04] (64.16s)
authenticated, they will be able to book
[01:06] (66.64s)
appointments under this page. And there
[01:09] (69.28s)
are three steps to complete this
[01:11] (71.36s)
process. First, you would choose your
[01:13] (73.92s)
dentist. Second, you would select the
[01:16] (76.48s)
service with a date and time. And you're
[01:19] (79.28s)
going to see if a time slot is already
[01:21] (81.60s)
booked or not. And finally, you can
[01:24] (84.24s)
confirm the booking. We also have the
[01:26] (86.64s)
option to go back and modify our
[01:29] (89.36s)
booking. So once you confirm, the
[01:31] (91.60s)
appointment will be saved to the
[01:33] (93.36s)
database and you're going to get an
[01:35] (95.44s)
email in your inbox with the details.
[01:38] (98.40s)
And under the dashboard page, you will
[01:40] (100.56s)
be able to see your booking under this
[01:42] (102.96s)
upcoming appointments section. Now, the
[01:45] (105.60s)
most exciting feature is talking with
[01:48] (108.16s)
the AI voice agent. But to be able to
[01:51] (111.12s)
unlock it, you have to be using one of
[01:53] (113.60s)
our paid plans which can be found under
[01:56] (116.48s)
the pro page. We have one free plan and
[01:59] (119.76s)
two paid plans. Once you subscribe to
[02:02] (122.40s)
one of the paid plans, you will get the
[02:04] (124.64s)
invoice in your inbox and you will
[02:07] (127.20s)
access to the voice agent which can
[02:09] (129.60s)
provide information about the services
[02:11] (131.84s)
we have and you can talk with this
[02:13] (133.68s)
assistant about anything related to
[02:16] (136.16s)
dental health. Let's go ahead and start
[02:18] (138.40s)
a call just to see how it sounds. I'll
[02:21] (141.28s)
start the call. Let's give access to the
[02:25] (145.04s)
microphone.
[02:26] (146.80s)
>> Hi there, this is Riley, your dental
[02:29] (149.12s)
assistant from Dentwise. I'm here to
[02:31] (151.44s)
help you with all your dental needs. I
[02:33] (153.52s)
can provide information about our
[02:34] (154.88s)
service prices, give you immediate tips
[02:37] (157.28s)
for dental pain or concerns, help you
[02:39] (159.52s)
understand different treatment options,
[02:42] (162.16s)
and share oral health prevention advice.
[02:45] (165.12s)
What can I help you with today?
[02:47] (167.60s)
Oh, yeah. Can you please provide me the
[02:49] (169.92s)
services and fees that you have?
[02:53] (173.36s)
>> Of course. I'd be happy to explain our
[02:54] (174.96s)
service pricing. Here's what we offer.
[02:57] (177.20s)
Regular dental checkup, $120.
[03:00] (180.32s)
>> So, as you can tell, it sounds super
[03:02] (182.56s)
realistic and natural. Now, once you
[03:04] (184.96s)
complete this course, you can take this
[03:07] (187.04s)
voice agent to the next level by adding
[03:09] (189.60s)
some advanced workflows, which is
[03:11] (191.76s)
something that we will talk about later
[03:13] (193.52s)
in the video. When it comes to the
[03:15] (195.12s)
payments, it is optimized following the
[03:17] (197.60s)
best practices. To give you an example,
[03:20] (200.16s)
let's say you subscribe to this $9 a
[03:22] (202.88s)
month paid plan. And now you want to
[03:25] (205.12s)
upgrade to the other plan that we have.
[03:27] (207.36s)
In this case, you only need to pay the
[03:29] (209.52s)
difference, which is $10. So these are
[03:32] (212.32s)
the details that your users will thank
[03:34] (214.64s)
you for. And you will learn how to
[03:36] (216.64s)
implement all of these in this one
[03:38] (218.80s)
single tutorial. You will also learn the
[03:40] (220.96s)
best practices with Git and GitHub. This
[03:44] (224.00s)
includes things like creating a branch,
[03:46] (226.88s)
committing our changes, creating a pull
[03:49] (229.36s)
request, and merging the changes, which
[03:52] (232.00s)
is the workflow that you would like to
[03:53] (233.84s)
follow when working with teammates. When
[03:56] (236.48s)
it comes to the technologies, we will be
[03:58] (238.48s)
using Nex.js with the app router, which
[04:01] (241.44s)
includes the latest features like server
[04:04] (244.00s)
components, server actions, API routes,
[04:06] (246.96s)
and so on and so forth. We will use
[04:09] (249.04s)
tailwind CSS and chatsen for styling.
[04:12] (252.16s)
Tenstack query for data fetching. Clerk
[04:15] (255.04s)
for authentication and payments.
[04:17] (257.36s)
Postgress for our database. Recent for
[04:20] (260.64s)
sending emails. WBY for the AI voice
[04:23] (263.52s)
agents and code rabbit for the code
[04:26] (266.24s)
optimizations in our pull requests. So
[04:29] (269.12s)
that's the entire project. As usual, we
[04:31] (271.60s)
are going to deploy it at the end so
[04:33] (273.68s)
that you can put that live link on your
[04:36] (276.00s)
resume. The deployment platform we are
[04:38] (278.40s)
going to be using is going to be Savala,
[04:40] (280.96s)
which is super easy to set up and
[04:43] (283.20s)
developer friendly. We will get into the
[04:45] (285.36s)
details later in the video. With all
[04:47] (287.28s)
that being said, if you're ready, let's
[04:49] (289.28s)
get started. The source code of this
[04:51] (291.84s)
project will be free in the description.
[04:54] (294.00s)
Give it a star and feel free to use it
[04:56] (296.16s)
however you wish. One last quick
[04:57] (297.92s)
announcement. I have a premium Udemy
[05:00] (300.24s)
course with more than 120 hours. We are
[05:03] (303.60s)
building all kinds of projects from
[05:05] (305.52s)
React to Nex.js to Python and Go. If
[05:08] (308.88s)
you're interested, there are over 5,000
[05:11] (311.20s)
students and everyone enjoys it so far.
[05:13] (313.84s)
You can find the discount link in the
[05:15] (315.60s)
description. Thanks for watching and
[05:17] (317.28s)
let's get started.
[05:19] (319.28s)
So, to get started with I have created
[05:22] (322.16s)
an empty folder on my desktop called
[05:24] (324.96s)
Dentwise. This is the name that I came
[05:27] (327.20s)
up with. You can call this anything that
[05:29] (329.12s)
you wish. Just go ahead and open up this
[05:31] (331.52s)
folder in VS Code. Now we would like to
[05:34] (334.24s)
initialize a NexJS application. I will
[05:37] (337.20s)
open up my terminal. The shortcut is
[05:40] (340.16s)
command J or control J. Okay. Now let's
[05:43] (343.52s)
say MPX create next app. And normally I
[05:48] (348.80s)
would say something like add latest. But
[05:51] (351.76s)
in this case, let's go with a specific
[05:53] (353.76s)
version so that if you're watching this
[05:55] (355.84s)
video in the future, it should still
[05:57] (357.92s)
work out as expected. Okay, now this is
[06:00] (360.96s)
the version that we'll be using and we
[06:02] (362.88s)
would like to initialize this NexJS
[06:04] (364.80s)
application under the current folder,
[06:07] (367.28s)
right? So we'll put dot at the end. And
[06:10] (370.08s)
then this will ask us couple of
[06:11] (371.60s)
different questions where we would like
[06:13] (373.60s)
to use TypeScript and for the llinter
[06:16] (376.56s)
I'll go with biome but you can use
[06:18] (378.88s)
ESLint as well. I would say just select
[06:22] (382.16s)
what I do so that we can have the exact
[06:24] (384.48s)
same codebase.
[06:26] (386.32s)
Then let's uh let's say we would like to
[06:28] (388.24s)
use tailwind source directory app router
[06:31] (391.44s)
turbo pack and we don't really want to
[06:33] (393.84s)
customize the default import alias which
[06:36] (396.80s)
is add and if you don't know what that
[06:38] (398.96s)
is I can explain this later in the video
[06:41] (401.36s)
just say no for now and then this will
[06:44] (404.24s)
install all the packages and create all
[06:47] (407.20s)
the files and folders that we would
[06:49] (409.04s)
need. Now, while this is installing,
[06:51] (411.84s)
let's see what we're going to be using
[06:53] (413.52s)
next, which is going to be shaden. So,
[06:56] (416.48s)
this will give us all the components
[06:58] (418.40s)
that we are going to need. So, let's say
[07:00] (420.56s)
if you want to use a button, we don't
[07:02] (422.56s)
really need to create this from scratch.
[07:04] (424.80s)
We will just paste this code to our
[07:06] (426.64s)
terminal and we're going to get this
[07:08] (428.40s)
button. And this is how easy it is to
[07:11] (431.44s)
use it. But first, let's say go under
[07:13] (433.92s)
the documentation. We would like to set
[07:16] (436.80s)
this up in a Nex.js JS application and
[07:20] (440.16s)
we would like to first copy this
[07:21] (441.76s)
command.
[07:23] (443.76s)
Now, if you have never used Nex.js, I
[07:26] (446.16s)
think that's completely fine. In this
[07:27] (447.84s)
video, I'll try to walk you through the
[07:30] (450.40s)
basics. If you just have a basic
[07:33] (453.20s)
understanding of React, I think that
[07:35] (455.04s)
should be fine. And here I have a
[07:37] (457.68s)
warning that says there is another
[07:40] (460.56s)
latest version of Nex.js, but that's
[07:43] (463.04s)
completely fine. We can skip this. Let's
[07:45] (465.52s)
say clear. And I don't know why my
[07:48] (468.64s)
terminal looks a little bit weird, but
[07:50] (470.48s)
I'll just paste this in. MPX chat at
[07:54] (474.08s)
latest and init.
[07:57] (477.68s)
Okay. So, let's just use everything as
[07:59] (479.68s)
default. I'll press enter. And this will
[08:03] (483.04s)
do a couple of different things. So,
[08:05] (485.04s)
here it says updating CSS variables
[08:08] (488.00s)
under this file. Installing a
[08:10] (490.48s)
dependency. And then it created this
[08:13] (493.84s)
utils.ts. ts file.
[08:16] (496.56s)
So we have this file where we have the
[08:19] (499.84s)
popular function CN coming from shad CN.
[08:23] (503.28s)
Um you don't really need to think about
[08:24] (504.80s)
it too much. Basically shaden will be
[08:27] (507.12s)
using it under the hood and then under
[08:29] (509.44s)
the app it updated these CSS variables.
[08:34] (514.24s)
Okay. Now let's say we would like to use
[08:36] (516.56s)
a button.
[08:38] (518.64s)
We would basically copy this and paste
[08:40] (520.80s)
it. But later in the video, we'll be
[08:43] (523.44s)
using most of these components. So, we
[08:45] (525.92s)
don't really want to type this out every
[08:48] (528.24s)
single time and say, you know, a
[08:50] (530.24s)
different components like alerts or, I
[08:52] (532.80s)
don't know, like table. So, instead, I
[08:55] (535.04s)
would like to install all of them, all
[08:57] (537.28s)
of these at once. So, how can we make
[08:59] (539.92s)
that work? Well, just open up your
[09:02] (542.16s)
terminal and instead of saying add
[09:04] (544.56s)
button, just say add. Okay? Don't say
[09:07] (547.04s)
anything else. It is going to show you
[09:09] (549.28s)
the entire list. And here it says if you
[09:12] (552.16s)
want to select all of them just press A.
[09:14] (554.80s)
I'll press A and I'll say enter to
[09:17] (557.52s)
submit. So it's going to install all
[09:20] (560.16s)
these components
[09:23] (563.28s)
and once it is done we should have a
[09:26] (566.32s)
components folder under the source.
[09:29] (569.52s)
Let's see now it is generating it I
[09:32] (572.72s)
believe.
[09:36] (576.48s)
Okay. So under the source we got the
[09:39] (579.84s)
components the UI folder and we have all
[09:43] (583.20s)
these files. So if you want to have a
[09:45] (585.92s)
card component this is the code this has
[09:48] (588.32s)
been written. Now don't get me wrong you
[09:50] (590.48s)
don't need to update this code at all.
[09:52] (592.48s)
If you just want to use the card
[09:54] (594.16s)
component all you have to do let's go
[09:56] (596.96s)
into the homepage and let's see how we
[09:59] (599.12s)
can use it. So under the app under the
[10:02] (602.56s)
page which is our homepage I'll just
[10:05] (605.04s)
delete everything and let's say
[10:07] (607.68s)
something like maybe just a button right
[10:12] (612.24s)
we are going to import it from the
[10:14] (614.08s)
components.
[10:17] (617.04s)
Let's close this off and let's say click
[10:19] (619.20s)
me. So in this file there is a lot of
[10:22] (622.24s)
code right? So it has button variants,
[10:24] (624.88s)
the button component and the export. But
[10:28] (628.24s)
you don't really need to think about
[10:29] (629.60s)
this at all. All you have to do just
[10:32] (632.08s)
import the component and use it. Now
[10:35] (635.04s)
let's run our application. Let's say
[10:37] (637.36s)
clear the terminal and mpm run that.
[10:43] (643.28s)
Let's visit localhost 3000.
[10:48] (648.64s)
Okay. So this is the button that we have
[10:51] (651.28s)
and we didn't really need to write any
[10:53] (653.44s)
code at all. We just installed it from
[10:55] (655.36s)
the chaten. Okay. So we'll be using
[10:58] (658.72s)
these other components a lot later in
[11:00] (660.96s)
the video. For now let's just try to set
[11:03] (663.20s)
up different things. Right. So in our
[11:05] (665.76s)
application we are going to have this
[11:07] (667.60s)
beautiful purple theme. And I have
[11:10] (670.48s)
grabbed this theme from a website called
[11:13] (673.52s)
tweak CN. Let's type this out. So this
[11:17] (677.92s)
is built for chaten. Basically they have
[11:21] (681.04s)
all kinds of uh let's say themes. I'll
[11:23] (683.92s)
say start customizing.
[11:26] (686.08s)
And from here this is the default. And
[11:28] (688.40s)
you can select any of them really. The
[11:31] (691.28s)
one that I selected I believe it was the
[11:34] (694.08s)
dark matter. And if you want to use this
[11:37] (697.04s)
theme you would say give me the code.
[11:40] (700.80s)
And here I think I'll just use the hex
[11:43] (703.44s)
values. And I'll just say copy. Now we
[11:46] (706.96s)
would like to uh we would like to update
[11:49] (709.52s)
the index.css file. So they have this
[11:52] (712.64s)
root object.
[11:54] (714.72s)
Let's scroll. They have dark and they
[11:57] (717.76s)
have theme inline. Right? And at the
[12:01] (721.12s)
very end they also have body. So we have
[12:03] (723.60s)
copied all of them. We will go under the
[12:06] (726.72s)
global CSS. Let's shrink this which is
[12:10] (730.08s)
the theme in line. Let's shrink root and
[12:13] (733.12s)
dark. Okay. So we can delete all of them
[12:16] (736.24s)
because we're just going to paste it in,
[12:18] (738.00s)
right? You can grab this from the source
[12:20] (740.32s)
code as well. Okay, just save. Now we
[12:23] (743.28s)
should be able to have this purple theme
[12:26] (746.48s)
that works out of the box. So this is
[12:28] (748.88s)
how cool it is. And if you want to
[12:31] (751.04s)
change the theme, you can do it at the
[12:33] (753.20s)
end of this video. Just select any of
[12:35] (755.36s)
them. I think this one looks pretty
[12:37] (757.28s)
cool. You would just copy the code and
[12:39] (759.76s)
do exactly what we have done and you
[12:42] (762.64s)
would have this theme. All right. So
[12:44] (764.72s)
this is how we can tweak our chat theme.
[12:47] (767.84s)
Right now let's go ahead and update the
[12:50] (770.40s)
title of this application. And for this
[12:53] (773.68s)
we can visit the source folder under the
[12:56] (776.72s)
layout. Here we can see we have the
[12:59] (779.52s)
entry point right which is our root
[13:02] (782.16s)
layout. So this is where we return our
[13:05] (785.04s)
actual application right. So we have the
[13:07] (787.84s)
children which is basically the which is
[13:10] (790.96s)
basically our application. Right? So
[13:13] (793.36s)
here let's update the meta data. This is
[13:16] (796.24s)
the title of our application of our web
[13:19] (799.20s)
app. So here I'll say dance wise. This
[13:22] (802.32s)
is the name that we're going to be
[13:23] (803.68s)
using. Let's say AI powered
[13:27] (807.92s)
dental assistant.
[13:31] (811.68s)
And then for the description, let's put
[13:34] (814.24s)
something like get
[13:37] (817.44s)
instant dental advice
[13:41] (821.68s)
through voice calls
[13:48] (828.56s)
with our AI assistant.
[13:53] (833.52s)
And I'll just say available.
[13:57] (837.52s)
How do we type this? Let's say
[13:59] (839.12s)
available,
[14:00] (840.64s)
I believe. Is this correct? I hope so.
[14:04] (844.40s)
Um, here I'll just say 247.
[14:09] (849.68s)
Okay. So, we can just update them like
[14:12] (852.00s)
customize it. But this is what I'll
[14:13] (853.60s)
have. This is our title, the
[14:15] (855.52s)
description, and we're just going to
[14:17] (857.28s)
leave everything as it is. This file
[14:19] (859.60s)
might look pretty uh pretty basic at
[14:21] (861.60s)
this point. We are basically getting
[14:23] (863.36s)
some fonts. This is what Nex.js does by
[14:26] (866.48s)
default. Um, and to be able to use
[14:29] (869.44s)
these, you would just say under the body
[14:32] (872.24s)
class name, you know, the variable,
[14:34] (874.88s)
whatever that is, dot variable. And
[14:37] (877.84s)
we're using both of these.
[14:40] (880.56s)
Okay. So, that's our layout set up. And
[14:44] (884.32s)
let's check it out. Now, we have this
[14:46] (886.08s)
title as well as the description. You
[14:48] (888.64s)
can see it under the page source. Let's
[14:52] (892.16s)
just say
[14:54] (894.64s)
description.
[14:57] (897.04s)
I'll just say Ctrl F. Okay, here we can
[15:00] (900.32s)
see we got this content as our
[15:02] (902.40s)
description. So that means it is
[15:04] (904.64s)
actually working. Now you might be
[15:06] (906.56s)
wondering how do we create different
[15:08] (908.72s)
pages. So by default this is the
[15:11] (911.28s)
homepage because it is directly under
[15:13] (913.60s)
the app folder, right? This is a special
[15:16] (916.16s)
folder in NexJS. Let me try to create an
[15:19] (919.76s)
about page. So if you want to create an
[15:22] (922.32s)
about page, you would first create
[15:24] (924.40s)
folder and then you would put this
[15:26] (926.88s)
special file name. So it has to be
[15:29] (929.20s)
called page.tsx
[15:31] (931.52s)
or if you're using JavaScript, that
[15:33] (933.52s)
would be JSX. And now I'll just say
[15:36] (936.88s)
RFCE. And to be able to get this
[15:39] (939.28s)
snippet, I'm using this extension.
[15:42] (942.16s)
Probably all of you guys already have
[15:44] (944.32s)
it, but if you don't have if you don't
[15:46] (946.64s)
have this, go ahead and install it. And
[15:48] (948.88s)
then you should be able to say RFC and
[15:51] (951.76s)
get the snippet. Now let's say this is
[15:54] (954.40s)
the about page. Save and test it out.
[15:59] (959.04s)
We'll go to slash about. And here we go.
[16:02] (962.88s)
It is working. Now if you want to have
[16:05] (965.20s)
about slash I don't know like test.
[16:09] (969.20s)
Currently it is 404 but if you would
[16:11] (971.60s)
like to create that it would be nested
[16:14] (974.32s)
under this folder. So you would say test
[16:17] (977.36s)
and then again you would create the
[16:19] (979.12s)
page.tsx into this folder. Right? So
[16:22] (982.72s)
here you would say page.tsx and you get
[16:25] (985.04s)
the point.
[16:26] (986.96s)
Like you're going to learn a lot more
[16:28] (988.56s)
about these folder structures in the
[16:31] (991.28s)
upcoming minutes. But for now let's try
[16:33] (993.68s)
to delete this folder because we are not
[16:37] (997.20s)
going to need it. Um I think now we can
[16:39] (999.92s)
set up the authentication. For this
[16:42] (1002.24s)
we're going to be using clerk. So, you
[16:44] (1004.32s)
can find the link in the description. Go
[16:46] (1006.32s)
ahead and create an account. It is
[16:48] (1008.24s)
completely free to get started with up
[16:50] (1010.72s)
to 10,000 monthly users. You don't
[16:53] (1013.52s)
really need to pay anything, which is
[16:56] (1016.08s)
pretty generous. And this is going to
[16:58] (1018.40s)
give us all kinds of components like
[17:01] (1021.12s)
these that you can see on the screen
[17:03] (1023.28s)
without you doing too much work. So like
[17:06] (1026.80s)
we will set up the authentication in
[17:08] (1028.96s)
seconds so that we can actually focus on
[17:11] (1031.92s)
our application, right? We don't really
[17:14] (1034.16s)
want to spend 5 hours setting up the
[17:17] (1037.12s)
authentication. So just go ahead and
[17:19] (1039.60s)
sign in. This is what I'll be doing. Let
[17:22] (1042.56s)
me just sign in with my Google account.
[17:25] (1045.04s)
You can use GitHub as well. Basically
[17:27] (1047.20s)
from the dashboard just say create an
[17:29] (1049.28s)
application and then we can um you can
[17:33] (1053.76s)
like add all kinds of signin options
[17:36] (1056.08s)
like Google username, email, GitHub.
[17:39] (1059.84s)
Let's just have Google and GitHub as
[17:42] (1062.16s)
well as the email address. But if you
[17:44] (1064.24s)
want you can add more. It'll just work
[17:46] (1066.80s)
out of the box. So these are the ones
[17:49] (1069.04s)
that I'll have. For the application name
[17:51] (1071.28s)
I'll just say dent.
[17:54] (1074.48s)
Let's say create application and they
[17:57] (1077.04s)
have all kinds of SDKs. We'll be using
[17:59] (1079.28s)
Nex.js.
[18:01] (1081.04s)
So we have like we have to get the first
[18:04] (1084.40s)
command and paste it to our terminal. So
[18:08] (1088.16s)
I'll just kill this. Clear paste this in
[18:11] (1091.52s)
mpm install cler next.js
[18:16] (1096.00s)
and then we would like to set up the env
[18:18] (1098.48s)
file. I'll copy this.
[18:22] (1102.08s)
go under the I think under the source it
[18:25] (1105.92s)
should be or actually in the root I'll
[18:28] (1108.00s)
just create the env paste this in where
[18:31] (1111.60s)
we have the publishable key and then the
[18:33] (1113.52s)
secret key
[18:36] (1116.24s)
and then we would like to create the
[18:37] (1117.92s)
middleware.ts TS file which is something
[18:40] (1120.40s)
that clerk uses under the hood and since
[18:43] (1123.76s)
we are using the source folder it should
[18:45] (1125.76s)
be within this folder right so let's go
[18:48] (1128.40s)
ahead and just say middleware where yes
[18:52] (1132.08s)
and paste this in if you are not using
[18:54] (1134.72s)
the source folder it should be under the
[18:58] (1138.88s)
and this is what they say as well if you
[19:01] (1141.04s)
just read it and then we can basically
[19:04] (1144.32s)
make this work it says go under the
[19:07] (1147.20s)
layout and wrap your application. Well,
[19:09] (1149.84s)
I don't really know why it is not
[19:11] (1151.36s)
visible. Let's refresh.
[19:15] (1155.52s)
Okay, I think this is not really pretty
[19:18] (1158.24s)
visible. I don't know why this is the
[19:20] (1160.56s)
case. Normally, it it was pretty clean
[19:22] (1162.56s)
and crisp, but basically, let's just
[19:25] (1165.52s)
test it out here. We'll go under the
[19:28] (1168.48s)
app, under the layout, and we're going
[19:31] (1171.36s)
to wrap everything with let's say clerk
[19:34] (1174.80s)
provider component.
[19:38] (1178.24s)
and let's try to import it.
[19:42] (1182.56s)
I'll just copy this entire thing.
[19:48] (1188.32s)
Okay. So, we're using the clerk
[19:50] (1190.08s)
provider, right? Which is coming from
[19:52] (1192.80s)
clerk next.js. So, since we wrapped our
[19:55] (1195.68s)
entire application with this, we can use
[19:58] (1198.80s)
clerk components within our entire
[20:01] (1201.60s)
application. So, let's go into the
[20:04] (1204.32s)
homepage. Let me delete this. I'll just
[20:06] (1206.72s)
have a div for now. Let's say on page.
[20:11] (1211.36s)
This could be within an H1.
[20:15] (1215.36s)
And then let's put a sign up button
[20:19] (1219.20s)
which is coming from clerk.
[20:22] (1222.64s)
And this could be like it could be
[20:24] (1224.88s)
self-closed or
[20:27] (1227.60s)
an actual full component. Let's say sign
[20:34] (1234.96s)
And let's go and check this out under
[20:37] (1237.36s)
the homepage.
[20:39] (1239.44s)
I think we are not running the app.
[20:51] (1251.92s)
Okay, let's wait for this to be loaded.
[20:54] (1254.24s)
Okay, so this is the sign up button.
[20:56] (1256.00s)
That looks ugly, but we are going to
[20:57] (1257.76s)
make it look nice. If you click to it,
[21:00] (1260.00s)
it should take you to the sign up page
[21:02] (1262.40s)
and you can sign up easily. But instead
[21:04] (1264.88s)
of going into this page, we can make
[21:07] (1267.12s)
this work like a model. So here I'll say
[21:10] (1270.32s)
mode should be equal to model. I believe
[21:13] (1273.36s)
by default it is redirect. But now let's
[21:16] (1276.32s)
say it is going to be model. Here we go.
[21:19] (1279.36s)
Let me just zoom out. It is working
[21:22] (1282.64s)
pretty clean, right? We have this
[21:24] (1284.64s)
animation. And let's try to sign up with
[21:27] (1287.60s)
our Google account.
[21:32] (1292.00s)
Let me select my account.
[21:34] (1294.24s)
And currently under the users, we don't
[21:36] (1296.40s)
really have anything, right?
[21:42] (1302.32s)
Okay, we just signed up. Let's see. It
[21:45] (1305.28s)
should fetch them. Maybe just refresh
[21:47] (1307.52s)
this page. Here we go. This user has
[21:50] (1310.32s)
just signed up now. We signed up, but we
[21:53] (1313.36s)
still see this component, right? So, we
[21:55] (1315.84s)
can get rid of this. I will say
[21:59] (1319.36s)
signed
[22:01] (1321.12s)
out which is a component that Flor has.
[22:08] (1328.16s)
So this should be coming from Nex.js.
[22:10] (1330.48s)
Let's import it. Okay. So this basically
[22:13] (1333.28s)
says if user is signed out, show them a
[22:16] (1336.56s)
sign up button. And we can do the same
[22:18] (1338.88s)
thing. Let's say if user is signed in,
[22:26] (1346.56s)
show them a sign out button, right?
[22:29] (1349.60s)
Let's say sign up button and just
[22:32] (1352.56s)
display it.
[22:35] (1355.36s)
Let's say log out. Okay, so at this
[22:38] (1358.16s)
point this feels like an English
[22:39] (1359.76s)
sentence, right? Let's try to see. Since
[22:42] (1362.40s)
we are signed in, we see the log out
[22:44] (1364.64s)
button. If I click to it, now we are
[22:47] (1367.04s)
logged out. We can see the sign up
[22:48] (1368.80s)
button. So this is how easy it is to get
[22:51] (1371.44s)
started with Clerk with their
[22:53] (1373.44s)
authentication system. Now later in the
[22:56] (1376.08s)
video we'll be using the subscriptions
[22:58] (1378.64s)
which is for billing but for now let's
[23:01] (1381.20s)
just leave it for the upcoming sections.
[23:04] (1384.00s)
Of course we're going to make the UI
[23:05] (1385.68s)
look very nice. We're going to have
[23:07] (1387.60s)
these beautiful buttons that are not
[23:10] (1390.32s)
going to look like this. But for now
[23:12] (1392.16s)
we're just trying to set up our
[23:13] (1393.84s)
codebase. And while we are setting up
[23:16] (1396.48s)
the env file, let's go ahead and get the
[23:20] (1400.24s)
connection string for our database which
[23:22] (1402.88s)
is going to be coming from posgress
[23:24] (1404.96s)
right. So let's say pg which stands for
[23:28] (1408.32s)
posgress and our posgress provider is
[23:31] (1411.52s)
going to be neon. You can find the link
[23:33] (1413.84s)
in the description. Again they have free
[23:36] (1416.08s)
plan so you don't really need to worry
[23:38] (1418.32s)
about by paying or setting up your
[23:40] (1420.88s)
credit card. It is completely free to
[23:43] (1423.28s)
get started with and we'll go ahead and
[23:46] (1426.24s)
set up a Postgress database in seconds.
[23:49] (1429.52s)
Okay, so I already have an account. I'll
[23:52] (1432.00s)
just go ahead and log in and then we are
[23:55] (1435.20s)
going to create a project. So let me
[23:57] (1437.44s)
just zoom in. We'll say create a
[23:59] (1439.68s)
project. Uh for the name I'll just say
[24:02] (1442.56s)
Dwise and I will leave everything as it
[24:05] (1445.76s)
is. You can update the region uh but
[24:09] (1449.12s)
I'll just leave it as it is. Let's say
[24:11] (1451.44s)
create.
[24:14] (1454.16s)
Then I can zoom out. Now we'll just say
[24:16] (1456.88s)
connect to your database. Let's say
[24:19] (1459.36s)
connect which is going to give us a
[24:21] (1461.28s)
connection string. Now we are going to
[24:23] (1463.52s)
be using Prisma later in the video. So
[24:26] (1466.08s)
let's say select the Prisma. Um first
[24:29] (1469.04s)
get the environment variable which is
[24:31] (1471.76s)
this one.
[24:33] (1473.76s)
Now here they have another comment. They
[24:35] (1475.84s)
say if you're using the version Prisma
[24:38] (1478.80s)
that is less than 5.10
[24:41] (1481.36s)
copy this one but we're going to be
[24:43] (1483.12s)
using a version above this. So go ahead
[24:46] (1486.24s)
copy the very first uh database URL
[24:49] (1489.84s)
variable. Okay. So copy it paste this in
[24:53] (1493.44s)
and that's all we have to do at the
[24:55] (1495.04s)
moment. We just got our connection
[24:57] (1497.12s)
string for our Postgress database. Later
[25:00] (1500.88s)
in the video we're going to set this up
[25:02] (1502.96s)
with Prisma.
[25:04] (1504.96s)
So you will see how that work. And the
[25:08] (1508.08s)
very last thing that we need to set up
[25:10] (1510.16s)
is our voice agent which is going to be
[25:12] (1512.96s)
coming from Bobby. Again you can find
[25:15] (1515.52s)
the link in the description and it is
[25:17] (1517.52s)
completely free to get started with. So
[25:20] (1520.00s)
you don't really need to pay anything to
[25:21] (1521.84s)
be able to follow along. Okay. So I'll
[25:24] (1524.40s)
just go ahead and log in. I already have
[25:27] (1527.36s)
an account. So let me just say open the
[25:30] (1530.00s)
dashboard. So I am in the dashboard now.
[25:32] (1532.80s)
And by default, you would have $10 of
[25:35] (1535.84s)
credit, right? I've used a ton and I
[25:38] (1538.80s)
still have the half of it. So, uh I
[25:41] (1541.84s)
think by default, you will have
[25:43] (1543.36s)
something like 10 credits down here.
[25:46] (1546.64s)
Now, let's go ahead and create an
[25:48] (1548.32s)
assistant. This might look a little bit
[25:50] (1550.56s)
confused because they have bunch of
[25:52] (1552.32s)
different things, but it is actually
[25:54] (1554.16s)
pretty uh like beginner friendly. First,
[25:57] (1557.92s)
we would like to create an assistant. So
[26:00] (1560.24s)
here just select the assistance and
[26:02] (1562.24s)
let's say create an assistant. Now they
[26:04] (1564.56s)
have different templates but we would
[26:06] (1566.56s)
like to start with a with a blank
[26:09] (1569.20s)
template right for the assistant name.
[26:11] (1571.68s)
So we can just say something like
[26:13] (1573.92s)
dentwise or we can put anything really.
[26:16] (1576.64s)
I'll just say blank template and create
[26:19] (1579.20s)
the assistant. So later in the video we
[26:22] (1582.08s)
are going to put the system prompt like
[26:25] (1585.04s)
what should be the first message when
[26:27] (1587.60s)
when the voice agent starts talking
[26:31] (1591.28s)
right it's going to say hey this is me
[26:33] (1593.44s)
from Dentwise I can help you with this
[26:36] (1596.40s)
this this stuff like that right now just
[26:38] (1598.96s)
go ahead and create it and for now just
[26:41] (1601.20s)
copy the assistant ID and we're going to
[26:43] (1603.84s)
paste this into thev file so right after
[26:47] (1607.92s)
the database URL I'll say next
[26:50] (1610.64s)
underscore public and I'll say bobby
[26:55] (1615.36s)
assistant
[26:57] (1617.92s)
id and just paste this in. All right, so
[27:00] (1620.88s)
these are the things that we'll be using
[27:02] (1622.72s)
later in the video which is set we just
[27:05] (1625.36s)
set that up under the env file. Okay, so
[27:08] (1628.88s)
we'll get into this dashboard in detail
[27:11] (1631.44s)
in the upcoming sections. Okay, so I
[27:14] (1634.48s)
think that's it for this section where
[27:16] (1636.32s)
we set it up Tailwind, we set it up
[27:19] (1639.04s)
chaten, Nex.js, we have some components,
[27:23] (1643.20s)
right? All of them coming from shaden,
[27:25] (1645.76s)
we have clerk authentication working as
[27:28] (1648.88s)
expected. UI is not really looking
[27:31] (1651.28s)
great, but that's fine. We are just
[27:33] (1653.28s)
getting started with it. So we can go
[27:35] (1655.84s)
ahead and actually create a git
[27:38] (1658.56s)
repository, right? And push this to
[27:41] (1661.04s)
GitHub. So here I am in my GitHub
[27:43] (1663.52s)
account. I just said create a new
[27:45] (1665.68s)
repository. For the name I'll just say
[27:48] (1668.00s)
then twice and then I'll leave
[27:50] (1670.64s)
description empty. And for now I'll make
[27:53] (1673.12s)
it private. Once I publish this video
[27:56] (1676.00s)
it's going to be public but for now uh
[27:58] (1678.48s)
it'll just be private. Okay. So let's
[28:00] (1680.80s)
say create the repository and let's
[28:04] (1684.16s)
commit our changes first. So here I'll
[28:07] (1687.12s)
just close everything. Here we can see
[28:09] (1689.84s)
the node modules have been ignored as
[28:12] (1692.16s)
well as the env file right we can just
[28:15] (1695.04s)
push everything to GitHub and let's go
[28:17] (1697.68s)
ahead click to this just say stage all
[28:20] (1700.96s)
the changes so first we're going to
[28:22] (1702.96s)
stage them and let's put a message like
[28:26] (1706.96s)
project setup and then I'll just say
[28:30] (1710.48s)
commit
[28:33] (1713.20s)
and which branch is this let's see I'll
[28:36] (1716.64s)
just command shiftp toggle status this
[28:38] (1718.88s)
bar. Okay, so by default it is main.
[28:41] (1721.36s)
That is fine. We just have our very
[28:43] (1723.20s)
first commit. Let's go ahead copy this
[28:47] (1727.84s)
which says push an existing repository
[28:50] (1730.72s)
from the command line. So we have an
[28:53] (1733.92s)
existing repository, right? We would
[28:56] (1736.08s)
like to push this to GitHub. Let's kill
[28:59] (1739.20s)
the terminal. Say clear. And by the way,
[29:02] (1742.32s)
you can kill this with command C, right?
[29:05] (1745.12s)
Or control C. I'll paste this in.
[29:09] (1749.28s)
Run this
[29:11] (1751.68s)
and let's refresh this page.
[29:15] (1755.52s)
Okay, so this is our source code that is
[29:20] (1760.48s)
right here. But now it's it's been
[29:22] (1762.64s)
pushed to GitHub. Now that was the
[29:25] (1765.76s)
master branch, right? Um from here on
[29:28] (1768.88s)
out in every upcoming sections we are
[29:31] (1771.92s)
going to create a new branch and create
[29:34] (1774.80s)
pull requests and then merge them which
[29:37] (1777.76s)
is a real world implementation. Okay, so
[29:40] (1780.80s)
just keep that in mind. In the upcoming
[29:42] (1782.88s)
sections, we are going to create new
[29:44] (1784.72s)
branches just like as if we are in a
[29:47] (1787.76s)
company, right? We we have different
[29:49] (1789.68s)
teammates, we would create a new branch
[29:53] (1793.20s)
and send pull request. If they like it,
[29:55] (1795.76s)
they would merge that. But if there is
[29:58] (1798.00s)
something missing, they would add a
[29:59] (1799.92s)
comment where we need to have another
[30:02] (1802.48s)
commit. Right? I know that this sounds a
[30:04] (1804.80s)
little bit weird or complicated, but I
[30:07] (1807.12s)
think we're going to see it in detail in
[30:08] (1808.96s)
the upcoming sections. All right, so
[30:11] (1811.60s)
pause the video, take a look at the
[30:13] (1813.36s)
codebase. If there is anything that you
[30:15] (1815.84s)
didn't really understand, you can ask it
[30:18] (1818.00s)
in the Discord server or under the
[30:20] (1820.08s)
comments. But I think everything should
[30:22] (1822.48s)
make sense so far. And with that, I'll
[30:24] (1824.80s)
see you in the next section. And
[30:26] (1826.64s)
actually, just one more thing before we
[30:28] (1828.64s)
end this section. At the end of this
[30:30] (1830.48s)
course, we are going to deploy our
[30:32] (1832.32s)
project using Savala. And currently,
[30:34] (1834.96s)
they give you $50 pre credit if you use
[30:37] (1837.76s)
the link in the description. Go ahead
[30:39] (1839.60s)
and get them before they expire. So,
[30:41] (1841.84s)
with that, hopefully I'll see you in the
[30:43] (1843.68s)
next section.
[30:45] (1845.68s)
In this section, we are going to get
[30:47] (1847.36s)
started with the landing page that we're
[30:50] (1850.24s)
going to have. So, this is mostly just
[30:52] (1852.80s)
UI, right? Some designs. So, we're going
[30:55] (1855.36s)
to have a header component. We will have
[30:57] (1857.84s)
the hero section, couple of more
[31:00] (1860.32s)
sections like pricing and at the very
[31:03] (1863.20s)
end we're going to have a footer
[31:04] (1864.96s)
component. So let's see how we can build
[31:07] (1867.36s)
them. These are the images that I will
[31:09] (1869.92s)
provide you. You can find it from the
[31:12] (1872.24s)
source code. So let's go ahead and
[31:14] (1874.56s)
actually get these images.
[31:17] (1877.36s)
Find the source code. The link will be
[31:19] (1879.28s)
in the description and go under the
[31:21] (1881.52s)
public folder. You're going to see a
[31:23] (1883.44s)
couple of different files. So, let me
[31:25] (1885.68s)
actually copy them and paste it right
[31:28] (1888.32s)
here. So, you're going to see the
[31:30] (1890.24s)
audio.png
[31:32] (1892.16s)
brain calendar confused robot, right?
[31:36] (1896.00s)
And then we'll have this call to action
[31:38] (1898.48s)
email sent. And then we'll have two more
[31:42] (1902.00s)
which is hero and logo. So, this is the
[31:46] (1906.08s)
one that we're going to be using for the
[31:47] (1907.84s)
hero section. And then our dent wise
[31:50] (1910.64s)
logo. I have generated all of them with
[31:53] (1913.84s)
AI with chat GPT. So you can use any of
[31:57] (1917.52s)
them and even you can change it if you
[31:59] (1919.92s)
really wanted to. Now let's go under the
[32:02] (1922.32s)
source under the components. I will
[32:04] (1924.64s)
create one more folder called landing
[32:07] (1927.76s)
and we're going to put all the
[32:09] (1929.20s)
components of the landing page under
[32:11] (1931.92s)
this folder. Right now let's go into the
[32:14] (1934.56s)
homepage and basically we can delete
[32:17] (1937.20s)
everything that we have here. Under the
[32:19] (1939.76s)
return statement, we're going to put our
[32:21] (1941.84s)
components. So, first I'll just return a
[32:24] (1944.64s)
div. And let's say this is going to have
[32:27] (1947.04s)
class name of minimum height screen,
[32:30] (1950.40s)
let's say. And then the background is
[32:32] (1952.56s)
going to be this background variable
[32:35] (1955.36s)
color that we are using. And then we can
[32:38] (1958.08s)
basically put all of our components. So
[32:40] (1960.72s)
let's say header, which is something
[32:42] (1962.96s)
that we are going to create. Let's say
[32:45] (1965.52s)
we are going to have the hero section.
[32:48] (1968.88s)
And I'll duplicate this. We'll have how
[32:51] (1971.92s)
it works.
[32:54] (1974.16s)
Duplicate it. We will have what to ask.
[32:59] (1979.84s)
And then we will have a pricing section.
[33:02] (1982.96s)
Let's say then we're going to have a
[33:05] (1985.20s)
call to action. Finally, the footer
[33:08] (1988.80s)
component. So let's try to create every
[33:11] (1991.60s)
single one of these one by one. Under
[33:14] (1994.40s)
the landing, you will have header.tsx.
[33:16] (1996.56s)
uh tsx let's say rfce save and this
[33:20] (2000.72s)
should be it and just import it make
[33:23] (2003.52s)
sure it is coming from the components
[33:26] (2006.40s)
okay so we're not using the button we're
[33:28] (2008.80s)
not using any of these let's delete them
[33:32] (2012.40s)
copy the hero let's do the same thing
[33:38] (2018.24s)
so I'll do the same thing for all of
[33:40] (2020.08s)
these I can just pause the video create
[33:43] (2023.12s)
all of them and then come back to the
[33:46] (2026.32s)
And as I create them, I'll just import
[33:49] (2029.28s)
one by one from these specific files.
[33:52] (2032.80s)
Okay. So, as you can tell, I have
[33:54] (2034.72s)
created the rest of them and basically
[33:57] (2037.60s)
just imported in these uh in this file.
[34:00] (2040.64s)
Now, let's go ahead take a look at it.
[34:02] (2042.40s)
Under the homepage, um I think we need
[34:04] (2044.96s)
to run the application.
[34:07] (2047.68s)
I'll say clear this up. MPM rundev
[34:13] (2053.52s)
and just refresh.
[34:15] (2055.52s)
And first we're going to get started
[34:17] (2057.28s)
with the header component.
[34:20] (2060.16s)
Okay. Now in our application we will be
[34:22] (2062.72s)
using the dark mode. Right? So we'll
[34:25] (2065.20s)
just have dark mode only. As you can
[34:27] (2067.20s)
tell everything is just black in this
[34:29] (2069.60s)
application. So to be able to make that
[34:31] (2071.92s)
work we can visit the layout file
[34:37] (2077.04s)
under the root. So source app and visit
[34:41] (2081.12s)
layout.tsx file. And here under the
[34:44] (2084.16s)
body, we'll just say add the dark class
[34:47] (2087.20s)
all the time. Now, if you save,
[34:49] (2089.28s)
hopefully everything should be in the
[34:50] (2090.96s)
dark mode by default. Okay, now let's
[34:53] (2093.68s)
get started with the header component.
[34:56] (2096.72s)
So, I'll go ahead close this, open this
[34:59] (2099.36s)
up, and get started with it. So, just
[35:01] (2101.84s)
before we get into coding, let's pretty
[35:04] (2104.08s)
quickly see the end result. So, we
[35:06] (2106.48s)
basically have three different sections,
[35:08] (2108.72s)
right? On the left, we have the logo
[35:11] (2111.44s)
with the image. We have, let's say,
[35:14] (2114.56s)
three different links and then two
[35:16] (2116.56s)
different buttons. So, these are going
[35:18] (2118.32s)
to be the clerk buttons and we're going
[35:20] (2120.48s)
to see how to make them look this
[35:22] (2122.80s)
beautiful. Okay, so let's get started
[35:25] (2125.52s)
with it. This is going to be a nav
[35:28] (2128.16s)
component. I'll say nav. And we would
[35:30] (2130.88s)
like to give some classes. I'll say
[35:32] (2132.96s)
class name. We want this to be fixed to
[35:35] (2135.60s)
the top. And let's say right of zero and
[35:39] (2139.28s)
I'll say zind index of 50. So it is
[35:42] (2142.00s)
always at the top. So at the top of
[35:44] (2144.32s)
everything let's say petting x of six
[35:47] (2147.60s)
from the horizontal direction. Let's say
[35:50] (2150.16s)
petting y of two. And then we'll say
[35:52] (2152.72s)
border bottom. And let's say border this
[35:56] (2156.00s)
is the color that we will give which is
[35:58] (2158.32s)
border. And let's change the opacity by
[36:01] (2161.76s)
50%. And let's say pg dash background.
[36:06] (2166.16s)
I'd like to update this by changing the
[36:08] (2168.64s)
opacity with 80. And then I'll just say
[36:11] (2171.28s)
backdrop blur medium. And you're going
[36:13] (2173.92s)
to see what this does. And let's say
[36:16] (2176.00s)
height will be 16, which is like 64
[36:19] (2179.04s)
pixels. Okay. Within this, we can have a
[36:22] (2182.00s)
div. Let's say this is going to have a
[36:24] (2184.48s)
maximum width. Let's say class name max
[36:27] (2187.84s)
width of 6x large. And you can see what
[36:31] (2191.36s)
that equals to which is 15 I mean 1152
[36:35] (2195.60s)
pixels. And then we'll say something
[36:37] (2197.68s)
like MX auto so that it is centered
[36:40] (2200.96s)
correctly or nicely. We'll say flex
[36:44] (2204.24s)
justify between and items centered right
[36:47] (2207.60s)
items center. Then we can have a link
[36:50] (2210.48s)
component which is going to be coming
[36:52] (2212.56s)
from next link. Let's say this will take
[36:56] (2216.64s)
us to the home screen. when we click to
[37:00] (2220.00s)
it. I think this should be h right not
[37:03] (2223.60s)
two. Um let's say class name will be
[37:07] (2227.60s)
flex item centered and we can give some
[37:11] (2231.20s)
gap like two. Now this length will have
[37:13] (2233.84s)
the image which is going to be our logo
[37:16] (2236.88s)
and let's say this is going to be
[37:18] (2238.16s)
self-closed coming from next image. Um
[37:21] (2241.68s)
here the source is going to be under the
[37:24] (2244.80s)
public logo.png PNG and we would like to
[37:28] (2248.40s)
give some alt
[37:31] (2251.52s)
let's say dent wise logo
[37:38] (2258.16s)
and then let's give a width of let's say
[37:40] (2260.48s)
32 height of 32 as well and class name
[37:44] (2264.16s)
I'll just say width of 11
[37:47] (2267.44s)
and right after this I'll have a span
[37:49] (2269.84s)
let's say dent y which is our text say
[37:53] (2273.52s)
class name we can make it bold bold or
[37:56] (2276.80s)
in this case I'll go with semib bold and
[37:59] (2279.60s)
let's say text will be large
[38:02] (2282.80s)
which is 18 pixels and let's see the
[38:05] (2285.28s)
output as we build it okay so we're just
[38:08] (2288.08s)
getting there step by step let's go
[38:10] (2290.64s)
ahead right after the link we're going
[38:13] (2293.60s)
to put the div
[38:16] (2296.48s)
so this will be hidden okay but medium
[38:20] (2300.80s)
screens and above it is going to be
[38:22] (2302.56s)
visible okay so this is how we can make
[38:25] (2305.20s)
responsive design medium screens and
[38:27] (2307.60s)
above it should be visible but if it is
[38:30] (2310.16s)
less than medium screens it's going to
[38:32] (2312.08s)
be hidden and let's say items will be
[38:35] (2315.20s)
centered let's say gap of eight and
[38:39] (2319.36s)
we'll just give all this space then we
[38:41] (2321.60s)
can have a blank in this case I'll use
[38:44] (2324.72s)
href and this is not going to take us to
[38:46] (2326.96s)
any place you know this is just for just
[38:50] (2330.40s)
to make UI look nice um let's give some
[38:53] (2333.36s)
classes you can always customize this.
[38:56] (2336.08s)
By the way, I'm just here to build a
[38:59] (2339.20s)
beautiful UI and that's it. Let's say
[39:01] (2341.36s)
text muted foreground and then we'll say
[39:04] (2344.64s)
on hover we can update the text to be
[39:08] (2348.24s)
just foreground. And this is going to
[39:10] (2350.56s)
say how it works.
[39:14] (2354.32s)
Okay. So let's save delete this empty
[39:16] (2356.88s)
space and we can basically duplicate
[39:19] (2359.36s)
this twice. So this will be pricing
[39:24] (2364.88s)
and everything will be the same. And for
[39:27] (2367.36s)
this one, we'll just say above.
[39:30] (2370.64s)
Okay. So just go outside of this div.
[39:33] (2373.76s)
We're going to put one more div that
[39:35] (2375.76s)
will have our buttons. So let's say
[39:38] (2378.00s)
class name, legs, items will be
[39:40] (2380.64s)
centered, gap will be three, let's say.
[39:44] (2384.88s)
And then we can have a sign in button
[39:48] (2388.48s)
which will be coming from clerk.
[39:52] (2392.48s)
And let's try to duplicate this. This
[39:55] (2395.36s)
time it'll be sign up button. And just
[39:58] (2398.56s)
make sure that you import them from
[40:00] (2400.32s)
color conjs.
[40:02] (2402.96s)
Okay. Now here I'll just say sign in and
[40:07] (2407.52s)
sign up.
[40:10] (2410.56s)
And let's see the output.
[40:13] (2413.60s)
Okay. So this is not really working
[40:15] (2415.68s)
properly. Let's see why this is the
[40:17] (2417.68s)
case. We just set fixed top zero, right
[40:21] (2421.52s)
zero and let's say left zero as well so
[40:23] (2423.92s)
that it is centered.
[40:26] (2426.40s)
Okay. Um here there's an overflow that
[40:29] (2429.12s)
we're going to fix but other than this
[40:31] (2431.20s)
it is working correctly. Now these
[40:33] (2433.36s)
buttons should look like this right? It
[40:36] (2436.64s)
should look like an actual button. So if
[40:39] (2439.12s)
you want to customize it, you can go
[40:41] (2441.52s)
here and create your actual buttons.
[40:43] (2443.92s)
Right? For this, we'll be using button
[40:46] (2446.08s)
coming from chaten. Um just go ahead
[40:49] (2449.44s)
import it from the UI button file. And
[40:53] (2453.44s)
here we can just say login
[40:56] (2456.96s)
and you can pass the variant. Let's say
[40:59] (2459.68s)
this is going to be ghost. These are all
[41:02] (2462.56s)
the options. We'll use ghost for this
[41:04] (2464.48s)
one. And we can say size will be small.
[41:08] (2468.16s)
Let's do the same thing for the sign up
[41:10] (2470.24s)
button. Copy this. Paste this in. And
[41:13] (2473.76s)
here I'll just say sign up.
[41:17] (2477.44s)
Um I'll just leave the variant as it is.
[41:20] (2480.08s)
Like I'll delete that. We'll use the
[41:21] (2481.84s)
default. And size will be small. Now
[41:24] (2484.80s)
let's see. Okay. So this is the exact
[41:27] (2487.12s)
same output that we want to have, right?
[41:31] (2491.84s)
Okay. So if you click it, that would
[41:35] (2495.04s)
take you to the signin page. but instead
[41:37] (2497.52s)
we would like to have the mode to be
[41:40] (2500.72s)
equal to model. Let's copy this and
[41:44] (2504.64s)
paste it right here.
[41:47] (2507.44s)
So now if you click to them you're going
[41:49] (2509.12s)
to see the model. Now one thing that I
[41:52] (2512.00s)
don't really like is that if you hover
[41:53] (2513.92s)
over a button the cursor is not a
[41:56] (2516.48s)
pointer. This is because of Tailwind CSS
[41:59] (2519.92s)
version 4. If you want to update it you
[42:03] (2523.52s)
can go under the global.css CSS and I'll
[42:06] (2526.56s)
just paste four lines of code.
[42:10] (2530.24s)
Let me copy it and paste it. You can
[42:12] (2532.80s)
grab this from the source code under the
[42:14] (2534.80s)
global.css.
[42:16] (2536.64s)
Basically, this will add cursor pointer
[42:18] (2538.88s)
to buttons by default. So, under the
[42:21] (2541.68s)
button, this is the trick that we do.
[42:24] (2544.16s)
And this is coming from sha uh tailwind
[42:27] (2547.12s)
tailwind CSS documentation. just say
[42:30] (2550.32s)
this is coming
[42:34] (2554.24s)
from payment documentation. So this is
[42:38] (2558.16s)
where I copied and pasted.
[42:41] (2561.84s)
Okay. So now we have this cursor pointer
[42:44] (2564.88s)
as you can see. But now let's get into
[42:47] (2567.60s)
the hero section. So as you can tell
[42:50] (2570.40s)
this is looking absolutely beautiful
[42:52] (2572.64s)
which means we might need to copy and
[42:54] (2574.96s)
paste some of the parts like the create
[42:58] (2578.00s)
background right or some of these
[43:00] (2580.88s)
decorative elements. So what I would
[43:03] (2583.44s)
suggest you to do go ahead visit the
[43:06] (2586.08s)
source code and have this hero file open
[43:10] (2590.00s)
because we will just copy and paste
[43:12] (2592.24s)
couple of different things so that we so
[43:14] (2594.48s)
that we don't really need to waste too
[43:16] (2596.32s)
much time. Okay, I hope that makes
[43:18] (2598.32s)
sense. Just have the source code open as
[43:20] (2600.72s)
we build this component. So, we just
[43:23] (2603.28s)
built the header. Now, it's time to
[43:25] (2605.36s)
build the hero section. First, I'll
[43:28] (2608.32s)
delete everything that we have inside.
[43:30] (2610.96s)
And this is going to be a section
[43:33] (2613.84s)
element. Okay. Now, let me just try to
[43:37] (2617.76s)
disable the AI that I have, otherwise
[43:40] (2620.72s)
it's going to get a little bit annoying.
[43:43] (2623.04s)
Okay. And to be able to get this pallet,
[43:45] (2625.44s)
I'm just pressing commandshiftp
[43:49] (2629.12s)
or control shiftp then type like toggle
[43:52] (2632.88s)
status bar visibility and it should
[43:55] (2635.36s)
toggle the state. Okay. Now let's say
[43:57] (2637.84s)
the class name for this will be
[43:59] (2639.68s)
relative. Height will be screen. Let's
[44:02] (2642.64s)
say items centered but maybe first flex
[44:07] (2647.28s)
and then I'll say overflow of hidden.
[44:09] (2649.68s)
Then we can just say padding top of 20.
[44:12] (2652.96s)
Okay, within this we would like to have
[44:15] (2655.12s)
the grid background. So this is
[44:17] (2657.52s)
something that I have generated with AI.
[44:21] (2661.20s)
So I'll copy and paste it because like
[44:23] (2663.60s)
if you delete this part and tell me to
[44:25] (2665.92s)
type it, I cannot really do it and I
[44:28] (2668.16s)
don't really mind it because AI can
[44:30] (2670.16s)
build it for me in seconds. So this is
[44:32] (2672.56s)
how I am using AI every single every
[44:34] (2674.96s)
single day to get this kind of
[44:37] (2677.20s)
unnecessary work done for me. Okay, copy
[44:40] (2680.56s)
and paste this from the source code and
[44:43] (2683.04s)
I can add a comment like grid
[44:46] (2686.64s)
background.
[44:49] (2689.68s)
Um, let's see if we can see anything.
[44:52] (2692.32s)
Okay, so this is that grid background.
[44:54] (2694.96s)
It is just fading away as it goes to the
[44:57] (2697.84s)
bottom.
[45:00] (2700.00s)
And then let's see what we want to add.
[45:02] (2702.32s)
Right after this grid background above
[45:04] (2704.80s)
the section, I will put some gradient.
[45:09] (2709.20s)
let's say orbs
[45:12] (2712.64s)
which are going to look like these. So
[45:14] (2714.88s)
this one and this one
[45:19] (2719.92s)
again this is something that I have
[45:21] (2721.68s)
generated with AI. So I'll just paste
[45:24] (2724.00s)
this in. Basically it is like
[45:26] (2726.64s)
positioning absolutely giving some width
[45:29] (2729.36s)
and height and the background color
[45:31] (2731.92s)
rounded full and making it blurred.
[45:34] (2734.32s)
Right. So we have two of them that look
[45:38] (2738.48s)
like these. Okay. So these are the
[45:40] (2740.56s)
things that we just copied and pasted.
[45:42] (2742.96s)
Let's go ahead create a div just above
[45:45] (2745.76s)
the section. Let me scroll down. This
[45:48] (2748.48s)
will have the class name of relative Z10
[45:51] (2751.84s)
with full and the petting x of six. Then
[45:55] (2755.20s)
we'll have one more div. Let's say this
[45:58] (2758.08s)
is going to be the maximum width of 7 x
[46:02] (2762.08s)
large. Let's grab this. center this
[46:05] (2765.04s)
beautifully with MX auto. And within
[46:07] (2767.92s)
this, I'll use the div with the grid
[46:11] (2771.92s)
class. Now, what we're going to be doing
[46:14] (2774.08s)
is this. We will have basically two grid
[46:17] (2777.60s)
items. So, this is the first one, the
[46:19] (2779.92s)
left hand side and the right hand side,
[46:22] (2782.80s)
right?
[46:24] (2784.56s)
So, let's go ahead and see how that
[46:26] (2786.48s)
work. We'll say grid larger screens and
[46:29] (2789.44s)
above. This should be grid columns of
[46:32] (2792.72s)
two and gap of 16. And we can center
[46:36] (2796.88s)
them vertically with items center. And
[46:40] (2800.08s)
then let's say we will have the left
[46:42] (2802.08s)
content. If you don't want to type this
[46:44] (2804.24s)
out, you can copy and paste it from the
[46:46] (2806.16s)
source code. But if you want to have it,
[46:48] (2808.24s)
let's just do it step by step. So first
[46:51] (2811.20s)
I'll say I'll say class name of space y
[46:54] (2814.56s)
of 10. We're just going to give some
[46:57] (2817.20s)
spacing. As you can tell from the y
[47:00] (2820.08s)
direction and within this we'll have one
[47:02] (2822.64s)
more div and this will have the class
[47:05] (2825.12s)
name space y of six. Okay. So this will
[47:10] (2830.24s)
have two different elements. The first
[47:12] (2832.80s)
one is the badge. So let's say badge and
[47:16] (2836.48s)
it is this one. This is what we would
[47:19] (2839.36s)
like to build
[47:23] (2843.60s)
here. This will be the div with a lot of
[47:27] (2847.28s)
classes.
[47:29] (2849.04s)
Let's say this is going to get the
[47:30] (2850.96s)
inline flex item centered
[47:34] (2854.88s)
not being checked but let's say items
[47:38] (2858.40s)
will be centered. Then we can say gap of
[47:41] (2861.76s)
two petting x of four y of two
[47:45] (2865.84s)
background gradient
[47:48] (2868.48s)
to right. So this is how we can use it.
[47:51] (2871.92s)
And then we'll say from this color just
[47:54] (2874.64s)
primary 10
[47:58] (2878.16s)
to let's say primary
[48:01] (2881.84s)
five. We are just updating the opacity.
[48:04] (2884.96s)
And then let's say round it full. It is
[48:07] (2887.12s)
a circle border
[48:10] (2890.40s)
say border color is going to be primary.
[48:14] (2894.56s)
And we can update the opacity. I'll be
[48:16] (2896.64s)
using 20. And then let's say backdrop
[48:19] (2899.44s)
blur small.
[48:21] (2901.92s)
Okay. So, within this div, we are going
[48:23] (2903.76s)
to have this beautiful
[48:27] (2907.12s)
circle, right? This a tiny thing. And
[48:30] (2910.40s)
we're going to make this to be like
[48:32] (2912.32s)
pulsing. Um, let's see what that means.
[48:35] (2915.28s)
I'll basically put it right here. So,
[48:38] (2918.16s)
it's going to have tiny width and
[48:39] (2919.76s)
height. It's going to get this
[48:41] (2921.28s)
background. It'll be a circle. And we'll
[48:44] (2924.00s)
make it to be, you know, animating. And
[48:47] (2927.44s)
you're going to see this in a second.
[48:49] (2929.20s)
and just have a span that says AI-P
[48:53] (2933.92s)
powered dental assistant.
[48:58] (2938.80s)
And for the class names, let's say text
[49:00] (2940.96s)
will be small. Um, we'll put the font to
[49:04] (2944.48s)
be medium. And finally, text color will
[49:07] (2947.92s)
be primary.
[49:13] (2953.76s)
Okay. So, this is what we have at the
[49:16] (2956.00s)
moment. Let's try to zoom in. And as you
[49:19] (2959.04s)
can tell, this is like animating.
[49:22] (2962.72s)
And as we put more and more content,
[49:25] (2965.04s)
this should go up. Now, let's build the
[49:28] (2968.24s)
rest of the left hand side. So, this is
[49:30] (2970.88s)
not a CSS class, right? This is not a
[49:33] (2973.36s)
CSS course. Maybe we can speed up this
[49:36] (2976.48s)
process a bit. What I'll be doing is
[49:38] (2978.72s)
just copy and paste step by step and
[49:41] (2981.52s)
just show you the entire code. Okay. So,
[49:44] (2984.16s)
this is what we had the badge, right?
[49:46] (2986.96s)
can shrink this and go right below to
[49:49] (2989.44s)
it. We will have a main heading.
[49:54] (2994.32s)
Okay, so this is what I just pasted. Let
[49:57] (2997.20s)
me show you. It is this entire H1. So we
[50:01] (3001.28s)
have all these classes just to make it
[50:03] (3003.28s)
responsive and like bold have the uh
[50:07] (3007.28s)
letter spacing a bit more tight. Then we
[50:10] (3010.16s)
have this span that says your dental.
[50:13] (3013.60s)
Then we give one line break questions
[50:16] (3016.96s)
and then answer it instantly text. So
[50:20] (3020.00s)
let's take a look at the end result. It
[50:22] (3022.48s)
should look like this. So there is
[50:24] (3024.32s)
literally nothing fancy going on here.
[50:26] (3026.88s)
Maybe the only thing that you can take a
[50:28] (3028.88s)
look at is this color where we go from
[50:31] (3031.76s)
this one to that one which is like a
[50:34] (3034.32s)
gradient and it is coming from this
[50:37] (3037.60s)
span. So background gradient to right
[50:40] (3040.48s)
from this color to that one. And to make
[50:43] (3043.20s)
it work, you need to add these two
[50:46] (3046.80s)
classes.
[50:48] (3048.32s)
Okay. So that's the entire thing. Let's
[50:51] (3051.20s)
shrink the main heading. Right after
[50:53] (3053.76s)
this one, we're going to put kind of
[50:55] (3055.92s)
like subtitle
[50:58] (3058.48s)
or like description, however you would
[51:00] (3060.72s)
like to call it. This is going to be a P
[51:03] (3063.44s)
tag. Let's paste this in. pretty basic
[51:07] (3067.28s)
CSS classes and that's what we have and
[51:11] (3071.12s)
right after this text we can put our
[51:13] (3073.44s)
buttons which are going to have like
[51:16] (3076.16s)
icons right one on this one and the
[51:18] (3078.64s)
other one here and for this we are going
[51:21] (3081.12s)
to be using lucid react say lucid react
[51:25] (3085.52s)
and this is something that chats already
[51:27] (3087.84s)
uses so we don't really need to install
[51:30] (3090.00s)
it so they have millions of different
[51:32] (3092.56s)
icons that we are going to be using
[51:34] (3094.96s)
let's see how we can just make that
[51:36] (3096.88s)
work. Right after this P tag, we had
[51:40] (3100.00s)
this div, right? Go below to it. So,
[51:43] (3103.12s)
we'll say CTA buttons. And let's say
[51:47] (3107.52s)
we're going to have a div. This is the
[51:49] (3109.52s)
parent. Class name will be flex. Flex of
[51:53] (3113.36s)
column. But in the small screens and
[51:56] (3116.40s)
above, they're going to be side by side.
[51:58] (3118.64s)
So, we can say flex direction will be
[52:00] (3120.96s)
row. And let's say gap of four so that
[52:04] (3124.08s)
they have some spacing. And we're going
[52:06] (3126.48s)
to make them to be sign up button.
[52:10] (3130.24s)
Let's say sign up button which are going
[52:12] (3132.64s)
to be coming from clerk.
[52:16] (3136.00s)
So import this one. Within this we'll
[52:18] (3138.64s)
have our button coming from chat cn. So
[52:22] (3142.00s)
just import that. Um here we'll say try
[52:26] (3146.32s)
voice chat
[52:29] (3149.28s)
or let's say try voice agent and for the
[52:34] (3154.08s)
button I think I'll just say size will
[52:36] (3156.88s)
be large and we would like to use an
[52:39] (3159.76s)
icon. So here I'll say make
[52:43] (3163.68s)
icon imported from blueid react and just
[52:47] (3167.28s)
self close it. Okay, so these are all
[52:50] (3170.16s)
the imports that we have. For the class
[52:53] (3173.04s)
name, I'll just say margin right of two,
[52:56] (3176.88s)
size of five. Okay, let's copy this and
[53:02] (3182.00s)
paste it right here. I'll just make the
[53:05] (3185.20s)
mode to be model.
[53:08] (3188.40s)
Let's do the same thing for this button.
[53:14] (3194.24s)
And the icon will be let's say calendar
[53:18] (3198.08s)
icon coming from Lucid React. Text is
[53:21] (3201.28s)
going to be book appointment.
[53:25] (3205.44s)
Okay. Now we can save and take a look at
[53:27] (3207.92s)
the end result. Well, let's make the
[53:30] (3210.72s)
colors
[53:32] (3212.56s)
to look like this. Right? We are going
[53:34] (3214.64s)
to change the variation on this one.
[53:37] (3217.20s)
I'll say variant could be outline.
[53:45] (3225.44s)
Okay. Now like they look exactly like
[53:49] (3229.60s)
this one. And if you click to them that
[53:52] (3232.48s)
would show you the model where you can
[53:55] (3235.68s)
basically sign up. Now we can even
[53:58] (3238.16s)
change the theme of this model so that
[54:00] (3240.56s)
it is looking like our actual
[54:02] (3242.88s)
application. Right? It is purple. It has
[54:05] (3245.36s)
the same styles. And to be able to make
[54:07] (3247.76s)
it work, we need to visit the layout.tsx
[54:11] (3251.28s)
file. And under this clerk provider, we
[54:15] (3255.04s)
will add the appearance, which is going
[54:18] (3258.32s)
to be an object. And we'll say
[54:21] (3261.28s)
variables. Again, this is another
[54:23] (3263.12s)
object. And we can say color primary.
[54:26] (3266.64s)
And we're going to use our variables.
[54:29] (3269.04s)
For this one, it'll be hash. let's say
[54:33] (3273.84s)
A53 and the rest of it I will just grab
[54:37] (3277.84s)
it from the source code which is four
[54:39] (3279.76s)
lines of code and put a comma.
[54:43] (3283.28s)
Okay, now this should be working out. If
[54:45] (3285.28s)
you click to here as you can tell we get
[54:48] (3288.00s)
the um the purple theme or sorry the
[54:51] (3291.76s)
orange theme right not the purple and it
[54:54] (3294.40s)
even says which one you use the last
[54:57] (3297.36s)
which is pretty cool. And finally on
[54:59] (3299.76s)
this left hand side we would like to put
[55:01] (3301.84s)
some testimonials right we're going to
[55:04] (3304.00s)
have five different user images and then
[55:06] (3306.64s)
this rating. So let's see how we can
[55:09] (3309.12s)
make that work. First we'll go under the
[55:12] (3312.48s)
hero.tsx.
[55:14] (3314.08s)
Let's go under this sign up button and
[55:16] (3316.48s)
under this div which are the CTA
[55:19] (3319.44s)
buttons. Um we'll just say user
[55:22] (3322.96s)
testimmonials
[55:25] (3325.68s)
and then we can have a div. Let's say
[55:27] (3327.76s)
class name is going to be padding top of
[55:30] (3330.32s)
eight. So we have some spacing and we'll
[55:32] (3332.88s)
have one more div within this. Let's say
[55:35] (3335.28s)
class name flex item centered
[55:39] (3339.44s)
say items center gap of six. And then
[55:44] (3344.32s)
now let's say the actual user avatars
[55:48] (3348.32s)
which are the images right um let me fix
[55:52] (3352.32s)
the typo avatars. Within this I'll have
[55:55] (3355.76s)
one div and we'll say class name will be
[55:59] (3359.12s)
flex and we'll say space x of three but
[56:03] (3363.52s)
we'll put minus at the beginning so that
[56:06] (3366.96s)
we have this um we have this effect
[56:10] (3370.08s)
right every single one of them will go
[56:12] (3372.24s)
to the left a bit so that like it is on
[56:14] (3374.96s)
top of each other and it looks pretty
[56:17] (3377.52s)
pretty fine right um here we will use
[56:21] (3381.04s)
the image component from next image
[56:24] (3384.96s)
And for the source, I'll be getting it
[56:27] (3387.04s)
from the Unsplash. What you can do,
[56:29] (3389.68s)
install the image, put it under the
[56:31] (3391.52s)
public. I think that would be maybe a
[56:34] (3394.56s)
better option, but in this case, I'll
[56:37] (3397.28s)
just have it just like this. Okay, so
[56:40] (3400.16s)
this is coming from Unsplash. And you
[56:42] (3402.64s)
can grab this from the source code if
[56:44] (3404.48s)
you don't want to type this out, which
[56:46] (3406.96s)
probably you don't you don't want to.
[56:49] (3409.28s)
This is insane. Just copy it and paste
[56:51] (3411.28s)
it from the source code. So, right after
[56:54] (3414.00s)
this image, I will put another one
[56:56] (3416.32s)
because we'll have five different
[56:58] (3418.00s)
images.
[56:59] (3419.68s)
Let me copy the three
[57:02] (3422.64s)
the rest the rest of them, right? Okay.
[57:05] (3425.68s)
So, in total, we have five different
[57:07] (3427.68s)
images. They are almost the same. The
[57:10] (3430.96s)
only thing that changes is the source
[57:12] (3432.88s)
and the alt.
[57:15] (3435.52s)
Okay. Now, one thing, if you take a look
[57:18] (3438.80s)
at the browser, it's going to crash
[57:21] (3441.12s)
because it says you're getting some
[57:22] (3442.88s)
images from Unsplash, but you didn't
[57:25] (3445.84s)
configure it under the next config. So,
[57:29] (3449.20s)
let's go under this file, and we're
[57:31] (3451.44s)
going to add our configuration into this
[57:33] (3453.92s)
object. So, let's say for the images, we
[57:37] (3457.68s)
would like to update this object where
[57:40] (3460.40s)
the remote patterns is going to be an
[57:42] (3462.64s)
array of objects. So let's say protocol
[57:47] (3467.12s)
protocol will be https
[57:49] (3469.92s)
and let's say host name is going to be
[57:53] (3473.12s)
images
[57:54] (3474.96s)
unsplash.com.
[57:57] (3477.84s)
So basically this says allow all the
[58:00] (3480.16s)
images are coming from unsplash to be
[58:03] (3483.52s)
optimized.
[58:05] (3485.76s)
Okay. Now we can see we got all these
[58:08] (3488.48s)
let me zoom in all these images with the
[58:11] (3491.36s)
speedful effect.
[58:13] (3493.60s)
Okay. Right next to it, we're going to
[58:15] (3495.44s)
put five stars and a text.
[58:23] (3503.68s)
Okay. So, let's scroll to the bottom.
[58:25] (3505.52s)
So, right after this latest image and
[58:28] (3508.48s)
this div, just go outside of it and I'll
[58:31] (3511.20s)
say rating and stats.
[58:35] (3515.60s)
And instead of typing this out, it is
[58:38] (3518.00s)
like 10 lines of code. I'll paste and
[58:40] (3520.56s)
walk through it. So we'll import the
[58:43] (3523.12s)
star icon. Basically we create five
[58:46] (3526.32s)
different of them which is height and
[58:49] (3529.28s)
width of four. And instead of doing this
[58:51] (3531.68s)
you can also say size of four. It is
[58:54] (3534.08s)
basically the exact same thing. And
[58:56] (3536.16s)
we're going to fill this in with this
[58:58] (3538.16s)
color and this is the text color and the
[59:00] (3540.96s)
rest is the content.
[59:03] (3543.36s)
And here we go. This is the output. So
[59:06] (3546.24s)
that was the entire left hand side. I
[59:08] (3548.40s)
think this is looking pretty cool. Um,
[59:10] (3550.56s)
now let's try to build the right hand
[59:12] (3552.56s)
side, which is just our image. And maybe
[59:15] (3555.76s)
we can put some of these,
[59:18] (3558.32s)
how do we call them? Like gradient
[59:21] (3561.04s)
decoratives right
[59:24] (3564.00s)
here. I'll just go ahead. Let's find the
[59:26] (3566.32s)
left hand side, which was this, the left
[59:29] (3569.28s)
content. Go outside of it, and we're
[59:32] (3572.08s)
going to put the right content. Now say
[59:35] (3575.92s)
content, which is our hero image. And
[59:39] (3579.76s)
this is going to get the div with the
[59:42] (3582.88s)
class name of let's say relative
[59:45] (3585.84s)
and let's say petting left will be eight
[59:49] (3589.20s)
only in the large screens and above. So
[59:51] (3591.76s)
within this div we're going to have one
[59:54] (3594.00s)
more div I believe.
[59:56] (3596.72s)
Let's say this is our decorative
[59:59] (3599.04s)
elements. I'll just copy and paste them.
[60:03] (3603.28s)
So here under this parent let's paste
[60:06] (3606.16s)
these. Grab them from the source code.
[60:08] (3608.80s)
I'll say
[60:10] (3610.96s)
uh maybe just gradient
[60:16] (3616.08s)
orbs. Okay, so these are the elements
[60:19] (3619.68s)
and then right below to it we're going
[60:21] (3621.52s)
to put our image. This is going to be
[60:24] (3624.56s)
self closed coming from next image.
[60:27] (3627.68s)
Source is going to be /hero.png.
[60:32] (3632.40s)
Um for the width I'll say 600. Let's
[60:36] (3636.16s)
duplicate it. height will be the same.
[60:38] (3638.88s)
Let's give it an old tag. Let's say um
[60:42] (3642.64s)
maybe dentwise
[60:48] (3648.40s)
And finally class name of full
[60:53] (3653.52s)
height. Let's say auto.
[60:58] (3658.48s)
Okay, I think this should be fine. Now
[61:01] (3661.28s)
let's see how that would look like.
[61:04] (3664.32s)
Okay, so that's pretty much it for the
[61:06] (3666.80s)
hero section. It looks exactly the same
[61:09] (3669.68s)
as what we have here in the end result.
[61:12] (3672.64s)
Now in the next part, we would like to
[61:15] (3675.52s)
build the how it works section, right?
[61:18] (3678.48s)
So this is something that we can build
[61:20] (3680.40s)
next. So here under the homepage, we
[61:23] (3683.76s)
will get the how it works component and
[61:27] (3687.20s)
keep building it. So here for the very
[61:30] (3690.08s)
first div let's actually convert this to
[61:32] (3692.72s)
be a section and then for the class
[61:35] (3695.60s)
names we'll just say something like
[61:37] (3697.52s)
relative let's say petting y of 32 x of
[61:42] (3702.72s)
six we'll say overflow should be hidden
[61:46] (3706.00s)
and then zindex of 10 I think maximum
[61:49] (3709.92s)
width of 7 xar and mx odo
[61:54] (3714.96s)
okay within this section we're going to
[61:57] (3717.28s)
have the header component first. So
[62:00] (3720.24s)
let's say this will be a div with the
[62:02] (3722.96s)
class name of text center and margin
[62:07] (3727.04s)
bottom of 20. Let's see where we are
[62:10] (3730.72s)
building at the moment. Okay, so it is
[62:12] (3732.96s)
this header section. Once we build it,
[62:15] (3735.68s)
we're going to build these three
[62:17] (3737.92s)
different steps, right? So let's first
[62:20] (3740.32s)
get this one.
[62:24] (3744.48s)
Within this, I'll have a div with some
[62:28] (3748.40s)
class names. Let me copy it and paste
[62:30] (3750.72s)
it. You can always type this out. I just
[62:33] (3753.44s)
don't want to waste any time because I
[62:35] (3755.92s)
have already I have already done this,
[62:38] (3758.72s)
right? I have written this already. So,
[62:40] (3760.96s)
you can pause the video, take a look at
[62:42] (3762.72s)
it. This will give some different
[62:45] (3765.20s)
colors, make it centered, things like
[62:47] (3767.68s)
that. And within this, we're going to
[62:49] (3769.60s)
have a zap icon which will look like
[62:52] (3772.64s)
this. This is coming from lucid react
[62:55] (3775.68s)
and this will take the class name let's
[62:58] (3778.16s)
say size of four and text will be
[63:01] (3781.44s)
primary right after this I'll put a span
[63:05] (3785.44s)
let's say simple process
[63:08] (3788.56s)
and let's give some classes
[63:12] (3792.16s)
I'll say text will be small font will be
[63:15] (3795.84s)
medium and then I think text color can
[63:19] (3799.12s)
be just primary as well okay save it And
[63:22] (3802.88s)
right after this span and after this div
[63:25] (3805.76s)
we'll go ahead create an H2 that is
[63:29] (3809.28s)
going to be this part.
[63:33] (3813.28s)
So let me provide this to you. It is
[63:36] (3816.00s)
actually like seven lines of code maybe.
[63:40] (3820.24s)
So these are all the classes to make it
[63:42] (3822.56s)
a little bit more responsive. A span and
[63:45] (3825.76s)
another one. And in the middle we have a
[63:48] (3828.16s)
line break. Right after this H2, we'll
[63:51] (3831.36s)
have a P tag. Let's say class name. Um,
[63:54] (3834.64s)
let's say text will be X large. Text
[63:57] (3837.92s)
will be muted foreground. Max width 3x
[64:02] (3842.56s)
large, which is equal to like 768
[64:06] (3846.96s)
pixels. Let's say MX auto.
[64:10] (3850.64s)
And I think for the leading, we can just
[64:12] (3852.80s)
say relaxed. And if you hover over this,
[64:16] (3856.08s)
you can see what that does. It'll make
[64:18] (3858.08s)
the line height to be equal to this
[64:20] (3860.48s)
value. And for the content, you can put
[64:23] (3863.36s)
anything but this is what I will have.
[64:29] (3869.44s)
Okay. So that's the content that I will
[64:31] (3871.44s)
just provide. Then after this div. So
[64:35] (3875.60s)
that was our header component, right?
[64:37] (3877.92s)
Let's shrink this. We'll go ahead and
[64:40] (3880.24s)
put our steps.
[64:43] (3883.04s)
Let's see the current output.
[64:46] (3886.48s)
Okay. So this is what we have just
[64:48] (3888.00s)
built. Now it is time to build these
[64:50] (3890.56s)
steps one by one. So once we build one
[64:53] (3893.68s)
of them we can just copy and paste and
[64:56] (3896.16s)
change the step the icon as well as the
[64:59] (3899.68s)
content right the styles are almost the
[65:02] (3902.56s)
same. And here I don't know if it is
[65:04] (3904.80s)
visible from the video but there is a
[65:07] (3907.12s)
connection line right it goes like from
[65:09] (3909.68s)
here all the way up to until the end.
[65:13] (3913.28s)
Let's create that first. So here within
[65:16] (3916.16s)
these steps let's first create a div.
[65:19] (3919.68s)
Let's say class name will be relative
[65:22] (3922.40s)
and then I'll just say connection line
[65:27] (3927.44s)
and I'll paste this in. This is coming
[65:29] (3929.92s)
from source code. We'll just have this
[65:32] (3932.40s)
connection line. Okay. Now let's try to
[65:35] (3935.84s)
build the steps one by one.
[65:39] (3939.44s)
I'll have a div.
[65:42] (3942.00s)
This will use the grid layout. So I'll
[65:44] (3944.40s)
say grid larger screens and above. We
[65:47] (3947.04s)
can just say grid colums of three and
[65:50] (3950.40s)
then I'll say gap of 12 and larger
[65:54] (3954.16s)
larger screens and above gap could be
[65:56] (3956.72s)
something like eight.
[65:59] (3959.52s)
Okay. Now let's say step one the very
[66:02] (3962.96s)
first step. This is going to be div
[66:07] (3967.20s)
class name of relative and let's say
[66:09] (3969.60s)
it's going to get the group class. Um
[66:13] (3973.44s)
within this we will have one more div
[66:16] (3976.24s)
again with a lot of classes and I would
[66:19] (3979.36s)
say just go ahead find the step one from
[66:22] (3982.16s)
the source code and just paste it. Now
[66:24] (3984.88s)
again this is not really a CSS course so
[66:27] (3987.44s)
let's not waste 2 hours 5 hours writing
[66:30] (3990.88s)
all these. So I'll just cut this. Okay,
[66:34] (3994.00s)
from the source code, find the step one.
[66:36] (3996.88s)
Copy that div and paste this in and
[66:39] (3999.76s)
import the image from next image. We are
[66:43] (4003.04s)
using audio.png.
[66:45] (4005.04s)
A lot of class names, but let's see how
[66:48] (4008.48s)
that look.
[66:50] (4010.96s)
Okay, so this is the very first step.
[66:53] (4013.28s)
When you hover over this styling
[66:55] (4015.12s)
changes, I think it looks pretty cool,
[66:58] (4018.24s)
but we don't really want to waste too
[66:59] (4019.84s)
much time. This is just styling. I hope
[67:02] (4022.48s)
you guys don't mind it. So that was the
[67:05] (4025.04s)
step one. Just uh let's do the same
[67:08] (4028.08s)
thing for step two as well. Here I'll go
[67:11] (4031.44s)
ahead say step two. Oops. What have I
[67:16] (4036.72s)
Let's say step two
[67:19] (4039.84s)
and I will paste it from the source code
[67:22] (4042.40s)
for the image. This time we are using
[67:24] (4044.48s)
brain.png.
[67:26] (4046.40s)
Let's save and let's get the step three
[67:29] (4049.52s)
as well.
[67:35] (4055.76s)
I am copying it from the source code and
[67:38] (4058.56s)
I'll just paste it.
[67:41] (4061.52s)
Okay, so imagine typing all these. I
[67:43] (4063.76s)
think it would easily take more than 20
[67:46] (4066.56s)
minutes. But here we go. We have the
[67:49] (4069.12s)
exact same output as we want to have.
[67:52] (4072.56s)
Now let's put this button. And I think
[67:54] (4074.96s)
that should be it.
[67:57] (4077.76s)
So we got the three different steps.
[68:00] (4080.40s)
Let's get outside of it.
[68:03] (4083.52s)
So, shrink the steps divot
[68:11] (4091.68s)
And let's try to have a div. And this
[68:14] (4094.96s)
div is going to have the class name of
[68:17] (4097.52s)
text center and let's say margin top of
[68:21] (4101.28s)
16. And I'll just get the sign up button
[68:25] (4105.44s)
which is going to be our beautiful
[68:27] (4107.36s)
button coming from chaten. and the icon
[68:30] (4110.80s)
from blue react. Okay, let's see how
[68:34] (4114.08s)
that look like.
[68:36] (4116.08s)
If you clicked it, you can get started
[68:38] (4118.64s)
by signing up. So that's why it is a
[68:41] (4121.20s)
sign up button in the first place. Okay,
[68:43] (4123.92s)
so that's it for the how it works
[68:46] (4126.16s)
section as well. I know that we have
[68:48] (4128.32s)
copied and pasted around 100 lines of
[68:51] (4131.04s)
code which were the steps, but I think
[68:53] (4133.60s)
that's completely fine. So we can
[68:55] (4135.52s)
basically get rid of this landing page
[68:58] (4138.56s)
as quickly as possible, right? Just
[69:01] (4141.12s)
build this entire page as quickly as
[69:03] (4143.36s)
possible so that so that we can get into
[69:05] (4145.84s)
the actual implementation like
[69:08] (4148.48s)
connecting to the voice agent, setting
[69:11] (4151.36s)
up our database, you know, sending
[69:13] (4153.60s)
emails,
[69:15] (4155.84s)
booking appointments, things like that.
[69:18] (4158.40s)
All right, so now we need to build the
[69:20] (4160.72s)
next section which is what to ask. So
[69:23] (4163.84s)
this is what I called it. This is the
[69:25] (4165.92s)
what to ask section and we're going to
[69:28] (4168.88s)
be building it under this component.
[69:34] (4174.00s)
Now just before we build it, let's take
[69:35] (4175.92s)
a look at the end result and analyze the
[69:38] (4178.80s)
UI elements that we have. So this is the
[69:41] (4181.52s)
component that we are going to be
[69:42] (4182.96s)
building. And this part is exactly the
[69:46] (4186.00s)
same what we have just built above,
[69:49] (4189.12s)
right? So this is the exact same thing
[69:51] (4191.68s)
with the badge, the title and then the
[69:55] (4195.12s)
description. So the content is just
[69:57] (4197.36s)
different and the icon. So we can just
[70:00] (4200.00s)
copy and paste that part. And the rest
[70:02] (4202.32s)
is also similar. Basically we have a
[70:05] (4205.28s)
left hand side and a right hand side
[70:08] (4208.40s)
just like what we had in the hero
[70:10] (4210.48s)
section. So instead of typing this out
[70:13] (4213.36s)
the entire section, I'll just copy and
[70:15] (4215.68s)
paste. Oops. So we'll just copy and
[70:18] (4218.16s)
paste this. If you want to, you can take
[70:20] (4220.48s)
a look at the source code. So, head over
[70:22] (4222.88s)
to what to ask.tsx file, copy the entire
[70:26] (4226.80s)
thing and just paste this in, which is
[70:29] (4229.28s)
like 150 lines of code. Um, if you
[70:32] (4232.80s)
wanted to, you can pause the video and
[70:34] (4234.96s)
just try to see the entire classes and
[70:38] (4238.08s)
try to learn by reading the code. But in
[70:41] (4241.60s)
this case, I'll just copy and paste and
[70:44] (4244.08s)
see the end result. Okay, so this is
[70:47] (4247.28s)
what we have at the moment for this
[70:49] (4249.36s)
section. So let's not waste any time.
[70:52] (4252.40s)
Please don't get mad just because we
[70:54] (4254.24s)
copy and paste. At this point, we are
[70:56] (4256.80s)
just duplicating the same classes over
[70:59] (4259.92s)
and over again. Okay, now we can go into
[71:03] (4263.44s)
the pricing section.
[71:05] (4265.76s)
And again, I'd like to just copy and
[71:08] (4268.08s)
paste it from the source code. Uh
[71:10] (4270.56s)
because this is only UI, it doesn't
[71:12] (4272.96s)
contain any logic at all. Let's just
[71:16] (4276.00s)
save. So this is the entire code and see
[71:19] (4279.20s)
the output. So right after this section
[71:22] (4282.56s)
we have the pricing section. Just like
[71:25] (4285.04s)
previously we are using the crit
[71:26] (4286.96s)
background like in the hero section.
[71:30] (4290.88s)
This this is like a little bit different
[71:33] (4293.52s)
uh but the like idea is the same. Then
[71:36] (4296.56s)
we have three different cards. One of
[71:38] (4298.80s)
them is highlighted because this is the
[71:41] (4301.84s)
most popular plan. Um, so yeah, that's
[71:45] (4305.60s)
the entire thing. Again, please don't
[71:48] (4308.00s)
get mad just because we copy and paste.
[71:50] (4310.40s)
I just want to complete this entire
[71:52] (4312.40s)
homepage as quickly as possible so that
[71:55] (4315.20s)
we can get into the actual logic. Okay.
[71:58] (4318.80s)
Now, let's get the call to action
[72:01] (4321.36s)
section as well, which is this part.
[72:05] (4325.20s)
Again, we have a left hand side and then
[72:07] (4327.68s)
a right hand side.
[72:10] (4330.64s)
So, I will go into the source code. I
[72:12] (4332.96s)
will find the call to action file which
[72:17] (4337.44s)
is this one and I'll delete everything.
[72:20] (4340.08s)
Paste that in. It is less than 100 lines
[72:22] (4342.80s)
of code. Okay, let's see.
[72:27] (4347.60s)
This is like this is the exact same
[72:29] (4349.68s)
thing that we have in the demo. And
[72:32] (4352.00s)
finally, let's get the footer element.
[72:36] (4356.08s)
So under the footer.tsx tsx under the
[72:39] (4359.52s)
source code. Just go ahead copy and
[72:43] (4363.76s)
and it should be working out as
[72:45] (4365.44s)
expected. All right, so that's it for
[72:47] (4367.92s)
the entire landing page. I know that it
[72:50] (4370.24s)
was a little bit annoying to copy and
[72:52] (4372.32s)
paste at all, but I see a lot of
[72:54] (4374.24s)
comments where people say just don't
[72:56] (4376.88s)
spend too much time on styling rather
[72:59] (4379.60s)
just copy and paste, you know, just
[73:02] (4382.32s)
handle UI as quickly as possible so that
[73:04] (4384.88s)
we can see the actual full stack
[73:07] (4387.04s)
development and that's what we try to
[73:09] (4389.44s)
do. We have a really nice looking
[73:11] (4391.44s)
landing page. I think we built this in
[73:13] (4393.92s)
in 1 hour or something, right? Um in the
[73:17] (4397.84s)
next sections we can get started with
[73:19] (4399.92s)
the database connections you know
[73:22] (4402.56s)
creating a schema explaining more about
[73:25] (4405.20s)
Prisma handling the payments things like
[73:28] (4408.00s)
that. Okay. So I hope you enjoyed this
[73:30] (4410.16s)
entire section. Just before we end it
[73:32] (4412.48s)
let's add a commit create a new branch
[73:35] (4415.28s)
and publish our changes. So to create a
[73:38] (4418.88s)
new branch let's go into the VS Code.
[73:41] (4421.76s)
close everything and I'll press
[73:43] (4423.84s)
commandshiftp
[73:45] (4425.36s)
and I'll just say toggle status bar
[73:47] (4427.44s)
visibility. Okay, so from here let's
[73:50] (4430.08s)
just say create a new branch and for the
[73:53] (4433.44s)
name I'll just say landing page. You can
[73:56] (4436.80s)
call anything but this is the name that
[73:58] (4438.80s)
I'll be going with. Now the branch has
[74:01] (4441.68s)
been created right we would like to
[74:04] (4444.16s)
stage uh stage all of our changes
[74:08] (4448.40s)
and let's put a commit message. I'll say
[74:10] (4450.80s)
something like blending page completed
[74:15] (4455.36s)
and we'll just say commit this change.
[74:18] (4458.16s)
Now this has been committed. Let's say
[74:20] (4460.08s)
publish this branch so that it's going
[74:22] (4462.40s)
to be pushed to GitHub. Let's say then
[74:28] (4468.64s)
like find the uh source code on the
[74:31] (4471.60s)
GitHub. Okay. So here we can see just
[74:34] (4474.32s)
two seconds ago we got a recent pull
[74:37] (4477.12s)
request. So let's say go ahead create
[74:40] (4480.32s)
this pull request and here it says you
[74:43] (4483.36s)
are putting all your changes from
[74:45] (4485.52s)
landing to the master branch. This is
[74:48] (4488.32s)
what we would like to do right. So let's
[74:50] (4490.24s)
pretty quickly see this. Um here
[74:53] (4493.20s)
basically we had our master branch right
[74:56] (4496.72s)
we had some commits in the past. Now we
[74:59] (4499.92s)
just created a new commit but we have
[75:02] (4502.56s)
created this under the separate branch
[75:06] (4506.08s)
and we called this as landing page right
[75:10] (4510.00s)
so that was our branch now we are done
[75:12] (4512.72s)
with it we committed our change now we
[75:15] (4515.36s)
would like to take all these changes and
[75:18] (4518.00s)
put it into the master right because
[75:20] (4520.88s)
like we are done with it and we want to
[75:22] (4522.96s)
have this in in the actual code in the
[75:26] (4526.72s)
actual branch so this is what we are we
[75:29] (4529.36s)
just send a pull request. So I'll say
[75:32] (4532.24s)
create the pull request
[75:34] (4534.80s)
and imagine you have another teammate in
[75:37] (4537.60s)
your team which is the senior developer.
[75:40] (4540.16s)
He will come here and take a look at the
[75:42] (4542.56s)
commit. Um it'll just see all the
[75:45] (4545.28s)
changes that you have.
[75:47] (4547.92s)
So like they're going to see you have
[75:50] (4550.16s)
updated the next config.ts, you have
[75:53] (4553.04s)
added all these files and they will
[75:55] (4555.28s)
analyze your code. If they don't like
[75:57] (4557.44s)
it, they can add a comment like please
[76:01] (4561.28s)
fix this part. Make this code a little
[76:03] (4563.04s)
bit more clean and they'll just say
[76:05] (4565.68s)
start a review. Once you are done with
[76:08] (4568.48s)
it, you're going to update your code and
[76:10] (4570.80s)
you will send another commit. Right? And
[76:13] (4573.20s)
if they accept it, they'll just say
[76:15] (4575.36s)
merge the pull request. Okay? So you can
[76:18] (4578.16s)
think of it like this is how that would
[76:20] (4580.40s)
work in a real world application, right?
[76:23] (4583.28s)
in a teammate work in teamwork I mean
[76:26] (4586.88s)
let's go into the source code we have
[76:30] (4590.24s)
under the pull requests so this is the
[76:33] (4593.12s)
one that we had now this is open right
[76:36] (4596.40s)
your teammate would say something like
[76:38] (4598.40s)
merge the pull request if they if they
[76:41] (4601.44s)
want to merge this right once you click
[76:43] (4603.84s)
to this it would take the latest changes
[76:46] (4606.96s)
and put it into the master branch so
[76:49] (4609.52s)
your code would be so your master branch
[76:52] (4612.32s)
would be up to date with the landing
[76:54] (4614.64s)
page branch. So, I hope that makes
[76:56] (4616.80s)
sense. At the beginning, it is kind of
[76:58] (4618.64s)
confusing if you have never used this
[77:00] (4620.56s)
workflow, but you better get used to it
[77:02] (4622.88s)
because this is what you're going to be
[77:04] (4624.48s)
using when you join to a company, right?
[77:07] (4627.68s)
And here in this case, I have a code
[77:09] (4629.68s)
rabbit that is going to review my code
[77:12] (4632.72s)
and tell me if there is anything that
[77:14] (4634.64s)
I'm doing wrong or any kind of
[77:17] (4637.36s)
suggestion that we would need. And if
[77:19] (4639.28s)
you would like to integrate it for free
[77:21] (4641.28s)
just like what I have done. I'll leave
[77:23] (4643.12s)
the link in the description. Go ahead
[77:24] (4644.88s)
and sign up with your GitHub account and
[77:27] (4647.20s)
you're going to get the first 14 day for
[77:30] (4650.00s)
completely free. Um just give it a go
[77:32] (4652.32s)
and test it out. This is what I have
[77:34] (4654.56s)
done. So you can basically think of it
[77:36] (4656.72s)
like an AI senior developer in your
[77:39] (4659.36s)
team. Whenever you have something bad in
[77:42] (4662.08s)
your code like some something that
[77:44] (4664.48s)
should be fixed or if you are not
[77:46] (4666.64s)
following some best practices, this is
[77:48] (4668.88s)
going to give you those suggestions
[77:51] (4671.36s)
which are the things that we'll get into
[77:53] (4673.12s)
in a second. And whenever you send a
[77:55] (4675.52s)
pull request uh before you merge it,
[77:58] (4678.72s)
right? Just before you say merge the
[78:00] (4680.96s)
pull request, you should be able to see
[78:03] (4683.12s)
all the things that you have in your
[78:04] (4684.72s)
code that is not okay, right? And uh
[78:08] (4688.72s)
like if you're doing some wipe coding
[78:11] (4691.20s)
recently they have this CLI feature
[78:14] (4694.40s)
basically if you're using something like
[78:16] (4696.72s)
cloud code or cursor or any kind of
[78:20] (4700.00s)
similar agentic tools u you can
[78:22] (4702.80s)
integrate it pretty easily and before
[78:25] (4705.12s)
you commit your changes in your terminal
[78:27] (4707.84s)
it'll tell you like what is wrong with
[78:30] (4710.48s)
your code and what you should fix. Okay.
[78:33] (4713.44s)
So this is definitely something that I
[78:35] (4715.20s)
have recently. This is my basically
[78:37] (4717.52s)
favorite tool and they they were kind
[78:39] (4719.68s)
enough to sponsor this video. Okay, so
[78:42] (4722.40s)
here we can see we got the summary by
[78:44] (4724.64s)
code rabbit in this pull request. Here
[78:47] (4727.12s)
are the new features that we have added
[78:49] (4729.36s)
the styling and refactoring that we have
[78:52] (4732.40s)
done. Then if you want to see the file
[78:54] (4734.96s)
changes pretty quickly, you can just see
[78:57] (4737.92s)
uh they have like really I don't know
[79:00] (4740.48s)
how to say but really small tiny
[79:03] (4743.04s)
summaries summaries of every single file
[79:06] (4746.00s)
right and then they even have sequence
[79:08] (4748.24s)
diagram like what happens if user clicks
[79:10] (4750.96s)
to the login or sign up button things
[79:14] (4754.16s)
like that but we are not really
[79:16] (4756.16s)
interested in this one at the moment.
[79:18] (4758.00s)
Let's scroll to the bottom and see some
[79:20] (4760.72s)
potential issues and their fixes. Right
[79:23] (4763.44s)
here it says anchor targets missing for
[79:26] (4766.48s)
navbar links. Well, that's correct. So,
[79:29] (4769.52s)
here under the page.tsx.
[79:34] (4774.48s)
Um, here it says you should wrap the how
[79:37] (4777.44s)
it works component with a section and
[79:40] (4780.24s)
give it an ID so that when you click to
[79:42] (4782.64s)
this, it should take you to that
[79:45] (4785.36s)
section. Right? This is something that
[79:47] (4787.04s)
you can definitely do. I will skip this
[79:49] (4789.68s)
so that we don't really waste any time.
[79:52] (4792.00s)
But basically, this will always tell you
[79:54] (4794.32s)
what you're doing wrong in your code. So
[79:57] (4797.04s)
again, we have something else. It says
[80:00] (4800.16s)
wire the CTA buttons to real actions.
[80:03] (4803.04s)
Currently, it says I think this is not
[80:05] (4805.20s)
doing anything or both CTA buttons
[80:08] (4808.24s)
render without any navigation or
[80:09] (4809.92s)
handler. So they do nothing when
[80:12] (4812.08s)
clicked. So that's correct. This is the
[80:14] (4814.56s)
button where we have under the let's see
[80:18] (4818.24s)
the CTA.tsx.
[80:20] (4820.88s)
Let's go ahead and find that component.
[80:23] (4823.44s)
So we have CTA
[80:26] (4826.72s)
and we have some buttons but when you
[80:28] (4828.56s)
click to it nothing happens. Right? So
[80:30] (4830.72s)
this is something that you can fix as
[80:32] (4832.64s)
well but I'm just going to skip that.
[80:35] (4835.12s)
Now imagine if you have something
[80:37] (4837.28s)
security related in your code. Um code
[80:40] (4840.32s)
rabbit will just find it and it'll tell
[80:42] (4842.24s)
you hey you have a potential issue go
[80:44] (4844.80s)
ahead fix this uh fix this part in your
[80:47] (4847.44s)
code so that your code is safe to
[80:49] (4849.84s)
interact with. Okay so these are all the
[80:52] (4852.88s)
things it is mostly related to links
[80:55] (4855.44s)
where they don't they don't really do
[80:57] (4857.76s)
anything. So, in this case, I'll just go
[80:59] (4859.68s)
ahead and merge the pull request. And
[81:02] (4862.80s)
we're going to be using this a lot later
[81:04] (4864.72s)
in the video. Okay. Um, I'll just say
[81:08] (4868.08s)
confirm the merge
[81:11] (4871.36s)
and then we'll go into our codebase in
[81:14] (4874.48s)
VS Code. Let's say switch to the master
[81:17] (4877.76s)
branch. Now, we are going to see that
[81:20] (4880.56s)
most of the things that we have done are
[81:22] (4882.72s)
gone, right? So, we don't really have
[81:25] (4885.84s)
let's go into the page. we don't really
[81:27] (4887.92s)
have the latest changes. So you can
[81:30] (4890.16s)
click to this. This will basically get
[81:32] (4892.64s)
all the latest changes from the master
[81:35] (4895.04s)
branch. Okay. So these are the things
[81:37] (4897.36s)
that we have just done. Now our codebase
[81:39] (4899.92s)
is up to date. These are the latest
[81:42] (4902.24s)
changes that we have done. And just to
[81:45] (4905.60s)
let you know, Code Rebbit also has a VS
[81:48] (4908.32s)
code extension. This is what I'm using
[81:50] (4910.24s)
as well. Um like feel free to check it
[81:52] (4912.96s)
out. It should be somewhere here. again
[81:56] (4916.64s)
uh if you use the link in description I
[81:58] (4918.56s)
think you're going to get 14 days for
[82:00] (4920.80s)
free for this extension as well. Okay,
[82:03] (4923.68s)
so with that hopefully I'll see you in
[82:05] (4925.52s)
the next section.
[82:07] (4927.68s)
In this section we are going to get
[82:09] (4929.36s)
started with our database. So currently
[82:12] (4932.08s)
we have a project which has a database
[82:14] (4934.96s)
but within this we don't really have any
[82:17] (4937.12s)
data or any tables right so we would
[82:19] (4939.76s)
like to create a schema and to be able
[82:22] (4942.16s)
to do it we are going to be using
[82:24] (4944.08s)
Prisma. Now you might be asking what is
[82:26] (4946.32s)
Prisma in the first place? Well, it is
[82:28] (4948.48s)
an RM which stands for object relational
[82:31] (4951.04s)
mapping. I know this sounds complicated
[82:33] (4953.20s)
if you have never used it. So you can
[82:35] (4955.44s)
basically think of Prisma as a
[82:38] (4958.08s)
translator between your code and your
[82:40] (4960.88s)
database. So Prisma is not your
[82:43] (4963.52s)
database, right? It is just a tool that
[82:46] (4966.08s)
allows you to communicate with your
[82:48] (4968.48s)
database. So instead of writing complex
[82:51] (4971.28s)
SQL code, you will write some JavaScript
[82:54] (4974.24s)
code or in this case TypeScript code and
[82:57] (4977.20s)
that is going to translate it for your
[82:59] (4979.68s)
database. Okay, so this is not your
[83:01] (4981.68s)
database. This is Prisma. This is some
[83:04] (4984.00s)
tool that we're going to be using on the
[83:05] (4985.68s)
back end that is going to allow us to,
[83:08] (4988.32s)
you know, communicate with our database
[83:10] (4990.40s)
to get some data, delete, update, things
[83:12] (4992.96s)
like that. And here I have an example
[83:15] (4995.28s)
that I can show you. So what happens if
[83:18] (4998.00s)
you don't use Prisma and if you use
[83:19] (4999.92s)
Prisma like what is the differences?
[83:22] (5002.24s)
Okay. So here without using Prisma let's
[83:24] (5004.72s)
say you want to create a user. So you
[83:27] (5007.12s)
would basically write some row SQL code
[83:30] (5010.32s)
like insert into the users table. Here
[83:33] (5013.44s)
are the fields and the values. And here
[83:36] (5016.80s)
we go the values for the email and for
[83:39] (5019.20s)
the name. But if we take a look at the
[83:41] (5021.28s)
example where we use Prisma it's really
[83:44] (5024.48s)
beginner friendly right? You say hey
[83:46] (5026.64s)
Prisma go ahead under the user table and
[83:49] (5029.44s)
create some data right and here is the
[83:51] (5031.92s)
email and here is the name. So this
[83:54] (5034.24s)
looks more like JavaScript, right?
[83:56] (5036.40s)
That's why we say object relational
[83:58] (5038.88s)
mapping
[84:00] (5040.40s)
and another example. So these are basic
[84:02] (5042.56s)
examples but I think you get the point.
[84:05] (5045.52s)
So instead of saying select all from the
[84:07] (5047.92s)
users where email is equal to the email
[84:11] (5051.28s)
that we are getting as an argument
[84:13] (5053.20s)
instead you would say hey Prisma go
[84:15] (5055.68s)
ahead under the users table find first
[84:18] (5058.48s)
with this email. Right? So this is a lot
[84:20] (5060.80s)
more convenient and as the example gets
[84:23] (5063.68s)
larger and more complex like Prisma is
[84:26] (5066.96s)
really beginner friendly. Okay. So
[84:29] (5069.68s)
that's why we're going to be using it
[84:31] (5071.36s)
and there is something called Prisma
[84:33] (5073.20s)
schema. This is where you can define
[84:35] (5075.36s)
your database design and this is what
[84:37] (5077.20s)
we're going to be doing in a second. So
[84:39] (5079.12s)
in this file you will have like which
[84:41] (5081.52s)
tables you want to have right which
[84:43] (5083.68s)
fields do you want the relations etc.
[84:46] (5086.96s)
right? all kinds of things that you are
[84:48] (5088.64s)
going to define in this file. So first
[84:51] (5091.28s)
let's go ahead and uh install some
[84:54] (5094.08s)
packages. I'll open up my terminal.
[84:56] (5096.72s)
First we'll say mpm install prisma and
[84:59] (5099.84s)
let's use a specific version. So it is
[85:02] (5102.24s)
going to be let's say 6.16.2.
[85:06] (5106.16s)
This is the latest version as I am
[85:08] (5108.24s)
recording this video. But in the future
[85:10] (5110.16s)
you will have major updates. But if you
[85:12] (5112.64s)
use this one um like your code is going
[85:14] (5114.96s)
to work even if you're watching this in
[85:16] (5116.96s)
the future, right? And now let's say
[85:18] (5118.88s)
d-savedev.
[85:21] (5121.04s)
This is going to be a dev dependency. So
[85:24] (5124.24s)
let's go ahead and install it. Then
[85:26] (5126.88s)
we're going to run couple of different
[85:28] (5128.64s)
scripts. Now let's actually install one
[85:30] (5130.88s)
more package. Let's say mpm install at
[85:33] (5133.76s)
prisma/client.
[85:36] (5136.40s)
And again let's use the same version
[85:38] (5138.80s)
6.16.2.
[85:41] (5141.52s)
Um this is going to be an actual
[85:45] (5145.12s)
dependency right not for the uh
[85:47] (5147.60s)
development. So just go ahead run this.
[85:51] (5151.28s)
So this is what we what we are going to
[85:53] (5153.04s)
be using in the development and this is
[85:54] (5154.96s)
what will be used in the production.
[85:57] (5157.36s)
Okay. Now let's run mpx prisma init
[86:03] (5163.84s)
like this. So this is going to create a
[86:07] (5167.28s)
folder with a file and let's see. Okay,
[86:11] (5171.92s)
so a couple of different things
[86:13] (5173.36s)
happened. We have this Prisma folder
[86:15] (5175.92s)
with the schema.prisma file, right? So
[86:18] (5178.96s)
this is where we can define our tables.
[86:21] (5181.76s)
Um in this example, we are using
[86:23] (5183.44s)
Postgress and our environment variable
[86:25] (5185.68s)
is database URL, right? So this is the
[86:28] (5188.72s)
exact same thing that we have. Um now
[86:31] (5191.92s)
let's go here. I think I'll delete the
[86:34] (5194.16s)
output. We don't really need it. Uh I
[86:36] (5196.24s)
don't really want this to be generated
[86:37] (5197.92s)
under this location. So I'll just go
[86:40] (5200.24s)
ahead delete that. Um this should be
[86:43] (5203.04s)
fine. Now we are going to define our
[86:46] (5206.24s)
tables. So we're going to have actually
[86:48] (5208.48s)
three different tables. One for the
[86:50] (5210.96s)
user, right? One for the doctors and one
[86:54] (5214.56s)
for the appointments.
[86:57] (5217.20s)
So these are like all the three models
[86:59] (5219.60s)
that we are going to create. Now let's
[87:01] (5221.84s)
get started with the user model. So to
[87:04] (5224.80s)
create a new model or a table you would
[87:07] (5227.84s)
say model and whatever the name is. In
[87:10] (5230.56s)
this case we are going to say user.
[87:12] (5232.32s)
Okay. So this is the convention that you
[87:14] (5234.00s)
would be doing. And then every single
[87:16] (5236.32s)
user is going to have an ID field. So
[87:19] (5239.04s)
let's say type of this is going to be
[87:20] (5240.96s)
string and this is going to be a primary
[87:23] (5243.60s)
key. So we'll just add the ID. And then
[87:26] (5246.32s)
let's say by default you can give this a
[87:28] (5248.64s)
value which could be a UU ID or there is
[87:32] (5252.08s)
a better option CU ID. Okay. So this is
[87:35] (5255.36s)
what we're going to have. And once you
[87:37] (5257.60s)
save it should format. I was going to
[87:40] (5260.40s)
say but looks like it doesn't. I think
[87:42] (5262.96s)
that's fine. It's already formatted. Um
[87:45] (5265.84s)
if it doesn't in your case go ahead find
[87:48] (5268.72s)
this extension. I think let me just
[87:51] (5271.52s)
shrink those. I have an extension called
[87:54] (5274.96s)
Prisma I believe. Okay. So you should
[87:58] (5278.16s)
install this and your code should be
[88:00] (5280.88s)
highlighted as I have.
[88:04] (5284.08s)
Okay. Then every single user is going to
[88:06] (5286.64s)
have an email field, right? So let's say
[88:09] (5289.04s)
type of this is also going to be string
[88:11] (5291.52s)
and this has to be unique, right? And we
[88:14] (5294.80s)
can even format this in a better way
[88:17] (5297.44s)
just like that. Um, every single user,
[88:20] (5300.08s)
let's say, is going to have a first
[88:23] (5303.92s)
And let's say this is going to be type
[88:25] (5305.60s)
of string. And if you want to make this
[88:27] (5307.76s)
to be optional, you can add a question
[88:29] (5309.84s)
mark. And let's duplicate this. We're
[88:32] (5312.48s)
going to get the last name as well.
[88:34] (5314.80s)
These fields are going to be coming
[88:36] (5316.72s)
from, let's say, clerk, right? And then
[88:39] (5319.92s)
let's say every single user could have a
[88:42] (5322.80s)
phone field. Again, I'll just say this
[88:45] (5325.52s)
will be string but optional. And then
[88:48] (5328.32s)
when users are signed up, we would like
[88:50] (5330.56s)
to have the created at field. Let's say
[88:53] (5333.84s)
this is going to be type of date time.
[88:56] (5336.40s)
And by default, it's going to be equal
[88:58] (5338.96s)
to now, right? Once they sign up, that's
[89:01] (5341.84s)
going to be that time. So that we can
[89:03] (5343.76s)
show something like user is member since
[89:08] (5348.16s)
let's say September 2025, right? This is
[89:11] (5351.04s)
a create that date. Now in the same way,
[89:13] (5353.76s)
I'll just duplicate this. Let's say this
[89:16] (5356.48s)
time this is going to be updated at. So
[89:19] (5359.92s)
these are the fields that you would like
[89:21] (5361.44s)
to have. Um so let's say it's going to
[89:24] (5364.48s)
be daytime and updated at. Okay. So once
[89:30] (5370.08s)
you like if you're using Prisma for the
[89:32] (5372.32s)
very first time, this could look a
[89:34] (5374.48s)
little bit complicated, but I think as
[89:36] (5376.72s)
you use it more and more, it just feels
[89:39] (5379.04s)
like a baby tool. Uh one more thing on
[89:42] (5382.08s)
every single user we would like to have
[89:44] (5384.64s)
a field called cler ID. Um I'll explain
[89:48] (5388.96s)
why but for now let's just say type will
[89:51] (5391.36s)
be string and this is also going to be
[89:54] (5394.08s)
unique. Now you might be asking why do
[89:56] (5396.32s)
we need this field in the first place.
[89:58] (5398.32s)
So here's a quick diagram that I have.
[90:00] (5400.88s)
So basically when users sign up first
[90:03] (5403.60s)
they are stored in clerk dashboard
[90:06] (5406.16s)
right. So at the moment we have this
[90:08] (5408.16s)
user who is signed up but it is not in
[90:10] (5410.96s)
the database it is just on the clerk
[90:13] (5413.60s)
right clerk stores them for the
[90:15] (5415.76s)
authentication and at some point we
[90:18] (5418.16s)
would like to take this user and also
[90:20] (5420.56s)
save it to our database and once we save
[90:23] (5423.60s)
we would like to know which user this is
[90:26] (5426.16s)
right so we're going to have the clerk
[90:28] (5428.24s)
ID which is let's see so you can have
[90:32] (5432.40s)
like copy user ID if you copy it like
[90:35] (5435.36s)
this is the clerk ID for this user.
[90:38] (5438.16s)
Okay, so I hope that's kind of like this
[90:40] (5440.24s)
will make sense in a couple of minutes.
[90:42] (5442.40s)
But basically this is the thing that we
[90:45] (5445.20s)
want to have once we have the user in
[90:47] (5447.44s)
the database. We would like to have this
[90:49] (5449.60s)
field so that we know which user this is
[90:52] (5452.64s)
in the clerk dashboard. Okay. So
[90:55] (5455.52s)
basically to identify the users.
[90:59] (5459.20s)
So with this I think that's it for the
[91:01] (5461.28s)
user model at the moment. Let's save and
[91:04] (5464.80s)
create another model. So we're going to
[91:07] (5467.12s)
have the doctor model
[91:11] (5471.68s)
as well as the appointments. So first
[91:14] (5474.88s)
let's get started with this one. And
[91:16] (5476.80s)
just before we build it, let's try to
[91:18] (5478.64s)
format our code a bit. So basically I'll
[91:21] (5481.20s)
press tab until they are all in the same
[91:27] (5487.12s)
Okay. Now for the doctors, we are going
[91:29] (5489.36s)
to have couple of different fields. Let
[91:31] (5491.52s)
me just copy them and paste them.
[91:33] (5493.68s)
Basically, they are the exact same thing
[91:35] (5495.52s)
that we have above, right? Every doctor
[91:38] (5498.08s)
is going to have an ID which is type of
[91:40] (5500.16s)
string, primary key and by default
[91:43] (5503.36s)
Prisma can decide it. Then like each
[91:46] (5506.56s)
doctor is going to have a name, email
[91:48] (5508.72s)
which should be unique, own,
[91:50] (5510.72s)
specialtity, bio and image URL. Bio
[91:54] (5514.16s)
could be optional, right? And then we
[91:56] (5516.40s)
could add a gender for every single
[91:58] (5518.56s)
doctor. Now, instead of saying type of
[92:00] (5520.80s)
string, we would like to have an enum
[92:03] (5523.28s)
called gender, right? And let's try to
[92:05] (5525.76s)
create it. I'll just scroll to the
[92:07] (5527.68s)
bottom. Let's say enum. Um, here I'll
[92:10] (5530.56s)
say gender.
[92:12] (5532.56s)
And enum is basically something that you
[92:14] (5534.88s)
want to have some specific values,
[92:17] (5537.20s)
right? So, here I'll say male and
[92:20] (5540.00s)
female, right? Okay. Now, this has two
[92:22] (5542.56s)
different values. You can hover over
[92:25] (5545.68s)
this. I think it doesn't show us but
[92:28] (5548.48s)
basically the gender is going to be one
[92:30] (5550.56s)
of them. If you try to put something
[92:32] (5552.32s)
else your code is going to break right
[92:35] (5555.04s)
uh it's going to throw some errors and
[92:37] (5557.36s)
then a doctor could be active or not. So
[92:40] (5560.80s)
basically admin can deactivate their
[92:43] (5563.44s)
their accounts. So for that reason we'll
[92:46] (5566.24s)
just have this is active boolean
[92:49] (5569.76s)
and by default this could be equal to
[92:52] (5572.16s)
true. So let's say the default value is
[92:54] (5574.72s)
going to be true. And finally, just like
[92:57] (5577.60s)
above, we would like to have the created
[92:59] (5579.84s)
at and updated at fields. Now that's it
[93:02] (5582.96s)
for the doctor and user uh models. We
[93:06] (5586.48s)
would like to create the appointment
[93:08] (5588.80s)
table as well, right? Let's say model
[93:11] (5591.76s)
appointment.
[93:14] (5594.16s)
Okay. Now, this is going to have
[93:16] (5596.64s)
actually some relationships with these
[93:19] (5599.44s)
other tables, right? because a user
[93:22] (5602.32s)
could have multiple different
[93:24] (5604.32s)
appointments and a doctor also can has
[93:26] (5606.88s)
some like can have some appointments. So
[93:29] (5609.68s)
to give you an example, let's say John
[93:32] (5612.24s)
has booked an appointment for Dr. Jane.
[93:35] (5615.76s)
Now John is associated with this
[93:38] (5618.40s)
appointment and same as Dr. Jane, right?
[93:42] (5622.08s)
They are both part of this appointment.
[93:44] (5624.56s)
So we need we need to like set up some
[93:47] (5627.92s)
um relationships and let's see how we
[93:50] (5630.00s)
can make that work. So first off let's
[93:52] (5632.40s)
put the fields that the appointment
[93:54] (5634.72s)
table is going to have. So every single
[93:57] (5637.12s)
appointment as usual is going to have
[93:59] (5639.28s)
the ID field the date time and we're
[94:02] (5642.88s)
going to store it as string duration by
[94:06] (5646.00s)
default is going to be 30 in minutes
[94:08] (5648.64s)
right and this is type of integer and
[94:11] (5651.20s)
then the status of the appointment. Now
[94:14] (5654.16s)
this is going to be a type of enum.
[94:17] (5657.12s)
Let's say appointment
[94:20] (5660.24s)
status and we're going to create it.
[94:23] (5663.04s)
Let's go ahead actually duplicate this.
[94:25] (5665.84s)
And the name of this enum is appointment
[94:28] (5668.40s)
status. And it's going to have two
[94:30] (5670.56s)
different values. So it will be either
[94:32] (5672.88s)
confirmed
[94:34] (5674.40s)
or completed.
[94:37] (5677.44s)
So when you first book an appointment,
[94:39] (5679.84s)
it's going to be confirmed. And once you
[94:42] (5682.16s)
are done with that appointment, it's
[94:44] (5684.32s)
going to be equal to completed. We're
[94:46] (5686.48s)
going to see it later in the video, but
[94:48] (5688.40s)
that's that's it for now. Let's say the
[94:50] (5690.72s)
type of this by default is going to be
[94:53] (5693.44s)
confirmed. And you cannot put anything
[94:55] (5695.76s)
else here. Your code is going to break.
[94:58] (5698.40s)
So it could be either completed or
[95:01] (5701.52s)
confirmed in this case. Um then we can
[95:04] (5704.56s)
have some field. Let me format this. So,
[95:08] (5708.40s)
we're going to have a field for the
[95:10] (5710.64s)
note, right? Notes like it's going to be
[95:14] (5714.16s)
it's going to be optional. You don't
[95:15] (5715.60s)
really have to put this, but if you
[95:18] (5718.32s)
wanted to, you can add and the reason
[95:20] (5720.64s)
for the appointment. So, this is going
[95:22] (5722.80s)
to be either like teeth cleaning,
[95:25] (5725.20s)
emergency visit, consultation, things
[95:28] (5728.08s)
like that. Um, as usual, we're going to
[95:30] (5730.88s)
put the created at as well as the
[95:33] (5733.28s)
updated ad fields. Since we have done
[95:35] (5735.52s)
this multiple times, instead of typing
[95:37] (5737.52s)
it out, I'm just copying and pasting.
[95:40] (5740.00s)
Okay. Now, as I said, an appointment is
[95:42] (5742.80s)
going to be related to a user as well as
[95:45] (5745.44s)
a doctor, right? So, here I'll just say
[95:47] (5747.84s)
foreign keys and we'll say user ID,
[95:51] (5751.36s)
which is going to be type of string and
[95:54] (5754.56s)
duplicate it. We'll also have a doctor
[95:57] (5757.76s)
ID that is related to this appointment.
[96:00] (5760.96s)
And let's put the relationships
[96:05] (5765.84s)
Okay. So, we'll say a user will be
[96:08] (5768.64s)
related to an appointment. And here is
[96:11] (5771.44s)
the type of this user. And then we're
[96:13] (5773.76s)
going to say at relation. So, if you are
[96:16] (5776.88s)
seeing this for the first time, it's
[96:18] (5778.56s)
absolutely normal if it looks confusing.
[96:21] (5781.44s)
So, we all been there, that's fine. Just
[96:23] (5783.60s)
follow along. So, the fields that we
[96:25] (5785.76s)
would like to have the relation with in
[96:28] (5788.08s)
the appointment table is user ID. We'll
[96:30] (5790.80s)
say user ID. And then we're going to put
[96:32] (5792.96s)
a comma. It's going to reference to the
[96:35] (5795.52s)
ID in the user table. Right? So it's
[96:38] (5798.88s)
going to be referencing to this field
[96:41] (5801.12s)
which is the primary key. And then we
[96:43] (5803.44s)
will say on delete this is optional.
[96:46] (5806.32s)
We'll say cascade.
[96:49] (5809.36s)
So what that means basically if you
[96:51] (5811.76s)
delete a user account it's going to
[96:54] (5814.80s)
delete all the appointments related to
[96:57] (5817.20s)
that user. Right? So once you delete a
[96:59] (5819.84s)
user, let's say a user deletes their
[97:02] (5822.00s)
account by default like all the
[97:05] (5825.20s)
appointments will be deleted for that
[97:07] (5827.36s)
user. So that's what we say. And then I
[97:10] (5830.00s)
think we can just duplicate this. We'll
[97:12] (5832.24s)
say we're going to have another relation
[97:14] (5834.48s)
to a doctor which is type of doctor.
[97:17] (5837.68s)
Let's format this.
[97:20] (5840.32s)
The uh like relation fields is going to
[97:23] (5843.20s)
be doctor ID. is going to reference to
[97:26] (5846.00s)
the ID field under the doctor table and
[97:29] (5849.84s)
same on the lead cascade. Now why this
[97:32] (5852.64s)
is complaining because we have to map
[97:35] (5855.44s)
this with these models and it is really
[97:39] (5859.44s)
easy to implement here. Basically, I'll
[97:41] (5861.84s)
just say a doctor could have some
[97:44] (5864.48s)
appointments,
[97:46] (5866.16s)
right? And we'll say it's going to be
[97:48] (5868.08s)
type of appointment but an array of them
[97:50] (5870.96s)
because a doctor could have multiple
[97:52] (5872.96s)
appointments. Right? Now, this is not
[97:55] (5875.28s)
complaining anymore. Let's do the same
[97:57] (5877.28s)
thing for the user here. Let's go and
[98:00] (5880.40s)
say appointments.
[98:04] (5884.40s)
So, instead of calling this user, maybe
[98:06] (5886.16s)
we should call this like patients, but I
[98:08] (5888.48s)
think that's fine. Um, this would also
[98:10] (5890.80s)
work perfectly fine. And I'll basically
[98:13] (5893.52s)
copy this entire line. Paste this in.
[98:16] (5896.32s)
Now, we don't really have anything
[98:17] (5897.92s)
complaining in our code. Once again, we
[98:20] (5900.80s)
put the relationships under the
[98:22] (5902.64s)
appointment. And we had to do
[98:24] (5904.80s)
relationships under these tables as
[98:27] (5907.36s)
well. Here, I'll actually say
[98:29] (5909.20s)
relationships
[98:32] (5912.56s)
and do it here as well. Okay. So, that's
[98:36] (5916.16s)
the entire schema. file. Let's just zoom
[98:39] (5919.36s)
out and try to go over it once again. So
[98:42] (5922.00s)
we have three different models or three
[98:44] (5924.64s)
different tables, right? We have a user
[98:47] (5927.12s)
model with all these fields which are
[98:49] (5929.60s)
pretty easy to understand. At the end we
[98:51] (5931.92s)
have a relationship which means a user
[98:54] (5934.40s)
could have multiple appointments and
[98:57] (5937.04s)
here is the type and we included the
[99:00] (5940.24s)
model with the relationships for the
[99:02] (5942.88s)
user. Right? Then we have done the exact
[99:05] (5945.20s)
same thing for the doctors. all these
[99:07] (5947.68s)
fields that a doctor could have which is
[99:10] (5950.56s)
pretty easy to understand and then the
[99:12] (5952.64s)
relationship then we include it right
[99:15] (5955.28s)
here. Okay. Now one more thing just
[99:18] (5958.00s)
before we push this to our neon database
[99:22] (5962.40s)
this is optional but I'd like to mention
[99:24] (5964.40s)
this. So instead of seeing this as a
[99:26] (5966.72s)
user right I would like to see the table
[99:29] (5969.52s)
name as users right to be able to make
[99:32] (5972.16s)
that work we'll just say double at map
[99:35] (5975.52s)
this with the user's name okay so let's
[99:39] (5979.12s)
copy it and go do the same thing for
[99:42] (5982.40s)
doctors
[99:44] (5984.96s)
and for uh I mean same for the
[99:47] (5987.04s)
appointments table
[99:50] (5990.08s)
okay let's say appointments
[99:54] (5994.16s)
okay So believe it or not that's the
[99:55] (5995.92s)
entire thing for the schema. Now what we
[99:59] (5999.20s)
have done is basically a local file in
[100:02] (6002.08s)
our laptop right nobody knows about this
[100:05] (6005.12s)
we need to take this and push this to
[100:07] (6007.76s)
our neon database and I have included
[100:10] (6010.88s)
this in the notes. Basically, we'll say
[100:13] (6013.52s)
mpx prisma db push, right? We're going
[100:17] (6017.04s)
to take all the changes in our laptop
[100:19] (6019.04s)
and push this to our database or to neon
[100:23] (6023.52s)
in this case. Let's open up the
[100:25] (6025.36s)
terminal. Clear this up. And I'll say
[100:28] (6028.16s)
mpx prisma db push.
[100:33] (6033.28s)
So, this is going to take a couple of
[100:34] (6034.88s)
seconds.
[100:37] (6037.04s)
And then we should be able to see all of
[100:38] (6038.96s)
our tables right here. Currently we
[100:41] (6041.36s)
don't have anything right.
[100:45] (6045.04s)
Okay. So looks like our database is now
[100:47] (6047.44s)
in sync with our Prisma schema. It is
[100:49] (6049.76s)
done in 13 seconds. Let's go ahead and
[100:52] (6052.96s)
refresh. We should be able to have the
[100:55] (6055.92s)
appointments, doctors, and users. Okay,
[100:59] (6059.28s)
here we can see we don't really have any
[101:00] (6060.96s)
data, but all these columns are right
[101:03] (6063.68s)
here.
[101:05] (6065.28s)
Okay, so that's perfect. Now it's time
[101:08] (6068.00s)
to go ahead do something else. which is
[101:10] (6070.72s)
about syncing the user from clerk
[101:13] (6073.76s)
dashboard to our database. So I'll leave
[101:16] (6076.72s)
this section here. In the next one,
[101:18] (6078.32s)
we're going to be doing that. So I'll
[101:20] (6080.16s)
see you there. Well, actually just
[101:21] (6081.92s)
before we end the section, we would like
[101:23] (6083.84s)
to create a new branch and commit our
[101:26] (6086.32s)
changes. So I'll open up the toggle bar,
[101:29] (6089.84s)
I mean status bar, right? I'm I'm going
[101:32] (6092.40s)
to toggle this with
[101:35] (6095.20s)
command shiftp. Okay. So we'll go ahead
[101:38] (6098.08s)
create a new branch. Let's say Prisma-
[101:42] (6102.24s)
schema. You can call this anything, but
[101:44] (6104.08s)
this is the name that I'll be going
[101:45] (6105.52s)
with. And then we would like to go here,
[101:49] (6109.12s)
stage all of our changes, and then add a
[101:52] (6112.16s)
commit like
[101:54] (6114.40s)
schema. Prisma file added. And then
[101:58] (6118.96s)
let's say commit publish the branch.
[102:01] (6121.44s)
Let's go into the source code, which was
[102:04] (6124.64s)
Dentwise.
[102:06] (6126.16s)
And then we're going to create the pull
[102:08] (6128.00s)
request.
[102:10] (6130.80s)
Okay, let's say create this and then
[102:13] (6133.92s)
I'll wait for the code suggestions from
[102:16] (6136.48s)
code ravbit and then if you are happy
[102:19] (6139.04s)
with it, we can just merge the pull
[102:21] (6141.36s)
request. And once again, if you would
[102:23] (6143.44s)
like to get this extension for free or
[102:25] (6145.92s)
this tool, you'll find a link in the
[102:28] (6148.00s)
description. Go ahead and give access to
[102:31] (6151.20s)
your repositories. This is what I have
[102:33] (6153.12s)
done. is completely safe and it should
[102:35] (6155.52s)
give you a review in a couple of
[102:37] (6157.92s)
minutes. All right, so we got a quick
[102:40] (6160.40s)
summary by code rabbit. You can pause
[102:42] (6162.72s)
the video and read it just to see what
[102:44] (6164.88s)
we have added. And here is a quick
[102:47] (6167.04s)
walkthrough of the files and the changes
[102:50] (6170.16s)
that we have done the sequence diagram
[102:52] (6172.80s)
like here is how that work. When you
[102:54] (6174.72s)
create an appointment, what is happening
[102:56] (6176.72s)
in the background and once you delete a
[102:59] (6179.28s)
user or a doctor, what is going to
[103:01] (6181.76s)
happen? It's going to basically cascade
[103:04] (6184.80s)
delete the related appointments and this
[103:07] (6187.44s)
is what I have tried to explain. Let's
[103:09] (6189.52s)
take a look at this once again. I'll go
[103:11] (6191.52s)
into the schema.prisma file. So whenever
[103:14] (6194.72s)
you delete a user account from your
[103:17] (6197.20s)
database, it's going to delete all the
[103:19] (6199.44s)
appointments that is related to that
[103:21] (6201.52s)
user because of this field. And same for
[103:24] (6204.80s)
the doctors.
[103:27] (6207.36s)
Okay, so this is what they try to
[103:29] (6209.04s)
explain. And let's scroll to the bottom
[103:31] (6211.52s)
and see if we have any code suggestions.
[103:34] (6214.24s)
Well, we just got only one which is
[103:36] (6216.24s)
about the package version. I think this
[103:38] (6218.96s)
is a valid version. So, we can ignore
[103:41] (6221.04s)
this suggestion. This is completely
[103:43] (6223.36s)
fine. Um, I have tested out like it is
[103:46] (6226.16s)
working without any issues. And other
[103:48] (6228.56s)
than this, we don't really have any
[103:50] (6230.48s)
suggestions or errors in our code, which
[103:53] (6233.20s)
means that's perfectly good. Right now,
[103:55] (6235.68s)
we'll just go ahead and merge this pull
[103:57] (6237.60s)
request. confirm it. Once it is done, we
[104:01] (6241.28s)
can go back to VS Code. Let's close
[104:04] (6244.00s)
everything. Switch to the master. Now,
[104:07] (6247.84s)
looks like we lost all of our changes,
[104:10] (6250.00s)
but that's fine. We'll just say sync
[104:12] (6252.32s)
this up and we should get the latest
[104:15] (6255.60s)
changes in a second. Here we go.
[104:19] (6259.44s)
Okay, so these are all the things that
[104:21] (6261.12s)
we have included. So, with that, that's
[104:23] (6263.52s)
going to be it for this section.
[104:24] (6264.88s)
Hopefully, I'll see you in the next one.
[104:27] (6267.68s)
So in this section we are going to solve
[104:29] (6269.84s)
a problem that we currently have in our
[104:32] (6272.40s)
codebase or in our application. So we
[104:35] (6275.36s)
have signed up with an account but this
[104:37] (6277.92s)
account is not in our database right
[104:40] (6280.16s)
under the users we cannot find that
[104:42] (6282.24s)
user. So we basically currently storing
[104:45] (6285.52s)
the user under the clerk but not in our
[104:48] (6288.16s)
database. So we need to make it work in
[104:51] (6291.04s)
a way that once user signed up. We would
[104:53] (6293.92s)
like to take this user and save it to
[104:56] (6296.16s)
the database. So you have two different
[104:58] (6298.48s)
options for this. The first one is using
[105:01] (6301.28s)
web hooks which is the best way to do
[105:04] (6304.24s)
it. Um in this case we'll be using the
[105:06] (6306.80s)
second way which is just having a user
[105:10] (6310.24s)
sync server action and I'll explain what
[105:14] (6314.72s)
that means. So I would say this is the
[105:16] (6316.88s)
best way but it's a little bit more
[105:18] (6318.48s)
involved. Let's keep it simple and
[105:20] (6320.96s)
implement this way. Okay. So let's see
[105:23] (6323.92s)
what we're going to be doing in our
[105:25] (6325.36s)
codebase. So first we need to create an
[105:28] (6328.40s)
instance to be able to communicate with
[105:31] (6331.44s)
our database. Right? And this instance
[105:34] (6334.08s)
is going to be a Prisma instance. So
[105:36] (6336.80s)
under the lib I'll say prisma.ts.
[105:40] (6340.96s)
And to get this file content, we will
[105:43] (6343.60s)
just say Nex.js Prisma best practices.
[105:49] (6349.76s)
Let's search for it and click to this
[105:52] (6352.56s)
comprehensive guide.
[105:54] (6354.88s)
Okay. So here it says go under the lip
[105:57] (6357.28s)
under this file, copy this content and
[105:59] (6359.76s)
paste this in. Now why do we want to
[106:02] (6362.24s)
follow this best practice? Well, you
[106:04] (6364.48s)
can't read it. But basically, if you
[106:06] (6366.72s)
don't do it in this way, you are going
[106:08] (6368.48s)
to get some errors, right? And this is
[106:11] (6371.68s)
often occurs due to NexJS hot reloading
[106:14] (6374.64s)
feature in development where you create
[106:17] (6377.36s)
multiple instances of the Prisma client.
[106:20] (6380.40s)
So to get rid of this error, we are
[106:22] (6382.80s)
going to copy this and this is going to
[106:25] (6385.04s)
create a global instance. And if you
[106:27] (6387.36s)
already have one Prisma client, you
[106:29] (6389.68s)
don't need to create it again. I know
[106:31] (6391.60s)
that sounds complicated if you are using
[106:33] (6393.36s)
this for the very first time. But
[106:35] (6395.52s)
basically this is a file that you don't
[106:38] (6398.24s)
really need to understand completely
[106:40] (6400.80s)
just understand the concept that we are
[106:43] (6403.36s)
creating a Prisma client right a Prisma
[106:46] (6406.24s)
instance and we are caching it. So
[106:48] (6408.64s)
that's it. So I'll just add a comment
[106:51] (6411.04s)
here. Let's say create
[106:55] (6415.12s)
a Prisma
[106:57] (6417.04s)
instance
[106:58] (6418.72s)
and cache it
[107:01] (6421.76s)
in development.
[107:04] (6424.88s)
So if you are in production we don't
[107:06] (6426.56s)
really cach it but in development we
[107:09] (6429.12s)
would like to cach this for the best
[107:11] (6431.84s)
performance. Okay. So all in all we are
[107:14] (6434.88s)
just creating this Prisma instance and
[107:17] (6437.44s)
we are going to be importing this in
[107:19] (6439.28s)
other files so that we can communicate
[107:21] (6441.60s)
with our database. I hope this is not
[107:24] (6444.08s)
really confusing. Um now let's go ahead
[107:26] (6446.80s)
under the lib create a folder called
[107:29] (6449.76s)
actions which are going to be our server
[107:32] (6452.48s)
actions and let's create one for the
[107:35] (6455.28s)
users. So let's say users.ts ts and a
[107:39] (6459.12s)
server action is basically just a
[107:41] (6461.84s)
function that runs on the server side
[107:45] (6465.04s)
and to be able to make this a server
[107:47] (6467.36s)
action file we'll just add this
[107:49] (6469.44s)
directive at the very top I'll say use
[107:52] (6472.32s)
server okay now we would like to import
[107:55] (6475.60s)
the current user from clerk let me just
[107:58] (6478.96s)
get it in this way okay and then we
[108:01] (6481.20s)
would like to get the prisma from from
[108:04] (6484.40s)
this file that we have Right. Okay. Now
[108:07] (6487.84s)
let's create a function. I'll say export
[108:10] (6490.48s)
async function called sync
[108:15] (6495.12s)
user.
[108:17] (6497.04s)
This is not going to take any arguments.
[108:19] (6499.20s)
But basically this will have a try and
[108:21] (6501.60s)
catch block where we would like to take
[108:23] (6503.92s)
the user from clerk and save it to our
[108:26] (6506.96s)
postgress database. So let's get the
[108:29] (6509.68s)
currently authenticated user by calling
[108:32] (6512.40s)
await current user. Okay. So if we don't
[108:35] (6515.76s)
have the user, if this is undefined or
[108:38] (6518.00s)
null, that means user is not
[108:40] (6520.00s)
authenticated, we'll just return and
[108:42] (6522.24s)
we're not going to do anything. But if
[108:44] (6524.72s)
we have the user that is authenticated,
[108:47] (6527.20s)
we can check if it is existed in our
[108:49] (6529.20s)
database or not. I'll say existing user
[108:52] (6532.64s)
say await prisma dot user. Right here we
[108:56] (6536.56s)
can see it is type safe because we have
[108:58] (6538.72s)
the user table and then we can say even
[109:01] (6541.44s)
like doctor or appointment. So here
[109:04] (6544.40s)
we'll say userfind
[109:07] (6547.04s)
unique
[109:09] (6549.28s)
and let's say where the email field or
[109:12] (6552.96s)
actually the clerk ID field is equal to
[109:16] (6556.40s)
user do ID because remember this user is
[109:20] (6560.32s)
coming from clerk right current user it
[109:23] (6563.12s)
is coming from clerk so we're going to
[109:24] (6564.96s)
check this field if it is equal to user
[109:27] (6567.76s)
id if we have an existing user that
[109:30] (6570.96s)
means we don't need to save this user to
[109:33] (6573.52s)
the database again. We'll just say
[109:35] (6575.52s)
return the existing user. Don't do
[109:37] (6577.76s)
anything. But if we don't have it, we
[109:39] (6579.92s)
would like to basically say, hey,
[109:42] (6582.08s)
Prisma, go ahead under the user table
[109:44] (6584.72s)
and create this new user. And here is
[109:48] (6588.40s)
the data.
[109:51] (6591.04s)
Okay. So, we're going to pass the clerk
[109:53] (6593.28s)
ID, which is user dot ID field. We're
[109:57] (6597.28s)
going to have the first name, which is
[109:59] (6599.84s)
going to be equal to user. Oops. Let's
[110:02] (6602.56s)
say user dot first name. And then we can
[110:05] (6605.36s)
add the let's say last name.
[110:09] (6609.20s)
Basically duplicate this last name. And
[110:12] (6612.48s)
then we can have the email field.
[110:15] (6615.60s)
That's user.
[110:18] (6618.24s)
Well, actually email addresses. This is
[110:20] (6620.48s)
how clerk stores it. This is an array.
[110:22] (6622.80s)
And we're going to get the first email
[110:25] (6625.36s)
address, which is the primary one. And
[110:27] (6627.60s)
then if user has a phone number, we can
[110:29] (6629.92s)
also get that. We'll say user phone pawn
[110:33] (6633.28s)
numbers. That's an array. We will go
[110:36] (6636.32s)
ahead get the first phone number. Okay.
[110:40] (6640.40s)
So that's that's the data that we would
[110:42] (6642.56s)
like to save. Let's say const this is
[110:45] (6645.20s)
our new DB user. And we can just return
[110:48] (6648.72s)
this from this uh from this method.
[110:51] (6651.28s)
Right? I'll say return DB user. In the
[110:54] (6654.40s)
catch, we're going to just get the
[110:56] (6656.56s)
error. and let's say error in sync
[111:03] (6663.12s)
server action. You can handle this in a
[111:05] (6665.84s)
better way but I'll just keep it simple
[111:07] (6667.68s)
and leave it as it is. Okay. So now we
[111:10] (6670.64s)
would like to call this method right
[111:12] (6672.56s)
once user logs in let's run the
[111:15] (6675.44s)
application by the way I'll clear this.
[111:18] (6678.08s)
Let's say mpm run that.
[111:25] (6685.44s)
So when the user visits our application
[111:28] (6688.00s)
if they are authenticated they should be
[111:30] (6690.00s)
able to like they should be saved to our
[111:33] (6693.44s)
database right currently let's try to
[111:36] (6696.08s)
log in
[111:40] (6700.96s)
okay so we are logged in but we are
[111:42] (6702.96s)
still in this landing page let's go
[111:45] (6705.36s)
ahead and fix this first I will create a
[111:49] (6709.20s)
different page let's say something like
[111:51] (6711.68s)
a dashboard
[111:54] (6714.40s)
and we'll say page.tsx tsx
[111:58] (6718.08s)
for now let's say dashboard and leave it
[112:02] (6722.16s)
simple
[112:04] (6724.00s)
um here under the homepage right here we
[112:07] (6727.68s)
could say something like handle the
[112:10] (6730.16s)
redirection right so I will shrink the
[112:12] (6732.88s)
left hand side let's say this is going
[112:14] (6734.72s)
to be an async function
[112:17] (6737.20s)
um here I'll say const user say await
[112:21] (6741.12s)
get the current user from clerk right um
[112:24] (6744.72s)
here I'll say If there is a user, if
[112:27] (6747.68s)
user is authenticated, let's just
[112:29] (6749.76s)
redirect them to dashboard, let's say
[112:33] (6753.20s)
slash dashboard. And this redirect
[112:36] (6756.56s)
should be coming from next navigation.
[112:40] (6760.16s)
Okay. So, we can even add a comment like
[112:42] (6762.56s)
redirect
[112:44] (6764.40s)
user to dashboard.
[112:48] (6768.96s)
And like this is a page.tsx file. By
[112:52] (6772.40s)
default, it is a server component. So
[112:54] (6774.88s)
you can use async await. But if you put
[112:58] (6778.08s)
something like use client then you
[113:00] (6780.56s)
cannot use the async or await right. So
[113:03] (6783.52s)
this is something that we're going to
[113:04] (6784.80s)
see later in the video in detail.
[113:07] (6787.68s)
Okay. Now let's see if we are redirected
[113:10] (6790.24s)
to the dashboard. That's correct because
[113:12] (6792.40s)
we are authenticated now. Right. And now
[113:14] (6794.88s)
let's try to create a component so that
[113:17] (6797.68s)
we can call our action because we
[113:20] (6800.80s)
created this but we are never calling
[113:22] (6802.88s)
it. I will go under the components
[113:26] (6806.80s)
and I'll create something like user
[113:30] (6810.08s)
sync.ts
[113:32] (6812.08s)
or tsx. Let's say rfce.
[113:35] (6815.84s)
This is not going to return any kind of
[113:38] (6818.48s)
JSX, right? Instead, we would like to
[113:40] (6820.88s)
say const
[113:42] (6822.80s)
get two different things from use user
[113:46] (6826.64s)
from clerk. And since we are using a
[113:49] (6829.12s)
hook, we are in next.js, JS this should
[113:51] (6831.36s)
be a client component so we will add
[113:53] (6833.92s)
this directive called use client because
[113:56] (6836.48s)
we are using a hook right and then from
[113:59] (6839.36s)
here we'll get is signed in and then is
[114:02] (6842.80s)
loaded so here I'll say I will have a
[114:06] (6846.16s)
use effect let's initialize it pretty
[114:08] (6848.64s)
quickly
[114:12] (6852.24s)
and within this I'll have a function
[114:14] (6854.64s)
let's say con handle user sync
[114:19] (6859.76s)
basically within this function we're
[114:21] (6861.68s)
going to call our server action
[114:24] (6864.72s)
say async
[114:27] (6867.20s)
arrow function
[114:29] (6869.76s)
we're going to build it and eventually
[114:31] (6871.60s)
we're going to call this method okay so
[114:34] (6874.16s)
here I'll say if clerk is loaded and if
[114:38] (6878.56s)
user is signed in in this case we can
[114:41] (6881.76s)
just go ahead run the
[114:44] (6884.72s)
let's say sync user server action
[114:48] (6888.96s)
okay and we and even run this like wrap
[114:52] (6892.00s)
this with the try and catch
[115:01] (6901.28s)
let's say fail to sync user
[115:06] (6906.88s)
okay and here for the dependency array
[115:10] (6910.00s)
I'll say is loaded and is signed it
[115:13] (6913.44s)
whenever those updates whenever those
[115:15] (6915.68s)
changes we would like to run this use
[115:17] (6917.44s)
effect and we don't really want to
[115:19] (6919.20s)
return anything. So this component
[115:21] (6921.52s)
doesn't render anything.
[115:24] (6924.24s)
Okay, so that's the entire thing. Now we
[115:26] (6926.40s)
need to call this component at some
[115:28] (6928.08s)
point. Okay, so this component is
[115:30] (6930.48s)
calling our server action and we would
[115:33] (6933.20s)
like to
[115:34] (6934.96s)
call this component maybe in layout.
[115:38] (6938.40s)
Let's go under the source app
[115:41] (6941.92s)
here just above the children. I'll put
[115:44] (6944.40s)
the sync user component or it was user
[115:48] (6948.40s)
sync.
[115:50] (6950.96s)
Okay, I'll just get this one.
[115:54] (6954.64s)
Okay, once again, this is not the best
[115:56] (6956.56s)
way of implementing, but it'll
[115:58] (6958.32s)
definitely work. The best way would be
[116:00] (6960.64s)
to implement this with web hooks. Okay,
[116:04] (6964.32s)
let's save. Um, now let's go here. We'll
[116:09] (6969.04s)
just refresh our page. Hopefully we
[116:11] (6971.52s)
should be able to have the user in the
[116:13] (6973.52s)
database. Here we can see whenever you
[116:16] (6976.88s)
refresh the page
[116:19] (6979.12s)
the layout file is going to run and it's
[116:21] (6981.68s)
going to run this component which is
[116:24] (6984.00s)
going to run our server action which is
[116:27] (6987.28s)
going to run this logic and if user
[116:30] (6990.48s)
doesn't exist in our database it's going
[116:32] (6992.96s)
to run this part where it'll create the
[116:35] (6995.68s)
user. So since we already have this
[116:38] (6998.96s)
user, even if you refresh, it's not
[116:41] (7001.44s)
going to create it again, right? Because
[116:44] (7004.40s)
it's already in the database. I hope
[116:47] (7007.60s)
that makes sense. Once again, keep this
[116:49] (7009.68s)
in mind. The best way would be to
[116:51] (7011.52s)
implement this with web hooks, which is
[116:53] (7013.92s)
something that we have done on the
[116:55] (7015.36s)
channel multiple times at this point. So
[116:57] (7017.92s)
for this project, I'll just do it in
[117:00] (7020.16s)
this way, which is perfectly fine for
[117:02] (7022.32s)
your side projects. Okay. So let's go
[117:05] (7025.36s)
ahead close everything and create a new
[117:09] (7029.36s)
branch and commit our changes. So here
[117:13] (7033.44s)
let's say sync user
[117:17] (7037.52s)
I'll go ahead add a commit message say
[117:21] (7041.44s)
user sync added
[117:25] (7045.12s)
commit publish it and here I'm getting
[117:28] (7048.00s)
these notifications from two different
[117:30] (7050.56s)
extensions one is code rabbit where you
[117:33] (7053.36s)
can start a review or the other one is
[117:35] (7055.84s)
to create a pull request and this is
[117:38] (7058.24s)
coming from this extension
[117:40] (7060.72s)
called github pull pull requests.
[117:46] (7066.40s)
You can just do the pull request from VS
[117:48] (7068.96s)
Code, but just to keep it beginner
[117:51] (7071.20s)
friendly, I'll go ahead do it from the
[117:53] (7073.68s)
GitHub.
[117:56] (7076.72s)
So, let's say compare and create the
[117:59] (7079.36s)
pull request. As usual, I'll just wait
[118:02] (7082.32s)
for the code suggestions coming from
[118:04] (7084.56s)
code rabbit and then we are going to end
[118:06] (7086.64s)
this section. So, here we got the quick
[118:09] (7089.36s)
summary by code rabbit. Let's zoom in
[118:11] (7091.52s)
and read it. The new features that we
[118:13] (7093.92s)
have introduced are the dashboard page.
[118:16] (7096.96s)
Now signed in users visiting the
[118:18] (7098.96s)
homepage are now redirected to the
[118:21] (7101.12s)
dashboard and we have added this
[118:23] (7103.04s)
background job where user profile syncs
[118:26] (7106.00s)
automatically after they sign in. Right?
[118:28] (7108.80s)
And let's scroll to the bottom. Here are
[118:30] (7110.80s)
the files with the related changes. The
[118:33] (7113.92s)
sequence diagram. I think that's a
[118:36] (7116.00s)
perfect way to visualize what is
[118:37] (7117.92s)
happening in our application. Basically
[118:40] (7120.24s)
let's zoom in and see um here
[118:45] (7125.04s)
once you know once the root layout has
[118:48] (7128.24s)
been mounted we are going to run the
[118:50] (7130.96s)
server action that we have right it's
[118:53] (7133.20s)
going to take the user and save it to
[118:56] (7136.00s)
the database right you can see it like
[118:58] (7138.32s)
this is exactly what is happening and if
[119:00] (7140.72s)
user is already existed nothing is going
[119:03] (7143.52s)
to happen right okay let's scroll to the
[119:05] (7145.84s)
bottom I'll just zoom out here we have a
[119:08] (7148.32s)
code suggestion
[119:10] (7150.08s)
It says you should add the use server
[119:12] (7152.56s)
under the sync user server action which
[119:15] (7155.52s)
is something that we have done. So this
[119:17] (7157.36s)
is something really important. That's
[119:19] (7159.20s)
why code rabbit tells you to do it even
[119:21] (7161.84s)
if you have done it before. It just
[119:23] (7163.84s)
warns you right. This should run on the
[119:26] (7166.80s)
server side because we are accessing our
[119:28] (7168.96s)
database. Um so this should be marked
[119:31] (7171.28s)
with use server and this is exactly what
[119:34] (7174.16s)
we have done. So we can skip this
[119:36] (7176.56s)
because we already have done this. And
[119:39] (7179.04s)
here it says you can just add a like go
[119:42] (7182.16s)
check. Um I think clerk will always give
[119:44] (7184.96s)
us some email addresses. So you can of
[119:47] (7187.68s)
course at this part to make your code a
[119:50] (7190.48s)
little bit more safe. But what we have
[119:52] (7192.32s)
is also pretty safe at this point. So
[119:54] (7194.96s)
with all that being said, we can merge
[119:57] (7197.20s)
this pull request.
[120:00] (7200.00s)
Okay, it's done. Let's go back to our
[120:03] (7203.04s)
codebase. switch to the master branch
[120:06] (7206.72s)
and you can just sync this up. So this
[120:09] (7209.52s)
is going to pull all the changes from
[120:12] (7212.00s)
your GitHub account. Okay. And you can
[120:15] (7215.04s)
click to this multiple times by the way.
[120:17] (7217.20s)
Like nothing is going to happen like
[120:18] (7218.88s)
it's not wrong. It's just going to get
[120:20] (7220.80s)
you the latest changes. Okay. So with
[120:23] (7223.60s)
that I think that's going to be it for
[120:25] (7225.36s)
this section as well. In the next
[120:27] (7227.28s)
section, we can get started with the
[120:29] (7229.20s)
admin page where as an admin, we should
[120:32] (7232.08s)
be able to create some doctors, you
[120:35] (7235.12s)
know, update their profiles, make them
[120:37] (7237.52s)
active or inactive, see all the
[120:40] (7240.16s)
appointments and a lot of different
[120:42] (7242.00s)
thing, right? So, uh hopefully I'll see
[120:44] (7244.48s)
you in the next section.
[120:46] (7246.64s)
In this section, we are going to get
[120:48] (7248.40s)
started with the admin page. Just before
[120:51] (7251.20s)
we get into coding, I'd like to mention
[120:53] (7253.44s)
that you can find all these diagrams in
[120:56] (7256.00s)
the description for completely free.
[120:58] (7258.40s)
Just go ahead find the admin page and
[121:01] (7261.28s)
I'll just walk you through as we build
[121:03] (7263.52s)
every single one of these components.
[121:06] (7266.00s)
So, basically, this is the UI that we're
[121:08] (7268.56s)
going to have. First, we will have a
[121:10] (7270.56s)
navbar at the top. Then, a welcome
[121:13] (7273.28s)
section, some uh let's say analytics or
[121:16] (7276.80s)
statistics, however you'd like to call
[121:18] (7278.64s)
it. Then the doctors in our database. If
[121:22] (7282.16s)
you click to this button, a dialogue
[121:24] (7284.64s)
will open where you can add a new doctor
[121:27] (7287.60s)
and provide the details like name,
[121:30] (7290.40s)
email, phone, things like that. And if
[121:33] (7293.20s)
you click to this edit button, another
[121:35] (7295.92s)
dialogue will pop up and it's going to
[121:38] (7298.88s)
get you the details of that doctor.
[121:41] (7301.12s)
Right? So if you click to this one, I
[121:43] (7303.28s)
mean this edit button, you will just see
[121:45] (7305.28s)
this dialogue with the data filled in
[121:47] (7307.76s)
and you can update it by clicking to
[121:50] (7310.08s)
this button. Okay? And then if you
[121:52] (7312.56s)
scroll to the bottom after the doctors,
[121:55] (7315.04s)
you will see all the recent
[121:56] (7316.88s)
appointments. And if you click to like
[121:59] (7319.92s)
click to these buttons, um it's going to
[122:02] (7322.32s)
update this state. If it is confirmed,
[122:04] (7324.48s)
it's going to be completed. And if it is
[122:06] (7326.56s)
completed, it'll be confirmed. Okay? So
[122:09] (7329.04s)
I hope that made sense. uh just don't
[122:11] (7331.12s)
worry about this. We'll just go step by
[122:13] (7333.04s)
step. Okay. So, first uh we need to have
[122:16] (7336.16s)
this page called /admin. Right?
[122:19] (7339.12s)
Currently, I think we're going to get
[122:20] (7340.80s)
404. Uh just make sure that you're
[122:23] (7343.52s)
running this application.
[122:25] (7345.84s)
Okay. Now, let's go ahead and create it.
[122:28] (7348.24s)
Under the source, under the app, I'll
[122:31] (7351.04s)
say admin. Let's create a page.
[122:35] (7355.84s)
And for now, let's say admin page. Okay.
[122:39] (7359.60s)
Now, how are we going to check if the
[122:41] (7361.36s)
user is admin or not? So, there will be
[122:44] (7364.24s)
only one admin. And here I'll go ahead
[122:48] (7368.00s)
um implement this by adding an
[122:50] (7370.24s)
environment variable. So, I'll say admin
[122:53] (7373.36s)
email and this is going to be equal to
[122:56] (7376.48s)
my email. So, the account that I that I
[123:00] (7380.00s)
am logged in, right? So, let's open up
[123:02] (7382.48s)
the cleric dashboard.
[123:06] (7386.00s)
So, just to show you what I mean, um, I
[123:08] (7388.48s)
need to show you the dashboard, but
[123:10] (7390.72s)
basically I signed up with this account.
[123:13] (7393.36s)
So, that was my email. Go ahead, put
[123:15] (7395.52s)
your own email that you signed in.
[123:19] (7399.52s)
So, if you put something like test, like
[123:21] (7401.84s)
it's not going to work. Okay. So, put
[123:23] (7403.68s)
the email that you actually signed up to
[123:26] (7406.40s)
this application. And I don't know why
[123:28] (7408.64s)
this doesn't load.
[123:32] (7412.80s)
Okay. Let's wait for this to load and
[123:35] (7415.12s)
then we can just keep keep going. So
[123:38] (7418.32s)
under the page.tsx I will go ahead mark
[123:41] (7421.84s)
this with async and first I'll get the
[123:45] (7425.12s)
user. So let's say await current user
[123:48] (7428.72s)
which is coming from clerk and we'll say
[123:51] (7431.04s)
if there is not a user which means if
[123:53] (7433.84s)
user is not authenticated just redirect
[123:56] (7436.40s)
them to the home screen.
[124:00] (7440.40s)
Okay. And then we'll say get the uh
[124:04] (7444.64s)
admin email.
[124:10] (7450.48s)
So this is how we can get that value. Um
[124:13] (7453.36s)
then I would like to get the currently
[124:15] (7455.60s)
authenticated user. Right? We need to
[124:17] (7457.68s)
check if this is admin or not. So I'll
[124:20] (7460.00s)
say user email and we'll say here's the
[124:23] (7463.20s)
current user, right? We'll say user
[124:25] (7465.84s)
email addresses and just go ahead get
[124:28] (7468.56s)
the very first one. Then we'll say if
[124:31] (7471.28s)
there is not an admin email or let's say
[124:34] (7474.88s)
if user email is not equal to the admin
[124:38] (7478.64s)
email which means user is not authent I
[124:41] (7481.20s)
mean user is not the admin right user is
[124:44] (7484.32s)
not the admin and here we just say user
[124:48] (7488.16s)
is not logged in in this case again
[124:50] (7490.40s)
we're going to take them to let's say
[124:52] (7492.72s)
dashboard in this case if they are not
[124:55] (7495.44s)
logged in they'll be in the landing page
[124:58] (7498.32s)
but if they are not the admin they will
[125:00] (7500.32s)
be in the dashboard page right and here
[125:02] (7502.96s)
we can say you are the admin
[125:07] (7507.52s)
if they can see this page
[125:10] (7510.32s)
and let's take a look at it now it says
[125:12] (7512.32s)
you are the admin because I am logged in
[125:15] (7515.76s)
with this account let's see it so this
[125:18] (7518.88s)
is the account that I am currently
[125:20] (7520.56s)
logged in right it is this email and we
[125:23] (7523.60s)
just said that
[125:25] (7525.60s)
under the MV file this is the admin
[125:28] (7528.40s)
email. So if you put something like test
[125:31] (7531.84s)
save and save this file now, we
[125:35] (7535.12s)
shouldn't be able to see that page. If I
[125:37] (7537.36s)
try to visit the admin page, it's going
[125:40] (7540.56s)
to redirect me to the dashboard. Okay, I
[125:43] (7543.44s)
hope that makes sense. Let's bring this
[125:45] (7545.92s)
back. Go ahead, put your own email that
[125:48] (7548.64s)
you are currently logged in with clerk.
[125:53] (7553.04s)
Okay, so we just check uh we we did the
[125:56] (7556.80s)
server check, right? Now we would like
[125:58] (7558.80s)
to return a client component so that we
[126:01] (7561.60s)
can use some hooks, you know, fetch data
[126:03] (7563.92s)
on the client side. Here I'll say admin
[126:07] (7567.52s)
dashboard and we can just say client. So
[126:11] (7571.04s)
this is going to be a client component
[126:13] (7573.44s)
and we can create this under the admin
[126:16] (7576.00s)
folder. So we'll say client.tsx tsx
[126:19] (7579.76s)
that's entire file file name. Now let's
[126:23] (7583.04s)
say rfce and we are going to mark this
[126:26] (7586.24s)
with use client. Once again we are
[126:29] (7589.04s)
calling this with this directive so that
[126:31] (7591.60s)
we can uh call some hooks and do some
[126:34] (7594.72s)
client related stuff because by default
[126:37] (7597.92s)
this is going to be running on the
[126:39] (7599.68s)
server side. And now let's import it and
[126:43] (7603.28s)
save both of these files. Okay. Now in
[126:46] (7606.48s)
this component we are going to fetch
[126:48] (7608.16s)
some data right so we're going to get
[126:50] (7610.16s)
the doctors we're going to get the
[126:53] (7613.20s)
appointments let's say and we're going
[126:55] (7615.28s)
to you know display them on the UI but
[126:58] (7618.64s)
how do we fetch the data well we can use
[127:01] (7621.28s)
the fetch method which is pretty basic
[127:03] (7623.92s)
you don't really want to do it in a
[127:05] (7625.52s)
production grade application right
[127:07] (7627.68s)
instead you would like to use our
[127:09] (7629.84s)
favorite technology which is the ten
[127:12] (7632.16s)
stag query and let's search for it I'll
[127:15] (7635.52s)
say 10stack query Nex.js app router. So
[127:19] (7639.76s)
go ahead type this out and we are going
[127:21] (7641.84s)
to get an example
[127:24] (7644.96s)
and if you zoom out you're going to see
[127:27] (7647.04s)
the initial setup. Just click to it. Um
[127:30] (7650.08s)
I think this is the pages router and
[127:33] (7653.04s)
let's scroll. We should be able to see
[127:35] (7655.12s)
the app router or is it okay so this is
[127:38] (7658.40s)
the app router actually. So we'll go
[127:40] (7660.56s)
ahead and copy everything.
[127:43] (7663.12s)
You don't really need to understand this
[127:44] (7664.80s)
completely but basically let's try to
[127:47] (7667.44s)
copy and paste and I'll try to explain.
[127:51] (7671.28s)
So here under the source under the
[127:54] (7674.16s)
components I'll create the providers
[127:57] (7677.68s)
folder and within this I'll just say
[127:59] (7679.76s)
tenstack
[128:03] (7683.12s)
provider.tsx.
[128:06] (7686.24s)
Now in the documentation they put this
[128:08] (7688.64s)
file under the under the app under the
[128:12] (7692.00s)
providers file. It is just an example,
[128:15] (7695.04s)
right? We can absolutely put it in a
[128:17] (7697.20s)
different file. So that's not really
[128:19] (7699.20s)
important. Just go ahead paste this in
[128:21] (7701.76s)
which is around 50 lines of code. You
[128:24] (7704.16s)
can't read these comments. I'm just
[128:25] (7705.76s)
going to leave them. But I think first
[128:27] (7707.92s)
we need to import the package, right?
[128:30] (7710.16s)
And let's go ahead open up the terminal.
[128:33] (7713.60s)
Kill the application. And I'll say mpm
[128:35] (7715.92s)
install.
[128:38] (7718.00s)
Let's say tenstack react query. And
[128:40] (7720.40s)
let's go with a specific version which
[128:42] (7722.64s)
is going to be 5.89.
[128:45] (7725.84s)
Oops, not here, but it should be on the
[128:48] (7728.08s)
terminal. So 5.89.0.
[128:52] (7732.72s)
Okay, so we're going to run this. It's
[128:54] (7734.56s)
going to install the package. This is
[128:56] (7736.80s)
creating a query client, right? And at
[129:00] (7740.16s)
the end of the day, we are wrapping our
[129:02] (7742.24s)
entire application with the 10stack
[129:05] (7745.28s)
provider. Here I'll go ahead call this
[129:07] (7747.60s)
as 10 stack
[129:11] (7751.36s)
provider. Now let's save and try to use
[129:14] (7754.64s)
it under the layout. So here basically
[129:18] (7758.48s)
we can wrap our entire application with
[129:21] (7761.36s)
this pen stack provider. Okay, just
[129:24] (7764.96s)
import it and let's cut this.
[129:29] (7769.04s)
Wrap our entire application with it. Now
[129:31] (7771.44s)
that means within our entire app we can
[129:34] (7774.32s)
use hooks functions coming from tenstack
[129:38] (7778.24s)
right we can use everything that
[129:40] (7780.08s)
tenstack provides
[129:42] (7782.32s)
so this is what what this file basically
[129:44] (7784.88s)
does okay I hope this is not really
[129:48] (7788.00s)
confusing even if it is like you don't
[129:50] (7790.00s)
need to understand everything here 100%.
[129:54] (7794.00s)
Okay, now let's go under here in this
[129:57] (7797.36s)
file. Um, now I think now I think before
[130:01] (7801.92s)
we do anything related to data fetching,
[130:04] (7804.72s)
we can just build some part of the UI
[130:07] (7807.04s)
and then come back to actual data
[130:09] (7809.20s)
fetching. So for now just forget about
[130:11] (7811.92s)
the ten stag. We just set that up,
[130:14] (7814.48s)
right? We created it under this file and
[130:17] (7817.36s)
we wrapped the layout our entire
[130:20] (7820.24s)
application with tenst tag. And once we
[130:23] (7823.28s)
use it, I think it's going to be even
[130:25] (7825.28s)
more clear. Okay, now let's go ahead and
[130:28] (7828.48s)
try to build the return statement under
[130:30] (7830.96s)
the dashboard client component. So I'll
[130:33] (7833.92s)
have a div with some classes like the
[130:36] (7836.72s)
minimum height should be screen and
[130:39] (7839.36s)
background is going to be the background
[130:42] (7842.00s)
variable that we have. And let's try to
[130:44] (7844.72s)
create a navbar component. So let's say
[130:48] (7848.96s)
navbar.
[130:50] (7850.96s)
Now, this is a bit different than what
[130:53] (7853.44s)
we have in the landing page. So, let's
[130:56] (7856.88s)
take a look at it. So, this is a lot
[130:59] (7859.68s)
different than what we have here, right?
[131:02] (7862.08s)
So, this is the navbar component that is
[131:04] (7864.64s)
going to be reusable. So, we're going to
[131:06] (7866.96s)
use it in the dashboard page, voice
[131:09] (7869.28s)
page, and you know, all the other pages,
[131:12] (7872.32s)
the admin page, so on and so forth. So
[131:15] (7875.52s)
let's go ahead copy this name under the
[131:18] (7878.88s)
source under the components we can just
[131:21] (7881.52s)
put the navbar.tsx
[131:23] (7883.60s)
file let's say rfce
[131:26] (7886.96s)
and import it. Now this is not going to
[131:29] (7889.52s)
contain any logic. So we can just be a
[131:32] (7892.08s)
little bit fast and copy and paste as we
[131:34] (7894.64s)
go. You can open up this open up this
[131:37] (7897.44s)
file from the source code and just copy
[131:39] (7899.76s)
and paste just like what I do. So first
[131:42] (7902.48s)
let's get the user from clerk and since
[131:45] (7905.84s)
we are using a hook we can wrap this I
[131:48] (7908.72s)
mean we can mark this with the client
[131:50] (7910.64s)
directive right okay so import the user
[131:55] (7915.04s)
I mean import the use user from cleric
[131:58] (7918.00s)
nextjs and the other thing that we are
[132:00] (7920.64s)
going to import is the path name so go
[132:03] (7923.52s)
ahead import this from next navigation
[132:06] (7926.64s)
and we will be using this to show the
[132:10] (7930.08s)
active link
[132:11] (7931.68s)
So let me show you what I mean.
[132:15] (7935.12s)
So here we are in the voice page, right?
[132:17] (7937.84s)
I don't know if it is visible from the
[132:19] (7939.68s)
video, but the color of this is a little
[132:22] (7942.16s)
bit different than these three links.
[132:25] (7945.12s)
Okay, so this is a little bit more
[132:26] (7946.72s)
highlighted and we are going to
[132:28] (7948.40s)
determine this color depending on the
[132:30] (7950.40s)
path name. So if it is like let's say
[132:33] (7953.92s)
voice that link will be highlighted. If
[132:36] (7956.80s)
it is equal to pro, the pro link will be
[132:39] (7959.28s)
highlighted. so on and so forth. Okay,
[132:42] (7962.16s)
now let's have a nav component and it's
[132:45] (7965.76s)
going to take a couple of different
[132:47] (7967.28s)
classes which I can basically copy and
[132:50] (7970.16s)
paste because it doesn't contain any
[132:52] (7972.48s)
logic at all. So it is fixed to the top
[132:56] (7976.32s)
um left right zero and zindex of 50 and
[133:00] (7980.24s)
some colors right some borders height um
[133:03] (7983.20s)
so on and so forth. Now we'll have one
[133:05] (7985.84s)
div which is going to be our wrapper
[133:07] (7987.92s)
div. And let's close this off. So this
[133:11] (7991.76s)
has like maximum width and the content
[133:14] (7994.56s)
is centered. Then we can have our logo
[133:18] (7998.16s)
right. So here I'll just say the logo
[133:20] (8000.88s)
section.
[133:23] (8003.36s)
Um let's say we'll have a div and let's
[133:26] (8006.56s)
give some classes like flex item
[133:29] (8009.60s)
centered.
[133:31] (8011.76s)
Let's say items center and gap could be
[133:36] (8016.48s)
something like eight maybe. Okay. Then
[133:39] (8019.84s)
I'm going to pass this part where we
[133:41] (8021.52s)
have a link. Once you click to it, it'll
[133:44] (8024.08s)
take you to the dashboard page. And
[133:46] (8026.56s)
we're going to use the image coming from
[133:48] (8028.72s)
next. Okay. So, import them from next
[133:51] (8031.44s)
image and next link. And this should be
[133:54] (8034.16s)
good to go. Then we would like to put
[133:56] (8036.16s)
the navigation links, right? So right
[133:59] (8039.52s)
after the logo div
[134:03] (8043.12s)
we'll have one more div. Let's say class
[134:05] (8045.60s)
name flex items center and gap is going
[134:09] (8049.92s)
to be six. Now let me just copy and
[134:12] (8052.88s)
paste the very first one and you're
[134:14] (8054.72s)
going to understand the rest of it.
[134:17] (8057.04s)
Okay. So this is the link and let's get
[134:19] (8059.36s)
the home icon from Lucid React. So we
[134:22] (8062.56s)
are giving these classes no matter what.
[134:25] (8065.36s)
But if the path name is equal to
[134:27] (8067.12s)
dashboard, we are going to add these
[134:29] (8069.44s)
classes. But if we are not in the
[134:31] (8071.52s)
dashboard, we're going to see this
[134:33] (8073.44s)
classes. Okay. So now let me get the
[134:36] (8076.08s)
rest of it. If you don't want to type
[134:38] (8078.48s)
this out, just go ahead grab them from
[134:40] (8080.96s)
the source code. This is not really
[134:43] (8083.04s)
important at all. So this is the
[134:45] (8085.84s)
appointments link. Next, we're going to
[134:48] (8088.64s)
get the voice and pro links.
[134:53] (8093.20s)
Okay. import the make icon as well as
[134:56] (8096.72s)
the crown icon.
[134:59] (8099.20s)
Okay, now let's save and test it out
[135:03] (8103.04s)
here. I'll just go into the dashboard
[135:05] (8105.76s)
page or maybe the admin page.
[135:09] (8109.04s)
Okay, I think we close the I mean we
[135:11] (8111.92s)
killed the application. Let's see mpm
[135:14] (8114.00s)
rundev.
[135:17] (8117.12s)
And while it is running the application,
[135:19] (8119.28s)
we can add the right hand inside on the
[135:22] (8122.24s)
navbar which is this part right we are
[135:25] (8125.76s)
going to get the user put their name
[135:28] (8128.24s)
email and this is the component what we
[135:30] (8130.96s)
call user button. So let's see how we
[135:33] (8133.68s)
can build it. So after the link and
[135:36] (8136.40s)
after these two divs I'll have this
[135:39] (8139.76s)
right section element. If you don't want
[135:43] (8143.04s)
to type this out, just copy it from the
[135:46] (8146.08s)
source code. Let's say flex items will
[135:49] (8149.12s)
be centered and gap is going to be four.
[135:52] (8152.72s)
And we'll have one more div.
[135:55] (8155.52s)
It's going to have the same same
[135:56] (8156.96s)
classes, but this time let's say gap
[135:59] (8159.04s)
three. And the very first one is going
[136:01] (8161.76s)
to be the username and then the let's
[136:04] (8164.56s)
say the email address. So this is what
[136:08] (8168.48s)
I'm going to do. Oops.
[136:11] (8171.60s)
So we will say if user has first name
[136:14] (8174.00s)
display it put an empty space and then
[136:16] (8176.56s)
the last name. Then do the same thing
[136:18] (8178.48s)
for the email address. Right? Um then
[136:21] (8181.84s)
finally right after this span and after
[136:24] (8184.96s)
this div we're just going to put
[136:26] (8186.96s)
something like user button component. So
[136:30] (8190.48s)
we are going to import it from clerk and
[136:32] (8192.88s)
just call it right here. Now we can take
[136:36] (8196.48s)
a look at it under the admin page.
[136:40] (8200.16s)
Hopefully it should be working out as
[136:41] (8201.84s)
expected.
[136:44] (8204.56s)
Okay. Um, so this part is not really
[136:47] (8207.92s)
working. Let's see what is the problem.
[136:51] (8211.52s)
Probably this right section should be.
[136:54] (8214.72s)
So I just pause the video and find the
[136:57] (8217.52s)
solution. So find the logo div, right?
[137:01] (8221.12s)
Cut this part and scroll to the very
[137:03] (8223.44s)
bottom and paste it somewhere here.
[137:06] (8226.32s)
Right, just above the nav. Now let's
[137:09] (8229.20s)
save. Hopefully, it should be working
[137:10] (8230.88s)
out. Here we go. We have the logo, the
[137:13] (8233.44s)
links, and the user information as well
[137:16] (8236.72s)
as the user button. So, we can manage
[137:19] (8239.04s)
your account. Um, we can check out the
[137:21] (8241.52s)
security tab, things like that. So, this
[137:24] (8244.32s)
is coming from clerk and we can even
[137:26] (8246.64s)
sign out. Now, since in the navbar we
[137:29] (8249.60s)
don't really have the admin link, we
[137:31] (8251.60s)
cannot really see the highlight, but
[137:34] (8254.16s)
let's go into the dashboard page. I'll
[137:36] (8256.96s)
search for the dashboard page. In here,
[137:39] (8259.76s)
let's just put the navbar component.
[137:44] (8264.24s)
Okay, import it. And here we can just
[137:49] (8269.04s)
you know, dashboard.
[137:53] (8273.68s)
Let's go into the dashboard page. Now,
[137:55] (8275.68s)
as you can tell, the link is now
[137:57] (8277.84s)
highlighted.
[137:59] (8279.36s)
I hope you can see it from the video,
[138:01] (8281.04s)
but there is definitely a change on the
[138:03] (8283.36s)
color, right? Even on the hover state is
[138:06] (8286.40s)
a bit different.
[138:08] (8288.64s)
Okay, so that's it for the entire navbar
[138:11] (8291.04s)
component. No logic at all. We are just
[138:13] (8293.52s)
getting one hook, right, which is the
[138:16] (8296.72s)
path name so that we can put these
[138:19] (8299.12s)
classes dynamically and we are getting
[138:21] (8301.20s)
the user from clerk so that we can
[138:23] (8303.60s)
display the email and the full name.
[138:26] (8306.64s)
Okay, so that was the very first thing
[138:28] (8308.80s)
that we have under the admin dashboard
[138:32] (8312.40s)
client file. Now, let's build the next
[138:35] (8315.12s)
component under the admin page, which is
[138:38] (8318.32s)
going to be this. Let me show you pretty
[138:40] (8320.56s)
quickly. So, it's going to be this
[138:42] (8322.80s)
welcome section. Once again, it doesn't
[138:45] (8325.36s)
have any logic at all. Just some text,
[138:48] (8328.00s)
right? We get the username, the first
[138:50] (8330.32s)
name, display it, and put some beautiful
[138:52] (8332.64s)
icon. That's it. But here, we actually
[138:55] (8335.44s)
have some logic because we fetch these
[138:58] (8338.72s)
numbers from the database. So first
[139:01] (8341.60s)
let's try to build it and then we're
[139:03] (8343.28s)
going to get into the next component. So
[139:06] (8346.40s)
right after the navbar um here we can
[139:08] (8348.96s)
have a div with some classes which is
[139:12] (8352.08s)
going to be maximum width of 7x large MX
[139:15] (8355.28s)
auto some petting from all directions
[139:18] (8358.56s)
and then let's say the admin welcome
[139:22] (8362.88s)
section. So this doesn't contain any
[139:25] (8365.92s)
logic. Once again, let's save a bit time
[139:29] (8369.12s)
and just paste this in. Please feel free
[139:32] (8372.00s)
to copy this from the source code. Okay,
[139:34] (8374.32s)
so it is just a div with 20 lines of
[139:36] (8376.96s)
code. Um, go ahead import the settings
[139:40] (8380.40s)
icon and we would like to get the user
[139:43] (8383.04s)
from clerk. So you already know this how
[139:46] (8386.00s)
to do it. Let's say give me the user
[139:50] (8390.16s)
from use user hook.
[139:53] (8393.92s)
Okay, let's save and take a look at the
[139:55] (8395.92s)
output. I'll just go right here and
[139:59] (8399.84s)
under the admin page. Okay, so that's
[140:02] (8402.96s)
the welcome text that we have. I think
[140:05] (8405.12s)
that looks pretty cool. And we're going
[140:07] (8407.04s)
to be using similar design in couple of
[140:09] (8409.36s)
different places. Now it's time to build
[140:12] (8412.32s)
the next component where we have some
[140:14] (8414.96s)
stats, right? And here we need to fetch
[140:17] (8417.44s)
some data. And now it is time to
[140:19] (8419.68s)
actually use 10 stack, right? Um let's
[140:23] (8423.12s)
see how we can make that work. So in
[140:25] (8425.84s)
tenstack we have two different things.
[140:28] (8428.48s)
One is a query where you fetch some
[140:32] (8432.16s)
data, right? This is basically a get
[140:34] (8434.64s)
request so that you can some fetch data,
[140:38] (8438.56s)
right? And then we have what we call
[140:40] (8440.88s)
mutations.
[140:43] (8443.84s)
And mutation is basically when you want
[140:46] (8446.64s)
to update something, delete something or
[140:49] (8449.60s)
create something, right? So we're going
[140:51] (8451.60s)
to be using mutation whenever we want to
[140:53] (8453.92s)
create a doctor or edit their profile.
[140:56] (8456.88s)
But if we want to just fetch data, we're
[140:59] (8459.28s)
going to be using queries. Right now,
[141:01] (8461.92s)
let's go ahead and follow a convention.
[141:05] (8465.04s)
So for the server actions, we are going
[141:08] (8468.00s)
to be using this folder. Let's say under
[141:10] (8470.72s)
the lib under the actions, I will create
[141:13] (8473.76s)
a file called doctors.
[141:16] (8476.96s)
Let's say doctors.
[141:19] (8479.20s)
And let's mark this with use server
[141:22] (8482.40s)
directive. Just for now, follow along
[141:24] (8484.88s)
with me. It's going to make sense, I
[141:26] (8486.96s)
promise. So, first I'll have a function.
[141:29] (8489.60s)
Let's say export async function
[141:33] (8493.60s)
and for the name we can say something
[141:35] (8495.84s)
like get doctors and this is not going
[141:39] (8499.36s)
to take any pars at the moment. So here
[141:42] (8502.64s)
let's try to have a try and catch block
[141:47] (8507.12s)
and under the try basically we're going
[141:49] (8509.52s)
to call our Prisma instance right. So
[141:52] (8512.56s)
here I'll say we're going to get all the
[141:54] (8514.64s)
doctors from the database and we'll say
[141:57] (8517.36s)
Prisma let's import it from the Prisma
[142:00] (8520.40s)
file and we'll say go under the doctor
[142:02] (8522.80s)
table and find many right
[142:07] (8527.76s)
okay so normally that should be it but
[142:10] (8530.64s)
let's add some objects which is going to
[142:13] (8533.52s)
be things like uh including the count um
[142:17] (8537.60s)
for appointments and we can do some
[142:20] (8540.16s)
ordering right so Well, like you'll see
[142:22] (8542.72s)
what that means. First, let's say
[142:24] (8544.48s)
include open up an object. Let's say
[142:27] (8547.52s)
underscore count. And we would like to
[142:30] (8550.16s)
count the number of appointments for
[142:32] (8552.80s)
every single doctor, right?
[142:36] (8556.08s)
So, we would like to count the number of
[142:38] (8558.24s)
appointments because here in in this
[142:41] (8561.60s)
example like once we fetch the doctor
[142:44] (8564.16s)
profile, we would like to show how many
[142:46] (8566.72s)
appointments that this doctor has. Same
[142:49] (8569.68s)
for this one, right? So that's why we go
[142:52] (8572.00s)
under the appointments and count them.
[142:55] (8575.04s)
Then we can order by like let's say
[142:58] (8578.32s)
order these doctors.
[143:00] (8580.80s)
Just put a comma. Let's say order by
[143:03] (8583.68s)
with the created at field and let's put
[143:06] (8586.48s)
it in the descending order. Okay. Now
[143:10] (8590.00s)
let's say something like return the
[143:12] (8592.24s)
doctors which is an array of doctors.
[143:14] (8594.96s)
Right? These are coming from the
[143:16] (8596.56s)
database. And in the catch I'll um I'll
[143:19] (8599.44s)
just say console log
[143:21] (8601.68s)
error fetching doctors and we can just
[143:24] (8604.32s)
throw an error.
[143:27] (8607.28s)
Okay.
[143:28] (8608.96s)
So that's the entire function that we
[143:30] (8610.88s)
would need to fetch all the doctors in
[143:33] (8613.36s)
the database right and we are going to
[143:35] (8615.76s)
even count the number of appointments.
[143:39] (8619.20s)
But now this this is just an array of
[143:41] (8621.44s)
doctors. This doesn't give you the
[143:43] (8623.84s)
count. So what we can do we'll just say
[143:46] (8626.48s)
okay go ahead return the actual doctor's
[143:49] (8629.52s)
array but get every single doctor
[143:53] (8633.92s)
and return an object just keep the
[143:56] (8636.64s)
doctor fields as it is but on top of it
[143:59] (8639.52s)
add this appointment
[144:02] (8642.64s)
count which is equal to doctor
[144:08] (8648.00s)
dot um count and this should be an error
[144:12] (8652.16s)
function by the way let's say
[144:14] (8654.08s)
doctor.count.app
[144:15] (8655.92s)
appointments.
[144:17] (8657.92s)
So, I know that this might look a little
[144:19] (8659.60s)
bit weird for the very first time if
[144:21] (8661.68s)
you're using it, but basically, let's
[144:24] (8664.00s)
delete this part. We are trying to find
[144:26] (8666.56s)
every single doctor in the database.
[144:28] (8668.88s)
Then, we wanted to count the amount of
[144:31] (8671.44s)
appointments for every single one of
[144:33] (8673.28s)
them. So, we had one object with the
[144:36] (8676.48s)
include field and we wanted to order
[144:39] (8679.20s)
this with the create that date in the
[144:41] (8681.76s)
descending order. And you can add
[144:43] (8683.76s)
ascending as well if you really wanted
[144:45] (8685.68s)
to. Okay, finally we're just returning
[144:48] (8688.56s)
this array. Um, that's the server file.
[144:52] (8692.40s)
This is where we fetch the doctors from
[144:55] (8695.44s)
the database. Now we'll create a hooks
[144:58] (8698.56s)
folder. So you're going to see how it'll
[145:01] (8701.84s)
how it'll look like. So here I'll say
[145:04] (8704.24s)
something like use dash doctors. Let's
[145:07] (8707.84s)
say yes. Now since this is a hook we can
[145:12] (8712.08s)
mark this with the use client directive
[145:15] (8715.60s)
and then we can create a function let's
[145:18] (8718.00s)
say use get doctors and this hook is
[145:22] (8722.56s)
going to call this server action and for
[145:26] (8726.16s)
this now we are going to be using a
[145:28] (8728.08s)
query. So let's say use query and this
[145:31] (8731.84s)
is going to give us some result back
[145:34] (8734.24s)
right and we would like to return that
[145:37] (8737.28s)
result. So whatever is coming from the
[145:39] (8739.84s)
database now this wants you to add an
[145:42] (8742.48s)
object with the query key here we can
[145:45] (8745.68s)
say something like get
[145:48] (8748.64s)
doctors right so this could be anything
[145:51] (8751.20s)
you can even put hello but it should be
[145:53] (8753.52s)
something that makes sense and then we
[145:55] (8755.84s)
can say the query function that we would
[145:58] (8758.48s)
like to run is our action which is get
[146:03] (8763.60s)
dockers
[146:05] (8765.60s)
okay so believe it or not that's the
[146:07] (8767.28s)
entire thing we are calling a query
[146:09] (8769.84s)
which is going to call our server action
[146:12] (8772.32s)
right this is the method that is going
[146:14] (8774.32s)
to be running and it's going to get us
[146:16] (8776.56s)
all the doctors okay now how do we call
[146:20] (8780.32s)
this hook well it is just like it is
[146:23] (8783.12s)
exact same way calling this hook right
[146:26] (8786.16s)
so here I'll duplicate this this time
[146:28] (8788.80s)
we'll say use
[146:30] (8790.96s)
get doctors this is how we called our
[146:34] (8794.08s)
hook and this is going to give us some
[146:36] (8796.88s)
data Right. So we can get the data and
[146:39] (8799.76s)
also we can get fields like is loading
[146:42] (8802.64s)
right if there is any errors if it is
[146:45] (8805.52s)
like fetched is fetching millions of
[146:48] (8808.40s)
different things. So that's the beauty
[146:50] (8810.08s)
of a 10stack query, right? You don't
[146:52] (8812.48s)
really need to build all these states by
[146:55] (8815.12s)
yourselves, right? You don't need to
[146:57] (8817.04s)
create them. Um like 10stack query is
[146:59] (8819.84s)
going to provide that. Okay. So instead
[147:02] (8822.24s)
of just calling this data, let's make it
[147:04] (8824.48s)
even better, right? This is optional,
[147:07] (8827.20s)
but I'll call this as doctors. And by
[147:10] (8830.72s)
the default like like the default value
[147:13] (8833.28s)
can be an empty array. Now let's say
[147:15] (8835.68s)
console log give us all the doctors in
[147:18] (8838.72s)
the database. I think this should give
[147:20] (8840.72s)
us an empty array. Let's see on the
[147:23] (8843.12s)
client.
[147:25] (8845.12s)
Okay. So because in the database we
[147:27] (8847.44s)
don't really have any doctors. But that
[147:29] (8849.68s)
means this is actually working. We are
[147:31] (8851.84s)
able to fetch everything without any
[147:34] (8854.16s)
issues. And if you take a look at the
[147:36] (8856.16s)
terminal there are literally no errors.
[147:39] (8859.76s)
So once again let me walk you through it
[147:41] (8861.52s)
what we are doing. So we have created a
[147:44] (8864.48s)
server action where we can fetch the
[147:47] (8867.52s)
doctors by using Prisma and we are
[147:50] (8870.80s)
calling this action under our custom
[147:54] (8874.00s)
hook. Right? This is the function that
[147:56] (8876.40s)
is going to be running once you call
[147:58] (8878.56s)
your hook and we are calling this hook
[148:00] (8880.88s)
in our component so that we can actually
[148:03] (8883.52s)
fetch the doctors. So I hope that makes
[148:06] (8886.08s)
sense. This is the basics of tens query.
[148:09] (8889.52s)
This is the convention that we are going
[148:11] (8891.20s)
to follow. Basically we are going to
[148:13] (8893.44s)
create a server action and from a custom
[148:16] (8896.56s)
hook we are going to be calling that
[148:18] (8898.48s)
server action and we are going to call
[148:20] (8900.88s)
this um call this hook in a component.
[148:25] (8905.52s)
So in the same way let's duplicate this
[148:28] (8908.24s)
this entire line and this time it'll be
[148:31] (8911.28s)
use get appointments.
[148:36] (8916.08s)
Okay. Now it's your challenge to go
[148:38] (8918.40s)
ahead and implement this but if you
[148:40] (8920.48s)
can't just uh follow my solution right
[148:43] (8923.84s)
so this is going to be appointments
[148:48] (8928.16s)
now here it says you have two is loading
[148:51] (8931.36s)
let's try to rename them I'll say
[148:53] (8933.84s)
doctors loading
[148:58] (8938.00s)
and for this one we can say something
[149:00] (8940.80s)
like appointments
[149:04] (8944.08s)
loading okay now Let's create this
[149:07] (8947.20s)
custom hook. Under the hooks, this time
[149:10] (8950.32s)
I'll say use dash appointment.
[149:16] (8956.64s)
And mark this with use client. Let's say
[149:20] (8960.08s)
export function.
[149:23] (8963.68s)
This is the name.
[149:25] (8965.76s)
Okay. For now, we can just leave it as
[149:27] (8967.44s)
it is. Let's try to import it.
[149:31] (8971.92s)
Okay. Now, we need to go ahead. uh what
[149:34] (8974.64s)
this says.
[149:37] (8977.76s)
Okay, so here it says you're not
[149:39] (8979.20s)
returning anything, which is fine. We're
[149:41] (8981.36s)
going to fix that. Now, we need to go
[149:43] (8983.36s)
under the actions and create a file
[149:47] (8987.20s)
called appointments.ts.
[149:51] (8991.92s)
Again, this is a server action file. So,
[149:54] (8994.48s)
we'll say use server and we can create
[149:57] (8997.68s)
our server action to be able to fetch
[150:00] (9000.48s)
all the appointments in our database.
[150:03] (9003.20s)
Now when we want to fetch the
[150:04] (9004.96s)
appointments, we would like to get
[150:06] (9006.80s)
couple of different fields. So what do I
[150:09] (9009.12s)
mean? So this is the table where we will
[150:12] (9012.48s)
display all the appointments. So when we
[150:14] (9014.80s)
fetch the appointments, we would like to
[150:16] (9016.64s)
fetch the patient data like user first
[150:19] (9019.92s)
name, last name as well as the email.
[150:22] (9022.56s)
And for the doctor, we can get the full
[150:25] (9025.04s)
name. And if we wanted to, we can even
[150:27] (9027.36s)
fetch the image URL. So let's go ahead
[150:30] (9030.32s)
and see how we can make that work. I'll
[150:32] (9032.96s)
say export async function
[150:36] (9036.88s)
and we would like to have this method
[150:38] (9038.96s)
called get appointments.
[150:44] (9044.88s)
Okay. So I'll have the try and catch
[150:47] (9047.44s)
block. Let's import the Prisma. So I'll
[150:50] (9050.32s)
say await Prisma appointment table.
[150:54] (9054.24s)
Let's find many. And we can add our
[150:58] (9058.48s)
let's say options our arguments. So here
[151:01] (9061.44s)
we'll say go ahead include for the user
[151:05] (9065.92s)
these fields the first name
[151:09] (9069.84s)
let's say true so that it can fetch it.
[151:12] (9072.32s)
Let's duplicate this last name
[151:17] (9077.28s)
and then we would like to get the email.
[151:21] (9081.04s)
Let's say true. Why this is complaining?
[151:26] (9086.16s)
First name does not exist under the
[151:28] (9088.80s)
user.
[151:31] (9091.44s)
What do you mean? Let's say user.
[151:35] (9095.92s)
Wait, do I have a typo? Include user
[151:39] (9099.36s)
find many.
[151:41] (9101.76s)
Okay, I think after the user, we should
[151:44] (9104.16s)
say like select these fields which is
[151:47] (9107.28s)
the first name, last name, and we have
[151:51] (9111.52s)
email. Let's take a look at the schema
[151:54] (9114.32s)
file. Under the user, we have email,
[151:56] (9116.96s)
first name, and last name. So, these are
[151:59] (9119.20s)
the things that we would like to include
[152:01] (9121.12s)
because if you take a look at the
[152:02] (9122.56s)
appointment
[152:04] (9124.40s)
in the appointment, we don't really have
[152:06] (9126.32s)
the user full name or user email, right?
[152:09] (9129.76s)
They are just some relations. So, that's
[152:11] (9131.84s)
why we cannot just have these fields
[152:14] (9134.32s)
under the appointment. We have to go
[152:16] (9136.48s)
under the user table and grab these
[152:19] (9139.44s)
fields specifically.
[152:21] (9141.60s)
And for the doctor, let's put a comma
[152:24] (9144.56s)
here. I'll say for the doctor, go ahead
[152:26] (9146.96s)
and select
[152:28] (9148.96s)
the name. And I think I'll just get the
[152:32] (9152.40s)
image, the image URL. Let's make it to
[152:35] (9155.04s)
be equal to true. And if you really
[152:37] (9157.92s)
wanted to,
[152:40] (9160.96s)
I think we are missing an object here.
[152:43] (9163.84s)
Okay. So here we can go ahead say order
[152:47] (9167.68s)
by create that and I'll just leave it as
[152:51] (9171.12s)
descending.
[152:53] (9173.12s)
So that's the entire query that we have.
[152:55] (9175.28s)
I know it's a little bit large or big
[152:58] (9178.32s)
but that's fine. Let's say these are
[153:01] (9181.20s)
going to be all the appointments
[153:06] (9186.72s)
and we would like to return it at the
[153:08] (9188.56s)
end of the day and if we have any errors
[153:11] (9191.28s)
can just throw an error.
[153:14] (9194.80s)
Okay. So once again we are saying Prisma
[153:17] (9197.12s)
hey go ahead under the appointment table
[153:19] (9199.68s)
find all the appointments and for each
[153:22] (9202.40s)
appointment include some user data and
[153:25] (9205.36s)
doctor right so that we can nicely
[153:28] (9208.48s)
display them. So these are the
[153:30] (9210.48s)
appointment data like status reason date
[153:33] (9213.84s)
and time but these are the relations
[153:37] (9217.36s)
under the user table and doctor table.
[153:41] (9221.76s)
I hope that makes sense.
[153:44] (9224.32s)
So that's the entire server action. We
[153:47] (9227.12s)
would like to call this under our hook.
[153:50] (9230.56s)
So we'll go here and we will just say
[153:53] (9233.20s)
const result. Once again use query
[153:59] (9239.12s)
where we can have a query key. We can
[154:01] (9241.36s)
call this as get appointments. And for
[154:03] (9243.92s)
the query function we'll just call our
[154:06] (9246.40s)
server action which was get
[154:09] (9249.52s)
appointments.
[154:12] (9252.72s)
And finally we'll just say return this
[154:14] (9254.96s)
result.
[154:16] (9256.56s)
Okay. So once again I'd like to walk you
[154:18] (9258.64s)
through it pretty quickly. We have
[154:20] (9260.72s)
created a server action where we can
[154:23] (9263.36s)
fetch the appointments from the
[154:25] (9265.04s)
database. So this is a server action
[154:27] (9267.36s)
under the actions. Then we have a client
[154:31] (9271.28s)
hook so that we can like use the query
[154:35] (9275.44s)
call this method that is going to give
[154:36] (9276.96s)
us a result. And we are going to call
[154:39] (9279.44s)
this custom hook under the component.
[154:42] (9282.96s)
Now we can say console log give us the
[154:45] (9285.92s)
doctors and the appointments. So both of
[154:49] (9289.84s)
them should be empty arrays because we
[154:52] (9292.40s)
don't have anything in the database. But
[154:55] (9295.20s)
uh at least we know that we can actually
[154:57] (9297.52s)
fetch them without getting any errors.
[155:00] (9300.96s)
So if you see your terminal, you
[155:02] (9302.72s)
shouldn't have any errors just like what
[155:04] (9304.96s)
I have. All right. Now let's calculate
[155:08] (9308.48s)
these stats like the total doctors,
[155:11] (9311.84s)
active doctors, the total appointments
[155:14] (9314.64s)
as well as well as the completed
[155:16] (9316.64s)
appointments. Right? So we are going to
[155:18] (9318.80s)
go ahead and create an object. So I'm
[155:21] (9321.12s)
going to delete the console log. Let's
[155:23] (9323.04s)
say const stats. And this should be
[155:26] (9326.16s)
pretty easy to implement. So we'll say
[155:28] (9328.32s)
total doctors which is going to be
[155:31] (9331.28s)
doctors.length. And let's say active
[155:34] (9334.88s)
doctors. And in this case, we're going
[155:37] (9337.60s)
to basically get every single doctor and
[155:40] (9340.48s)
filter this out. If it is equal to
[155:42] (9342.96s)
active, we're going to just get the
[155:44] (9344.80s)
length, right? That's pretty basic
[155:47] (9347.04s)
JavaScript. And let's do the total
[155:49] (9349.84s)
appointments. Again, um
[155:52] (9352.48s)
appointments.length.
[155:54] (9354.08s)
And instead of pending, we'll actually
[155:56] (9356.56s)
say completed
[155:58] (9358.72s)
appointments.
[156:01] (9361.68s)
And again, we're going to filter out.
[156:04] (9364.00s)
We'll say if status is equal to
[156:06] (9366.96s)
completed and this should be completely
[156:08] (9368.88s)
type safe here. We can say okay we're
[156:12] (9372.08s)
going to get the length and that's it.
[156:14] (9374.08s)
So I'll add a comment. Let's say
[156:15] (9375.84s)
calculate
[156:18] (9378.32s)
stats
[156:20] (9380.08s)
from real data.
[156:24] (9384.32s)
Okay. Now let's add a loading state as
[156:26] (9386.96s)
well. It'll say if the doctor's loading
[156:32] (9392.96s)
or if the appointment's loading. For
[156:35] (9395.28s)
now, we can just return a P tag that
[156:37] (9397.60s)
says loading. Later in the video, we can
[156:41] (9401.12s)
make this a better loading state.
[156:44] (9404.96s)
Okay, so that was the welcome section.
[156:48] (9408.08s)
Right after this, we would like to show
[156:50] (9410.00s)
that component. So here I'll go ahead
[156:53] (9413.04s)
say admin stats
[156:58] (9418.72s)
and I'll just pass all these stats into
[157:01] (9421.52s)
it. So here let's say the total
[157:05] (9425.12s)
doctors.
[157:08] (9428.80s)
Okay. So active doctors total
[157:11] (9431.52s)
appointments and completed appointments.
[157:13] (9433.68s)
We'll just pass them one by one. Let's
[157:16] (9436.00s)
go and create this component.
[157:18] (9438.96s)
So under the components we can create a
[157:21] (9441.92s)
folder called admin
[157:24] (9444.48s)
and every every component that is
[157:27] (9447.04s)
related to admin page can go here.
[157:29] (9449.84s)
Right? So here I'll say admin stats.tsx.
[157:38] (9458.56s)
Okay. Let's import it. Type script is
[157:40] (9460.64s)
going to complain. So let's go ahead and
[157:43] (9463.04s)
add these props as well as an interface.
[157:46] (9466.80s)
Basically here we'll just say these are
[157:49] (9469.84s)
all the fields that we are going to get
[157:51] (9471.76s)
as a prop and let's say admin stats
[157:58] (9478.08s)
props and we can create this either as
[158:00] (9480.88s)
an interface or type here I'll say
[158:03] (9483.28s)
interface admin stats props and they
[158:06] (9486.88s)
will all be number right now if you
[158:09] (9489.36s)
delete this part since we give the
[158:11] (9491.92s)
interface if you press control space
[158:14] (9494.88s)
you're going to see that this is
[158:16] (9496.16s)
completely mediately type save and you
[158:18] (9498.72s)
can get all of them one by one.
[158:21] (9501.92s)
Okay, so the rest of this component is
[158:25] (9505.12s)
just going to be UI. No logic at all.
[158:28] (9508.48s)
This is what we are building. We're just
[158:31] (9511.20s)
going to put an icon, the number as well
[158:34] (9514.16s)
as the text. So you can copy this
[158:37] (9517.04s)
component from the source code. This is
[158:39] (9519.36s)
what I'll be doing. Again, no logic at
[158:42] (9522.56s)
all. It's completely, you know, just UI.
[158:46] (9526.08s)
So here I'll get the return statement.
[158:49] (9529.04s)
Delete it and paste this in. Now we are
[158:51] (9531.68s)
using the card component. Let's go ahead
[158:54] (9534.40s)
and import them as well. At the very top
[158:57] (9537.92s)
we will import cart from shadian UI and
[159:01] (9541.36s)
some icons. Okay, let's save and take a
[159:04] (9544.40s)
look at it.
[159:06] (9546.48s)
This is what we have, right? The exact
[159:08] (9548.56s)
same output that we would like to have
[159:11] (9551.04s)
just like in the demo application. For
[159:13] (9553.76s)
now, we can see zero for everything
[159:15] (9555.92s)
because we don't really have anything in
[159:17] (9557.84s)
the database other than users. But as we
[159:20] (9560.96s)
build more and more, these values should
[159:24] (9564.00s)
update. And just in case if you are
[159:26] (9566.32s)
wondering about this code, please go
[159:28] (9568.40s)
ahead pause the video and take a look at
[159:30] (9570.64s)
it. So everything that you see here is
[159:33] (9573.12s)
just coming directly from Shadzen
[159:35] (9575.60s)
documentation. So just say chaten card
[159:38] (9578.80s)
component
[159:41] (9581.04s)
and the usage that you see here like the
[159:44] (9584.00s)
code right like everything is exactly
[159:47] (9587.28s)
coming from the documentation this is
[159:49] (9589.76s)
what I have done of course I built it
[159:52] (9592.00s)
previously so that's why I'm just
[159:53] (9593.92s)
copying and pasting and it doesn't like
[159:56] (9596.24s)
it doesn't contain any logic at all so
[159:58] (9598.64s)
we shouldn't be wasting one hour just by
[160:01] (9601.36s)
typing these components and classes. I
[160:04] (9604.80s)
hope you don't mind it. Now this section
[160:07] (9607.28s)
is getting pretty long. Let's go ahead
[160:09] (9609.44s)
take a five minute break and continue
[160:11] (9611.76s)
with the next component which is going
[160:14] (9614.00s)
to be this doctor management uh
[160:17] (9617.44s)
component. So I'll see you in a couple
[160:19] (9619.52s)
of minutes.
[160:21] (9621.20s)
All right. So let's move on. So this is
[160:23] (9623.68s)
where we left. We would like to build
[160:25] (9625.60s)
this component so that we can fetch the
[160:27] (9627.92s)
doctors, display them and then with this
[160:30] (9630.88s)
button we will be able to see this model
[160:33] (9633.84s)
where we can add a new doctor right so
[160:36] (9636.80s)
first let's try to build this UI and
[160:39] (9639.28s)
then we're going to get into the
[160:41] (9641.28s)
dialogues which are these components
[160:44] (9644.00s)
right okay so I'll go into VS code visit
[160:48] (9648.16s)
this file and right after the admin
[160:50] (9650.64s)
stats I will call this component called
[160:54] (9654.00s)
doctors management and we are going to
[160:57] (9657.52s)
create this. Now this is not going to
[160:59] (9659.44s)
take any props. So I'll just copy the
[161:01] (9661.84s)
name. Go under the components under the
[161:04] (9664.56s)
admin
[161:06] (9666.08s)
create this file.tsx
[161:10] (9670.16s)
and let's import it.
[161:12] (9672.96s)
Okay. Now let's get into this component.
[161:16] (9676.40s)
And first we would like to call a hook.
[161:19] (9679.76s)
So we'll say use get doctors.
[161:23] (9683.28s)
And we can basically dstructure the
[161:26] (9686.40s)
data. So I'll say give me the data. And
[161:29] (9689.36s)
we can call this as doctors. You don't
[161:32] (9692.32s)
have to, but this is just I think makes
[161:34] (9694.72s)
our code look a lot more cleaner. So
[161:37] (9697.36s)
doctors and let's say the initial value
[161:40] (9700.00s)
could be an empty array until we fetch
[161:42] (9702.88s)
that. Okay. Then we're going to have
[161:45] (9705.28s)
couple of different states. So let me
[161:47] (9707.68s)
show you what they are going to be. So
[161:50] (9710.64s)
if the add dialogue is open or not.
[161:54] (9714.24s)
Initially it is not open. Once you click
[161:56] (9716.64s)
to it it becomes true. So we can see it.
[161:59] (9719.84s)
And then we have another dialogue. So if
[162:02] (9722.16s)
you click to this um edit button
[162:06] (9726.16s)
you're going to see another dialogue.
[162:08] (9728.00s)
Right? So let's create two different
[162:10] (9730.16s)
states for them. The first one is going
[162:12] (9732.48s)
to be is add dialogue open. And
[162:15] (9735.92s)
initially it's going to be false. Let's
[162:18] (9738.56s)
import the use state and then we'll do
[162:21] (9741.36s)
the same thing for the edit dialogue.
[162:24] (9744.32s)
Okay. So this is my hook call my states
[162:27] (9747.60s)
and I will create one more state. Let's
[162:30] (9750.32s)
say const selected
[162:33] (9753.12s)
doctor and let's say set selected
[162:35] (9755.68s)
doctor. This is going to be equal to
[162:37] (9757.68s)
null. And once you click to let's say
[162:42] (9762.08s)
this button the selected doctor is going
[162:44] (9764.88s)
to be this. Right? If you click to this
[162:47] (9767.44s)
one, this is the selected doctor. So we
[162:50] (9770.08s)
are going to update the state so that we
[162:52] (9772.48s)
can just fill in the model immediately.
[162:55] (9775.76s)
Right? So once you click to this button,
[162:58] (9778.40s)
this model will open and we're going to
[163:00] (9780.80s)
take this data and display it right
[163:02] (9782.72s)
here. And if user wants to update it,
[163:05] (9785.20s)
they can, you know, change it on top of
[163:07] (9787.36s)
it. I hope that makes sense. Now these
[163:10] (9790.16s)
are all the states. Let's create two
[163:12] (9792.64s)
different functions. Let's say handle
[163:15] (9795.20s)
edit doctor. And for now, let's leave it
[163:18] (9798.72s)
as an empty arrow function. And I will
[163:21] (9801.76s)
duplicate this. We're going to have
[163:23] (9803.84s)
another one. Let's say handle
[163:27] (9807.52s)
close edit dialogue.
[163:31] (9811.12s)
Okay. So, we're going to get into the
[163:32] (9812.72s)
details of these functions. But first,
[163:35] (9815.36s)
let's build the return statement. So,
[163:37] (9817.60s)
this is going to be an empty fragment
[163:39] (9819.44s)
because we're going to have couple of
[163:40] (9820.88s)
different children. Let's first import
[163:43] (9823.60s)
the card. Um here let's just have a
[163:46] (9826.56s)
class name like margin bottom of 12. And
[163:50] (9830.24s)
here we'll have a card header component.
[163:54] (9834.24s)
So this card header is going to be this
[163:56] (9836.72s)
part.
[163:58] (9838.24s)
Let me show you. Okay. So this is the
[164:01] (9841.20s)
card header, right? And this entire
[164:03] (9843.68s)
thing is the card itself.
[164:07] (9847.04s)
Okay. So let's go ahead and try to build
[164:09] (9849.20s)
it. First, I'll import this from the UI
[164:12] (9852.48s)
folder. And this will take a couple of
[164:14] (9854.80s)
different classes. I'll say class name
[164:17] (9857.84s)
flex flex row um items centered. So
[164:23] (9863.12s)
here, let's say items center and just by
[164:26] (9866.40s)
between. I think we don't need to flex
[164:28] (9868.56s)
row by default. It should be a row
[164:30] (9870.96s)
already. So let's delete this. And then
[164:33] (9873.52s)
we'll have a div. So this is the parent
[164:36] (9876.64s)
component that doesn't really need any
[164:38] (9878.56s)
classes. But we're going to have a card
[164:41] (9881.04s)
title inside of it, right? Say card
[164:44] (9884.56s)
title and we can import this. I'll give
[164:48] (9888.00s)
some classes like flex items center
[164:52] (9892.80s)
and gap of two. Now this is going to
[164:55] (9895.84s)
have the icon which is stethoscope
[165:00] (9900.24s)
icon coming from lucid react. And let's
[165:04] (9904.16s)
say class name is going to be size of
[165:08] (9908.40s)
five and text could be primary.
[165:12] (9912.88s)
Okay. Then we can say doctor's
[165:15] (9915.68s)
management. So that's the title. Right
[165:18] (9918.48s)
after this, we're going to put the
[165:20] (9920.00s)
description. So I'll say card
[165:22] (9922.96s)
description.
[165:25] (9925.12s)
And for the content, I'll just copy it
[165:28] (9928.24s)
and paste it. You can update it as you
[165:31] (9931.04s)
wish, but this is what I'm going to
[165:33] (9933.12s)
have. And then right after this div,
[165:35] (9935.92s)
we're going to have the button. Let's
[165:38] (9938.80s)
say this is going to be the button
[165:40] (9940.56s)
coming from chaten. And we can give some
[165:43] (9943.92s)
props like the on click. What happens
[165:48] (9948.00s)
when you click to it? Well, basically we
[165:50] (9950.48s)
will update the state which is to add
[165:53] (9953.44s)
the dialogue open to be equal to true.
[165:56] (9956.64s)
And then let's give some classes. I'll
[165:59] (9959.04s)
say background gradient to right
[166:03] (9963.92s)
from primary color to primary
[166:09] (9969.12s)
as well but oops let's say to primary
[166:13] (9973.76s)
but we're going to change the opacity a
[166:15] (9975.76s)
bit like 80 um then I'll say on hover
[166:19] (9979.68s)
state
[166:21] (9981.60s)
I think we can say something like from
[166:24] (9984.32s)
primary 90
[166:27] (9987.44s)
to primary 100.
[166:30] (9990.56s)
Okay, so these are the classes that I'm
[166:32] (9992.88s)
going to have for this button. Feel free
[166:34] (9994.72s)
to update it. And uh inside this button,
[166:37] (9997.52s)
we'll just say add doctor. And just
[166:39] (9999.60s)
before that, we are going to put a icon.
[166:42] (10002.72s)
So let's say plus icon
[166:46] (10006.88s)
class name will be margin right of two
[166:50] (10010.00s)
and size of four. Okay. So let's save
[166:53] (10013.12s)
and take a look at take a look at the
[166:55] (10015.20s)
output. Okay. So this is what we would
[166:57] (10017.60s)
like to have right once you click to it
[167:00] (10020.24s)
model should be open and we're going to
[167:02] (10022.96s)
get there step by step. So that was the
[167:05] (10025.84s)
cart header itself. Now right after this
[167:08] (10028.96s)
we can have the card content.
[167:12] (10032.80s)
So let's import this one as well. Um
[167:16] (10036.00s)
this is going to have a div let's say
[167:18] (10038.96s)
last name of space y of four. And
[167:23] (10043.52s)
basically we would like to take the
[167:25] (10045.44s)
doctor's array and map through it.
[167:27] (10047.68s)
Right? For every single doctor, we would
[167:30] (10050.56s)
like to return something.
[167:34] (10054.24s)
In this case, we are going to return a
[167:36] (10056.40s)
def. Let's give the key and make the
[167:38] (10058.64s)
react happy. So we'll say it should be
[167:40] (10060.72s)
something unique like doctor ID. And we
[167:43] (10063.68s)
can give some classes just to make it
[167:45] (10065.68s)
look nice. Um then we can have one div
[167:49] (10069.12s)
with the class name of let's say flex
[167:52] (10072.96s)
then items center and then I think gap
[167:56] (10076.40s)
of or this should be fine. Then within
[167:59] (10079.44s)
this div we're going to have an image
[168:01] (10081.60s)
component. Let's get this from next
[168:04] (10084.32s)
image.
[168:06] (10086.96s)
Okay, just import that. And for the
[168:09] (10089.04s)
source, we will say doctor
[168:12] (10092.16s)
image URL, right?
[168:15] (10095.92s)
And then we can have the alt text as
[168:19] (10099.76s)
let's say doctor.name.
[168:23] (10103.04s)
Then we can have the width let's say 48.
[168:27] (10107.28s)
I'll do the same thing for the height.
[168:33] (10113.20s)
And then we can just add some classes.
[168:35] (10115.36s)
So I'll say size of 12 rounded full
[168:40] (10120.48s)
object should be covered and then ring
[168:43] (10123.52s)
of two let's say and ring color is going
[168:46] (10126.32s)
to be background and this ring is going
[168:48] (10128.96s)
to give some box shadow right. Okay. So
[168:52] (10132.64s)
that's the image itself. We can save
[168:56] (10136.16s)
this. Right after the image we're going
[168:59] (10139.04s)
to have one more div. And this div is
[169:01] (10141.84s)
not going to have any class names but
[169:04] (10144.16s)
inside we're going to have some content
[169:06] (10146.48s)
like the doctor name doctor specialtity.
[169:09] (10149.60s)
If the gender is equal to male we're
[169:11] (10151.84s)
going to show this one otherwise it's
[169:13] (10153.84s)
going to be female. Right? So go right
[169:16] (10156.96s)
after this span and after this div we're
[169:19] (10159.76s)
going to have one more div. Let's say
[169:22] (10162.24s)
this time the class name is going to be
[169:24] (10164.16s)
flex. Items will be center and gap of
[169:28] (10168.40s)
four. Let's say margin top of one.
[169:31] (10171.28s)
Within this, we're going to have one div
[169:33] (10173.92s)
and this is going to include the doctor
[169:36] (10176.88s)
email. So, let me go ahead and paste
[169:40] (10180.16s)
this in. So, let's import the mail icon.
[169:44] (10184.72s)
And then we would like to get the phone
[169:47] (10187.20s)
number as well. So, I'll basically go
[169:50] (10190.08s)
right after this div and have the exact
[169:54] (10194.32s)
same thing, but this time we're going to
[169:56] (10196.16s)
display the doctor phone. And by the
[169:58] (10198.64s)
way, this is what we are building at the
[170:00] (10200.80s)
moment, right? So this is the entire
[170:02] (10202.96s)
part. And recently we just built the
[170:05] (10205.36s)
email and the phone number. So that's
[170:08] (10208.24s)
going to be the gender, you know, the
[170:10] (10210.32s)
specialtity, doctor, full name, image.
[170:13] (10213.36s)
Okay. Now we would like to build the
[170:15] (10215.68s)
right hand side where we are going to
[170:17] (10217.92s)
display amount of appointments that this
[170:20] (10220.80s)
doctor has, if their account is active
[170:23] (10223.76s)
or inactive. And then we're going to put
[170:26] (10226.08s)
this action. Let's go ahead and try to
[170:29] (10229.04s)
build it. So after the doctor phone, we
[170:31] (10231.84s)
have four different divs, right? Not the
[170:34] (10234.16s)
first one, not the second one, not the
[170:36] (10236.32s)
third, but after the fourth one, just go
[170:40] (10240.08s)
here and create one more div. And have
[170:43] (10243.84s)
the class name flex item center gap of
[170:48] (10248.40s)
three. And here we'll have one div.
[170:51] (10251.84s)
Let's say this is going to take the
[170:53] (10253.44s)
class name of text center. And we're
[170:56] (10256.80s)
going to put one div. Let's say class
[170:59] (10259.84s)
name of font semi bold
[171:04] (10264.32s)
and text color is going to be primary.
[171:07] (10267.04s)
And within this div, we will basically
[171:08] (10268.96s)
say doctor.appoint
[171:11] (10271.68s)
count. Okay. And right after this div, I
[171:15] (10275.60s)
will have the appointments text.
[171:19] (10279.68s)
So we just put the number. We would like
[171:22] (10282.40s)
to put this text as well.
[171:27] (10287.60s)
So this is going to be inside a div just
[171:30] (10290.48s)
like this. And then go ahead after this
[171:34] (10294.16s)
closing div, we're going to check if the
[171:36] (10296.48s)
account is active or not. If it is
[171:38] (10298.96s)
active, we're going to show a badge.
[171:42] (10302.96s)
Let's import it from UI folder. Right?
[171:45] (10305.68s)
I'll zoom out. So if it is active, we're
[171:48] (10308.56s)
going to show this batch and it says
[171:50] (10310.56s)
active. But if it is not active, we'll
[171:53] (10313.20s)
just show the inactive batch. And this
[171:55] (10315.92s)
is coming from chaten. Once again, we
[171:59] (10319.20s)
don't really need to customize this. It
[172:02] (10322.08s)
has almost every single style that we
[172:04] (10324.88s)
would need.
[172:06] (10326.64s)
All right. So right after this check, we
[172:09] (10329.04s)
can go ahead and add a button, which is
[172:11] (10331.52s)
going to be this action. So once you
[172:13] (10333.92s)
click it, it's going to update this
[172:16] (10336.00s)
state so that we can see the model. So
[172:18] (10338.80s)
I'll go ahead paste this in. We have a
[172:21] (10341.20s)
button size of small variant is outline.
[172:24] (10344.24s)
This is the class name and on click it's
[172:26] (10346.88s)
going to call this method called handle
[172:30] (10350.16s)
addedit doctor. Right? This is what we
[172:32] (10352.56s)
have above for now. It doesn't do
[172:34] (10354.96s)
anything but that's fine. We're going to
[172:37] (10357.12s)
build it. And for the content, we will
[172:39] (10359.68s)
have the edit icon which look like this
[172:42] (10362.96s)
and the text that says edit. Okay. So
[172:46] (10366.56s)
just save it and let's see the output.
[172:49] (10369.44s)
Now we are not going to see anything
[172:51] (10371.20s)
because we don't have any doctors in the
[172:53] (10373.20s)
database but we are about to add one by
[172:56] (10376.56s)
implementing this button where we can
[172:59] (10379.76s)
display a model. So right after this
[173:03] (10383.04s)
card just go ahead call a component
[173:06] (10386.16s)
called add doctor
[173:10] (10390.88s)
dialogue
[173:12] (10392.80s)
and this is going to take two different
[173:14] (10394.80s)
props. Let's say is open
[173:19] (10399.76s)
and we're going to pass our state which
[173:21] (10401.76s)
says is add dialogue open and what
[173:25] (10405.44s)
happens if you want to close this. So
[173:27] (10407.76s)
we'll say on close you should update our
[173:30] (10410.56s)
state to be false. Let's say set is at
[173:34] (10414.32s)
dialog open will be equal to false. Now
[173:37] (10417.68s)
let's try to create this component.
[173:40] (10420.80s)
So under the components under the admin.
[173:43] (10423.60s)
So let me show you once again under the
[173:45] (10425.68s)
source components admin. We are going to
[173:49] (10429.52s)
create this.tsx
[173:52] (10432.08s)
right let's save and try to import it.
[173:56] (10436.48s)
And this is going to complain because we
[173:58] (10438.88s)
need to pass the props.
[174:01] (10441.36s)
And let's go ahead and build this
[174:03] (10443.68s)
component now. So first let's say we're
[174:06] (10446.48s)
going to get the is open and on close
[174:10] (10450.64s)
props. But now TypeScript is not really
[174:13] (10453.28s)
happy. So let's try to add the
[174:15] (10455.12s)
interface. We'll say interface
[174:18] (10458.48s)
at doctor dialogue props. So this is
[174:22] (10462.48s)
basically the convention. Whatever the
[174:24] (10464.56s)
component name is, just put it and then
[174:26] (10466.88s)
at the end put the props. So let's say
[174:29] (10469.44s)
we're going to get the is open field
[174:31] (10471.60s)
which is a boolean and then we can have
[174:35] (10475.12s)
let's say the on close method which is
[174:38] (10478.40s)
going to be a function where we don't
[174:40] (10480.88s)
really return anything right let's say
[174:43] (10483.12s)
void and then we'll say here are the
[174:45] (10485.92s)
props
[174:48] (10488.24s)
for this component. Now if you delete
[174:50] (10490.32s)
them you can see it should be type safe
[174:53] (10493.68s)
and TypeScript is happy with us at the
[174:56] (10496.32s)
moment. Okay. Now let's try to build the
[174:59] (10499.76s)
rest of this component. So first off we
[175:02] (10502.56s)
would need a state in this component so
[175:05] (10505.04s)
that we can store the doctor data.
[175:07] (10507.76s)
Right? We would like to create a doctor
[175:10] (10510.16s)
where where they have a name specialtity
[175:13] (10513.76s)
and all these fields that you can see.
[175:16] (10516.00s)
So let's go ahead and create this state.
[175:18] (10518.56s)
So this is going to be the new doctor.
[175:20] (10520.80s)
Let's say use state and this is going to
[175:22] (10522.72s)
be an object where we have name, email,
[175:26] (10526.08s)
phone and the rest of it. Now the gender
[175:28] (10528.96s)
could be male by default. And let's type
[175:31] (10531.60s)
cast this by saying as the gender,
[175:34] (10534.64s)
right? And we are going to import it
[175:36] (10536.48s)
from Prisma.
[175:38] (10538.48s)
Okay. And here you can see this has two
[175:40] (10540.72s)
different values, right? And by default
[175:43] (10543.12s)
it could be active as well. Now we would
[175:46] (10546.24s)
need a mutation.
[175:48] (10548.48s)
So when you click this button what is
[175:50] (10550.32s)
going to happen right in the database we
[175:52] (10552.80s)
are going to basically do something
[175:55] (10555.44s)
where we can create the user. So for
[175:58] (10558.56s)
this we are going to need a server
[176:00] (10560.40s)
action. Let's go under the lib actions
[176:03] (10563.92s)
and under the doctors.ts file we are
[176:06] (10566.96s)
going to create another function. This
[176:09] (10569.68s)
time instead of get we're going to do
[176:12] (10572.32s)
create. Right? So here I'll say export
[176:16] (10576.48s)
async function create doctor and if you
[176:21] (10581.04s)
want to create a doctor you would like
[176:23] (10583.28s)
to provide some input right but now the
[176:26] (10586.16s)
type of this is equal to any let's try
[176:28] (10588.96s)
to make this type safe I will say this
[176:31] (10591.28s)
is going to be create doctor
[176:34] (10594.80s)
input
[176:37] (10597.28s)
and we can create this as an interface
[176:39] (10599.76s)
so I'll just copy the name let's say
[176:42] (10602.16s)
interface
[176:43] (10603.60s)
And let's say user is going to give us a
[176:47] (10607.20s)
name which is going to be type of
[176:49] (10609.92s)
string. Let's say we're going to have
[176:51] (10611.92s)
the email again a type of string. And I
[176:55] (10615.60s)
am too lazy to type this out. Basically
[176:58] (10618.24s)
I will copy and paste. So they're going
[177:00] (10620.80s)
to they're going to pass a phone
[177:02] (10622.80s)
speciality gender and is active. And
[177:06] (10626.32s)
this is going to be coming from the
[177:08] (10628.48s)
Prisma client. Okay. Now if you take a
[177:11] (10631.68s)
look at this so if you say something
[177:13] (10633.44s)
like input dot you can see it is type
[177:16] (10636.24s)
safe. So here let's just have a try and
[177:19] (10639.28s)
catch block in the try first I'll say if
[177:22] (10642.96s)
there is no input.name
[177:26] (10646.00s)
and we will say or if there is not an
[177:29] (10649.04s)
email we would like to throw an error.
[177:31] (10651.20s)
We'll say name and email are required
[177:34] (10654.24s)
then we can actually create the doctor.
[177:36] (10656.96s)
So say await prisma go under the doctor
[177:40] (10660.32s)
table and create something and here's
[177:43] (10663.04s)
the data that I will pass you right. So
[177:45] (10665.92s)
say data here are all the inputs right
[177:50] (10670.16s)
all input fields and on top of it we
[177:52] (10672.72s)
would like to include the image URL and
[177:55] (10675.60s)
we are going to generate an image URL by
[177:59] (10679.28s)
calling a function. Let's say generate
[178:02] (10682.48s)
avatar and we're going to pass the input
[178:06] (10686.40s)
name as well as the input dot image.
[178:11] (10691.04s)
So let's say sorry not image but it
[178:14] (10694.00s)
should be input gender. And this is the
[178:17] (10697.60s)
function that we are going to create.
[178:19] (10699.68s)
Basically depending on the gender it's
[178:22] (10702.56s)
going to create this kind of an avatar.
[178:25] (10705.68s)
If it is a female, this would be the
[178:27] (10707.76s)
type of avatar
[178:29] (10709.92s)
avatar that the doctor would get. But if
[178:32] (10712.80s)
if the doctor is male, they're going to
[178:35] (10715.04s)
get a male avatar image. And these are
[178:38] (10718.08s)
going to be selected randomly. And for
[178:40] (10720.32s)
these type of images, we're going to be
[178:42] (10722.24s)
using this API, which is something that
[178:44] (10724.40s)
I will provide you in a second. But
[178:46] (10726.80s)
basically, if you want to get a male
[178:49] (10729.04s)
profile image, you would put boy in the
[178:52] (10732.24s)
URL and then you can give a username. So
[178:55] (10735.12s)
if you give John, this is what you're
[178:56] (10736.80s)
going to get. If you give something like
[178:58] (10738.80s)
Bob, it's going to be different, right?
[179:01] (10741.28s)
And if you want to get some female
[179:03] (10743.44s)
images, you would say girl.
[179:06] (10746.48s)
And we are going to decide this with the
[179:09] (10749.28s)
gender field. Okay, let's go ahead. Now
[179:12] (10752.32s)
I will create this under the utils. It's
[179:15] (10755.52s)
going to be a utility function. You can
[179:17] (10757.92s)
find this from the source code under the
[179:20] (10760.16s)
utils.ts. So it is going to be like five
[179:23] (10763.28s)
lines of code where we get the name as
[179:26] (10766.32s)
well as the gender field and we are
[179:29] (10769.04s)
going to have this API URL and if the
[179:32] (10772.16s)
gender is female we will say it's going
[179:34] (10774.64s)
to be girl and whatever the username is
[179:37] (10777.60s)
but if it is male it's going to be boy
[179:39] (10779.92s)
right super simple function nothing
[179:42] (10782.08s)
complicated there is just a regular
[179:44] (10784.48s)
expression here which is fine okay now
[179:47] (10787.84s)
let's go ahead and try to call this
[179:49] (10789.92s)
method
[179:51] (10791.84s)
and just import it from the utils
[179:54] (10794.32s)
folder. And that's the entire thing for
[179:56] (10796.64s)
this create method. This is going to
[179:58] (10798.88s)
return us the doctor. And from this
[180:02] (10802.32s)
method, we can just return it. Now,
[180:04] (10804.64s)
there is one more thing that we would
[180:06] (10806.08s)
like to do. Basically, once you add a
[180:08] (10808.96s)
new doctor, let me show you this. So,
[180:12] (10812.16s)
once you say add the doctor, you would
[180:14] (10814.56s)
like to prefetch this data. Right here
[180:18] (10818.08s)
we can do this on the server side by
[180:21] (10821.36s)
calling the revalidate path method.
[180:24] (10824.48s)
We'll say revalidate path which is
[180:26] (10826.80s)
coming from nextjs right from next cache
[180:31] (10831.76s)
and the path that we would like to
[180:33] (10833.68s)
refetch is going to be slashadmin.
[180:37] (10837.20s)
Okay. So this is going to get rid of the
[180:39] (10839.52s)
cached data and it's going to give you
[180:41] (10841.84s)
the latest data. Right? And here on the
[180:44] (10844.88s)
catch we can handle it properly. As
[180:47] (10847.84s)
usual I'll go ahead put the console log
[180:50] (10850.80s)
right but on top of it I will add this
[180:53] (10853.36s)
check where we say if this is the case
[180:56] (10856.56s)
that means a doctor with this email
[180:58] (10858.96s)
already existed right just try to use a
[181:02] (10862.24s)
different email and here it says code
[181:04] (10864.80s)
does not exist on this type um let's
[181:07] (10867.68s)
just say error of you know type of any.
[181:11] (10871.60s)
But if this is not the case, we're going
[181:13] (10873.20s)
to throw a general error. So that's the
[181:16] (10876.32s)
entire method around 20 lines of code.
[181:19] (10879.12s)
Not that complicated. We have this
[181:21] (10881.12s)
validation check. Then we create the
[181:23] (10883.44s)
doctor. Um get rid of the cache. Return
[181:26] (10886.64s)
the doctor as it is. Okay. So that was
[181:29] (10889.68s)
the server action. Now we would like to
[181:32] (10892.88s)
call this mutation under our custom
[181:35] (10895.84s)
hook. So let's go under the source under
[181:39] (10899.68s)
the hooks where we have use doctors
[181:42] (10902.88s)
instead of a get request instead of a
[181:46] (10906.24s)
query this time we will use a mutation
[181:48] (10908.88s)
and it is going to be actually pretty
[181:50] (10910.96s)
similar. So let's say export function
[181:53] (10913.68s)
and create our custom hook. Let's say
[181:56] (10916.08s)
use create doctor. You can call this
[181:59] (10919.28s)
anything that you wish but this is the
[182:01] (10921.12s)
name that we are going to have. Let's
[182:03] (10923.20s)
say we are going to return the result of
[182:05] (10925.92s)
our mutation. So I'll say use mutation
[182:10] (10930.72s)
and we could do it just like above. So
[182:12] (10932.96s)
this is going to give us something.
[182:15] (10935.52s)
Let's say const result and then we could
[182:18] (10938.72s)
return that result. So it's the exact
[182:21] (10941.04s)
same thing really.
[182:23] (10943.28s)
I think this is a little bit more
[182:24] (10944.56s)
beginner friendly to read. So this is
[182:27] (10947.60s)
going to get a prop or an argument where
[182:31] (10951.12s)
the mutation function is going to be our
[182:34] (10954.08s)
server action. Let's say create doctor
[182:38] (10958.56s)
and this is going to be coming from the
[182:40] (10960.32s)
actions. And what happens if you have
[182:43] (10963.04s)
the success case or if you have some
[182:45] (10965.60s)
errors? Well, you can handle this by
[182:47] (10967.76s)
adding the on success.
[182:50] (10970.40s)
For now, let's just say console log uh
[182:52] (10972.96s)
you know doctor created
[182:57] (10977.84s)
and let's duplicate this. We're going to
[182:59] (10979.76s)
say on error we can get the error itself
[183:04] (10984.16s)
and let's say
[183:06] (10986.24s)
error while creating doctor right.
[183:13] (10993.04s)
Okay. So we're going to make this look a
[183:14] (10994.80s)
little bit more better, a little bit
[183:16] (10996.48s)
more clean. But for now just let's let's
[183:19] (10999.36s)
leave it as it is. Right? We are calling
[183:21] (11001.52s)
our mutation which is the create doctor
[183:24] (11004.00s)
and we are returning the result. So
[183:26] (11006.56s)
let's save and go into this component
[183:29] (11009.12s)
and try to call our hook. So here I'll
[183:32] (11012.40s)
say use create doctor and we're going to
[183:37] (11017.28s)
dstructure a couple of different values.
[183:39] (11019.84s)
So when we use a query we would say give
[183:42] (11022.56s)
us the data right here. you can still do
[183:45] (11025.04s)
it but mostly you would say give me the
[183:47] (11027.44s)
mutate function and instead of
[183:49] (11029.60s)
dstructuring
[183:51] (11031.12s)
um like I will just call this as the
[183:53] (11033.76s)
mutation
[183:55] (11035.28s)
and before I do it let me show you the
[183:57] (11037.84s)
other things so we have like is pending
[184:00] (11040.56s)
state which means if you are in the
[184:02] (11042.80s)
loading state while this is creating or
[184:05] (11045.04s)
not right so we're going to be using
[184:07] (11047.20s)
these values for now I'll just say
[184:09] (11049.84s)
instead of dstructuring I'll say create
[184:13] (11053.92s)
mutation and you can call this anything
[184:16] (11056.32s)
really but this is the name that I am
[184:18] (11058.88s)
going to have and then just before we
[184:20] (11060.88s)
build the return statement let's have
[184:23] (11063.12s)
couple of different functions so let's
[184:25] (11065.12s)
say con handle phone change for now
[184:28] (11068.56s)
let's leave it empty we are going to be
[184:31] (11071.20s)
using this method so that we can format
[184:34] (11074.48s)
the phone number right it should be in
[184:37] (11077.52s)
this beautiful format um then we are
[184:40] (11080.88s)
going to have one more function so what
[184:43] (11083.12s)
happens when you click to the save
[184:44] (11084.88s)
button. So I'll say handle save and for
[184:48] (11088.56s)
now this could be an arrow function as
[184:50] (11090.80s)
well an empty function and then let's
[184:53] (11093.36s)
say const handle close. So what happens
[184:56] (11096.96s)
if you want to close the model we are
[185:00] (11100.08s)
going to implement all of them one by
[185:01] (11101.76s)
one but first let's check the shaden
[185:04] (11104.80s)
documentation for the dialogue
[185:07] (11107.52s)
component.
[185:10] (11110.32s)
Okay, so this is the type of component
[185:12] (11112.64s)
that we are going to be building and
[185:14] (11114.64s)
here is the usage. So you would wrap
[185:16] (11116.80s)
everything with the dialogue and then
[185:19] (11119.44s)
you have the trigger which is this
[185:21] (11121.52s)
button and then you have the content
[185:24] (11124.16s)
right dialog content where you can have
[185:26] (11126.88s)
header, title, description, so on and so
[185:29] (11129.76s)
forth. So everything is coming from the
[185:32] (11132.00s)
documentation. Just keep that in mind.
[185:34] (11134.88s)
Now let's go here and instead of
[185:37] (11137.36s)
returning a div, we're going to return
[185:39] (11139.76s)
the dialogue component. Let's import
[185:42] (11142.80s)
this from the UI folder. And then this
[185:45] (11145.76s)
is going to take the open state. We will
[185:48] (11148.40s)
say is open which is coming from the
[185:52] (11152.24s)
props. And then what happens on open
[185:55] (11155.52s)
change basically we're going to call the
[185:57] (11157.92s)
handle close method. And once you close
[186:00] (11160.80s)
the model, we are going to call the on
[186:03] (11163.28s)
close method. And we would like to reset
[186:06] (11166.56s)
our state. So what I'll be doing is
[186:09] (11169.28s)
this. Basically, we'll say set new
[186:12] (11172.16s)
doctor with the initial states. And then
[186:15] (11175.68s)
right inside this dialogue, I will have
[186:18] (11178.48s)
dialogue content. Let's import it from
[186:22] (11182.32s)
UI folder, not from the radics. And this
[186:25] (11185.60s)
is going to take the class name where
[186:27] (11187.68s)
I'll say smaller screens and above. The
[186:29] (11189.76s)
maximum width should be 500 pixels. Then
[186:32] (11192.96s)
we can add the dialogue header which is
[186:36] (11196.08s)
going to be looking like this. So this
[186:38] (11198.88s)
is the header component.
[186:42] (11202.80s)
I'll just paste this in. Let's import
[186:44] (11204.72s)
the header.
[186:47] (11207.52s)
Let's get the title
[186:50] (11210.24s)
as well as the description. Just make
[186:52] (11212.32s)
sure that you import all of them from
[186:54] (11214.56s)
the UI folder, right? And this is the
[186:57] (11217.60s)
content that we're going to have. So,
[186:59] (11219.76s)
right after the header, we will have
[187:02] (11222.16s)
like we're going to have a couple of
[187:03] (11223.68s)
different divs and then we're going to
[187:05] (11225.60s)
put every single of these input elements
[187:08] (11228.56s)
with the label, the input, and we're
[187:11] (11231.12s)
going to do it couple of different times
[187:12] (11232.80s)
and then our actions.
[187:15] (11235.84s)
So, here I'll have a div. Let's say the
[187:18] (11238.80s)
class name this is going to get will be
[187:20] (11240.72s)
grid gap of four and petting y of four
[187:24] (11244.56s)
as well. Then one more div. Let me zoom
[187:30] (11250.24s)
Okay. So we'll have class name of grid.
[187:32] (11252.96s)
Let's say
[187:35] (11255.68s)
this time it'll have grid columns of
[187:38] (11258.24s)
two. And then we'll say gap of four.
[187:41] (11261.52s)
Then we can go inside where we'll have
[187:44] (11264.08s)
one more div. as the like that's for the
[187:47] (11267.68s)
parent where we can have the label as
[187:50] (11270.08s)
well as the input. So I'm just going to
[187:52] (11272.24s)
delete it. Paste this in. We have a div.
[187:56] (11276.16s)
Let's get the label from the UI folder.
[188:00] (11280.40s)
Okay, from here
[188:02] (11282.80s)
um and then we're going to get the
[188:04] (11284.56s)
input.
[188:06] (11286.32s)
Okay, please feel free to pause the
[188:08] (11288.32s)
video and type this out. Let's not waste
[188:10] (11290.64s)
any time. So let's save and see the
[188:13] (11293.60s)
output.
[188:14] (11294.96s)
If I click to this one, model should be
[188:17] (11297.04s)
open. We can see the label as well as
[188:19] (11299.20s)
the input, right?
[188:22] (11302.48s)
Okay. So that's what we are doing. And
[188:24] (11304.72s)
here we have an ID as well as the HTML
[188:27] (11307.28s)
form. So they are matching. And this is
[188:30] (11310.08s)
because if you click to the label, it
[188:33] (11313.52s)
should focus on the input. So that's one
[188:36] (11316.80s)
thing that I want to mention. Now, we're
[188:39] (11319.36s)
going to be basically doing the exact
[188:41] (11321.28s)
same thing couple of different times for
[188:43] (11323.68s)
the other fields. So, I will get this
[188:47] (11327.04s)
exact same thing
[188:49] (11329.52s)
where we have space y of two. This time
[188:52] (11332.48s)
it is for this field and once you type
[188:55] (11335.28s)
into it, it's going to update this
[188:57] (11337.52s)
field, right? It's going to keep
[188:59] (11339.28s)
everything as it is, but it's going to
[189:01] (11341.76s)
update the specialtity field. And here
[189:05] (11345.12s)
in this case, it's going to update the
[189:06] (11346.96s)
doctor name.
[189:08] (11348.72s)
Okay, now let's go after these two divs.
[189:12] (11352.16s)
Okay, so just go after them, paste this
[189:14] (11354.80s)
in. It's the exact same thing, but this
[189:17] (11357.52s)
time for email, and it's going to update
[189:19] (11359.76s)
the email state. Um, here we would like
[189:22] (11362.80s)
to do for the phone, the exact same
[189:26] (11366.08s)
thing. So, right after this, paste this
[189:32] (11372.48s)
Okay, what do you mean? It says this
[189:35] (11375.12s)
method doesn't expect any arguments. So
[189:38] (11378.16s)
let's go ahead say you're going to get a
[189:40] (11380.88s)
value sir and type of you will be
[189:43] (11383.36s)
string. Now this should be happy with
[189:47] (11387.60s)
Okay. So this is super basic react
[189:49] (11389.76s)
that's why we're just copying and
[189:51] (11391.28s)
pasting rather than explaining all the
[189:53] (11393.60s)
details. Right? So after the input after
[189:58] (11398.08s)
this div we're going to go ahead create
[190:00] (11400.24s)
one more div. Let's say class name of
[190:02] (11402.96s)
grit. Let's say grid columns of two and
[190:07] (11407.20s)
gap of four. And this is going to get
[190:10] (11410.56s)
the space Y2 class.
[190:15] (11415.84s)
It should be in this way. And then this
[190:18] (11418.32s)
is this will take a couple of different
[190:20] (11420.40s)
fields. And this is where we build the
[190:23] (11423.36s)
gender select element right here. This
[190:26] (11426.32s)
is what we call the select element.
[190:28] (11428.88s)
Let's double check.
[190:31] (11431.68s)
Okay. So you can add couple of different
[190:33] (11433.76s)
options right and again you can see the
[190:36] (11436.16s)
usage from the documentation or just
[190:39] (11439.12s)
follow along with me and find out right
[190:43] (11443.52s)
so right after this step I'm going to
[190:45] (11445.84s)
paste this in and I'll walk you through
[190:47] (11447.52s)
it first let's import the select get the
[190:51] (11451.44s)
trigger component
[190:53] (11453.60s)
the value
[190:56] (11456.08s)
content as well as the item. So once
[190:59] (11459.44s)
again everything is coming from the
[191:01] (11461.12s)
documentation but basically we have a
[191:03] (11463.84s)
trigger element as well as the content
[191:07] (11467.12s)
which has the value of male and female
[191:10] (11470.24s)
and this is the content that user will
[191:12] (11472.32s)
see. Once you select one of them, this
[191:14] (11474.88s)
function is going to run and we are
[191:16] (11476.96s)
going to update the gender field in our
[191:19] (11479.52s)
state. And here I just type cast this
[191:22] (11482.48s)
where we say value is going to be as a
[191:25] (11485.04s)
gender so that we know this is like male
[191:27] (11487.84s)
or female just to make our code type
[191:30] (11490.24s)
safe.
[191:31] (11491.76s)
Okay. So I think that's the entire
[191:33] (11493.36s)
thing. Um let's see how it look like. I
[191:36] (11496.88s)
mean how does that how does that look?
[191:39] (11499.60s)
Okay. So we can select this or this
[191:41] (11501.84s)
value. Now right next to it we're going
[191:44] (11504.32s)
to put the status. It's almost the exact
[191:47] (11507.44s)
same thing but this time it'll be you
[191:50] (11510.08s)
know active or inactive.
[191:53] (11513.20s)
So after this select and after this div
[191:56] (11516.56s)
let's go outside. Paste this in. So the
[191:59] (11519.68s)
status either active or inactive.
[192:03] (11523.52s)
Again let's just save this and see the
[192:05] (11525.92s)
output. Okay. So this is working as
[192:08] (11528.88s)
expected. Now right after this we're
[192:11] (11531.28s)
going to put our buttons.
[192:14] (11534.32s)
So after all these three divs go ahead
[192:17] (11537.20s)
and create this component called dialog
[192:20] (11540.00s)
putter and import it. Then we're going
[192:23] (11543.20s)
to put a button where it just says
[192:25] (11545.44s)
cancel. The variant is going to be
[192:27] (11547.84s)
outline. And once you click that it's
[192:30] (11550.08s)
going to close the model. Right after
[192:32] (11552.80s)
this we'll have one more button. Let's
[192:35] (11555.60s)
say this is going to take some props
[192:38] (11558.08s)
like on click we're going to call the
[192:40] (11560.96s)
handle save method and then let's give
[192:44] (11564.56s)
the class name background could be
[192:46] (11566.72s)
primary and on hover we are going to say
[192:49] (11569.68s)
bg primary
[192:53] (11573.28s)
90% of opacity. Now one more thing we
[192:57] (11577.28s)
will say this button is going to be
[192:59] (11579.20s)
disabled in these scenarios right let's
[193:02] (11582.88s)
say if there is not new doctor name or
[193:08] (11588.08s)
if there is not new doctor email
[193:12] (11592.00s)
basically if these fields are empty we
[193:15] (11595.04s)
want this button to be disabled let's do
[193:18] (11598.00s)
the same thing for the specialtity I
[193:21] (11601.60s)
don't know if that's how you pronounce
[193:22] (11602.80s)
it but I think that should be fine And
[193:25] (11605.44s)
finally if we are in the loading state
[193:28] (11608.00s)
and we can check this by saying create
[193:30] (11610.96s)
doctor mutation is pending right. So
[193:35] (11615.44s)
this is how we can check for it. We
[193:37] (11617.44s)
could either dstructure it that I showed
[193:40] (11620.16s)
you a couple of seconds ago or minutes
[193:42] (11622.16s)
ago. We can get you know is pending
[193:44] (11624.88s)
state demutate things like that or
[193:49] (11629.28s)
we can just say create doctor mutation
[193:52] (11632.72s)
dot is appending and for the content
[193:55] (11635.36s)
we'll just say add doctor
[194:01] (11641.60s)
right but we would like to see it if we
[194:04] (11644.16s)
are not in the loading state so I'm
[194:06] (11646.16s)
going to cut this I'll say if create
[194:09] (11649.44s)
doctor mutation is in the pending state
[194:12] (11652.64s)
we can say adding dot dot dot what else
[194:16] (11656.72s)
we'll say add doctor okay now let's try
[194:20] (11660.96s)
to see the output
[194:23] (11663.52s)
now this is disabled we cannot click to
[194:25] (11665.76s)
it once we pass all these fields then we
[194:29] (11669.36s)
can do but what happens if you click to
[194:31] (11671.68s)
it nothing happens because we didn't
[194:34] (11674.32s)
really implement this method okay now
[194:37] (11677.84s)
let's try to make that work now first
[194:40] (11680.24s)
off before we built this method I would
[194:42] (11682.80s)
like to handle the phone change and for
[194:46] (11686.16s)
this we are going to have a util
[194:48] (11688.00s)
function let's go under the utils and
[194:50] (11690.96s)
this is a function that I have generated
[194:53] (11693.20s)
with AI please feel free to copy and
[194:56] (11696.08s)
paste it from this file under the source
[194:58] (11698.24s)
code so this will take the phone number
[195:01] (11701.12s)
and it's going to beautifully update it
[195:03] (11703.92s)
right it's going to format it and this
[195:06] (11706.16s)
is something that you know if you delete
[195:07] (11707.92s)
and tell me to type out the rest of it I
[195:10] (11710.88s)
cannot not really do it because I didn't
[195:12] (11712.88s)
type this out which is completely fine.
[195:15] (11715.12s)
This is AI generated for in like 2
[195:17] (11717.92s)
seconds, right? It's going to take the
[195:20] (11720.00s)
value and beautifully formatted to us
[195:23] (11723.44s)
numbers. Okay, let's save and we're
[195:26] (11726.48s)
going to be calling this here.
[195:28] (11728.48s)
Basically, I will say call the format
[195:31] (11731.60s)
phone number method. We're going to pass
[195:34] (11734.40s)
the value and let's say this is going to
[195:37] (11737.52s)
give us the formatted value, right? And
[195:40] (11740.16s)
we can update our state. We'll say set
[195:42] (11742.48s)
new doctor. Just keep all the fields as
[195:45] (11745.60s)
it is. But on top of it, just update the
[195:48] (11748.48s)
phone number to be this formatted value.
[195:52] (11752.32s)
Let's just say formatted number.
[195:56] (11756.72s)
Okay. And let's actually say formatted
[195:59] (11759.76s)
phone number just to make it super
[196:03] (11763.20s)
beautiful to read. Okay. Now let's try
[196:06] (11766.24s)
to see the output. If you put like 555 1
[196:11] (11771.12s)
2 3 like automatically it updates for
[196:14] (11774.00s)
you. But if you didn't had this method
[196:18] (11778.48s)
right if you just had the value as it is
[196:22] (11782.88s)
if you type it doesn't format it. So
[196:25] (11785.12s)
that's why we have generated this
[196:27] (11787.28s)
method. Okay. Now that we have this
[196:29] (11789.76s)
working as expected we can save this
[196:31] (11791.84s)
file and get into the handle save
[196:34] (11794.08s)
method. So once we click to that add
[196:37] (11797.04s)
doctor button we are going to call this
[196:39] (11799.04s)
method where we would like to call our
[196:41] (11801.20s)
mutation. So we'll say create doctor
[196:43] (11803.68s)
mutation. Go ahead call the mutate
[196:46] (11806.16s)
method and we would like to pass this
[196:49] (11809.20s)
object where we will take the new doctor
[196:52] (11812.56s)
object and pass into it right and then
[196:56] (11816.56s)
if you wanted to we can add the on
[196:58] (11818.80s)
success field as well. So in another
[197:01] (11821.44s)
object you would have on success. You
[197:04] (11824.64s)
know what happens if it is done
[197:06] (11826.08s)
successfully? Well, we can close the
[197:08] (11828.48s)
model right. So we're going to call this
[197:10] (11830.72s)
method. Let's say call the on close
[197:13] (11833.52s)
method. And we can also update our state
[197:17] (11837.28s)
to be reseted. Well actually you know
[197:21] (11841.20s)
like instead of copying this and pasting
[197:25] (11845.44s)
we can sorry like instead of doing this
[197:29] (11849.28s)
part
[197:30] (11850.88s)
it is the exact same thing as this
[197:32] (11852.64s)
method right we can just say call the
[197:35] (11855.60s)
handle close method
[197:38] (11858.48s)
and this should be it and we can even
[197:40] (11860.24s)
shorten this let's go ahead just say
[197:43] (11863.28s)
handle close
[197:47] (11867.92s)
Okay, so that's what we're going to
[197:50] (11870.00s)
have. We can even shorten this. I'm just
[197:52] (11872.16s)
realizing, but we could just say handle
[197:54] (11874.48s)
close. And that's it. Let's put this as
[197:57] (11877.68s)
one line.
[197:59] (11879.76s)
Okay, so we're going to call the mutate
[198:02] (11882.64s)
method by passing our state. So this is
[198:04] (11884.96s)
the new doctor that we would like to
[198:06] (11886.80s)
create. And on success, just go ahead
[198:09] (11889.92s)
close the model and reset our state.
[198:13] (11893.44s)
Well, let's test it out. First, I'll
[198:15] (11895.44s)
open up my database so that we can check
[198:18] (11898.00s)
it out in real time.
[198:20] (11900.40s)
Let's put them side by side. Under the
[198:23] (11903.60s)
tables, we don't really have any
[198:25] (11905.60s)
doctors, right?
[198:28] (11908.16s)
Let's see it pretty quickly. We have a
[198:30] (11910.16s)
user, but no doctors at all. Let's add
[198:33] (11913.92s)
something. I'll just refresh.
[198:37] (11917.28s)
Okay, we have the aloing state.
[198:40] (11920.56s)
Let's add a doctor. So, I'll say Dr.
[198:43] (11923.12s)
John Smith.
[198:47] (11927.44s)
Let's put dot at the end. Let's say
[198:49] (11929.76s)
general dentistry.
[198:54] (11934.56s)
And for the email, we can just say John
[198:58] (11938.00s)
Smithgmail.com.
[199:02] (11942.80s)
If I can type correctly, I'll say 555 1
[199:06] (11946.16s)
2 3 4 5 6 7
[199:09] (11949.36s)
mail active. Let's say add the doctor.
[199:12] (11952.40s)
We have the loading state. It has been
[199:14] (11954.48s)
added. Hopefully, it should update the
[199:16] (11956.96s)
UI, but look looks like it doesn't. So,
[199:20] (11960.96s)
we can fix this. No worries. Let's take
[199:23] (11963.28s)
a look at the database and just refresh.
[199:27] (11967.28s)
Okay. So, we got the doctor, right? All
[199:30] (11970.16s)
the fields as provided and we even got
[199:33] (11973.28s)
an image URL. So, let's copy it, paste
[199:36] (11976.48s)
it right here. Okay. So that's the image
[199:39] (11979.60s)
URL for this user. Now if you go back to
[199:42] (11982.88s)
the local host, you're going to see this
[199:45] (11985.20s)
error and we can fix this by going into
[199:47] (11987.68s)
the next config.ts file just like what
[199:50] (11990.96s)
we have done here. We can add this
[199:53] (11993.60s)
domain as well for the images. Okay,
[199:56] (11996.32s)
once you have add this, if you refresh,
[199:59] (11999.68s)
it should work as expected.
[200:02] (12002.24s)
So let's wait for a second loading and
[200:05] (12005.28s)
then we should be able to see this new
[200:07] (12007.36s)
doctor that we just added. Okay, now
[200:10] (12010.24s)
everything is working as expected. But
[200:12] (12012.56s)
there is one thing that we need to fix.
[200:14] (12014.72s)
So once we add a new doctor, we would
[200:17] (12017.28s)
like to see it immediately, right? We
[200:19] (12019.44s)
don't really want to refresh this so
[200:21] (12021.52s)
that we can fetch the latest data. And
[200:24] (12024.72s)
to be able to implement this, we are
[200:26] (12026.64s)
going to visit our custom hook. Let's
[200:29] (12029.12s)
close this file. will go under the use
[200:31] (12031.52s)
doctors. So once we have create the
[200:34] (12034.88s)
doctor successfully, instead of just
[200:37] (12037.20s)
having a console log, we are going to
[200:39] (12039.28s)
update the cache on the client side. So
[200:42] (12042.08s)
this is how we can do it. Just follow
[200:44] (12044.24s)
along with me. So we're going to get a
[200:46] (12046.40s)
query client by using this hook from
[200:50] (12050.16s)
tenstack and then on success.
[200:53] (12053.92s)
Let's open up this function. I will say
[200:56] (12056.56s)
queryclient
[200:58] (12058.24s)
dot invalidate queries. Basically, we
[201:01] (12061.44s)
would like to run this query again so
[201:04] (12064.24s)
that we can get the latest data. And if
[201:06] (12066.88s)
you want to call this query, you just
[201:09] (12069.12s)
need to pass the query key here. I'll
[201:11] (12071.44s)
say here's the query key which is equal
[201:14] (12074.64s)
to get doctors.
[201:20] (12080.32s)
Okay. So once again once we successfully
[201:23] (12083.36s)
create a doctor this method is going to
[201:25] (12085.92s)
run and this is going to call this query
[201:29] (12089.52s)
right which is to get the doctors and
[201:32] (12092.88s)
clean the cache right clear the cache.
[201:35] (12095.36s)
Let's say invalidate
[201:38] (12098.00s)
related queries
[201:42] (12102.16s)
to refresh the data. Okay. Now let's try
[201:47] (12107.12s)
to test this out once again.
[201:51] (12111.04s)
I'll just refresh this page and create a
[201:54] (12114.48s)
new doctor. Let's this time say
[201:57] (12117.12s)
doctor.jane
[201:59] (12119.36s)
smmith
[202:01] (12121.12s)
again. We can say something like general
[202:03] (12123.20s)
dentistry. That's fine. janegmail.com
[202:07] (12127.76s)
or maybe Jane Smith.
[202:11] (12131.12s)
Let's give a different number this time.
[202:14] (12134.16s)
I'll do backwards because why not?
[202:18] (12138.64s)
Okay. something like this. Let's say
[202:20] (12140.32s)
female. And it should be active. Add the
[202:23] (12143.36s)
doctor. Once it is added here, you can
[202:25] (12145.84s)
see it immediately fetches the doctors
[202:28] (12148.80s)
because it's going to call this
[202:30] (12150.32s)
mutation.
[202:31] (12151.92s)
So I hope that we understand everything
[202:34] (12154.00s)
that is going on at the moment. Right?
[202:36] (12156.64s)
So that's the entire thing for the add
[202:39] (12159.52s)
doctor dialogue. Next, we would like to
[202:42] (12162.48s)
create another one so that we can edit
[202:45] (12165.60s)
the user profile. So we are going to
[202:48] (12168.32s)
click to this button right it's going to
[202:51] (12171.36s)
open up another model where we can
[202:53] (12173.92s)
update the user profile. So this is what
[202:56] (12176.64s)
we're going to build now. So let's get
[202:58] (12178.72s)
into the VS code. Before we build the
[203:01] (12181.60s)
component let's try to build the server
[203:04] (12184.16s)
action under the doctors.ts.
[203:06] (12186.96s)
So that was the create doctor method.
[203:09] (12189.68s)
Now let's say export async function.
[203:13] (12193.04s)
This time this is going to be update
[203:15] (12195.52s)
doctor. And if you want to update a
[203:18] (12198.00s)
doctor profile again we would like to
[203:20] (12200.24s)
pass some input and type for this is
[203:23] (12203.28s)
going to be pretty similar actually. So
[203:25] (12205.52s)
let's say interface update doctor input
[203:29] (12209.36s)
and this is going to extend let's say
[203:31] (12211.76s)
extends uh you know the create doctor
[203:34] (12214.88s)
input
[203:36] (12216.72s)
and we're going to pass the ID let's say
[203:39] (12219.52s)
string. So here we're using partial
[203:42] (12222.40s)
which means we might have some of these
[203:44] (12224.96s)
fields under this input or we might we
[203:48] (12228.24s)
might not have right maybe user only
[203:50] (12230.32s)
wants to update the name of the doctor
[203:52] (12232.88s)
and we will say you know just get
[203:55] (12235.20s)
everything and on top of it extend it
[203:57] (12237.36s)
with the ID so that we know which doctor
[203:59] (12239.92s)
that we are updating. So I hope that
[204:02] (12242.32s)
makes sense if not just follow along. I
[204:04] (12244.88s)
think it's going to be clear. So let's
[204:06] (12246.96s)
say this is the type of input and here
[204:09] (12249.12s)
if you just say input dot and you're
[204:11] (12251.60s)
going to see all these fields are
[204:14] (12254.16s)
partial right which means they are
[204:16] (12256.32s)
optional they have question mark but id
[204:19] (12259.04s)
is always going to be there because we
[204:21] (12261.12s)
are extending it okay now let's say try
[204:24] (12264.32s)
and catch block under the try first I
[204:27] (12267.28s)
would like to validate some required
[204:30] (12270.32s)
fields right so here I'll say if there
[204:32] (12272.96s)
is no input
[204:35] (12275.60s)
name or if there is not email we'll just
[204:38] (12278.72s)
say these fields are required then after
[204:42] (12282.64s)
this if check we can find the actual
[204:45] (12285.52s)
doctor right so I'll say const current
[204:48] (12288.48s)
doctor and let's say it's going to be
[204:50] (12290.96s)
equal to a wait prisma doctor right
[204:54] (12294.88s)
we'll go under this table and we would
[204:56] (12296.80s)
like to find a unique resource and we'll
[205:00] (12300.32s)
say where the ID is equal to this input
[205:04] (12304.88s)
do ID
[205:05] (12305.84s)
And we would like to select
[205:08] (12308.72s)
let's say the email field here. I'll
[205:11] (12311.84s)
just say email should be equal to true.
[205:14] (12314.08s)
So this is going to have the doctor with
[205:16] (12316.56s)
the email field because we're going to
[205:19] (12319.28s)
be using this email in a second. First
[205:21] (12321.60s)
let's say if there is not the current
[205:23] (12323.52s)
doctor. We'll just say doctor not found.
[205:26] (12326.80s)
And one more thing another if check that
[205:29] (12329.28s)
we're going to have. So if you want to
[205:31] (12331.68s)
update a profile and if you want to
[205:34] (12334.16s)
update the email before we update it, we
[205:37] (12337.28s)
should check if the new email existed or
[205:39] (12339.84s)
not because email field should be
[205:42] (12342.00s)
unique. So for that reason, I'll just
[205:44] (12344.48s)
add a comment. Let's say if email is
[205:47] (12347.68s)
changing,
[205:49] (12349.60s)
let's add it as a comment.
[205:54] (12354.32s)
Check if new email already exists. And
[205:58] (12358.80s)
here is how we can do it.
[206:01] (12361.92s)
I'll just say if input email is not
[206:04] (12364.80s)
equal to the current doctor email which
[206:07] (12367.28s)
means it is changing and we're going to
[206:09] (12369.44s)
check if it is already existed in the
[206:11] (12371.44s)
database or not. If it is existed we are
[206:14] (12374.24s)
going to throw an error but else we can
[206:16] (12376.64s)
go ahead and create the I mean update
[206:19] (12379.36s)
the new doctor. So we'll say await
[206:22] (12382.48s)
prisma doctor.update update and here are
[206:26] (12386.40s)
the fields that we would like to update.
[206:28] (12388.56s)
But first, we need to say which doctor
[206:30] (12390.72s)
that we would like to update. So we'll
[206:32] (12392.80s)
say here is the ID that is coming from
[206:35] (12395.20s)
the input do ID, right? And then we're
[206:38] (12398.24s)
going to pass another object.
[206:40] (12400.88s)
Um actually this is going to be the data
[206:42] (12402.88s)
field, right? Um here are the data that
[206:45] (12405.52s)
we would like to update. So we'll say
[206:47] (12407.28s)
name is going to be coming from
[206:48] (12408.88s)
input.name. So if user wants to update
[206:51] (12411.36s)
it, we're going to pass it as it is.
[206:53] (12413.68s)
Then let's duplicate it a couple of
[206:55] (12415.20s)
times. We're going to say email.
[206:58] (12418.32s)
Basically, all the fields that a doctor
[207:00] (12420.48s)
could have. Let's say phone. And after
[207:04] (12424.00s)
the phone, I have added these three
[207:06] (12426.24s)
different fields. Now, we don't really
[207:08] (12428.00s)
want to update the image URL. So, you
[207:10] (12430.16s)
don't really want to uh have this here,
[207:12] (12432.96s)
right? So, we're going to ignore this.
[207:15] (12435.36s)
Um, I think that's the entire method. We
[207:17] (12437.60s)
can just go ahead and just say return
[207:19] (12439.52s)
this doctor out of this method. And if
[207:22] (12442.32s)
we have any errors, we can handle this.
[207:24] (12444.56s)
I mean just by throwing the error,
[207:26] (12446.64s)
right? Um so that's the entire server
[207:29] (12449.36s)
action. We have a couple of different
[207:31] (12451.36s)
checks. And once everything is fine, we
[207:34] (12454.16s)
can update it in our database. And just
[207:36] (12456.80s)
one more thing you might be asking
[207:38] (12458.72s)
instead of doing all this where we get
[207:40] (12460.72s)
the input, put the name, email, phone,
[207:43] (12463.92s)
couldn't we just delete everything and
[207:45] (12465.76s)
just say get the input fields and spread
[207:48] (12468.64s)
this? Well, that's what I think as well,
[207:51] (12471.12s)
but I'll just leave a comment here. If
[207:53] (12473.60s)
you do this, it's going to trigger the
[207:55] (12475.84s)
unique constraint violation for email.
[207:59] (12479.36s)
So, you're going to get an error. That's
[208:01] (12481.28s)
what I had. So, that's why I'm going to
[208:03] (12483.20s)
leave it in this way. Okay. So, I will
[208:05] (12485.68s)
save this file and we're going to go
[208:07] (12487.52s)
into the hooks and specifically for the
[208:10] (12490.56s)
use doctor's file. Right. Within this
[208:13] (12493.52s)
file, we will actually have the exact
[208:15] (12495.52s)
same thing, but this time in the
[208:17] (12497.44s)
mutation, we are going to call the
[208:19] (12499.52s)
update doctor method. Right? So, I'll
[208:22] (12502.00s)
just paste this in. As you can tell, it
[208:24] (12504.16s)
is the exact same thing that we have up
[208:26] (12506.40s)
here. We're going to get the query
[208:28] (12508.08s)
client, return the result of this
[208:30] (12510.40s)
mutation. So, you can either assign it
[208:32] (12512.64s)
first and then return or just
[208:34] (12514.72s)
immediately return. That's the same
[208:36] (12516.48s)
thing basically. And here, let's get the
[208:39] (12519.52s)
server action. And once we update it
[208:42] (12522.56s)
successfully, we are going to call this
[208:45] (12525.36s)
query so that we can you know get the
[208:48] (12528.32s)
latest data instead of using the cache.
[208:51] (12531.44s)
Okay. So that's the entire hook. Let's
[208:53] (12533.52s)
save. Now we can go ahead create this
[208:56] (12536.24s)
component
[208:57] (12537.92s)
or the dialogue. Right? So I'll say here
[209:00] (12540.72s)
we will have the edit. Let's say doctor
[209:06] (12546.80s)
dialogue.
[209:09] (12549.84s)
And this is going to take a couple of
[209:11] (12551.60s)
different props just like what we have
[209:14] (12554.00s)
here. Maybe I can copy it, paste it or
[209:17] (12557.52s)
the is open. We'll say is edit dialogue
[209:20] (12560.32s)
open. And here we will do the same thing
[209:24] (12564.32s)
for the on close method. One more state
[209:26] (12566.80s)
that we are going to add which is going
[209:28] (12568.64s)
to be the doctor and this is going to be
[209:31] (12571.60s)
the selected doctor state. So basically
[209:35] (12575.36s)
let's say you click to this doctor right
[209:38] (12578.24s)
it's going to take that doctor's detail
[209:40] (12580.96s)
and put it into this component. So
[209:42] (12582.96s)
that's why we are passing it. Now let's
[209:45] (12585.36s)
copy this name. Go under the source and
[209:49] (12589.52s)
components under the admin. We're going
[209:51] (12591.68s)
to paste this in and let's say tsx.
[209:56] (12596.48s)
Let's try to import it and then get into
[209:59] (12599.68s)
the component itself. So this component
[210:02] (12602.48s)
is going to be really similar to this
[210:04] (12604.64s)
one because at the end of the day this
[210:06] (12606.72s)
will also be a dialogue. Right now just
[210:09] (12609.60s)
before we get into this I think we are
[210:11] (12611.52s)
missing we are missing couple of
[210:13] (12613.20s)
different things right here which are
[210:15] (12615.20s)
these methods. So when once we call this
[210:18] (12618.40s)
we are actually we should be passing the
[210:21] (12621.44s)
doctor. So I'll say here's the doctor
[210:23] (12623.92s)
argument and we are going to take this
[210:26] (12626.64s)
let's say this function gets the doctor
[210:28] (12628.88s)
you just type of doctor right um go
[210:32] (12632.24s)
ahead and import it in this case we'll
[210:34] (12634.48s)
say set selected doctor state with this
[210:38] (12638.32s)
one and then we're going to make this
[210:41] (12641.12s)
edit
[210:42] (12642.64s)
dialogue should be open right we'll just
[210:44] (12644.48s)
say this is equal to true um here this
[210:47] (12647.52s)
says
[210:49] (12649.44s)
some TypeScript errors we have the
[210:52] (12652.72s)
signature does not match. So to fix
[210:55] (12655.68s)
this, we are going to go ahead and here
[210:58] (12658.32s)
explicitly we'll just say this could be
[211:00] (12660.72s)
either a doctor or a null value. Now
[211:03] (12663.68s)
this should be happy with us. And once
[211:05] (12665.92s)
we close this dialogue, we'll say set is
[211:09] (12669.28s)
edit dialogue open should be equal to
[211:11] (12671.76s)
false because we just closed that. And
[211:14] (12674.64s)
the selected doctor is going to be equal
[211:17] (12677.76s)
to null since the dialogue is closed.
[211:21] (12681.20s)
Right? And we would like to call this as
[211:23] (12683.28s)
well once we click to the like once we
[211:26] (12686.80s)
call the on close method. And here I
[211:30] (12690.16s)
think this is wrong. Let's fix it. We
[211:32] (12692.80s)
will say handle
[211:35] (12695.04s)
closeedit dialogue. Okay. So once again
[211:39] (12699.12s)
what we have updated is this part as
[211:41] (12701.76s)
well as these two methods. Now let's go
[211:45] (12705.04s)
here under this component we are going
[211:47] (12707.36s)
to create an interface say interface and
[211:51] (12711.28s)
we can put this as this name. So we'll
[211:54] (12714.88s)
say ops at the end. This is taking the
[211:57] (12717.76s)
is open field which is a boolean value
[212:01] (12721.36s)
on close method which is basically a
[212:04] (12724.40s)
function that doesn't return anything.
[212:07] (12727.60s)
Let's say this is a function and it
[212:09] (12729.36s)
doesn't return anything. And then this
[212:11] (12731.52s)
is getting the doctor state. Let's
[212:14] (12734.88s)
scroll to the bottom. We are getting a
[212:16] (12736.64s)
doctor
[212:20] (12740.08s)
that could be either doctor type or it
[212:23] (12743.20s)
could be null.
[212:25] (12745.12s)
Okay. Now let's try to dstructure these
[212:28] (12748.72s)
fields
[212:30] (12750.40s)
there. Type safe. That's perfect. Okay.
[212:33] (12753.76s)
Now we're going to have one local state.
[212:36] (12756.40s)
I'll say const and let me give a little
[212:38] (12758.40s)
bit space. I'll say editing doctor and
[212:42] (12762.32s)
it said editing doctor
[212:48] (12768.88s)
let's say use state initially this could
[212:51] (12771.68s)
be null or we could just pass the doctor
[212:54] (12774.48s)
as it is and here let's say this could
[212:57] (12777.20s)
be type of doctor or it could be even
[212:59] (12779.60s)
null. Now, even though we set this up,
[213:02] (12782.64s)
like if you console log this, let's see
[213:05] (12785.44s)
what you're going to get. Let's say
[213:07] (12787.44s)
editing doctor. I think we are going to
[213:09] (12789.84s)
get undefined even though we passed it.
[213:12] (12792.32s)
This is how React works. Kind of
[213:14] (12794.48s)
annoying, but once you refresh,
[213:18] (12798.72s)
we should be able to see null, right?
[213:21] (12801.36s)
Even if you clicked it, it's still null.
[213:24] (12804.16s)
And you might be saying like, we have
[213:26] (12806.40s)
this, right? Why is it null? Well, let's
[213:29] (12809.92s)
not talk about the why, but we can talk
[213:32] (12812.00s)
about the solution here. We can add a
[213:34] (12814.48s)
key, which is going to be let's say
[213:36] (12816.88s)
selected doctor ID. Okay. Now, if you
[213:41] (12821.92s)
refresh this page,
[213:45] (12825.76s)
so it is null initially, but if you
[213:47] (12827.84s)
click to it here, we can see we are
[213:49] (12829.76s)
getting the actual doctor value, which
[213:52] (12832.16s)
is a Jane in this case. If you click to
[213:54] (12834.48s)
this one, it should be John.
[213:57] (12837.28s)
All right. So that's the solution. This
[213:59] (12839.12s)
is going to force this component to be
[214:02] (12842.16s)
mounted every single time the doctor
[214:04] (12844.72s)
field changes. I hope that makes sense.
[214:07] (12847.68s)
And to be honest, this is a bit advanced
[214:10] (12850.32s)
React. So if you have seen this for the
[214:12] (12852.96s)
very first time, that's really good. Uh
[214:15] (12855.36s)
because this is literally some advanced
[214:18] (12858.32s)
React. You wouldn't see this in other
[214:20] (12860.48s)
places.
[214:23] (12863.04s)
Seriously, like you you probably you
[214:25] (12865.28s)
have never seen this. Uh but anyways,
[214:27] (12867.68s)
let's go into this component. Let's
[214:29] (12869.76s)
import our hook which is use update
[214:32] (12872.56s)
doctor. We are going to get our mutation
[214:35] (12875.20s)
and then we can have the exact same
[214:37] (12877.76s)
method that we have here which is to
[214:41] (12881.04s)
handle the phone number change. So I'll
[214:44] (12884.08s)
go ahead paste this in import the method
[214:48] (12888.48s)
and this time we'll say set editing
[214:50] (12890.96s)
doctor. Um this value is going to be
[214:55] (12895.12s)
editing doctor just keep it as it is but
[214:57] (12897.92s)
we'll like to update the phone
[215:00] (12900.96s)
uh field but what this says I think we
[215:04] (12904.08s)
have some kind of a warning um basically
[215:08] (12908.24s)
it says this could be undefined so you
[215:10] (12910.56s)
have to add some check um I'll say if
[215:14] (12914.16s)
editing doctor if this is available
[215:16] (12916.72s)
right only then go ahead run this if
[215:19] (12919.84s)
this is null don't don't do it So this
[215:22] (12922.16s)
is how we make Typescript happy. And
[215:24] (12924.40s)
then on handle save we are going to be
[215:27] (12927.04s)
doing a similar thing what we have done
[215:30] (12930.00s)
right here. We are going to call our
[215:31] (12931.68s)
mutation and on success we can call the
[215:35] (12935.36s)
handle close method. So I will go into
[215:37] (12937.76s)
this component just paste this in on
[215:40] (12940.72s)
handle save on success we are going to
[215:43] (12943.12s)
call handle close which is basically is
[215:46] (12946.32s)
going to close the model and set this
[215:48] (12948.72s)
state to be equal to null. Okay, so
[215:51] (12951.76s)
really really easy three functions. I
[215:55] (12955.04s)
hope this is not really confusing. And
[215:57] (12957.44s)
then we're going to have the return
[215:59] (12959.12s)
statement. Let's give a little bit space
[216:02] (12962.24s)
which is going to be a dialogue
[216:04] (12964.08s)
component.
[216:05] (12965.68s)
So believe it or not, that's the exact
[216:08] (12968.00s)
same thing what we have here. We will
[216:10] (12970.32s)
just change a couple of different places
[216:12] (12972.72s)
like the content. Instead of saying add
[216:15] (12975.28s)
new doctor, we will say things like edit
[216:18] (12978.08s)
this current doctor. First, let me copy
[216:20] (12980.88s)
this dialogue. Paste this in and close
[216:24] (12984.16s)
this off. Okay. So, we are going to have
[216:26] (12986.72s)
this component. Let's import it.
[216:30] (12990.16s)
Then, um I think I'll just get the
[216:32] (12992.32s)
dialogue content as well right after
[216:34] (12994.88s)
this. Paste this in. Close this off. And
[216:38] (12998.48s)
import the component from the UI folder.
[216:42] (13002.08s)
Right. And let's get the content. So
[216:45] (13005.28s)
I'll copy this part, paste this in and
[216:49] (13009.20s)
import all of them one by one.
[216:54] (13014.96s)
Okay. So we're going to update the text.
[216:57] (13017.04s)
Let's say edit doctor.
[217:00] (13020.48s)
Um here we are going to update the
[217:03] (13023.04s)
description as well.
[217:06] (13026.24s)
Say information and status.
[217:11] (13031.44s)
Okay. then we'll go right after the
[217:14] (13034.16s)
header and we'll have a check. So here
[217:17] (13037.68s)
I'll say if there is editing doctor
[217:20] (13040.72s)
right in this case we would like to
[217:22] (13042.88s)
render render this right hand side which
[217:25] (13045.44s)
is going to be this div that is um that
[217:28] (13048.40s)
is getting the class name of grid and
[217:31] (13051.20s)
basically we are trying to build this
[217:33] (13053.84s)
model at the moment right so we have the
[217:36] (13056.08s)
header now it is time to build the
[217:38] (13058.16s)
actual inputs
[217:40] (13060.40s)
so since we have done this previously I
[217:42] (13062.64s)
think you can just copy and paste it
[217:44] (13064.48s)
from the source code it's not really
[217:46] (13066.56s)
that important at this point. So this is
[217:49] (13069.52s)
what I just pasted. Let's import the
[217:52] (13072.16s)
label as well as the input.
[217:55] (13075.20s)
Okay. So we're having the name specialty
[217:58] (13078.24s)
and we are updating only those fields
[218:00] (13080.96s)
once we type into the input. Then we are
[218:04] (13084.00s)
going to shrink this grid columns too.
[218:07] (13087.44s)
Go right after this div. Paste this in
[218:09] (13089.84s)
where we have the email field. And then
[218:12] (13092.88s)
right after this one, we're going to get
[218:14] (13094.80s)
one more div. So after this one, paste
[218:19] (13099.04s)
this in where we have the phone field.
[218:21] (13101.68s)
And then right after this one, we are
[218:23] (13103.84s)
going to paste the select items just
[218:26] (13106.48s)
like what we have done previously.
[218:29] (13109.52s)
So let's get all these components. Get
[218:32] (13112.08s)
the gender type from Prisma client.
[218:35] (13115.60s)
Select value content as well as the
[218:38] (13118.08s)
item.
[218:40] (13120.08s)
where we have the status that could be
[218:42] (13122.08s)
one of these values and the same for the
[218:45] (13125.36s)
sorry the gender. Okay, sorry the gender
[218:49] (13129.12s)
could be one of these fields under the
[218:51] (13131.20s)
select item and the status which could
[218:54] (13134.32s)
be one of these either active or
[218:56] (13136.80s)
inactive. And then we'll go right below
[219:00] (13140.48s)
to this conditional check, right? And
[219:03] (13143.20s)
we're going to add the dialogue footer
[219:05] (13145.60s)
component where we can
[219:08] (13148.56s)
let's see where we can call the handle
[219:11] (13151.76s)
close method as well as the handle save
[219:14] (13154.72s)
when we click one of these buttons.
[219:20] (13160.48s)
Okay, so I know that we have copied and
[219:22] (13162.48s)
pasted almost the entire thing, but
[219:25] (13165.12s)
that's basically the same content,
[219:26] (13166.88s)
right? You could probably make a
[219:29] (13169.28s)
reusable component instead of copying
[219:31] (13171.60s)
and pasting. But I think that's
[219:33] (13173.36s)
completely acceptable. It's really easy
[219:36] (13176.00s)
to understand in this way. Um I think we
[219:39] (13179.36s)
have everything as expected, right? We
[219:42] (13182.00s)
should just test this out. I'll go in
[219:45] (13185.44s)
admin dashboard.
[219:47] (13187.68s)
Let's try to update the name of this uh
[219:51] (13191.04s)
of this doctor. We'll say Jane Smith
[219:53] (13193.52s)
123. update.
[219:57] (13197.36s)
Okay, here we can see it's been updated.
[219:59] (13199.28s)
Let's refresh. It should still have that
[220:02] (13202.16s)
one, two, three at the end, which means
[220:04] (13204.48s)
this is actually coming from the
[220:06] (13206.24s)
database. Let's refresh.
[220:11] (13211.28s)
let's see. Okay, so this is the actual
[220:14] (13214.88s)
update. Let's put it back. And we can
[220:18] (13218.00s)
update multiple fields at at once,
[220:20] (13220.96s)
right? We can say inactive. Dentistry 1
[220:23] (13223.92s)
2 3. Save changes
[220:27] (13227.04s)
as you can tell.
[220:29] (13229.52s)
Now let's bring this back. I'll make
[220:31] (13231.36s)
this to be active. And this should be
[220:34] (13234.40s)
good to go. Now here we can see as we
[220:36] (13236.72s)
update the dashboard is also changing. I
[220:39] (13239.28s)
just realized that if this is inactive
[220:43] (13243.20s)
the active doctors is going to be one.
[220:45] (13245.76s)
So everything is in sync actually.
[220:50] (13250.48s)
Um okay so that's the entire thing for
[220:52] (13252.72s)
the admin dashboard. I would like to
[220:54] (13254.72s)
update this loading state which doesn't
[220:57] (13257.84s)
really look nice. So we are going to be
[221:01] (13261.76s)
going in this component and here this is
[221:04] (13264.40s)
your challenge to build a beautiful
[221:06] (13266.88s)
return statement but just to make you
[221:09] (13269.44s)
happy I think I will have one of them.
[221:13] (13273.04s)
So let's say loading UI
[221:17] (13277.20s)
and we can create this at the bottom of
[221:19] (13279.44s)
this component. So since it is 10 lines
[221:22] (13282.40s)
of code, I have just copied it and paste
[221:24] (13284.80s)
it from the source code. Feel free to
[221:26] (13286.72s)
pause the video and type this out or you
[221:29] (13289.12s)
could even change this UI completely.
[221:31] (13291.52s)
Let's refresh and see. Okay, so this is
[221:34] (13294.80s)
the component that we just added. It has
[221:36] (13296.96s)
the navbar and then the loading spinner
[221:40] (13300.40s)
as you can tell.
[221:42] (13302.64s)
Now with this I think we have completed
[221:44] (13304.72s)
the entire admin dashboard the admin
[221:48] (13308.08s)
page. So what we have learned in this
[221:50] (13310.16s)
section is
[221:52] (13312.48s)
this convention where we basically have
[221:55] (13315.20s)
some hooks that are calling our server
[221:58] (13318.16s)
actions right. So in this case we are
[222:00] (13320.64s)
getting the appointments and here we
[222:02] (13322.96s)
have everything related to doctors that
[222:05] (13325.60s)
could be either a mutation or some
[222:08] (13328.48s)
queries right and we have learned about
[222:11] (13331.20s)
how to update the cache as well right
[222:14] (13334.24s)
using tenstack query so with that let's
[222:17] (13337.44s)
try to open up the status bar and commit
[222:21] (13341.20s)
our changes first I'll create a new
[222:23] (13343.92s)
branch let's say admin page
[222:29] (13349.44s)
And then stage all of our changes
[222:33] (13353.12s)
for the message we could say admin page
[222:36] (13356.32s)
added and let's say commit I think then
[222:40] (13360.88s)
publish the branch go into source code
[222:45] (13365.76s)
and create the pull request.
[222:48] (13368.64s)
So we have so many changes. It's going
[222:50] (13370.88s)
to take a bit, you know, it's going to
[222:52] (13372.72s)
take a while for code rabbit to review
[222:55] (13375.76s)
the entire code and give us some
[222:57] (13377.36s)
suggestions. So let's wait for it and
[222:59] (13379.68s)
then come back to the video. So these
[223:01] (13381.84s)
are the new features that we have
[223:03] (13383.60s)
introduced in this section. Here are all
[223:06] (13386.40s)
the files with the related changes that
[223:08] (13388.88s)
we have done. You can pause the video
[223:10] (13390.80s)
and read it. Let's not waste any time by
[223:14] (13394.00s)
reading them, right? You can always do
[223:16] (13396.00s)
it. Then we have the sequence diagram
[223:19] (13399.20s)
where basically this says under the
[223:21] (13401.60s)
page.tsx tsx under the admin page there
[223:24] (13404.96s)
is an authentication check right if you
[223:27] (13407.52s)
are not the admin or if you don't have
[223:29] (13409.76s)
this environment variable is going to
[223:32] (13412.16s)
redirect you to the dashboard page but
[223:34] (13414.56s)
if you're admin you can see the admin
[223:37] (13417.28s)
dashboard client component which is
[223:40] (13420.00s)
going to handle the getting doctors
[223:42] (13422.72s)
getting appointments and then within
[223:45] (13425.44s)
this component we have the create doctor
[223:48] (13428.88s)
functionality right and then updating
[223:52] (13432.08s)
uh functionality as well. And here is
[223:54] (13434.16s)
how everything works. You can pause the
[223:57] (13437.04s)
video and take a look at it, but we have
[223:59] (13439.60s)
already explained this in detail. Then
[224:02] (13442.16s)
let's see some code suggestions from
[224:04] (13444.48s)
code rabbit. Um here it says instead of
[224:08] (13448.00s)
just getting the loading state, you
[224:10] (13450.32s)
could also get the error, right? This is
[224:12] (13452.96s)
something that you can get by the way
[224:14] (13454.56s)
from tenstack. You can also get the
[224:17] (13457.04s)
refetge method. So this is a really
[224:19] (13459.44s)
really good suggestion that you would
[224:21] (13461.68s)
like to have in a real world
[224:23] (13463.20s)
application. But here let's not waste
[224:25] (13465.76s)
any time but let's take a look at the
[224:28] (13468.24s)
code itself. So if there is any kind of
[224:30] (13470.64s)
an error you would like to show the
[224:33] (13473.12s)
error UI just like the loading UI and in
[224:36] (13476.24s)
the error UI you are going to call the
[224:38] (13478.48s)
on on retry method which is basically
[224:42] (13482.00s)
going to refetch the data again. Okay.
[224:45] (13485.36s)
So this is something that you can look
[224:47] (13487.28s)
into if you're interested. Then here
[224:50] (13490.24s)
some more optimization. It says only
[224:53] (13493.68s)
call this method if it is you know if it
[224:56] (13496.16s)
is already open. Let's scroll to the
[224:58] (13498.88s)
bottom.
[225:00] (13500.96s)
Okay. So this is one of the ways. Let's
[225:04] (13504.24s)
go into the component.
[225:08] (13508.48s)
So I think it should be under the edit
[225:12] (13512.08s)
doctor dialogue.
[225:15] (13515.76s)
So you remember we have this field but
[225:18] (13518.32s)
it was equal to null. So we went into
[225:20] (13520.80s)
the admin dashboard client and
[225:24] (13524.88s)
we have let's see where that was.
[225:32] (13532.88s)
So here we have included the key. So
[225:35] (13535.20s)
this is the other way of doing it. This
[225:37] (13537.20s)
is what we have selected. But if you
[225:39] (13539.68s)
want to, you could add this use effect
[225:42] (13542.24s)
that they show you. It is just a
[225:44] (13544.40s)
different way of doing it.
[225:47] (13547.28s)
And then like literally you can pause
[225:49] (13549.68s)
the video, take a look at all these
[225:51] (13551.36s)
suggestions. I think these are all the
[225:53] (13553.52s)
things that I would normally implement
[225:55] (13555.36s)
if this was my let's say actual project
[225:58] (13558.48s)
that I was building. But since this is
[226:00] (13560.96s)
just a tutorial, I would like to skip
[226:03] (13563.20s)
them and merge my pull request so that
[226:06] (13566.64s)
this tutorial doesn't get 10 hours.
[226:09] (13569.20s)
Okay, let's say merge this. Confirm.
[226:14] (13574.00s)
And then we'll go ahead get the latest
[226:16] (13576.96s)
changes. Let's close it all,
[226:22] (13582.72s)
get into the master branch, and sync
[226:25] (13585.36s)
this up.
[226:28] (13588.24s)
Okay, now our codebase is up to date. Do
[226:31] (13591.52s)
we have any errors? Oh, we don't. Okay,
[226:35] (13595.12s)
so that was the entire section.
[226:36] (13596.88s)
Hopefully, I'll see you in the next one.
[226:40] (13600.00s)
So, in this section, let's get started
[226:42] (13602.16s)
with the pro page. So, first like this
[226:45] (13605.28s)
is the UI that we're going to get into
[226:46] (13606.96s)
in a second. But first, let's create
[226:49] (13609.20s)
this page under the app folder. I'll say
[226:52] (13612.72s)
pro and then page.tsx. TSX
[226:56] (13616.96s)
let's say pro page and leave this as it
[227:02] (13622.72s)
Now we are going to have three different
[227:04] (13624.40s)
plans, right? The first one is free
[227:06] (13626.56s)
where you don't really pay anything and
[227:08] (13628.96s)
you're going to get access to these
[227:11] (13631.12s)
features. Let me change the color. Okay,
[227:14] (13634.56s)
so these are the features that you will
[227:16] (13636.16s)
unlock in the free plan. And then in the
[227:18] (13638.48s)
basic plan, you're going to pay $9 a
[227:20] (13640.80s)
month and you're going to get access to
[227:22] (13642.96s)
everything in free plus these features.
[227:26] (13646.08s)
And in the pro plan, everything in basic
[227:29] (13649.84s)
plus the rest of rest of these, right?
[227:33] (13653.04s)
And we are going to be implementing this
[227:35] (13655.20s)
payments, the subscriptions with clerk.
[227:38] (13658.56s)
So head over to your clerk dashboard.
[227:41] (13661.04s)
You can find the link in the
[227:42] (13662.32s)
description. Select your project, right?
[227:45] (13665.52s)
And then under the subscriptions, we are
[227:47] (13667.60s)
going to say get started.
[227:50] (13670.48s)
Okay. So, we're going to have three
[227:52] (13672.48s)
different plans, but for now, let's just
[227:54] (13674.64s)
say create the very first one.
[227:58] (13678.48s)
And this could be the free plan, right?
[228:00] (13680.64s)
Let's click to it. And we are going to
[228:03] (13683.20s)
include all these features. So,
[228:05] (13685.84s)
unlimited appointment booking,
[228:08] (13688.72s)
the basic text chat support, and
[228:11] (13691.44s)
appointment reminders. So these are the
[228:14] (13694.08s)
things that we can type out one by one.
[228:17] (13697.44s)
The only thing that you need to fill in
[228:19] (13699.36s)
is the name. So I'll just paste this in
[228:21] (13701.68s)
which is unlimited appointment booking.
[228:24] (13704.16s)
Right? You're going to get a key by
[228:26] (13706.16s)
default and you can leave the
[228:27] (13707.60s)
description like this is optional. Okay.
[228:30] (13710.08s)
I'll just say publicly available. Let's
[228:32] (13712.56s)
create this feature. And then I will
[228:34] (13714.88s)
create these two as well. Okay. So basic
[228:38] (13718.48s)
text chat support and appointment
[228:40] (13720.80s)
reminders. Here I'll say add another
[228:44] (13724.48s)
one. So here is the name and let's say
[228:46] (13726.96s)
create the feature and then one more
[228:50] (13730.00s)
which is going to be appointment
[228:52] (13732.32s)
reminders.
[228:57] (13737.60s)
Okay. So say create feature and we can
[229:01] (13741.36s)
save this.
[229:03] (13743.20s)
So that was the free plan. Let's go back
[229:06] (13746.08s)
and we are going to create one more plan
[229:08] (13748.72s)
and this is going to be AI. Let's say
[229:11] (13751.68s)
basic. We can leave the description
[229:14] (13754.64s)
empty. Okay. So, this is um optional as
[229:17] (13757.60s)
you can tell. For the base fee, we can
[229:20] (13760.40s)
say it's going to be $9 a month. You can
[229:23] (13763.68s)
add annual discount even have a free
[229:26] (13766.72s)
trial. So, these are the option that you
[229:28] (13768.64s)
can toggle. And for the free trial, you
[229:31] (13771.36s)
can say like it could be 7 days or 14
[229:34] (13774.48s)
days, right? But in my case, I'm not
[229:36] (13776.56s)
going to have free trial. Um let's add
[229:39] (13779.44s)
our features. So we are going to say
[229:42] (13782.32s)
everything in free plus these I mean
[229:45] (13785.28s)
everything in free plus these features.
[229:47] (13787.44s)
Right? Okay. So let's say like instead
[229:51] (13791.84s)
of including them I'll just type it out.
[229:54] (13794.56s)
Let's say everything
[229:59] (13799.12s)
in free.
[230:02] (13802.56s)
So that's the first feature that we're
[230:04] (13804.72s)
going to see. And then let's add the
[230:08] (13808.56s)
rest of it, right? Let's start with this
[230:10] (13810.96s)
one. 10 AI voice calls per month.
[230:23] (13823.76s)
Then the next one is going to be AI
[230:26] (13826.32s)
dental guidance.
[230:29] (13829.12s)
So here I just put the name and let's
[230:31] (13831.12s)
say create the feature. And then the
[230:33] (13833.28s)
other one is going to be priority
[230:35] (13835.68s)
support.
[230:37] (13837.28s)
Let's add one more.
[230:42] (13842.08s)
And then I'll just say create this one
[230:44] (13844.40s)
as well. Okay. So here I'll just say
[230:47] (13847.68s)
save this feature. I mean save this plan
[230:50] (13850.24s)
as well. Now you might be asking in our
[230:52] (13852.40s)
application we don't really have
[230:53] (13853.84s)
appointment reminders or we don't have
[230:56] (13856.40s)
something like um priority support,
[230:59] (13859.52s)
right? So that's true but we are just
[231:01] (13861.60s)
adding this just to have this beautiful
[231:03] (13863.60s)
table. You can always add these features
[231:06] (13866.96s)
once you complete the project. Right now
[231:09] (13869.36s)
let's go ahead create this plan where we
[231:11] (13871.92s)
will have everything in basic and these
[231:15] (13875.28s)
three features.
[231:17] (13877.28s)
So here I'll go back create one more
[231:20] (13880.72s)
plan
[231:23] (13883.76s)
for the name we can say AIO
[231:28] (13888.56s)
and this is going to be the key and if
[231:30] (13890.80s)
you're wondering what that is once we
[231:32] (13892.80s)
use it I think you will see it um here
[231:35] (13895.92s)
for the fee we can say $19 a month you
[231:39] (13899.12s)
can add annual discount free trial but
[231:42] (13902.08s)
I'll just skip them just to keep it
[231:44] (13904.40s)
simple in here we can say everything
[231:48] (13908.96s)
in basic
[231:51] (13911.12s)
add this feature let's say basic
[231:55] (13915.20s)
and on top of this let me add all these
[231:58] (13918.00s)
three features one by one
[232:01] (13921.60s)
so this is the first one
[232:05] (13925.28s)
and let's add the second one so I just
[232:07] (13927.92s)
added these two as well okay so let's
[232:10] (13930.48s)
say save and that should be it for all
[232:13] (13933.28s)
of our plants now it is time to actually
[232:15] (13935.68s)
use them in our code. So we will go
[232:18] (13938.40s)
ahead find the VS code right. Um here
[232:22] (13942.16s)
let's say this is going to be an async
[232:24] (13944.64s)
function because we're going to have one
[232:26] (13946.56s)
check by awaiting the current user.
[232:31] (13951.36s)
So we'll say con something like user and
[232:35] (13955.04s)
we'll say if there is no user maybe you
[232:38] (13958.00s)
can just redirect the user to the
[232:40] (13960.72s)
homepage right but else we can see the
[232:43] (13963.76s)
return statement. So let me zoom in
[232:47] (13967.20s)
under this return.
[232:49] (13969.68s)
Let's say we're going to have a
[232:52] (13972.00s)
fragment.
[232:53] (13973.68s)
The first thing that we would like to
[232:55] (13975.04s)
see is the navbar component. And right
[232:58] (13978.00s)
after this, we're going to have the
[232:59] (13979.60s)
actual content. So I'll say div. Let's
[233:02] (13982.80s)
give the class name maximum width of 7x
[233:06] (13986.00s)
large auto. Let's give
[233:10] (13990.00s)
some padding from like the x direction.
[233:13] (13993.84s)
is going to get six. Vertically, it's
[233:16] (13996.08s)
going to get eight. And then petting top
[233:18] (13998.48s)
of 24.
[233:21] (14001.76s)
Then within this div, we can have the
[233:24] (14004.08s)
welcome section. So let's see it what I
[233:27] (14007.20s)
mean. So we have the navbar at this
[233:30] (14010.16s)
point. On top of it, we're going to have
[233:32] (14012.56s)
the welcome section. So this is
[233:35] (14015.52s)
something that we have built in the past
[233:37] (14017.44s)
as well. But let's go ahead and see how
[233:39] (14019.76s)
we can build it. Once again, I'll have
[233:42] (14022.24s)
the margin bottom of 12 class and let's
[233:45] (14025.76s)
say overflow dash hidden.
[233:49] (14029.60s)
Within this, I will have one div. Let's
[233:52] (14032.40s)
say class name is going to be flex item
[233:54] (14034.88s)
centered. This is kind of annoying. In
[233:57] (14037.44s)
the past, when you just say IC, this
[233:59] (14039.92s)
would get get you the item center
[234:01] (14041.92s)
automatically. But now it gets this one
[234:04] (14044.72s)
just kind of annoying. uh let's say
[234:07] (14047.44s)
justify between we'll have background
[234:10] (14050.64s)
gradient to bottom right
[234:15] (14055.44s)
from this color which is primary and
[234:18] (14058.24s)
let's change the opacity. So from
[234:20] (14060.40s)
primary 10 to let's say background.
[234:26] (14066.40s)
Okay. So this should be let me zoom out
[234:28] (14068.96s)
to dash background. And then we can say
[234:32] (14072.08s)
rounded 3x large. We'll say petting of
[234:35] (14075.76s)
eight. Let's give the border and border
[234:39] (14079.20s)
primary of let's say 20. Within this,
[234:43] (14083.20s)
I'll have one div with the class name
[234:46] (14086.72s)
being space y of four. And then I'll
[234:50] (14090.24s)
have one more div. I'll say class name
[234:53] (14093.44s)
inline dash flex. If you don't want to
[234:56] (14096.40s)
type this out, just uh grab the code
[234:59] (14099.04s)
from the source code. I'll say items
[235:01] (14101.76s)
centered. Then within this, we'll say
[235:05] (14105.20s)
gap of two,
[235:08] (14108.08s)
padding x of three, y of one. I'll say
[235:12] (14112.16s)
background primary. Let's change the
[235:15] (14115.44s)
opacity. I'll say rounded full border
[235:19] (14119.84s)
and border primary.
[235:23] (14123.92s)
Oops. Let's just say border primary
[235:28] (14128.48s)
of 20.
[235:31] (14131.28s)
So AI kind of annoying at this point,
[235:33] (14133.36s)
but these are all the classes that I
[235:35] (14135.36s)
have. Within this div, we are going to
[235:38] (14138.08s)
have one div. Okay. So, it's going to be
[235:41] (14141.28s)
this this circle and it's going to be
[235:44] (14144.56s)
animated with the tailwind class. I'll
[235:48] (14148.72s)
go right here within this div paste this
[235:51] (14151.36s)
in. This is that circle. And right after
[235:54] (14154.24s)
this, we will have a span that says
[235:56] (14156.64s)
upgrade to pro.
[235:59] (14159.52s)
Okay. So after this div, let's go and
[236:02] (14162.80s)
get one more div that is going to have
[236:06] (14166.08s)
the H1 that says unlock premium AI
[236:09] (14169.76s)
dental care and this is the description
[236:13] (14173.76s)
which is this part the heading as well
[236:16] (14176.40s)
as the description and on the right hand
[236:19] (14179.20s)
side we would like to put this icon and
[236:22] (14182.16s)
beautifully design it.
[236:24] (14184.80s)
So here right after the P tag after
[236:27] (14187.76s)
these two divs I'll go ahead paste this
[236:30] (14190.80s)
in where we have the crown icon
[236:35] (14195.04s)
and we can save this see the output.
[236:39] (14199.12s)
Let's go under the pro page. Um here it
[236:43] (14203.12s)
says let's refresh.
[236:45] (14205.76s)
Let's refresh again.
[236:48] (14208.16s)
Am I running the application? Let me
[236:49] (14209.84s)
check. Yes, we are running. Uh maybe I
[236:53] (14213.36s)
just need to log in once again. Looks
[236:55] (14215.20s)
like we logged out. Okay, so we are
[236:57] (14217.76s)
redirected. It took a bit of time, but
[236:59] (14219.68s)
that's fine. Now let's go under the pro
[237:01] (14221.60s)
page. Oh wait, why am I running the demo
[237:05] (14225.20s)
application?
[237:07] (14227.52s)
Wait, I'll open up the terminal. This is
[237:09] (14229.60s)
running on localhost 3001. Okay, on my
[237:13] (14233.36s)
second screen, you cannot see it, but I
[237:15] (14235.92s)
am running the demo application. So let
[237:18] (14238.32s)
me kill the terminal and kill the other
[237:20] (14240.88s)
application as well. Now let's run this.
[237:24] (14244.48s)
Let's say mpm rundev.
[237:28] (14248.24s)
Okay, sorry about this. But now once you
[237:30] (14250.80s)
refresh, you're only going to see this
[237:32] (14252.56s)
part, not the pricing table. And we are
[237:35] (14255.52s)
going to implement this in a second.
[237:39] (14259.92s)
Okay, so this is the actual application
[237:41] (14261.84s)
that we have, right? The other one was
[237:43] (14263.84s)
the demo application that I built
[237:46] (14266.16s)
previously.
[237:47] (14267.92s)
Okay, so far so good. We have this
[237:50] (14270.96s)
welcome section as well as the navbar.
[237:53] (14273.68s)
Now let's go ahead and build the pricing
[237:56] (14276.08s)
table. So after the first, second,
[237:59] (14279.76s)
third, and fourth div. Okay, so just go
[238:02] (14282.80s)
right below to all of them, we will say
[238:05] (14285.60s)
the pricing
[238:08] (14288.24s)
section, which is going to be maybe five
[238:11] (14291.12s)
lines of code. Let's say space y of 8.
[238:15] (14295.20s)
Then we're going to have one div that is
[238:17] (14297.92s)
going to have the class name of text
[238:20] (14300.48s)
center
[238:22] (14302.24s)
and I'll just have the space Y of four.
[238:26] (14306.00s)
Then H2 which is going to say choose
[238:29] (14309.52s)
your plan
[238:35] (14315.04s)
and this is going to take some classes
[238:37] (14317.04s)
like text 3x large.
[238:40] (14320.96s)
Then let's say font is going to be bold.
[238:43] (14323.92s)
Right after this H2, we will have a P
[238:46] (14326.48s)
tag. Let's say class name is going to be
[238:49] (14329.92s)
text muted foreground. And here by the
[238:53] (14333.44s)
way, I don't know if you have realized,
[238:54] (14334.96s)
but I am doing this all the time. So I
[238:57] (14337.28s)
don't need to type this properly to get
[238:59] (14339.92s)
the auto suggestion. Right? So if it is
[239:02] (14342.72s)
text me to the foreground, I can say
[239:04] (14344.96s)
like te Mfr,
[239:08] (14348.40s)
right? It's going to understand it.
[239:10] (14350.48s)
Let's do it. I'll say TE empty you know
[239:14] (14354.32s)
FR and press command space I can see the
[239:17] (14357.52s)
suggestion so that's really cool and
[239:19] (14359.76s)
keep this in mind let's say maximum
[239:22] (14362.16s)
width of 2x large um MX of auto let's
[239:26] (14366.56s)
say then this is going to get a text
[239:29] (14369.20s)
which I am pretty lazy to type out let
[239:33] (14373.04s)
me copy and paste okay so pause the
[239:36] (14376.64s)
video type this out and we should be
[239:38] (14378.48s)
good to go so that was the text. Let's
[239:40] (14380.96s)
save. All right. But where the hell is
[239:43] (14383.68s)
our table, right? How can we get this
[239:46] (14386.40s)
pricing table? Well, we don't really
[239:48] (14388.48s)
need to write any code for this because
[239:51] (14391.36s)
we already have our plans. We created
[239:54] (14394.48s)
the billing, right? So, we'll just say,
[239:56] (14396.88s)
hey clerk, could you please give us a
[239:59] (14399.36s)
pricing table? So, under this p under
[240:02] (14402.24s)
this div, go ahead and call the pricing
[240:06] (14406.00s)
table which is coming from clerk
[240:08] (14408.08s)
next.js. JS.
[240:10] (14410.24s)
Okay, now let's save and hopefully we
[240:13] (14413.36s)
should be good to go. Um, it says this
[240:16] (14416.80s)
component cannot be rendered when
[240:18] (14418.32s)
billing is disabled, but I think we have
[240:20] (14420.40s)
enabled it, right? Let's see once again.
[240:25] (14425.68s)
Oh, come on. You might be kidding.
[240:29] (14429.04s)
Didn't we created all the plans under
[240:32] (14432.32s)
the billing?
[240:37] (14437.52s)
Not this but subscriptions.
[240:44] (14444.24s)
Okay, I'll just say enable the billing.
[240:46] (14446.24s)
I think we created the plans but looks
[240:48] (14448.40s)
like we didn't enable it.
[240:52] (14452.00s)
And for the payment gateway, we'll leave
[240:54] (14454.48s)
it as the recommended. Okay, that's
[240:56] (14456.80s)
fine. Let's say
[240:59] (14459.28s)
create bill and guide,
[241:02] (14462.64s)
which is something that you can do
[241:05] (14465.44s)
for now. Let's say maybe refresh this
[241:08] (14468.00s)
page. It should be working out. We just
[241:10] (14470.32s)
enabled it. Okay, here we go. This is
[241:12] (14472.56s)
the plan that we are currently using. By
[241:15] (14475.60s)
default, it is the free plan. And if you
[241:17] (14477.92s)
say subscribe to the basic plan, which
[241:20] (14480.56s)
is like $9 a month,
[241:23] (14483.36s)
you're going to get this beautiful popup
[241:26] (14486.40s)
and you can either pay with your actual
[241:29] (14489.04s)
card, but we are in testing mode. So,
[241:31] (14491.28s)
I'll just say pay with the test card.
[241:36] (14496.56s)
Okay, payment was successful. It is done
[241:39] (14499.44s)
and you should get an email. Let's
[241:41] (14501.52s)
actually check this out. So, here we can
[241:43] (14503.92s)
see 0 minutes ago. Just now we got the
[241:46] (14506.96s)
receipt from the Dentwise application.
[241:49] (14509.92s)
So, that's pretty cool. Um, let's leave
[241:52] (14512.48s)
this and we'll just say continue. Now,
[241:55] (14515.76s)
this is the active plan that we have and
[241:58] (14518.88s)
it takes us to the dashboard. But let's
[242:00] (14520.96s)
visit the pro page. This is the active
[242:03] (14523.44s)
plan that we are currently in. Now, how
[242:06] (14526.00s)
do we check if this plan is active or
[242:08] (14528.24s)
not in our code? Right? How do we know
[242:10] (14530.56s)
if this user is in the pro plan or if it
[242:14] (14534.00s)
is in the free plan? So, I'll show you
[242:16] (14536.48s)
in a second in the code. But one more
[242:18] (14538.88s)
thing I'd like to mention. So, from
[242:21] (14541.04s)
here, if you want to upgrade to AI Pro,
[242:24] (14544.32s)
you don't need to pay $19 because you
[242:27] (14547.44s)
already pay $9, right? If you say switch
[242:30] (14550.48s)
to this plan, you're gonna see that you
[242:33] (14553.52s)
have $9 discount. All you have to pay is
[242:36] (14556.80s)
like $10. Okay? And let's go here under
[242:40] (14560.96s)
the manage account. We can see the
[242:42] (14562.88s)
billing. Even if you say, you know, like
[242:45] (14565.76s)
cancel my plan, it's still going to be
[242:48] (14568.64s)
active for for like 30 days because we
[242:52] (14572.16s)
just paid it, right? Um so yeah, even if
[242:55] (14575.04s)
you cancel now, it's not going to be
[242:56] (14576.64s)
cancelled immediately. um you will still
[242:59] (14579.60s)
be able to use it for the next one month
[243:02] (14582.64s)
just like any other production grade
[243:04] (14584.72s)
application like Netflix, Spotify,
[243:07] (14587.68s)
things like that right okay now let me
[243:10] (14590.16s)
show you how we can check if this user
[243:12] (14592.88s)
is on the pro plan or not so I'll go
[243:16] (14596.56s)
here and first import the oath let's say
[243:19] (14599.68s)
await oath which is going to be coming
[243:21] (14601.92s)
from clerk and we can dstructure
[243:25] (14605.20s)
one thing called has okay Then let's say
[243:29] (14609.20s)
if user has and put an object here we
[243:33] (14613.20s)
are going to put our key let's say plan
[243:36] (14616.00s)
and the key that we have for this was
[243:39] (14619.36s)
AI_basic
[243:41] (14621.52s)
let um like let's remember that under
[243:45] (14625.20s)
the subscriptions
[243:46] (14626.96s)
and here you can see your monthly
[243:48] (14628.80s)
recurring revenue entire revenue right
[243:52] (14632.40s)
let's go here this is the user he's
[243:55] (14635.28s)
under the AI basic plan.
[243:58] (14638.40s)
Wait, how do we like how do we find our
[244:01] (14641.28s)
plans?
[244:03] (14643.12s)
Let's go under the subscriptions.
[244:05] (14645.76s)
Subscription plans. Okay, so under the
[244:08] (14648.80s)
subscription plans here you can see we
[244:11] (14651.20s)
have the plan keys, right? This is for
[244:13] (14653.84s)
the free plan, AI basic and AI pro. Now
[244:17] (14657.76s)
we're going to check if user has any of
[244:20] (14660.08s)
these, right? Which is AI_basic
[244:23] (14663.44s)
and AI plan. So here I'll say uh plan
[244:28] (14668.24s)
AI_basic
[244:30] (14670.64s)
or let's say let me copy this as well
[244:40] (14680.16s)
and we can assign this into a variable
[244:42] (14682.56s)
like has pro plan
[244:47] (14687.52s)
and user actually has one of them which
[244:49] (14689.76s)
is this one at the moment. Let's say
[244:51] (14691.92s)
console log has pro plan
[244:57] (14697.44s)
and see it under the terminal. Currently
[244:59] (14699.76s)
it is equal to true. Right? Because user
[245:02] (14702.72s)
is on this plan. This is going to be
[245:04] (14704.64s)
equal to true. And we can see it in the
[245:07] (14707.52s)
terminal. So this is how we can easily
[245:10] (14710.24s)
check if user has paid. if user is under
[245:13] (14713.76s)
the paid plan or under the free plan
[245:17] (14717.20s)
just in one line and this is coming
[245:19] (14719.68s)
directly from the clerk's documentation.
[245:22] (14722.64s)
So that's the entire thing for this
[245:24] (14724.64s)
entire section. We are going to be using
[245:26] (14726.80s)
this in the upcoming sections but in
[245:29] (14729.76s)
this page we don't really need to check
[245:31] (14731.76s)
for it because in our component we can
[245:35] (14735.76s)
already see if user is in the active
[245:38] (14738.56s)
plan or not. Right? Okay. Now let's go
[245:42] (14742.40s)
back into VS Code, open up our status
[245:45] (14745.44s)
bar,
[245:46] (14746.96s)
close everything, and let's create a new
[245:49] (14749.52s)
branch. Here we can say pro-page
[245:55] (14755.60s)
stage all of our changes, which is only
[245:57] (14757.84s)
one page with 60 lines of code. That's
[246:01] (14761.12s)
fine. Let's say pro page and pricing
[246:06] (14766.32s)
table added.
[246:09] (14769.60s)
Commit. publish this branch and let's go
[246:12] (14772.88s)
into the source code which was a dent
[246:16] (14776.32s)
wise and we are going to create the pull
[246:18] (14778.72s)
request as as usual.
[246:22] (14782.88s)
Okay, now let's wait for the code
[246:25] (14785.12s)
suggestions from code rabbit. I don't
[246:27] (14787.76s)
really think if we have any anything bad
[246:31] (14791.36s)
so far right at this point but let's
[246:34] (14794.16s)
just wait what we're going to get from
[246:36] (14796.48s)
our AI assistant. So after 5 minutes,
[246:40] (14800.00s)
here is our quick summary. Here are all
[246:42] (14802.56s)
the features that we have introduced,
[246:44] (14804.96s)
the file that we have updated with the
[246:47] (14807.76s)
summary itself, the sequence diagram
[246:50] (14810.00s)
that you can pause the video and take a
[246:52] (14812.00s)
look at it. And then let's scroll to the
[246:54] (14814.32s)
bottom here. This says instead of
[246:56] (14816.48s)
getting chrome icon from Lucid React,
[246:58] (14818.88s)
you should get this one. and a AI things
[247:02] (14822.24s)
like chrome icon doesn't existed because
[247:05] (14825.68s)
um you know the lucid react team have
[247:08] (14828.24s)
added this chrome icon after the crone
[247:11] (14831.84s)
itself so this is completely fine if we
[247:14] (14834.88s)
skip this because this like this icon is
[247:19] (14839.04s)
actually working right and we can see it
[247:22] (14842.08s)
here on the output then let's scroll to
[247:24] (14844.72s)
the bottom here I think we have a typo
[247:28] (14848.24s)
it says it should be bg G primary
[247:30] (14850.88s)
instead of like we are missing the Y at
[247:33] (14853.92s)
the end.
[247:36] (14856.24s)
Let's see where that is. BG primary.
[247:40] (14860.88s)
Okay. So here we should have a Y. We can
[247:43] (14863.68s)
add this. If I add it now, I have to add
[247:46] (14866.16s)
a new commit. So I'm not going to add it
[247:48] (14868.24s)
at this point. But in the next section,
[247:50] (14870.16s)
we can fix that. Um let's go back.
[247:54] (14874.80s)
And we don't really have any other
[247:56] (14876.40s)
suggestions. So that's cool. Let's say
[247:58] (14878.96s)
merge this pull request. Confirm it. And
[248:01] (14881.92s)
once this is done, we'll go back to VS
[248:04] (14884.88s)
Code.
[248:06] (14886.72s)
Switch to the master and get the latest
[248:10] (14890.48s)
changes. So currently we don't have the
[248:13] (14893.04s)
pro page, right? But once we sync this
[248:16] (14896.08s)
up with the master branch, we can
[248:18] (14898.80s)
actually see the latest changes.
[248:25] (14905.36s)
So these are all the things that we have
[248:27] (14907.20s)
added in this section. Hopefully I'll
[248:29] (14909.44s)
see you in the next one.
[248:32] (14912.08s)
In this section, we are going to get
[248:34] (14914.00s)
started with the voice page. This is the
[248:36] (14916.48s)
end result that we are going to have. So
[248:38] (14918.80s)
basically we have some text, you know,
[248:41] (14921.28s)
some cards just to make the UI look
[248:43] (14923.28s)
nice. And then right after this, we are
[248:45] (14925.76s)
going to have this widget where we can
[248:48] (14928.24s)
start the call with our AI voice agent.
[248:51] (14931.36s)
So, we're going to get into it in a
[248:53] (14933.04s)
couple of minutes. But first, let's try
[248:55] (14935.04s)
to build the UI elements. So, under the
[248:58] (14938.48s)
source, under the app, let's create the
[249:01] (14941.12s)
voice folder. And we are going to create
[249:03] (14943.52s)
the page.tsx file. Let's generate this
[249:06] (14946.96s)
and let's say something like voice page
[249:09] (14949.68s)
and pretty quickly test it out if it is
[249:12] (14952.40s)
existed or not. Okay. So, that's the
[249:14] (14954.88s)
voice page. Here we'll go ahead and
[249:17] (14957.92s)
check if user has access to the paid
[249:20] (14960.40s)
plan or not. Let me say this will be an
[249:23] (14963.04s)
async function and we can import the
[249:25] (14965.44s)
oath from clerk call this await and
[249:28] (14968.96s)
we'll say const
[249:31] (14971.28s)
and dstructure the has method. So I will
[249:34] (14974.16s)
say con has a pro plan. I'll say has and
[249:39] (14979.68s)
call the method pass an object which is
[249:42] (14982.48s)
going to be plan. And if you remember
[249:44] (14984.56s)
our plan was either AI basic or it was
[249:48] (14988.72s)
AI pro right I will copy this paste this
[249:52] (14992.72s)
in let's say it should be AI pro if user
[249:57] (14997.60s)
has one of them that means they are in
[250:00] (15000.40s)
the pro plan but if it is equal to false
[250:03] (15003.60s)
we'll say if has no pro plan then we can
[250:06] (15006.64s)
return something like pro plan required
[250:10] (15010.96s)
UI right so this is a component that we
[250:14] (15014.40s)
can build. Let's go under the source
[250:18] (15018.40s)
components and we are going to create
[250:20] (15020.64s)
this folder called voice. So we are
[250:23] (15023.44s)
going to create this folder called voice
[250:25] (15025.92s)
and every component that we would need
[250:28] (15028.24s)
in this voice page will go here. Right?
[250:30] (15030.88s)
So I'll copy this name. This is our very
[250:33] (15033.44s)
first component. Let's say tsx. This is
[250:36] (15036.48s)
just UI. It's not going to contain any
[250:39] (15039.20s)
logic at all. And you can copy this from
[250:42] (15042.24s)
the source code. Just find this file,
[250:45] (15045.04s)
copy the content, and paste this in.
[250:47] (15047.68s)
It's around 80 lines of code. I know
[250:50] (15050.00s)
that's a little bit annoying, but let's
[250:51] (15051.92s)
save and just see the output here. I
[250:54] (15054.80s)
will import this. And let's say if true
[250:57] (15057.60s)
so that we can actually see it.
[251:00] (15060.64s)
Okay. So, if you are not on the paid
[251:02] (15062.40s)
plan, this is what you're going to see.
[251:04] (15064.40s)
Once you click to it, it's going to take
[251:06] (15066.40s)
you to the pro page. Right. So far, no
[251:09] (15069.76s)
logic at all. Just a welcome section or
[251:12] (15072.64s)
kind of like banner. I don't know how
[251:14] (15074.88s)
you would like to call this. And then a
[251:16] (15076.88s)
cart. Okay. So again, no logic at all. I
[251:21] (15081.04s)
hope you guys don't mind it. Just some
[251:22] (15082.96s)
CSS classes. If we type it out, it would
[251:25] (15085.52s)
take 20 minutes, which is unnecessary.
[251:28] (15088.72s)
Okay. Let's do command C. In our case,
[251:31] (15091.76s)
we are going to see this part because we
[251:33] (15093.92s)
are in the pro plan, right? We can see
[251:36] (15096.08s)
the voice page that is going to look
[251:38] (15098.40s)
like this. Once again, we can copy and
[251:40] (15100.96s)
paste this part as well as this part.
[251:44] (15104.32s)
But we can actually build this in uh
[251:47] (15107.20s)
without copying because this contains
[251:49] (15109.60s)
some logic but these two doesn't.
[251:53] (15113.44s)
Okay. So I'll go under the VS code.
[251:56] (15116.24s)
Let's say in the return statement we'll
[251:58] (15118.40s)
have this part. Let me give a little bit
[252:02] (15122.32s)
space.
[252:04] (15124.64s)
Okay. So I'll have a div and let's put
[252:07] (15127.28s)
the class name maximum width of 7x large
[252:10] (15130.64s)
as usual. Um actually actually we're
[252:14] (15134.64s)
going to put it in a different place for
[252:16] (15136.56s)
now. Let's say minimum height should be
[252:18] (15138.88s)
screen and the background can be the
[252:22] (15142.72s)
background color and then right after
[252:24] (15144.96s)
this we're going to put the navbar
[252:26] (15146.88s)
component as usual and then we can put
[252:29] (15149.68s)
the main content. So I'll have the div
[252:33] (15153.04s)
let's say class name maximum width 7x
[252:36] (15156.00s)
large MX auto and here are the rest of
[252:39] (15159.20s)
the classes just some pettings and
[252:41] (15161.52s)
within this div we are going to have two
[252:43] (15163.68s)
different components one is the welcome
[252:46] (15166.32s)
section and then the feature cards. So
[252:48] (15168.96s)
once again this is the welcome section
[252:51] (15171.60s)
and then these are the feature cards.
[252:54] (15174.16s)
Let's try to copy and paste them from
[252:56] (15176.32s)
the source code. So go under the
[252:58] (15178.72s)
components under the voice
[253:01] (15181.76s)
create both of these files.
[253:07] (15187.20s)
Okay, let's try to import this and copy
[253:10] (15190.80s)
this name. Create it as well
[253:18] (15198.72s)
and just import it. Now from the source
[253:21] (15201.84s)
code, you should be able to copy them
[253:24] (15204.08s)
and paste them in. So, the welcome
[253:26] (15206.64s)
section is like 30 lines of code.
[253:30] (15210.64s)
And this one is going to be a little bit
[253:32] (15212.64s)
larger, but that's fine. Let's delete
[253:35] (15215.44s)
the content. Pastes in almost 80 lines
[253:38] (15218.72s)
of code where we just have some UI
[253:41] (15221.20s)
elements. No logic at all. Okay, let's
[253:44] (15224.32s)
save every single file and see the
[253:46] (15226.40s)
output.
[253:48] (15228.88s)
Okay, so this is what we would like to
[253:50] (15230.56s)
have a welcome section and some featured
[253:53] (15233.36s)
cards, you know, that says how to use
[253:55] (15235.36s)
it, some features just to make UI look
[253:58] (15238.40s)
nice. Okay, on top of them, we would
[254:01] (15241.04s)
like to add this widget where we can
[254:04] (15244.24s)
actually talk to our AI assistant. And
[254:07] (15247.36s)
to be able to implement this, we are
[254:09] (15249.36s)
going to be using VPY, which will allow
[254:11] (15251.44s)
us to build voice AI agents. So, we
[254:14] (15254.56s)
already have an account. Let's go ahead
[254:16] (15256.72s)
and log in. I'll just open up the
[254:18] (15258.96s)
dashboard. And I think I am already
[254:20] (15260.88s)
logged in. So I'll walk you through it
[254:23] (15263.12s)
all the steps. But first, let's visit
[254:25] (15265.36s)
the documentation.
[254:27] (15267.76s)
Okay. So this is going to allow us to
[254:30] (15270.16s)
build voice AI agents that can make and
[254:33] (15273.84s)
receive phone calls. Now on top of the
[254:36] (15276.56s)
phone calls, we can have web calls.
[254:38] (15278.80s)
Right? In our example, we'll be using
[254:40] (15280.88s)
the web calls, but you can definitely
[254:43] (15283.52s)
integrate the phone calls as well. If
[254:46] (15286.72s)
you would like to maybe at the end of
[254:48] (15288.48s)
this tutorial, you can go through this
[254:51] (15291.28s)
section where they show you all these
[254:53] (15293.44s)
steps. So, you would like to first
[254:55] (15295.44s)
create an assistant, which is something
[254:57] (15297.20s)
that we have already done. Add the first
[255:00] (15300.16s)
message, the system prompt, and then you
[255:03] (15303.04s)
would like to set up a phone number.
[255:05] (15305.44s)
They give you like up to 10 10 different
[255:09] (15309.04s)
phone numbers, but they are only for US.
[255:12] (15312.24s)
If you would like to have an
[255:13] (15313.44s)
international phone number, you would
[255:15] (15315.12s)
like to just import it into their
[255:17] (15317.60s)
dashboard. And you can grab them from, I
[255:20] (15320.16s)
believe, a platform like Tilio
[255:24] (15324.64s)
and then just scroll to the bottom.
[255:27] (15327.12s)
You're going to see that you need to
[255:28] (15328.48s)
attach your assistant to that phone
[255:30] (15330.32s)
number, make your first call. So you can
[255:33] (15333.36s)
go through these examples but in our
[255:35] (15335.84s)
case we would like to uh we would like
[255:38] (15338.48s)
to have some web calls right now they
[255:41] (15341.04s)
have two different ways of integrating
[255:44] (15344.00s)
it. The first one is the client side the
[255:46] (15346.80s)
other one is the server side. So this is
[255:49] (15349.04s)
best for userfacing applications voice
[255:52] (15352.16s)
widgets and mobile applications. So this
[255:54] (15354.96s)
is what we'll be using because we're
[255:56] (15356.56s)
going to build a voice widget right this
[255:59] (15359.28s)
is what we call that widget right. So
[256:02] (15362.32s)
for that reason we will go through the
[256:03] (15363.92s)
client side implementation. But if you
[256:06] (15366.96s)
would like to build some backend
[256:08] (15368.56s)
automation like bulk operations system
[256:11] (15371.36s)
integrations then you would like to see
[256:13] (15373.44s)
the server side call management section.
[256:16] (15376.80s)
So I'll walk you through the client
[256:18] (15378.32s)
side. You would like to install this
[256:20] (15380.16s)
package create a VP instance and then
[256:24] (15384.00s)
start the call by passing the assistant
[256:26] (15386.32s)
ID. Then Bobby is going to give you a
[256:28] (15388.72s)
bunch of different events like call
[256:30] (15390.96s)
started. Here is something that you can
[256:33] (15393.36s)
do once call ends like what's going to
[256:36] (15396.08s)
happen. You can redirect the user to the
[256:38] (15398.96s)
dashboard page you know do anything that
[256:41] (15401.20s)
you want and then you can also get the
[256:43] (15403.68s)
messages as you talk. What do I mean?
[256:46] (15406.56s)
Let's see here in this example as you
[256:49] (15409.44s)
can tell AI is talking right and as as
[256:52] (15412.80s)
he talk as he talks we are going to see
[256:55] (15415.28s)
all the messages um that VP is going to
[256:58] (15418.56s)
send us okay so there are there are a
[257:01] (15421.28s)
lot of different events and I think
[257:03] (15423.04s)
we're going to go over all of them or
[257:05] (15425.76s)
maybe some of them and then here is the
[257:08] (15428.40s)
widget implementation this is just an
[257:10] (15430.80s)
example but in our case we are going to
[257:13] (15433.04s)
build it from scratch so let's scroll to
[257:15] (15435.76s)
the top and go through these steps one
[257:18] (15438.08s)
by one. We would like to install VB AI
[257:21] (15441.68s)
/w. Right, I'll copy this. Go into my
[257:24] (15444.56s)
terminal.
[257:26] (15446.56s)
Let's open this up. I'll kill my
[257:28] (15448.72s)
terminal and I'll say something like,
[257:30] (15450.80s)
you know, paste this in. But I'd like to
[257:32] (15452.88s)
install a specific version so that if
[257:35] (15455.36s)
you're watching this in the future, it
[257:37] (15457.12s)
should still work out as expected. We'll
[257:39] (15459.68s)
go ahead uh we'll go ahead and say 2.3.9
[257:44] (15464.32s)
which is currently the latest version
[257:46] (15466.72s)
but it could change in the future.
[257:49] (15469.68s)
So while this is getting installed we
[257:52] (15472.24s)
can go under the lib and we can create a
[257:56] (15476.72s)
file called vapi.ts
[257:59] (15479.76s)
and this is what we're going to be
[258:01] (15481.28s)
doing. Basically, we will um instantiate
[258:05] (15485.04s)
our instance, right? I will paste this
[258:08] (15488.24s)
in and let's say export this this VBY
[258:11] (15491.76s)
instance. So, we're going to be using
[258:13] (15493.44s)
this in the upcoming minutes. And we
[258:16] (15496.08s)
would like to put a public API key. So,
[258:19] (15499.44s)
this is going to be coming from
[258:21] (15501.52s)
environment variables. So, we'll say
[258:23] (15503.28s)
process.v NV dot let's say next_public
[258:30] (15510.32s)
bobby
[258:32] (15512.00s)
api key
[258:34] (15514.56s)
but now let's try to create this under
[258:36] (15516.72s)
the nv file because we don't have it at
[258:39] (15519.20s)
the moment and the value is going to be
[258:41] (15521.60s)
equal to something that we're going to
[258:43] (15523.68s)
get from the dashboard okay so from here
[258:47] (15527.52s)
let's find the
[258:49] (15529.84s)
API keys and we would like to create a
[258:53] (15533.44s)
public key in my case I already have but
[258:56] (15536.64s)
if you don't have it you can say add key
[258:59] (15539.92s)
um name could be something like dentwise
[259:03] (15543.44s)
because why not say dentwise API key I
[259:07] (15547.04s)
think we can leave everything as it is
[259:08] (15548.96s)
selected assistant
[259:11] (15551.04s)
so this is the one that we have created
[259:13] (15553.04s)
right at the beginning of this tutorial
[259:16] (15556.96s)
make sure this is selected select the
[259:19] (15559.04s)
assistant itself
[259:21] (15561.76s)
and just say create this public token.
[259:27] (15567.04s)
Okay, let's copy the value
[259:30] (15570.00s)
and paste this in. And just to format
[259:32] (15572.64s)
this file, I'll take this put it right
[259:34] (15574.80s)
after the assistant ID. Okay, let's
[259:37] (15577.76s)
save. Go through here. Now, TypeScript
[259:40] (15580.48s)
says I don't know if this is undefined
[259:42] (15582.24s)
or not. So, we can say don't worry, we
[259:44] (15584.48s)
will always have this environment
[259:45] (15585.92s)
variable. You can either add exclamation
[259:48] (15588.40s)
point or you can say as string. So it's
[259:51] (15591.68s)
the same thing. Okay. Now we'll go under
[259:54] (15594.48s)
the page. Outside of these two divs, we
[259:58] (15598.32s)
can create the bobby fidget component.
[260:05] (15605.12s)
So let's go and create this file. Under
[260:07] (15607.60s)
the components under the voice, I'll say
[260:10] (15610.80s)
lobby widget.tsx.
[260:13] (15613.84s)
And we can generate the content for this
[260:16] (15616.08s)
file. You know, just keep it simple for
[260:18] (15618.56s)
now. and then import this component.
[260:22] (15622.08s)
Okay. Now, just before we build the
[260:24] (15624.56s)
widget itself, first we need to set up
[260:27] (15627.20s)
our assistant. So, let's go under the
[260:29] (15629.68s)
assistance.
[260:31] (15631.20s)
We already have something, but this is
[260:33] (15633.28s)
just a blank template with minimal
[260:35] (15635.76s)
defaults, right? So, we'd like to have a
[260:38] (15638.32s)
first message for our assistant and let
[260:41] (15641.36s)
the assistant know about our platform,
[260:44] (15644.16s)
right? what we have, what are our
[260:46] (15646.80s)
services like dental consultation, you
[260:50] (15650.72s)
know, teeth cleaning, emergency visit,
[260:53] (15653.12s)
what are the prices, things like that.
[260:55] (15655.68s)
So here we will add a first message and
[260:58] (15658.32s)
we are going to put a system prompt just
[261:01] (15661.12s)
to you know make our AI assistant know
[261:04] (15664.32s)
about our platform which is a dentwise
[261:07] (15667.44s)
right and now in this dashboard you can
[261:10] (15670.00s)
customize anything and everything in
[261:12] (15672.72s)
this case for the provider you can go
[261:14] (15674.96s)
from open AI all the way up to
[261:17] (15677.28s)
inflection AI I'll just leave everything
[261:19] (15679.60s)
as it is you can even change the model
[261:22] (15682.24s)
so in this case we are using one of the
[261:24] (15684.40s)
cheapest models
[261:25] (15685.68s)
If you would like to go even cheaper,
[261:27] (15687.52s)
you can select a mini model, right? But
[261:30] (15690.32s)
I'll just leave everything as it is.
[261:32] (15692.08s)
Then you can select the mode. In our
[261:33] (15693.92s)
case, assistant should speak first. Then
[261:37] (15697.36s)
the first message like what the AI
[261:40] (15700.24s)
assistant is going to say. So here, let
[261:42] (15702.72s)
me copy and paste it. And I'm going to
[261:44] (15704.72s)
provide this to you. Actually, let me
[261:47] (15707.12s)
tell you where you can copy it. Find the
[261:49] (15709.52s)
source code under the source under the
[261:52] (15712.56s)
lib. You're going to see this file
[261:54] (15714.72s)
called VPrompt.
[261:56] (15716.72s)
Yes. Okay. So, this is the first
[261:58] (15718.96s)
message. Go ahead, uncomment this, copy
[262:02] (15722.24s)
it, and then you can comment it again.
[262:05] (15725.28s)
So, just copy this and then paste it
[262:08] (15728.56s)
here for the first message. And what it
[262:11] (15731.44s)
says is basically, hey there, this is
[262:14] (15734.08s)
Riley, your dental assistant from
[262:16] (15736.08s)
Dentwise. I am here to help you with all
[262:18] (15738.80s)
your dental needs. I can provide
[262:20] (15740.96s)
information about our services. give you
[262:23] (15743.28s)
immediate tips for dental pain or
[262:26] (15746.08s)
concerns, help you understand different
[262:28] (15748.24s)
treatment options, and share oral health
[262:31] (15751.52s)
prevention advice. So, what can I help
[262:34] (15754.00s)
you with today? That's the very first
[262:36] (15756.32s)
message. And then for the system prompt,
[262:39] (15759.12s)
again, go ahead, copy everything that we
[262:41] (15761.76s)
have here, which is around 100 lines of
[262:44] (15764.40s)
code. Uncommented,
[262:46] (15766.72s)
copy, and then comment it again. And
[262:49] (15769.44s)
then you can delete this file from your
[262:51] (15771.44s)
source code. You don't really need it,
[262:53] (15773.28s)
but this will basically give an identity
[262:55] (15775.68s)
and purpose to our AI assistant. So here
[262:59] (15779.12s)
we say you are Riley. This is your name,
[263:01] (15781.84s)
an AI dental assistant for Dentwise,
[263:04] (15784.72s)
right? And you offer 24/7 support for
[263:08] (15788.32s)
dental concerns and questions. Please
[263:11] (15791.28s)
feel free to pause the video and read
[263:13] (15793.84s)
the rest of it. We're going to give a
[263:16] (15796.08s)
voice and persona, right? What should AI
[263:19] (15799.76s)
sound like, right?
[263:21] (15801.52s)
uh the conversation flow
[263:24] (15804.24s)
and if if user wants to have some
[263:27] (15807.20s)
appointments if they want to book some
[263:30] (15810.00s)
appointments this should say you know I
[263:32] (15812.48s)
cannot book you something because like
[263:35] (15815.36s)
it is including some payments the AI
[263:38] (15818.24s)
assistant will be here just to help them
[263:40] (15820.80s)
out with the information okay so you can
[263:43] (15823.68s)
read the rest of it this is the entire
[263:47] (15827.52s)
um the prompt just to let AI know what
[263:50] (15830.96s)
is all about. Okay, so I have copied it.
[263:53] (15833.68s)
I'm going to delete this part. Paste
[263:55] (15835.76s)
this in. That's a bit huge, but that's
[263:58] (15838.96s)
fine. The larger this is, the better it
[264:01] (15841.92s)
is. So that AI knows everything.
[264:05] (15845.68s)
You can include some files where you
[264:08] (15848.08s)
know where you have some, let's say,
[264:10] (15850.96s)
where you have some system prompt, but
[264:12] (15852.96s)
we just paste it instead. And feel free
[264:16] (15856.64s)
to take a look at all these
[264:18] (15858.32s)
configurations. So you can update the
[264:20] (15860.88s)
voice. In this case, I think we can use
[264:23] (15863.04s)
the Elliot. You have other voices as
[264:25] (15865.68s)
well, like Kylie, ages 23, female, and
[264:29] (15869.76s)
she has the American accent.
[264:32] (15872.96s)
We're going to go with uh Elliot. You
[264:35] (15875.60s)
can also change the voice provider. In
[264:38] (15878.16s)
this case, I'll be going with Fabi, but
[264:40] (15880.32s)
you can use any of them really.
[264:44] (15884.64s)
Okay, so there are a lot of different
[264:46] (15886.96s)
configurations that we can do, but let's
[264:49] (15889.52s)
try to keep it simple and leave
[264:51] (15891.76s)
everything as it is. We could include
[264:54] (15894.08s)
some tools, which are the things that
[264:56] (15896.00s)
I'll get into in a second. So that's
[264:58] (15898.72s)
everything that we would need for this
[265:00] (15900.48s)
AI assistant. We give the first message
[265:02] (15902.96s)
as well as the system prompt. Now, let's
[265:05] (15905.68s)
say publish.
[265:08] (15908.56s)
Okay, if you want to test it out, you
[265:10] (15910.48s)
can click to this button and talk to the
[265:12] (15912.80s)
assistant. Let's just see how it's going
[265:15] (15915.92s)
to look like.
[265:18] (15918.96s)
Let's give the access to the microphone.
[265:21] (15921.60s)
Hi there, this is Riley, your dental
[265:24] (15924.00s)
assistant from Dentwise. I'm here to
[265:26] (15926.16s)
help you with all your dental needs. I
[265:28] (15928.08s)
can provide information about our
[265:29] (15929.84s)
service prices, give you immediate tips
[265:32] (15932.16s)
for dental pain or concerns, help you
[265:35] (15935.12s)
understand different treatment options,
[265:37] (15937.20s)
and share oral health prevention advice.
[265:40] (15940.32s)
Okay.
[265:40] (15940.96s)
>> Okay. So, as you can tell, it is reading
[265:42] (15942.88s)
the first message. As soon as we start
[265:45] (15945.04s)
the call, we can end the call and we can
[265:47] (15947.68s)
get the messages. So, this is that event
[265:50] (15950.48s)
that we just talked about. Let's scroll
[265:52] (15952.72s)
to the top. So this event is getting
[265:56] (15956.00s)
sent by Voppy and in our application we
[265:59] (15959.28s)
are going to get the message display it
[266:01] (15961.44s)
right here just like what they do here.
[266:04] (15964.32s)
Okay. So for now everything is working
[266:06] (15966.16s)
as expected. We already have the
[266:08] (15968.16s)
assistant ID. All we have to do just go
[266:10] (15970.96s)
back into VS Code, find this VBY widget
[266:14] (15974.40s)
file and build the content.
[266:18] (15978.80s)
So first let me zoom in. This is going
[266:21] (15981.52s)
to be a client component. Let's uh let's
[266:24] (15984.40s)
go to the top and say use client. This
[266:27] (15987.28s)
is our directive. And then we're going
[266:29] (15989.28s)
to need couple of different states. Let
[266:31] (15991.52s)
me import the use state. So we are going
[266:34] (15994.08s)
to have a state if the call is active or
[266:36] (15996.32s)
not. If we are in the connecting state,
[266:38] (15998.88s)
if the AI is speaking, you know the
[266:42] (16002.00s)
messages initially is going to be an
[266:44] (16004.16s)
empty array and then if call has ended
[266:46] (16006.80s)
or not. Initially they are all equal to
[266:49] (16009.28s)
false. Then we would like to get the
[266:52] (16012.00s)
user from clerk and if you know we are
[266:55] (16015.68s)
going to check if clerk is loaded or
[266:57] (16017.68s)
not. So let's import this as well. Then
[267:00] (16020.56s)
we would like to have a message
[267:02] (16022.24s)
container ref. Let's import this hook
[267:05] (16025.76s)
because you know we are going to be
[267:07] (16027.28s)
using this whenever there is a new
[267:09] (16029.84s)
message. So we will make this to scroll
[267:12] (16032.56s)
automatically. That's why we are
[267:14] (16034.64s)
creating this reference. And to be able
[267:17] (16037.44s)
to implement this, we can just have a
[267:19] (16039.60s)
use effect with two lines of code. I'll
[267:23] (16043.20s)
just add a comment. Let's say auto
[267:26] (16046.32s)
scroll or messages.
[267:30] (16050.64s)
So this will basically make the scroll
[267:32] (16052.72s)
top position to be at the very end.
[267:35] (16055.28s)
Right? It is scrolling whenever messages
[267:38] (16058.72s)
changes. And then let's have one more
[267:41] (16061.44s)
use effect. Let's initialize this. Here
[267:47] (16067.84s)
I'll just add a comment. Let's say setup
[267:51] (16071.04s)
event listeners
[267:54] (16074.64s)
or voppy
[267:57] (16077.60s)
and now we'll be using all these events
[267:59] (16079.76s)
right. So first let's say
[268:02] (16082.96s)
give us the voppy instance which is
[268:05] (16085.12s)
coming from this file. So we are getting
[268:08] (16088.16s)
this so that we can interact with our VP
[268:11] (16091.28s)
assistant.
[268:12] (16092.80s)
So let's say voppy.on.
[268:15] (16095.76s)
We have all kinds of events. First we'll
[268:18] (16098.32s)
say once call starts please go ahead run
[268:21] (16101.68s)
this method which is handle call start.
[268:25] (16105.52s)
And we are going to create this method
[268:27] (16107.04s)
in a second. Let's add one more on. So
[268:30] (16110.88s)
at the end you can say dot on and let's
[268:32] (16112.96s)
actually format this. Okay. What happens
[268:35] (16115.84s)
once the call ends? We would like to
[268:38] (16118.96s)
call handle call end. So we will do this
[268:43] (16123.52s)
couple of different times. This time
[268:45] (16125.68s)
it's going to be on speech start.
[268:50] (16130.96s)
Let's duplicate this. This time it'll be
[268:53] (16133.20s)
speech end. Let's say handle
[268:57] (16137.12s)
speech start.
[269:02] (16142.88s)
And I'll do the exact same thing. Handle
[269:05] (16145.28s)
speech end.
[269:08] (16148.40s)
Let's duplicate this.
[269:10] (16150.80s)
When we got a message,
[269:13] (16153.68s)
let's say handle message. And finally,
[269:17] (16157.28s)
we'll have on. Once we got an error, we
[269:20] (16160.48s)
would like to handle it as well, right?
[269:22] (16162.40s)
Let's say handle error. Now, let's try
[269:25] (16165.52s)
to create all of these methods one by
[269:27] (16167.76s)
one. And we are going to create them
[269:29] (16169.76s)
under the use effect. So, first let's
[269:32] (16172.24s)
get started with this one. Handle call
[269:34] (16174.88s)
start. Once call starts, we can put a
[269:37] (16177.68s)
console log and update our states.
[269:40] (16180.08s)
Right? So set connecting is uh is going
[269:43] (16183.12s)
to be equal to false. Call active true
[269:45] (16185.92s)
and call ended is equal to be false.
[269:48] (16188.72s)
Then let's get the call end method. So
[269:52] (16192.40s)
you can copy them from the source code
[269:54] (16194.00s)
if you don't want to type it out. We'll
[269:56] (16196.08s)
say call ended and these are going to be
[269:58] (16198.48s)
the latest states. Then what happens
[270:01] (16201.76s)
once the speech starts? So we'll say AI
[270:05] (16205.36s)
started speaking. And by the way, this
[270:07] (16207.68s)
is going to be sent once AI starts
[270:09] (16209.92s)
talking, right? Not you. This is for AI.
[270:12] (16212.88s)
And same for AI as well. So we'll say AI
[270:17] (16217.04s)
stop talking.
[270:19] (16219.04s)
Um then we can update the state to be
[270:21] (16221.44s)
equal to false. And then finally, we're
[270:24] (16224.00s)
going to put these two methods. Let me
[270:26] (16226.00s)
save. Get the formatting. Okay. So for
[270:29] (16229.12s)
the handle message, we are going to
[270:30] (16230.72s)
check if the mode is final. we will
[270:34] (16234.00s)
update our state. We're going to keep
[270:35] (16235.92s)
all the previous messages and just add
[270:38] (16238.48s)
the new one on top of it. Okay. Then for
[270:42] (16242.08s)
the handle error, we'll just say vopy
[270:44] (16244.80s)
error and basically set these states to
[270:48] (16248.00s)
be false. All right. So these are all
[270:49] (16249.84s)
the methods. I know that we just copied
[270:51] (16251.84s)
and paste it which could be annoying but
[270:54] (16254.00s)
we are not really doing anything special
[270:55] (16255.92s)
other than updating the state. Right?
[270:58] (16258.56s)
You can pause the video, type it out or
[271:00] (16260.96s)
get it from the source code. And for the
[271:03] (16263.68s)
performance reasons, we could add a
[271:06] (16266.00s)
cleanup function where basically instead
[271:08] (16268.64s)
of listening for them, we will unlisten
[271:11] (16271.60s)
for them whenever the component
[271:13] (16273.60s)
unmounts. Right? This is for performance
[271:16] (16276.72s)
reasons. Um that's the entire use
[271:19] (16279.44s)
effect. Now we can add one check. I'll
[271:22] (16282.16s)
say if
[271:24] (16284.00s)
clerk is not loaded, we can just return
[271:27] (16287.20s)
nothing until it loads. We'll we're not
[271:29] (16289.92s)
going to see anything. Once clerk
[271:31] (16291.68s)
loaded, then we can see the widget and
[271:34] (16294.16s)
we will have one function. So, this is
[271:36] (16296.64s)
going to be outside of the use effect.
[271:39] (16299.20s)
We are done with it. We will have a
[271:41] (16301.12s)
method. Let's say con toggle call and
[271:45] (16305.44s)
I'll say async.
[271:48] (16308.72s)
So we will say if call is already active
[271:52] (16312.24s)
we'll just say wy just go ahead and stop
[271:55] (16315.04s)
this call please right but in the else
[271:58] (16318.08s)
case let's say else
[272:01] (16321.44s)
we will do some different things here
[272:04] (16324.32s)
the first thing I'll have is a try and
[272:06] (16326.64s)
catch block under the try I will update
[272:09] (16329.52s)
couple of different states so I will say
[272:11] (16331.84s)
set connecting to be equal to true no
[272:14] (16334.64s)
messages and call ended is equal to
[272:17] (16337.28s)
false and once Once we update these
[272:19] (16339.84s)
states, we are going to start the call.
[272:22] (16342.24s)
So I'll say await
[272:24] (16344.96s)
start and this is going to expect you to
[272:28] (16348.16s)
pass the assistant ID. So we can say
[272:30] (16350.88s)
process dom
[272:34] (16354.24s)
next public and we're going to pass the
[272:37] (16357.76s)
Bobby assistant ID.
[272:41] (16361.60s)
Okay. So just make sure that this is
[272:43] (16363.36s)
matching with whatever you have under
[272:45] (16365.52s)
the file. So maybe just copy it from
[272:49] (16369.12s)
here and paste this in so that you don't
[272:51] (16371.84s)
have any typos. So that's it for the
[272:54] (16374.32s)
entire try block. Under the catch, we
[272:57] (16377.04s)
can just get the error and maybe put a
[272:59] (16379.52s)
console log. Then set the connecting
[273:02] (16382.32s)
state to be equal to false because we
[273:04] (16384.72s)
failed to start the call. Right? That
[273:07] (16387.68s)
means we are not connecting anymore. All
[273:09] (16389.92s)
right. So I think we are completely done
[273:12] (16392.32s)
with the entire logic which is around
[273:15] (16395.12s)
100 lines of code. The rest is going to
[273:17] (16397.68s)
be UI. Um, we're going to get into the
[273:20] (16400.40s)
return statement in a second, but first
[273:22] (16402.96s)
I'd like to open up the global CSS file.
[273:26] (16406.96s)
We're going to add like 10 lines of
[273:28] (16408.64s)
code, which is going to be the animation
[273:31] (16411.60s)
when AI starts talking. So here you can
[273:34] (16414.56s)
see we have five different
[273:38] (16418.08s)
elements, right? They're going to be
[273:39] (16419.76s)
like uh moving like a wave, right? So
[273:43] (16423.76s)
you're going to see how that will look
[273:45] (16425.20s)
like. First, let's create a key frame
[273:48] (16428.32s)
under the global.css. So, let's say key
[273:51] (16431.12s)
frames. Then, we can give it a name. In
[273:54] (16434.32s)
this case, I'll just say sound dashwave.
[273:59] (16439.04s)
We're going to have the initial state,
[274:01] (16441.84s)
which is 0%.
[274:04] (16444.40s)
Let's duplicate this. We're going to
[274:05] (16445.84s)
have 50% and then once the animation
[274:09] (16449.60s)
completed, which is 100%. So, here the
[274:12] (16452.80s)
height should be equal to 10%.
[274:16] (16456.00s)
At the beginning it should be equal to
[274:18] (16458.00s)
height of 10% as well. But in the middle
[274:21] (16461.52s)
of the animation it should be 100%.
[274:25] (16465.92s)
Okay. And to be able to use this we are
[274:28] (16468.08s)
going to create a class. Let's say
[274:30] (16470.40s)
animate soundwave.
[274:34] (16474.00s)
And we'll just say animation should be
[274:36] (16476.72s)
sound wave.
[274:40] (16480.48s)
So make sure that this name is matching
[274:42] (16482.32s)
with this one.
[274:44] (16484.48s)
Duration could be 1.2 seconds. Ease in
[274:48] (16488.48s)
and out. And I'll just say it should be
[274:50] (16490.40s)
infinite. Okay. So, we just added 10
[274:53] (16493.20s)
lines of code for this animation. And
[274:55] (16495.60s)
you're going to see how to make it work.
[274:58] (16498.16s)
Let's just close everything. Go under
[275:00] (16500.64s)
the Bobby widget and get started with
[275:03] (16503.12s)
the return statement. So, the first
[275:05] (16505.20s)
thing that we would need is a div with
[275:08] (16508.00s)
some class names, right? which are going
[275:10] (16510.00s)
to be maximum width of 5x large center
[275:13] (16513.20s)
this with MX auto some petting flex
[275:16] (16516.24s)
overflow hidden and padding bottom of 20
[275:19] (16519.84s)
then we can do something like this let
[275:22] (16522.88s)
me just give a little bit space we are
[275:25] (16525.28s)
going to have the title first let's say
[275:28] (16528.00s)
title and it's going to be this section
[275:32] (16532.48s)
so this is what I call the title this
[275:35] (16535.36s)
component let's try to get this from the
[275:38] (16538.88s)
source code. It is like eight lines of
[275:41] (16541.68s)
code. Talk to your AI dental assistant.
[275:45] (16545.36s)
So, we have one H1 and then a
[275:47] (16547.60s)
description. Let's try to see that's how
[275:50] (16550.88s)
it look like, right? Then we are going
[275:53] (16553.12s)
to put these two cards. The first one is
[275:55] (16555.60s)
for AI and the other one is for the
[275:58] (16558.24s)
user.
[276:00] (16560.32s)
Let's try to do it. Just shrink the
[276:03] (16563.60s)
title and we're going to have the video
[276:06] (16566.16s)
call area. Now if you don't want to type
[276:09] (16569.28s)
this out, you can get it from the source
[276:11] (16571.12s)
code. But I think I'll just try to write
[276:14] (16574.16s)
some part of it. So I'll say div um
[276:17] (16577.52s)
class name is going to be grid. Let's
[276:19] (16579.84s)
say grid columns of one medium screens
[276:22] (16582.56s)
and above. They could be side by side.
[276:25] (16585.12s)
So I'll say grid um like grid columns of
[276:30] (16590.24s)
three. I think I've just did a mistake.
[276:32] (16592.88s)
This should be one and this should be
[276:35] (16595.52s)
two. Then we're going to give gap of
[276:37] (16597.92s)
let's say six and margin bottom of
[276:40] (16600.56s)
eight. Then the first card that we would
[276:43] (16603.36s)
like to build is the AI assistant.
[276:50] (16610.08s)
So this is the AI assistant card. Once
[276:52] (16612.32s)
we build it, we're going to build the
[276:54] (16614.48s)
user cart.
[276:56] (16616.88s)
Oops. Let's go ahead find this part and
[276:59] (16619.84s)
get started with our very first card
[277:02] (16622.16s)
component. Let's import it from the UI
[277:05] (16625.68s)
folder. Okay, so this will take some
[277:08] (16628.32s)
classes. Let me just paste them. And
[277:10] (16630.72s)
then we're going to have a div within
[277:12] (16632.56s)
this. And let's close this off.
[277:16] (16636.08s)
Again, just some classes just to make UI
[277:18] (16638.48s)
look nice. Then I'd like to put the AI
[277:21] (16641.36s)
voice animation, which is going to be
[277:24] (16644.16s)
this one. So here we can see background
[277:26] (16646.80s)
updates. And then we have these waves.
[277:29] (16649.84s)
Let's paste this in and I'll walk you
[277:31] (16651.36s)
through it. So this is the AI voice
[277:34] (16654.32s)
animation which is a div. If we are in
[277:37] (16657.52s)
the speaking state opacity would be 30
[277:40] (16660.56s)
but otherwise it is like invisible. And
[277:44] (16664.08s)
then for the wave animation we are going
[277:47] (16667.52s)
to create five different divs right
[277:50] (16670.40s)
which are these. So 1 2 3 4 5. And if we
[277:54] (16674.48s)
are speaking the height is going to be
[277:56] (16676.56s)
updated all the time. I mean if the AI
[277:58] (16678.88s)
is speaking right. So here we say if is
[278:01] (16681.84s)
speaking is equal to true go ahead add
[278:04] (16684.32s)
this animation otherwise don't do it and
[278:07] (16687.60s)
height is normally going to be 5%. But
[278:10] (16690.88s)
if we are in the speaking state it
[278:13] (16693.28s)
should always be updated and let's
[278:15] (16695.60s)
actually select all of these variables
[278:18] (16698.56s)
which is is speaking and I'll make them
[278:20] (16700.96s)
to be true so that we can see the
[278:23] (16703.44s)
output.
[278:26] (16706.96s)
So this is the animation that we can
[278:29] (16709.04s)
have, right? It's like infinitely going
[278:34] (16714.32s)
Let's bring this back. Now we shouldn't
[278:36] (16716.40s)
be able to see anything because opacity
[278:39] (16719.20s)
is equal to zero. If we are not in the
[278:41] (16721.84s)
speaking uh speaking state all right
[278:44] (16724.64s)
then we are going to shrink the AI voice
[278:47] (16727.76s)
animation. So that was the div. just go
[278:50] (16730.32s)
outside of it and get the other div
[278:55] (16735.28s)
which is this one. Let's import the
[278:57] (16737.52s)
image from next image and you're going
[279:00] (16740.00s)
to see how that look. Let's say this is
[279:02] (16742.24s)
equal to true.
[279:06] (16746.08s)
Okay. So as you can tell again there is
[279:08] (16748.00s)
a pulse animation going on right before
[279:11] (16751.36s)
right after the like the background. Let
[279:14] (16754.00s)
me zoom in. Okay. This is the background
[279:16] (16756.88s)
of the logo that we can see. And I'll
[279:19] (16759.68s)
just bring this back. So that's the
[279:21] (16761.84s)
entire thing for the
[279:24] (16764.56s)
AI assistant cart. Now we're going to
[279:26] (16766.64s)
build the user cart which is almost the
[279:29] (16769.44s)
exact same thing but without any
[279:31] (16771.36s)
animations, right? We have the user
[279:33] (16773.60s)
image, a text, and then the user full
[279:36] (16776.32s)
name. And then I think this like kind of
[279:39] (16779.76s)
like a batch that says ready. Okay. So,
[279:42] (16782.32s)
we're going to build this, but I think
[279:43] (16783.84s)
we forgot to add this text. This one,
[279:46] (16786.48s)
and then the speaker indicator, right?
[279:50] (16790.00s)
So, here, let's try to build it. Um,
[279:53] (16793.04s)
right after this div,
[279:56] (16796.72s)
let's say AI logo.
[280:02] (16802.00s)
So, right after this, I think we are
[280:03] (16803.92s)
going to pass the H2 and P tag. Let's
[280:08] (16808.24s)
see the output.
[280:10] (16810.24s)
Zoom out. Okay.
[280:12] (16812.48s)
Finally, let's put the speaking
[280:15] (16815.36s)
indicator right after the P tag. I'll
[280:17] (16817.76s)
just say speaking indicator.
[280:21] (16821.84s)
So once again, it doesn't have any logic
[280:24] (16824.24s)
other than some variables. We'll just
[280:26] (16826.88s)
say if it is speaking, we would like to
[280:28] (16828.56s)
have this class. Same for this div. And
[280:31] (16831.76s)
then if it is speaking, text should be
[280:34] (16834.16s)
speaking. But else if the call is
[280:36] (16836.72s)
active, it's going to be listening. call
[280:39] (16839.36s)
ended or we are in the waiting state.
[280:42] (16842.48s)
Let's see. Okay, so now we are in the
[280:44] (16844.72s)
waiting state. Once we start the call,
[280:47] (16847.28s)
the text should update.
[280:51] (16851.28s)
Okay, so that's the entire cart. Let's
[280:53] (16853.36s)
shrink it. I know that it was kind of
[280:55] (16855.52s)
annoying to copy and paste, but there is
[280:58] (16858.08s)
literally no logic in this component in
[281:01] (16861.84s)
the cart. Right now, let's get the user
[281:04] (16864.08s)
cart. I would say just copy it from the
[281:06] (16866.40s)
source code. There's maybe 20 lines of
[281:08] (16868.96s)
code. After this comment,
[281:12] (16872.16s)
okay, let's save. We have the user image
[281:14] (16874.88s)
URL, the user full name. If this is
[281:18] (16878.64s)
equal to undefined, we are going to see
[281:20] (16880.56s)
guest as their name. And then the badge
[281:23] (16883.28s)
is going to say ready.
[281:27] (16887.12s)
Okay, so let's see what it says. I'll
[281:30] (16890.32s)
refresh.
[281:32] (16892.16s)
Are we going to get the same error?
[281:34] (16894.16s)
Okay, so we need to add the
[281:35] (16895.76s)
image.clerk.com. clerk.com under the
[281:38] (16898.40s)
next config just like what we have done
[281:40] (16900.96s)
in the past. I'll duplicate this host
[281:44] (16904.40s)
name should be image.clerk.com.
[281:49] (16909.44s)
Now if you refresh it should be working
[281:51] (16911.36s)
out as expected. We should have two
[281:54] (16914.00s)
different cards.
[281:56] (16916.00s)
For some reason they are side by like
[281:58] (16918.08s)
they are not side by side. Let's go here
[282:00] (16920.96s)
and try to fix it.
[282:03] (16923.84s)
So, looks like I am missing a dash right
[282:06] (16926.48s)
here. MD grid columns of two. And now
[282:10] (16930.32s)
they are side by side. And the very last
[282:12] (16932.72s)
component that we need to add is going
[282:14] (16934.64s)
to be a button at the very bottom of
[282:17] (16937.04s)
this
[282:18] (16938.56s)
UI, right? It's going to say um it'll
[282:21] (16941.12s)
say start the call. So, let's go ahead
[282:24] (16944.56s)
scroll to the bottom right after the
[282:26] (16946.48s)
user card.
[282:28] (16948.56s)
and maybe outside of this div. I'll just
[282:31] (16951.20s)
say whole controls. Just make sure you
[282:35] (16955.36s)
are outside of the card and outside of
[282:37] (16957.28s)
this div. It's going to be 10 lines of
[282:39] (16959.84s)
code. Let's import the button.
[282:43] (16963.68s)
Okay, you can pause the video, type this
[282:45] (16965.60s)
out or grab it from the source code.
[282:48] (16968.80s)
Again, the button would be disabled if
[282:50] (16970.96s)
you are in this state. Once you clicked
[282:53] (16973.04s)
it, we are going to start the call or
[282:55] (16975.52s)
end it. And then if we are in the
[282:58] (16978.48s)
connecting state, we're going to see
[283:00] (16980.24s)
this kind of animation with the animate
[283:03] (16983.68s)
pink class which is coming from
[283:05] (16985.60s)
tailwind. And then the button text
[283:08] (16988.32s)
itself here. So let's say I mean let's
[283:11] (16991.76s)
save and see if you click it. As you can
[283:14] (16994.80s)
tell it is connecting then um AI should
[283:18] (16998.64s)
start speaking. Let me just allow the
[283:21] (17001.28s)
access.
[283:25] (17005.28s)
Hi there, this is Riley, your dental
[283:27] (17007.60s)
assistant from Dentwise. I'm here to
[283:29] (17009.68s)
help you with all your dental needs. I
[283:31] (17011.68s)
can provide information about our
[283:34] (17014.24s)
>> Okay, so that's awesome. I can end the
[283:36] (17016.32s)
call and as you can tell it says call
[283:39] (17019.20s)
ended. Now, one thing that we are
[283:41] (17021.28s)
missing is the message container, right?
[283:44] (17024.48s)
So, we are going to have the Dentwise
[283:46] (17026.40s)
AI. If the AI is speaking or if we are
[283:49] (17029.76s)
speaking, we should have our own name.
[283:52] (17032.64s)
So this is going to be a component that
[283:54] (17034.88s)
we are going to put right above the call
[283:57] (17037.28s)
controls. Here I'll say something like
[284:00] (17040.16s)
message
[284:01] (17041.68s)
container. And again please feel free to
[284:04] (17044.80s)
copy it from the source code. I know we
[284:07] (17047.20s)
have copied a lot of stuff but we are
[284:10] (17050.16s)
almost 5 hours in into this tutorial.
[284:13] (17053.04s)
Let's not waste any um any time by these
[284:16] (17056.48s)
stupid UI elements. Okay. So we'll say
[284:19] (17059.92s)
if message length is greater than zero
[284:22] (17062.80s)
we would like to render this right hand
[284:24] (17064.48s)
side which is the message container
[284:27] (17067.12s)
bunch of classes just to make it look
[284:29] (17069.28s)
nice and scroll behavior will be smooth.
[284:32] (17072.88s)
We're going to map 3D messages, put the
[284:35] (17075.28s)
message content. If the role is
[284:37] (17077.12s)
assistant, we will say Dentwise AI. If
[284:39] (17079.76s)
it is us, we're going to say you. And
[284:42] (17082.72s)
once the call ended, we will say by the
[284:45] (17085.92s)
system call ended. Thank you for using
[284:48] (17088.48s)
Dentwise AI. Okay, so that's the entire
[284:51] (17091.52s)
thing. Let's save and give it a go.
[284:56] (17096.48s)
Okay, so that was the previous messages.
[284:58] (17098.80s)
Right here it says call ended. Thank you
[285:00] (17100.88s)
for using Dentwise AI. Then you can
[285:03] (17103.60s)
navigate to different pages. But let's
[285:06] (17106.16s)
try to run this again. And actually I'll
[285:09] (17109.04s)
try to talk with AI. If I say I'd like
[285:12] (17112.32s)
to book an appointment, it should say
[285:14] (17114.72s)
no, I cannot do it. You should be doing
[285:16] (17116.56s)
it from the appointments page. Okay. So
[285:19] (17119.76s)
let's give it a go.
[285:26] (17126.64s)
Hi there. This is Riley, your dental
[285:28] (17128.96s)
assistant from Dentwise. I'm here to
[285:31] (17131.12s)
help you with all your dental needs. I
[285:33] (17133.12s)
can provide information about our
[285:34] (17134.80s)
service prices, give you immediate tips
[285:37] (17137.20s)
for dental pain or concerns, help you
[285:40] (17140.16s)
understand different treatment options,
[285:42] (17142.24s)
and share oral health prevention advice.
[285:45] (17145.12s)
What can I help you with today?
[285:48] (17148.24s)
>> Can you please provide information about
[285:50] (17150.56s)
your service prices? I'm really curious.
[285:54] (17154.88s)
>> Of course. Here's a clear breakdown of
[285:56] (17156.64s)
our service prices and what each
[285:58] (17158.08s)
includes.
[286:00] (17160.00s)
One, regular dental checkup, $120.
[286:03] (17163.28s)
What's included? A thorough oral
[286:04] (17164.80s)
examination, basic x-rays if needed, and
[286:07] (17167.12s)
an oral health assessment. Duration 3045
[286:10] (17170.96s)
minutes. Frequency recommendation every
[286:14] (17174.40s)
6 months to catch any potential problems
[286:16] (17176.56s)
early.
[286:18] (17178.32s)
Two, teeth cleaning $90. What's
[286:21] (17181.12s)
included? Okay.
[286:23] (17183.36s)
Removal
[286:24] (17184.16s)
>> plus polishing for a clean, healthy
[286:26] (17186.08s)
smile,
[286:26] (17186.72s)
>> dude.
[286:27] (17187.04s)
>> Duration.
[286:28] (17188.56s)
>> Listen. Okay. Um, I was wondering if I
[286:32] (17192.56s)
>> Sorry about that.
[286:33] (17193.04s)
>> Yeah. Yeah, you're good. I'm wondering
[286:34] (17194.72s)
if I can book an appointment with your
[286:37] (17197.12s)
help. Is that possible?
[286:40] (17200.24s)
>> I'm glad you're interested in booking an
[286:41] (17201.92s)
appointment. However, I can't directly
[286:43] (17203.76s)
book appointments for you since that
[286:45] (17205.68s)
process involves secure payment details,
[286:47] (17207.60s)
which I don't handle. You can easily
[286:49] (17209.44s)
book through the Dentwise platform
[286:51] (17211.60s)
though. There you'll be able sad. All
[286:54] (17214.08s)
right, I'm just call and deleting this
[286:56] (17216.80s)
application.
[286:57] (17217.76s)
>> No, I'm just kidding. But here we can
[286:59] (17219.52s)
see AI doesn't allow us to book an
[287:02] (17222.08s)
appointment with like from the voice
[287:04] (17224.56s)
page. If you want to book something, we
[287:06] (17226.88s)
should be visiting the appointments
[287:08] (17228.48s)
page. So this is something that we're
[287:10] (17230.64s)
going to do in the next section. But for
[287:12] (17232.96s)
now, I think we are completely done with
[287:15] (17235.52s)
the voice page. So the entire
[287:18] (17238.24s)
functionality is working smoothly
[287:20] (17240.32s)
without any issues or problems. But I
[287:23] (17243.36s)
would say this workflow is pretty simple
[287:25] (17245.44s)
at this point. You can just start the
[287:27] (17247.68s)
call and talk with AI which is fine. But
[287:30] (17250.32s)
I think it is simple, right? It's a
[287:32] (17252.56s)
little bit simple. And if you want to
[287:34] (17254.48s)
take it to the next level, here is a
[287:36] (17256.56s)
challenge that I have. But for now,
[287:38] (17258.72s)
don't worry about it. Please think about
[287:40] (17260.80s)
this once you complete the entire
[287:43] (17263.28s)
tutorial. Okay? So this will basically
[287:46] (17266.00s)
allow you to book an appointment just by
[287:49] (17269.28s)
talking to this AI assistant. So let's
[287:52] (17272.00s)
say you would say something like I want
[287:54] (17274.40s)
to book an appointment and here is the
[287:56] (17276.56s)
date and time the doctor that I would
[287:58] (17278.64s)
like to have an appointment with and
[288:01] (17281.44s)
then the service like teeth cleaning.
[288:04] (17284.64s)
your AI assistant which is VPY in the
[288:07] (17287.52s)
background would do a web hook call to
[288:10] (17290.56s)
your API and in your endpoint you would
[288:13] (17293.68s)
save that appointment to the database
[288:16] (17296.48s)
which is Postgress and then maybe send
[288:18] (17298.96s)
an email to the user. So this is the
[288:21] (17301.60s)
workflow that we're going to do manually
[288:24] (17304.08s)
under the appointments page in the next
[288:26] (17306.56s)
section but you could actually implement
[288:29] (17309.52s)
it with the AI assistant as well. And
[288:32] (17312.56s)
here is how to do it like in simple
[288:34] (17314.72s)
terms. Under the dashboard you have some
[288:38] (17318.08s)
tools. So this is the documentation. You
[288:40] (17320.64s)
can take a look at it. But here under
[288:43] (17323.44s)
the actual dashboard there are some
[288:46] (17326.24s)
tools and the one that you would like to
[288:49] (17329.36s)
use. So the tool that you would like to
[288:51] (17331.68s)
create would be API request where you
[288:54] (17334.40s)
would pass your request URL. This would
[288:56] (17336.80s)
be the uh let's say your endpoint,
[288:59] (17339.28s)
right? And then you would select the
[289:01] (17341.20s)
HTTP method. I think it would be post in
[289:03] (17343.92s)
this case, but you can do any other
[289:06] (17346.48s)
actions and then just implement it in
[289:09] (17349.36s)
your Nex.js API routes. So it would be
[289:12] (17352.88s)
under the app under the API folder. So
[289:16] (17356.40s)
this is a challenge that you can keep in
[289:18] (17358.48s)
mind once you complete the entire
[289:20] (17360.72s)
tutorial. Not for now. Just for now,
[289:23] (17363.20s)
don't worry about it. We are going to
[289:24] (17364.96s)
implement this in the next section
[289:27] (17367.28s)
manually under the appointments page.
[289:30] (17370.08s)
But the possibilities are endless by
[289:33] (17373.12s)
adding different tools like you can
[289:35] (17375.44s)
transfer the call, you can end it when
[289:38] (17378.08s)
users has something, you can do some
[289:40] (17380.24s)
queries, send text, integrate with third
[289:43] (17383.68s)
party services like Slack, Google sheet,
[289:46] (17386.24s)
Google calendar, things like that. But
[289:48] (17388.88s)
for now, we would like to keep it simple
[289:50] (17390.56s)
and just have this functionality where
[289:53] (17393.04s)
we can chat with AI with our AI
[289:55] (17395.28s)
assistant to get information about this
[289:58] (17398.40s)
Dentwise service. And if you want to
[290:00] (17400.96s)
build other kinds of tools, you can just
[290:03] (17403.28s)
go under the documentation um under the
[290:06] (17406.32s)
examples, you're going to see they have
[290:08] (17408.56s)
like bunch of different really really
[290:10] (17410.48s)
cool examples and you can go over it
[290:13] (17413.44s)
with step-by-step explanation.
[290:16] (17416.88s)
So definitely check it out if you're
[290:18] (17418.72s)
interested. But for now, I'll go ahead
[290:21] (17421.20s)
and commit my changes. Let's open up the
[290:24] (17424.00s)
terminal, kill this app,
[290:27] (17427.68s)
toggle the status bar, um create a new
[290:31] (17431.12s)
branch, something like voice dash page.
[290:37] (17437.44s)
Let's stage all of our changes.
[290:40] (17440.56s)
Let's say voice page addit.
[290:45] (17445.44s)
publish this branch and let's create the
[290:47] (17447.68s)
pull request.
[290:55] (17455.76s)
Okay, so probably we're going to get a
[290:57] (17457.92s)
bunch of different suggestions because
[291:00] (17460.32s)
currently our lobby widget, this is not
[291:03] (17463.84s)
really the best code. I would admit it.
[291:07] (17467.04s)
So it's like 300 lines of code and it's
[291:10] (17470.72s)
probably not the best code. I'll just
[291:12] (17472.24s)
add a comment, but maybe I would do it
[291:14] (17474.40s)
in the next section. So, this is not the
[291:16] (17476.32s)
best code. You can always optimize it.
[291:18] (17478.80s)
And I don't really want to get any
[291:20] (17480.48s)
reviews. I don't want code rabbit to
[291:23] (17483.60s)
embarrass me in front of you. So, for
[291:25] (17485.68s)
that reason, I'll just say merge the
[291:27] (17487.44s)
pull request.
[291:31] (17491.04s)
And then once this is done, we can go
[291:33] (17493.36s)
back to the master branch and get the
[291:37] (17497.36s)
latest changes. So, let's say sync this
[291:42] (17502.96s)
And with this that's the entire uh
[291:45] (17505.20s)
entire section. Hopefully in the next
[291:47] (17507.20s)
one we are going to build the
[291:48] (17508.64s)
appointments page. So I'll see you
[291:50] (17510.64s)
there. In this section we are going to
[291:53] (17513.28s)
get started with the dashboard page. So
[291:55] (17515.84s)
we are going to have three different
[291:57] (17517.44s)
components. The first one is the welcome
[292:00] (17520.00s)
section which is some UI element. Then
[292:02] (17522.80s)
we have two different components. Now
[292:05] (17525.44s)
this is what we call the main actions.
[292:07] (17527.92s)
And then finally, we have the activity
[292:10] (17530.16s)
overview. So, we can see the upcoming uh
[292:13] (17533.28s)
upcoming appointments as well as some
[292:16] (17536.00s)
stats. Let's go ahead and get started
[292:18] (17538.40s)
from top to bottom. First, I'll visit
[292:21] (17541.60s)
the dashboard page
[292:24] (17544.32s)
under the app under the dashboard. So,
[292:27] (17547.28s)
this doesn't have any content at the
[292:29] (17549.04s)
moment. We're going to have a couple of
[292:30] (17550.88s)
different components for the dashboard
[292:33] (17553.60s)
page. So let's create this folder and
[292:36] (17556.24s)
let's get into this file itself. We can
[292:39] (17559.84s)
make this to be an empty fragment. And
[292:42] (17562.48s)
then we have the navbar at the top. Then
[292:45] (17565.04s)
right after this, let me first kill the
[292:47] (17567.44s)
AI. Just copil it. I'll just disable it
[292:50] (17570.80s)
so that it is not really annoying. Okay.
[292:53] (17573.52s)
So let me give a bit space. This is the
[292:56] (17576.24s)
div that we are going to have with these
[292:58] (17578.88s)
class names. So maximum width of 7x
[293:02] (17582.88s)
large. center this and give some
[293:04] (17584.80s)
paddings. Um, we have used this couple
[293:07] (17587.36s)
of times I know but the best practice
[293:10] (17590.48s)
would be to create a component for this
[293:13] (17593.12s)
right something like a you know
[293:15] (17595.60s)
container wrapper or you know some kind
[293:18] (17598.16s)
of um some kind of component that is
[293:21] (17601.28s)
going to wrap your entire application.
[293:23] (17603.84s)
So instead of every time doing this you
[293:26] (17606.00s)
would say something like my wrapper
[293:29] (17609.44s)
right
[293:32] (17612.00s)
and wrap the content with it. Um but
[293:34] (17614.72s)
just to keep it simple this is what I'm
[293:36] (17616.56s)
using. Uh just keep that in mind. You
[293:38] (17618.88s)
can always optimize your code. Okay. So
[293:41] (17621.36s)
that's the div. We are going to have the
[293:43] (17623.52s)
welcome section component. It is not
[293:46] (17626.40s)
going to be coming from the voice uh
[293:49] (17629.20s)
voice component. Right. We are going to
[293:51] (17631.60s)
have another one for the dashboard page.
[293:55] (17635.52s)
So under the components under the
[293:57] (17637.60s)
dashboard I'll say welcome section.tsx.
[294:01] (17641.84s)
Let's try to import this component.
[294:04] (17644.88s)
Make sure it is coming from the
[294:06] (17646.24s)
dashboard folder. And you can get this
[294:09] (17649.20s)
content from uh from source code which
[294:12] (17652.08s)
is like 30 lines of code just some UI
[294:16] (17656.64s)
you know just only some classes. Let me
[294:19] (17659.60s)
kill the status bar. So we are getting
[294:21] (17661.84s)
the current user so that we can put
[294:23] (17663.92s)
their name like user first name and
[294:26] (17666.80s)
depending on the time we're going to
[294:28] (17668.96s)
either say good morning, afternoon or
[294:31] (17671.60s)
evening. So let's save and actually try
[294:34] (17674.64s)
to import it. Um
[294:38] (17678.80s)
here what it says.
[294:44] (17684.32s)
So we are not saying export default.
[294:47] (17687.04s)
Let's do it.
[294:51] (17691.60s)
Okay, now we don't have any errors and
[294:54] (17694.08s)
actually if you just copy it and paste
[294:56] (17696.00s)
it, you're not going to get any errors.
[294:58] (17698.32s)
Um, let's go ahead see that's the output
[295:01] (17701.92s)
and I can see good morning at the
[295:04] (17704.08s)
moment. Now, right after this component,
[295:06] (17706.80s)
we are going to have another one called
[295:08] (17708.96s)
main actions. So, I'll duplicate this.
[295:11] (17711.76s)
Let's say main actions
[295:15] (17715.84s)
and copy the name
[295:19] (17719.04s)
just create it under this folder under
[295:21] (17721.60s)
the components.
[295:25] (17725.20s)
Let's try to import it. So this is going
[295:28] (17728.00s)
to be these two cards.
[295:31] (17731.92s)
This will take you to the voice page if
[295:34] (17734.00s)
you click to the link. And this will
[295:35] (17735.76s)
take you to the appointments page. So
[295:38] (17738.64s)
again, they are just UI, no logic at all
[295:41] (17741.44s)
other than a link. If you wanted to, you
[295:44] (17744.16s)
can um type this out, but in this case,
[295:47] (17747.68s)
I would like to copy and paste because
[295:50] (17750.08s)
this is not a CSS course and we are like
[295:53] (17753.20s)
5 hours in into this tutorial.
[295:58] (17758.00s)
So I will delete this entire thing, copy
[296:00] (17760.24s)
it and paste it from the source code.
[296:04] (17764.00s)
Okay, so we just have a huge div with
[296:06] (17766.88s)
some classes.
[296:10] (17770.40s)
Um, here let's just say export default
[296:14] (17774.08s)
option and see the output.
[296:22] (17782.64s)
Let's refresh. Okay, so it is working as
[296:25] (17785.52s)
expected. If you click to it, that would
[296:27] (17787.92s)
take you to the voice page. And if you
[296:30] (17790.96s)
click to this one, it would take you to
[296:32] (17792.56s)
the appointments page, which is
[296:35] (17795.04s)
something that we don't have yet. Now,
[296:37] (17797.12s)
the other component that we would like
[296:38] (17798.80s)
to build is this overview. So, I'll go
[296:41] (17801.44s)
ahead
[296:43] (17803.36s)
duplicate this. Let's say activity
[296:48] (17808.32s)
overview.
[296:50] (17810.16s)
Copy the name and create it under the
[296:53] (17813.76s)
components.
[297:00] (17820.64s)
and import it in this file. Now let's
[297:02] (17822.96s)
save and get into this component. Now
[297:05] (17825.84s)
within this component we are going to
[297:07] (17827.68s)
have two different components. So the
[297:10] (17830.40s)
first one is the dental health overview
[297:13] (17833.68s)
and the other one is the next
[297:15] (17835.68s)
appointment. Okay. So let's say cut this
[297:19] (17839.36s)
div and paste this in where we have a
[297:22] (17842.00s)
dental health overview. Let's create
[297:24] (17844.48s)
this one under the components
[297:31] (17851.12s)
and just import it
[297:33] (17853.92s)
and do the same thing for this one.
[297:42] (17862.80s)
Okay. So, first we are going to get
[297:44] (17864.72s)
started with this overview component
[297:47] (17867.44s)
which is going to show us the completed
[297:49] (17869.84s)
visits, right? the completed
[297:51] (17871.68s)
appointments, total appointments that
[297:54] (17874.16s)
this user has when they sign up, the
[297:57] (17877.04s)
members and state and then some kind of
[297:59] (17879.92s)
like CTA buttons. Okay, so let's uh go
[298:04] (17884.08s)
into this component and try to build the
[298:07] (17887.36s)
logic. So here we would like to fetch
[298:09] (17889.76s)
some data and for this we can create a
[298:13] (17893.36s)
server action. Let's go under the let me
[298:17] (17897.20s)
find it under the source lab actions.
[298:21] (17901.12s)
This is related to appointments. So we
[298:23] (17903.84s)
can go into this file and let's shrink
[298:26] (17906.48s)
everything. I think that's the only
[298:28] (17908.24s)
method that we have. Let's create
[298:30] (17910.56s)
another one. So I'll say export async
[298:33] (17913.84s)
function get user
[298:37] (17917.84s)
let's say user appointment
[298:41] (17921.28s)
stats.
[298:43] (17923.52s)
And this is not going to take any
[298:44] (17924.88s)
arguments. Let's go into it. I'll say
[298:47] (17927.20s)
try and catch block first. Let's get the
[298:50] (17930.32s)
user ID from clerk. We'll say await
[298:55] (17935.28s)
oath call this method. This will give
[298:57] (17937.92s)
the authenticated users ID. And we can
[299:00] (17940.96s)
say if this is equal to null, right? If
[299:03] (17943.28s)
this is undefined like if this is a
[299:05] (17945.60s)
falsy value, we'll just say you must be
[299:08] (17948.16s)
authenticated. Then we can find the user
[299:11] (17951.68s)
uh from our database. We'll say const
[299:14] (17954.32s)
user. We're going to pass the clerk ID
[299:16] (17956.96s)
to be able to find it. We'll say await
[299:20] (17960.00s)
prisma dot let's say user find unique
[299:24] (17964.96s)
and we're going to pass our filter. I'll
[299:27] (17967.60s)
say where
[299:29] (17969.52s)
um the clerk ID this should be under an
[299:32] (17972.72s)
object. So clerk ID is equal to this
[299:35] (17975.68s)
user ID that we have right and if user
[299:38] (17978.88s)
is not in our database again we can say
[299:43] (17983.28s)
row new error
[299:47] (17987.60s)
maybe something like user not found.
[299:51] (17991.84s)
What else we can basically get the total
[299:54] (17994.88s)
count
[299:56] (17996.88s)
right the total count as well as the
[299:59] (17999.04s)
completed count for the appointments.
[300:02] (18002.32s)
So here this is what I'll be doing.
[300:06] (18006.08s)
I'm going to use promise.all await this.
[300:09] (18009.36s)
This is the first call where we get the
[300:11] (18011.60s)
total count. Right under the appointment
[300:14] (18014.16s)
we say count where this user is the
[300:17] (18017.60s)
owner of the appointment. And then we're
[300:19] (18019.92s)
going to count the like the completed
[300:22] (18022.72s)
count. Right? We say if the status is
[300:25] (18025.04s)
equal to completed go ahead give us
[300:27] (18027.52s)
those appointments. And this should be
[300:29] (18029.84s)
type safe I believe.
[300:33] (18033.28s)
Okay, completed or confirmed. In this
[300:36] (18036.24s)
case, we're going to get the completed
[300:38] (18038.00s)
appointments. So, this is going to make
[300:40] (18040.00s)
it faster because we are using
[300:42] (18042.00s)
promise.all. Let's say these calls will
[300:46] (18046.72s)
run in parallel. Let's say instead of
[300:50] (18050.16s)
waiting each other. Okay. And then
[300:54] (18054.56s)
finally we can just say return an object
[300:57] (18057.52s)
where the total appointments
[301:01] (18061.84s)
is equal to the total count and the
[301:05] (18065.12s)
completed appointments
[301:10] (18070.08s)
are equal to the uh completed count. And
[301:13] (18073.84s)
in the catch we can say something like
[301:17] (18077.68s)
they're both equal to zero or you can
[301:20] (18080.08s)
throw some error. And for the um you
[301:22] (18082.88s)
know just for the debugging purposes
[301:24] (18084.88s)
I'll put a console log. So that's our
[301:27] (18087.92s)
entire method. Now we can go ahead and
[301:30] (18090.40s)
call this under this component. Let's
[301:33] (18093.04s)
make this to be an async function. We
[301:36] (18096.48s)
can say await call this method
[301:40] (18100.32s)
and this is going to give us a value
[301:43] (18103.12s)
which we can call as appointment stats
[301:46] (18106.32s)
and then I I will also get the user
[301:50] (18110.00s)
object. Let's say await give us the
[301:52] (18112.64s)
current user from XJS and then let's
[301:56] (18116.72s)
build the return statement. Now since we
[301:59] (18119.36s)
already have the logic the return
[302:01] (18121.52s)
statement is just a card with with a
[302:05] (18125.60s)
beautiful styling. So if you would like
[302:07] (18127.84s)
to get it, you can do so from the source
[302:10] (18130.00s)
code. This is what I'll be doing. Let's
[302:12] (18132.48s)
copy it and paste it. Then import all
[302:15] (18135.20s)
these elements. So we'll get the cart
[302:18] (18138.56s)
cart header the title brain icon cart
[302:23] (18143.44s)
description as well as the content. I
[302:26] (18146.32s)
think we need to get some more icons
[302:29] (18149.20s)
like message square icon and we're going
[302:31] (18151.92s)
to get the format method from date fns.
[302:35] (18155.84s)
So you can import it. Let's get the link
[302:39] (18159.84s)
from next link button from the UI folder
[302:44] (18164.48s)
and this entire thing. Let's save and
[302:47] (18167.12s)
see the output.
[302:51] (18171.44s)
Um let's refresh maybe.
[302:54] (18174.40s)
Okay. So this is the component that we
[302:56] (18176.80s)
just have. This user is
[303:00] (18180.08s)
uh signed up in September. Total
[303:02] (18182.56s)
appointments is equal to zero and
[303:04] (18184.48s)
completed visits is also equal to zero.
[303:07] (18187.36s)
That's correct because we didn't have
[303:09] (18189.92s)
like we didn't created any appointments
[303:11] (18191.92s)
yet. In the next section actually we
[303:14] (18194.32s)
will create them and if it is completed
[303:17] (18197.04s)
it should update the UI. So this is
[303:19] (18199.36s)
working as expected. When you click to
[303:21] (18201.44s)
these when you click to these links they
[303:24] (18204.24s)
should also work out right. So as you
[303:27] (18207.04s)
can tell they are taking us to the
[303:28] (18208.80s)
related pages. So that's great. Now next
[303:33] (18213.04s)
thing that we need to build is this
[303:35] (18215.04s)
component. Let's go into that file and
[303:38] (18218.96s)
build it as well.
[303:41] (18221.36s)
Now in this component we will also be
[303:43] (18223.52s)
fetching some data. So let's mark this
[303:45] (18225.68s)
with the async keyword. And here is the
[303:48] (18228.24s)
data that we're going to fetch the
[303:50] (18230.48s)
appointments. But for this we need to
[303:52] (18232.64s)
build the server action. So we are going
[303:55] (18235.36s)
to call this but first we need to build
[303:57] (18237.20s)
it. Let's go under the actions
[303:59] (18239.44s)
appointments and create this method. So
[304:02] (18242.56s)
I will put it right uh right before to
[304:06] (18246.08s)
this one. say export
[304:09] (18249.68s)
async function put the name and this is
[304:13] (18253.12s)
not going to take any arguments so let's
[304:15] (18255.52s)
build it step by step it's going to be
[304:17] (18257.84s)
pretty easy I mean easier than what you
[304:20] (18260.16s)
think so first off we are going to get
[304:22] (18262.48s)
the user ID from clerk if user is not
[304:25] (18265.44s)
authenticated we would throw an error
[304:28] (18268.00s)
and then check this user in our database
[304:30] (18270.64s)
again if there is no user we'll say user
[304:32] (18272.96s)
not found uh you know just have some
[304:35] (18275.52s)
error on the UI Okay, but else we can
[304:38] (18278.00s)
actually get the appointments where this
[304:40] (18280.80s)
user is the owner of the appointment. So
[304:44] (18284.08s)
here we'll say go under the appointment
[304:46] (18286.48s)
table find many where the user is this
[304:50] (18290.32s)
current user right and for the user
[304:54] (18294.00s)
information and doctor we would like to
[304:56] (18296.08s)
include some fields for the user we're
[304:58] (18298.96s)
going to select the first name last name
[305:01] (18301.20s)
and email and for the doctor we're going
[305:03] (18303.60s)
to get the name as well as the image URL
[305:06] (18306.56s)
because if you remember under the
[305:08] (18308.64s)
appointment let's see it under the
[305:10] (18310.56s)
schema under the appointment you don't
[305:13] (18313.12s)
have any information about the user. We
[305:15] (18315.76s)
just have the relation. So we need to go
[305:18] (18318.00s)
under those tables to be able to fetch
[305:21] (18321.36s)
the details. So that's what we are doing
[305:24] (18324.32s)
with the include and select fields. Then
[305:28] (18328.08s)
we're going to order these appointments.
[305:30] (18330.72s)
And then at the end we can just say
[305:32] (18332.48s)
return the appointments as it is. And
[305:35] (18335.60s)
then in the catch let's handle it. I'll
[305:38] (18338.24s)
put my classic two lines a console log
[305:42] (18342.64s)
and then an error. So that's the entire
[305:45] (18345.52s)
method where we have two different
[305:47] (18347.68s)
checks you know just to check if user is
[305:50] (18350.16s)
authenticated and if we have the user in
[305:52] (18352.32s)
the database after that we are going to
[305:54] (18354.48s)
fetch all the appointments and return
[305:56] (18356.40s)
them. Now instead of returning the
[305:58] (18358.32s)
appointments as they are maybe we can
[306:01] (18361.20s)
transform it a bit so that our code is a
[306:04] (18364.56s)
little bit more easy to read in the
[306:06] (18366.48s)
upcoming sections. So here instead of
[306:08] (18368.96s)
just saying appointments I will actually
[306:11] (18371.76s)
map over this and I will get each uh
[306:16] (18376.16s)
appointment and we are going to call a
[306:18] (18378.08s)
method with it. So let's say transform
[306:21] (18381.92s)
appointment.
[306:24] (18384.88s)
So this is the method that we can create
[306:27] (18387.28s)
at the top of this file. So this
[306:29] (18389.92s)
function is going to get every single
[306:32] (18392.24s)
appointment and it's going to return the
[306:35] (18395.20s)
re uh it's going to return the
[306:37] (18397.12s)
appointment as it is but on top of it
[306:39] (18399.44s)
it's going to add these fields like the
[306:42] (18402.08s)
patient name, the patient email, doctor
[306:45] (18405.04s)
name, image URL as well as the date.
[306:48] (18408.08s)
Okay. So you can copy this from the
[306:50] (18410.00s)
source code. Again, we are just doing
[306:51] (18411.84s)
this so that our code is a little bit
[306:54] (18414.08s)
more easy to work with in the upcoming
[306:56] (18416.64s)
sections. So, when you want to use the
[306:58] (18418.96s)
PA patient name, as you can tell, this
[307:01] (18421.84s)
is already been combined, you know, with
[307:04] (18424.48s)
the first name and last name. Okay,
[307:08] (18428.80s)
now that's the entire thing. Just make
[307:11] (18431.44s)
sure you update this line. Let's go
[307:13] (18433.60s)
ahead import this method. Make sure it
[307:16] (18436.32s)
is the appointments, not the stats. Now
[307:19] (18439.52s)
that we have the appointments, we would
[307:21] (18441.36s)
like to get only the upcoming
[307:23] (18443.68s)
appointments, right? So, we would like
[307:25] (18445.36s)
to do some filtering because here we
[307:27] (18447.68s)
would like to see the next upcoming uh
[307:31] (18451.36s)
appointments.
[307:32] (18452.88s)
Okay. So, I will paste this in where we
[307:35] (18455.20s)
have this array that we are filtering
[307:37] (18457.92s)
out. We're going to import the pars ISO
[307:41] (18461.28s)
from data finess if it is the same day
[307:44] (18464.88s)
with today or if it is after today which
[307:48] (18468.48s)
is in the future right and we'll say if
[307:51] (18471.12s)
status is equal to confirmed so this
[307:53] (18473.68s)
will include all the upcoming
[307:55] (18475.44s)
appointments and we can get the earliest
[307:58] (18478.00s)
one by doing this so we're going to get
[308:00] (18480.64s)
the very first result then we can check
[308:03] (18483.76s)
if there is no next appointment we can
[308:06] (18486.56s)
just say return null don't return
[308:08] (18488.96s)
anything. But in the else case, we can
[308:12] (18492.80s)
calculate some variables. Let's import
[308:15] (18495.68s)
the format field or the method from data
[308:19] (18499.12s)
finns. And we're going to be using the
[308:21] (18501.68s)
these in a second. Before we build the
[308:24] (18504.24s)
return statement here, let's say instead
[308:26] (18506.72s)
of returning null, we can return a
[308:29] (18509.04s)
component like no appointments.
[308:35] (18515.60s)
and we can create it under the
[308:38] (18518.00s)
components.
[308:40] (18520.16s)
So under the components under the
[308:41] (18521.84s)
dashboard I'll just say no next
[308:45] (18525.20s)
appointments.tsx
[308:49] (18529.52s)
and let's say no next appointments. And
[308:53] (18533.36s)
this is just a UI so we can copy and
[308:55] (18535.76s)
paste the content from the source code.
[308:58] (18538.08s)
This is what I'll be doing. I'll just
[308:59] (18539.68s)
get it from the source code. We're going
[309:01] (18541.60s)
to import the card
[309:03] (18543.84s)
card header, card title, this icon
[309:08] (18548.96s)
link,
[309:10] (18550.56s)
um the button as well as the card
[309:12] (18552.96s)
content.
[309:15] (18555.84s)
Okay, so just 30 lines of code. Um save
[309:19] (18559.28s)
and let's import it. It should be no
[309:23] (18563.28s)
next appointments and let's say export
[309:25] (18565.76s)
default
[309:27] (18567.84s)
this
[309:29] (18569.36s)
uh component.
[309:35] (18575.92s)
Let's see the output
[309:39] (18579.36s)
by refresh. Okay, so there are no
[309:42] (18582.08s)
upcoming appointments for this account,
[309:44] (18584.32s)
right? So for this account, we don't
[309:46] (18586.32s)
really have any appointments. If you
[309:48] (18588.40s)
click it, it should take you to the
[309:50] (18590.80s)
dashboard appointments page. But if we
[309:53] (18593.84s)
have some appointments, we should see
[309:56] (18596.40s)
this UI which is going to be under the
[309:59] (18599.68s)
return statement. So again, it's not
[310:02] (18602.24s)
going to contain any logic. Let's just
[310:04] (18604.40s)
get it from the source code. I'll just
[310:06] (18606.96s)
copy it and paste it right here. And
[310:09] (18609.04s)
once again, I completely understand if
[310:11] (18611.36s)
you're getting annoyed by just copying
[310:13] (18613.60s)
and pasting, but I would say you
[310:15] (18615.36s)
shouldn't be because we are like 5 hours
[310:18] (18618.24s)
in and at this point you understand
[310:20] (18620.88s)
everything, right? So, we learned how to
[310:23] (18623.20s)
create server actions, how to use them
[310:25] (18625.76s)
in our server components. The rest is
[310:28] (18628.24s)
just UI. It's basically HTML and CSS.
[310:32] (18632.32s)
So, that's why I just find it pretty
[310:34] (18634.80s)
unnecessary to type all these out. Like
[310:38] (18638.24s)
what is the difference just copying and
[310:40] (18640.08s)
pasting and doing this like flex item
[310:44] (18644.08s)
centered
[310:45] (18645.60s)
in gap of three? It just doesn't make
[310:47] (18647.52s)
any sense, right? Um this is a full
[310:50] (18650.08s)
stack course. So that's why I just try
[310:53] (18653.60s)
to copy and paste these UI elements as
[310:56] (18656.40s)
much as possible and I hope you don't
[310:59] (18659.28s)
mind it. Let's import everything step by
[311:02] (18662.72s)
step.
[311:07] (18667.76s)
We'll get our icons
[311:10] (18670.40s)
and it should be good to go. Okay. So,
[311:13] (18673.52s)
currently we cannot really see this in
[311:15] (18675.60s)
the UI because we don't have any
[311:17] (18677.68s)
appointments. Uh once we have once we
[311:21] (18681.20s)
have some appointments, the UI should be
[311:23] (18683.12s)
updated and we should see something like
[311:25] (18685.36s)
this which is something that we can
[311:27] (18687.36s)
check in the upcoming sections. So, I
[311:30] (18690.16s)
believe that's the entire section for
[311:33] (18693.04s)
this one. Right. Let's go ahead open up
[311:36] (18696.00s)
the status bar and we are going to
[311:38] (18698.00s)
commit our changes under a new branch.
[311:40] (18700.72s)
So I'll say dashboard
[311:43] (18703.44s)
dash page. That's the branch name. We
[311:46] (18706.64s)
can stage all of our changes. Add a
[311:49] (18709.68s)
commit something like um I don't know,
[311:52] (18712.56s)
let's say dashboard page addit commit
[311:56] (18716.80s)
and publish the branch.
[312:00] (18720.64s)
Let's go into the source code.
[312:07] (18727.44s)
and we are going to create the pull
[312:09] (18729.28s)
request.
[312:11] (18731.36s)
Now we could wait for the suggestions
[312:13] (18733.52s)
from code rabbit. This is what I would
[312:15] (18735.84s)
do if this was my actual project, right?
[312:19] (18739.12s)
If this was my software as a service, I
[312:22] (18742.16s)
would wait for the comments so that I
[312:24] (18744.56s)
can optimize my optimize my codebase.
[312:27] (18747.36s)
But since this tutorial is just getting
[312:29] (18749.28s)
so long, I would like to skip the
[312:31] (18751.60s)
reviews and just merge the pull request.
[312:34] (18754.40s)
But you can definitely wait for it if
[312:36] (18756.32s)
you would like to if you want to
[312:38] (18758.32s)
optimize your codebase. So here I'll
[312:41] (18761.12s)
just say merge this pull request.
[312:45] (18765.60s)
Once it is done, we can go here, switch
[312:48] (18768.88s)
to the master
[312:51] (18771.92s)
and fetch the latest changes.
[312:59] (18779.36s)
Okay, it is done successfully. That
[313:01] (18781.52s)
means this section is also completed. So
[313:04] (18784.40s)
I'll see you in the next one. In this
[313:07] (18787.20s)
section, we are going to build the
[313:09] (18789.12s)
appointments page. So here are the steps
[313:12] (18792.00s)
how that would look like. So first we
[313:14] (18794.56s)
are able to see the doctors within this
[313:17] (18797.76s)
div and then at the bottom we can see
[313:20] (18800.32s)
our upcoming appointments with the
[313:22] (18802.80s)
doctor itself. you know the time, date
[313:25] (18805.68s)
and the service. So first we are going
[313:28] (18808.32s)
to get started with this section. Okay.
[313:31] (18811.52s)
So here this is what we call the
[313:33] (18813.52s)
progress steps and depending on which
[313:36] (18816.48s)
step you are it is going to be
[313:38] (18818.24s)
highlighted. So first you would click to
[313:40] (18820.72s)
a doctor. In this example I have chosen
[313:43] (18823.52s)
this one and then you would say continue
[313:45] (18825.76s)
to time selection.
[313:48] (18828.00s)
Then you are in the second step where
[313:50] (18830.40s)
you can select an appointment type, date
[313:53] (18833.44s)
as well as the available time. Now if it
[313:56] (18836.32s)
is already booked by someone else, you
[313:58] (18838.72s)
can see this is disabled. You cannot
[314:00] (18840.72s)
select it. But if you select something
[314:02] (18842.96s)
that is available like this one, you can
[314:05] (18845.44s)
see this button where we can review our
[314:07] (18847.92s)
booking. Once you click to it, you're
[314:10] (18850.80s)
going to see this uh UI where you can
[314:13] (18853.68s)
confirm the booking. Or if you want to
[314:16] (18856.08s)
modify it, you can click to this button
[314:18] (18858.72s)
and go back. And you can go back with
[314:20] (18860.88s)
this button as well. Once you confirm
[314:23] (18863.20s)
it, you will have a loading state and
[314:25] (18865.52s)
then a model popup. It'll say
[314:27] (18867.92s)
appointment confirmed. Um, here are the
[314:30] (18870.88s)
details. We just sent an email to your
[314:33] (18873.36s)
inbox. Okay, so I hope that makes sense.
[314:36] (18876.24s)
You can get these diagrams in the
[314:38] (18878.40s)
description for completely free. Now,
[314:40] (18880.64s)
let's jump into coding and build it. Now
[314:43] (18883.84s)
one thing that I want to fix currently
[314:46] (18886.16s)
if you click to this button it takes you
[314:48] (18888.64s)
/dashboard/appoints
[314:51] (18891.12s)
but I just want to have / appointments
[314:53] (18893.68s)
right not dashboard let me fix it this
[314:56] (18896.72s)
is going to be under the navbar
[314:58] (18898.64s)
component maybe in your case it is
[315:01] (18901.28s)
already fixed but here I'll just say you
[315:04] (18904.96s)
know dashboard slash appointments
[315:09] (18909.76s)
okay so um I will update this href
[315:13] (18913.76s)
Let's say this should be just slash
[315:16] (18916.16s)
appointments. And let's do the same
[315:18] (18918.32s)
thing for this part. Let's say slash
[315:21] (18921.68s)
appointments.
[315:24] (18924.24s)
Oops.
[315:27] (18927.12s)
Okay. Now I think we have the exact same
[315:30] (18930.00s)
thing in three different components
[315:32] (18932.48s)
which are the dental health overview.
[315:35] (18935.44s)
Let me search for it. Maybe in your case
[315:37] (18937.36s)
it is already fixed
[315:39] (18939.92s)
so you can ignore this. and then the
[315:43] (18943.04s)
main actions
[315:49] (18949.60s)
and then finally I think the no next
[315:53] (18953.12s)
appointments component.
[315:57] (18957.76s)
Okay, right here.
[316:00] (18960.88s)
So that was the link fix uh link fixes.
[316:04] (18964.96s)
Let's go under the app create this
[316:08] (18968.96s)
appointments folder and then I'll say
[316:12] (18972.16s)
page.tsx.
[316:14] (18974.96s)
This is going to be let me select both
[316:17] (18977.60s)
of them. Um let's say appointments page
[316:26] (18986.96s)
and then this is going to be a client
[316:28] (18988.96s)
component. So let's say use client
[316:33] (18993.44s)
directive.
[316:35] (18995.04s)
So before we build the return statement
[316:37] (18997.68s)
here, let's go ahead and get some states
[316:40] (19000.00s)
that we are going to need in this page.
[316:42] (19002.24s)
I will grab them from the source code.
[316:44] (19004.32s)
Let me paste and I will walk you through
[316:46] (19006.16s)
it. So here are all the states that we
[316:49] (19009.44s)
are going to be using. Let's import the
[316:51] (19011.60s)
state from React. So this is the state
[316:54] (19014.00s)
management for the booking process. This
[316:56] (19016.72s)
could be done with something like
[316:58] (19018.32s)
sustand for larger applications. Okay,
[317:01] (19021.20s)
just keep this in mind. This is some
[317:03] (19023.28s)
kind of um optimization that you could
[317:06] (19026.24s)
have done using something like sustain.
[317:08] (19028.80s)
But here in this example, I'll just keep
[317:10] (19030.56s)
it simple and just have bunch of
[317:12] (19032.88s)
different states. So we are going to
[317:14] (19034.80s)
have the selected dentist ID and it's
[317:17] (19037.76s)
going to be selected whenever you click
[317:20] (19040.48s)
to one of these, right?
[317:23] (19043.20s)
and then the selected date, time, the
[317:26] (19046.16s)
selected appointment type, current step
[317:28] (19048.88s)
that we are in if we should show the
[317:31] (19051.92s)
confirmation model or not and then the
[317:34] (19054.32s)
booked appointment. So we are going to
[317:36] (19056.56s)
have this state so that we can show the
[317:39] (19059.12s)
details right here in this model.
[317:43] (19063.20s)
Okay.
[317:44] (19064.72s)
So let's see how we are going to
[317:47] (19067.52s)
build the return statement. But just
[317:49] (19069.76s)
just before that let's have some
[317:51] (19071.52s)
functions. So I'll say con handle select
[317:56] (19076.00s)
dentist.
[317:58] (19078.16s)
Let's say we are going to get a doctor
[318:00] (19080.72s)
ID or dentist ID. That's the same thing.
[318:04] (19084.24s)
Let's say this will be type of string.
[318:07] (19087.76s)
And in this function we are going to do
[318:09] (19089.44s)
something for now. Let's just leave it
[318:11] (19091.12s)
as an empty method. And then we'll have
[318:13] (19093.76s)
closed handle book appointment. This
[318:16] (19096.88s)
could be an async error function and we
[318:20] (19100.24s)
can leave the logic empty for now. Okay.
[318:23] (19103.28s)
Now let's get into the return statement.
[318:25] (19105.44s)
So as always we're going to have the
[318:27] (19107.76s)
navbar component. Let's say this is
[318:30] (19110.88s)
going to be self closed
[318:33] (19113.52s)
and then we're going to have a div right
[318:35] (19115.68s)
below to it. So I will say class name
[318:38] (19118.72s)
and here are the classes. Right after
[318:41] (19121.12s)
this we're going to have the title which
[318:43] (19123.60s)
is kind of like the header component. So
[318:46] (19126.24s)
let's save and see the output what we're
[318:48] (19128.16s)
going to get under the slash
[318:50] (19130.48s)
appointments page.
[318:53] (19133.28s)
Okay. So this is the exact same thing
[318:54] (19134.96s)
that we have right here. Then the next
[318:57] (19137.92s)
thing that we can build is the progress
[319:00] (19140.40s)
steps. So I will go into VS code. Right
[319:04] (19144.16s)
after this header component, let's just
[319:06] (19146.56s)
say header instead of title.
[319:09] (19149.36s)
I will have the progress
[319:12] (19152.32s)
steps component. And we're going to pass
[319:15] (19155.36s)
the current step as a prop which is
[319:18] (19158.16s)
going to be the current step state.
[319:22] (19162.00s)
Okay. So it's this one. And initially it
[319:24] (19164.00s)
does one which is select dentist. Second
[319:26] (19166.80s)
step is select time. And the last one is
[319:29] (19169.60s)
to confirm.
[319:31] (19171.52s)
So let's say current step and we are
[319:34] (19174.88s)
going to create this under the
[319:36] (19176.88s)
components.
[319:38] (19178.48s)
Let's create one for the appointments.
[319:42] (19182.72s)
Paste this in. and let's say import this
[319:46] (19186.56s)
here in this file.
[319:49] (19189.04s)
Let's get into the component itself. So
[319:51] (19191.52s)
let's say this is taking some props such
[319:54] (19194.16s)
as the current step and this is a type
[319:56] (19196.48s)
of number right and at the top I will
[319:58] (19198.96s)
have an import which is an icon coming
[320:01] (19201.12s)
from lucid react and then the progress
[320:03] (19203.84s)
steps. So this is going to be let's say
[320:06] (19206.96s)
a constant and you know this is a
[320:09] (19209.60s)
hard-coded data that's why we are
[320:11] (19211.60s)
following this convention where it is
[320:14] (19214.40s)
completely uppercased right um here
[320:17] (19217.28s)
let's go ahead under the return
[320:18] (19218.88s)
statement let's say we're going to have
[320:21] (19221.12s)
a div and before we add the content let
[320:24] (19224.56s)
me show you the end result so here as
[320:27] (19227.84s)
you can tell we have a number right and
[320:30] (19230.40s)
then the text content which is coming
[320:32] (19232.80s)
from the array that we just have right
[320:35] (19235.44s)
select dentist choose time and confirm.
[320:38] (19238.56s)
This is the exact same thing. Now we are
[320:40] (19240.72s)
seeing an arrow at the end right we have
[320:43] (19243.44s)
one two but for the very last item we
[320:46] (19246.80s)
don't really see it and we can check
[320:48] (19248.56s)
this by using the index. So let's go
[320:51] (19251.20s)
ahead I will just grab this from the
[320:53] (19253.76s)
source code which is like 20 lines of
[320:55] (19255.60s)
code and here is how that work. So this
[320:58] (19258.40s)
is the entire div which is flex item
[321:00] (19260.72s)
centered gap four and let's say
[321:03] (19263.04s)
something like maybe margin bottom of
[321:05] (19265.92s)
four as well. So we have a progress
[321:08] (19268.64s)
tabs.m map for each step name we're
[321:11] (19271.12s)
going to get this step number and is
[321:13] (19273.36s)
active field right if it is active
[321:17] (19277.12s)
what's going to happen is that it's
[321:18] (19278.56s)
going to get these classes but else is
[321:21] (19281.04s)
going to get background muted. So here
[321:24] (19284.80s)
let's see in this example
[321:27] (19287.76s)
like this is the active one that's why
[321:30] (19290.00s)
it is a different color and these are
[321:32] (19292.48s)
muted
[321:34] (19294.80s)
and this is how we can check for it. So
[321:37] (19297.36s)
please just pause the video go over this
[321:39] (19299.76s)
code. Um at the very end we also say do
[321:43] (19303.36s)
not show the uh error for the last step.
[321:46] (19306.64s)
So super basic JavaScript at this point.
[321:49] (19309.44s)
That's the entire component. And I will
[321:51] (19311.76s)
just make this margin bottom to be
[321:53] (19313.52s)
eight. Let's save and see the output.
[321:57] (19317.84s)
Okay. So at the beginning, this is the
[321:59] (19319.60s)
first step that is selected. Um in the
[322:02] (19322.24s)
upcoming minutes, we're going to see
[322:04] (19324.00s)
those to be selected as well. Okay. So
[322:07] (19327.20s)
that was the very first component that
[322:09] (19329.12s)
we just built. Now it's time to build
[322:11] (19331.44s)
the dentist's component. Let's visit VS
[322:14] (19334.96s)
code under the page right after the
[322:17] (19337.76s)
progress steps. I will say if the
[322:21] (19341.04s)
current step is equal to one, we would
[322:24] (19344.40s)
like to render this right hand side
[322:27] (19347.04s)
which is going to be a component. Let's
[322:29] (19349.52s)
say doctor selection
[322:33] (19353.04s)
step.
[322:34] (19354.72s)
We are going to create this component
[322:36] (19356.48s)
and this is going to get some props like
[322:38] (19358.96s)
the selected dentist ID and then we're
[322:42] (19362.32s)
going to pass the on continue method.
[322:46] (19366.56s)
Let me type this out correctly. Okay. So
[322:50] (19370.88s)
if they say continue what's going to
[322:52] (19372.88s)
happen is that they are going to go into
[322:54] (19374.96s)
the next step. So once they click to it,
[322:57] (19377.68s)
we'll say current step should be equal
[322:59] (19379.60s)
to two. And then on select dentist,
[323:04] (19384.56s)
we're going to call our method which is
[323:06] (19386.56s)
handle select dentist. And let's build
[323:09] (19389.36s)
this method actually. So they are going
[323:11] (19391.92s)
to send us a doctor ID, right? A dentist
[323:15] (19395.20s)
ID. And we can just say set the selected
[323:18] (19398.72s)
dentist ID with this, you know, doctor
[323:23] (19403.36s)
ID. And actually let's just say dentist
[323:31] (19411.44s)
and then uh we can reset the state let's
[323:34] (19414.88s)
say set selected date to be empty and
[323:40] (19420.16s)
time as well as the type. Now why this
[323:43] (19423.44s)
is important because we are updating the
[323:45] (19425.68s)
dentist right I'll just add a comment
[323:47] (19427.84s)
let's say recent I mean sorry reset the
[323:52] (19432.56s)
state
[323:54] (19434.96s)
when dentist changes all right then we
[323:58] (19438.80s)
can go into this component let's see if
[324:01] (19441.20s)
we created it I don't think so let's
[324:03] (19443.84s)
just go under the components under the
[324:06] (19446.08s)
appointments let's say tsx
[324:10] (19450.08s)
and let's try to import it here in this
[324:12] (19452.32s)
file.
[324:14] (19454.08s)
Okay. Now we need to get the props and
[324:16] (19456.72s)
first I will create an interface for
[324:18] (19458.64s)
this at the very top. Oops. Let's say
[324:23] (19463.04s)
interface doctor selection step props.
[324:26] (19466.56s)
And here we'll say we are getting an
[324:28] (19468.40s)
object which is type of this. And then
[324:31] (19471.20s)
we can dstructure them. So we'll get on
[324:33] (19473.84s)
continue on select dentist and then the
[324:36] (19476.96s)
selected dentist ID which can be string
[324:40] (19480.16s)
or null. This is a method that doesn't
[324:42] (19482.48s)
return anything and take uh take the
[324:45] (19485.12s)
dentist ID as an argument and this is a
[324:47] (19487.12s)
method that doesn't return anything and
[324:49] (19489.36s)
just above the return statement we would
[324:51] (19491.60s)
like to fetch some data which are going
[324:53] (19493.84s)
to be the available doctors. So the
[324:56] (19496.64s)
doctors that has the is active field
[325:00] (19500.24s)
equal to true right to be able to build
[325:02] (19502.88s)
it we need to have a server action. So I
[325:06] (19506.48s)
will go under the let's say use doctor's
[325:09] (19509.76s)
hook first. Let's create our hook and
[325:12] (19512.48s)
then we are going to create the server
[325:14] (19514.40s)
action. So let me paste this in and I'll
[325:17] (19517.36s)
just zoom in.
[325:20] (19520.00s)
So this is literally a query. We give a
[325:22] (19522.96s)
query key which is get available doctors
[325:26] (19526.08s)
and we're going to call this method
[325:28] (19528.48s)
right the server action which is
[325:30] (19530.80s)
something that we can create. I will
[325:32] (19532.96s)
copy the name and we'll go under the
[325:35] (19535.36s)
doctor's file under the actions. Let's
[325:39] (19539.68s)
shrink everything in this file so that
[325:42] (19542.72s)
it is not really confusing. We'll say
[325:45] (19545.20s)
export async
[325:47] (19547.68s)
function get available doctors. And at
[325:50] (19550.48s)
this point it should be pretty easy to
[325:52] (19552.48s)
build this function. Here is my code
[325:54] (19554.56s)
that I will walk you through it step by
[325:56] (19556.48s)
step. So we have a try and catch block.
[325:59] (19559.36s)
Under the catch we handle the error and
[326:01] (19561.92s)
then in the try we say go under the
[326:04] (19564.40s)
doctor table find many where the active
[326:08] (19568.00s)
is active equal to true. Right? And then
[326:10] (19570.80s)
we can include the amount of
[326:13] (19573.04s)
appointments because we are going to
[326:15] (19575.12s)
display them right here. Right? This
[326:17] (19577.28s)
doctor has six appointments. This one
[326:19] (19579.76s)
has nothing. And then under the uh let's
[326:23] (19583.92s)
go under the VS code like under the
[326:26] (19586.24s)
return statement we will say get all the
[326:28] (19588.56s)
fields and add this appointment count on
[326:31] (19591.84s)
top of the object. So that's the entire
[326:34] (19594.40s)
thing and we are ordering by the name in
[326:37] (19597.12s)
ascending order. Okay, super simple
[326:40] (19600.24s)
nothing crazy just a query with the
[326:43] (19603.92s)
where uh I mean with the wear clause.
[326:47] (19607.52s)
Okay, let's import this. And by the way
[326:50] (19610.48s)
having a result is the exact same thing
[326:54] (19614.00s)
like doing this is the exact same thing
[326:56] (19616.88s)
doing with this right
[326:59] (19619.92s)
okay so this is just a little bit more
[327:01] (19621.52s)
beginner friendly to understand that's
[327:03] (19623.44s)
why I will leave it in this way
[327:07] (19627.60s)
now we are going to call our hook which
[327:09] (19629.44s)
is called use available doctors right
[327:11] (19631.84s)
I'll copy the name paste this in and
[327:15] (19635.20s)
call this hook which is going to give us
[327:17] (19637.44s)
some data
[327:20] (19640.48s)
And we can get the is loading state as
[327:23] (19643.28s)
well. Now data could be renamed to
[327:26] (19646.00s)
something else something like doctors or
[327:28] (19648.96s)
dentists right and let's say by default
[327:32] (19652.00s)
this is going to get the empty array as
[327:34] (19654.56s)
the initial value.
[327:37] (19657.04s)
Okay. Now we can say if we are in the
[327:39] (19659.44s)
loading state for now let's just return
[327:42] (19662.24s)
a H1 a class name
[327:47] (19667.76s)
of text or a start let's say we're going
[327:50] (19670.88s)
to fix this but for now this is a
[327:52] (19672.80s)
placeholder let's say loading.
[327:57] (19677.84s)
All right now let's get into the actual
[327:59] (19679.92s)
return statement. So this is going to be
[328:02] (19682.08s)
a div with the class name. Oops. Let's
[328:04] (19684.64s)
say class name space y of six and then
[328:09] (19689.12s)
we can delete the content. This is going
[328:11] (19691.28s)
to have an h2 that says choose your
[328:14] (19694.80s)
dentist
[328:16] (19696.64s)
and we can give some classes say class
[328:19] (19699.28s)
name text of 2x large and then let's say
[328:23] (19703.28s)
font semi bold. Right after this, we
[328:26] (19706.40s)
will have a div which is going to be
[328:28] (19708.16s)
this grid element, right? The grid
[328:31] (19711.44s)
parent where we can display cards right
[328:34] (19714.48s)
next to each other.
[328:37] (19717.20s)
So here honestly I'll just get the class
[328:39] (19719.52s)
name just like this grid medium screens
[328:42] (19722.88s)
and above this is going to get two grids
[328:45] (19725.04s)
side by side. Larger screens and above
[328:47] (19727.60s)
is going to get three and gap is going
[328:49] (19729.92s)
to be six. Then we can go ahead let me
[328:52] (19732.64s)
save. Within this we'll say doctors do
[328:55] (19735.52s)
map and for each of them we are going to
[328:58] (19738.40s)
return something. So here let's say this
[329:02] (19742.08s)
is what we are going to have
[329:04] (19744.96s)
um what am I doing?
[329:07] (19747.36s)
Okay it should be in this way. Now let's
[329:09] (19749.84s)
go ahead say for each doctor we are
[329:12] (19752.48s)
going to return a card component. Import
[329:15] (19755.52s)
the card and get into the props. So
[329:20] (19760.00s)
we're going to pass the key. Let me kill
[329:21] (19761.84s)
the AI. It is super annoying at this
[329:24] (19764.08s)
point.
[329:26] (19766.64s)
Let's just ignore this for like 40
[329:28] (19768.96s)
minutes.
[329:30] (19770.72s)
Okay, so the key should be something
[329:32] (19772.64s)
unique like doctor ID. And then we can
[329:36] (19776.48s)
give some classes and this is going to
[329:38] (19778.88s)
be dynamic. And I'll show you why.
[329:42] (19782.32s)
Because if a doctor is selected, we're
[329:44] (19784.96s)
going to see a border around it. So in
[329:48] (19788.00s)
this case if you select this doctor as
[329:50] (19790.48s)
you can tell there is an outline around
[329:54] (19794.48s)
and this is how we can check for it and
[329:56] (19796.96s)
on click let's say once you click to
[329:59] (19799.92s)
this we're going to call our method
[330:01] (19801.92s)
which is on select dentist and we're
[330:05] (19805.52s)
going to pass the doctor id into it
[330:09] (19809.28s)
right um that should be it you know what
[330:12] (19812.80s)
instead of calling this doctors I'll
[330:14] (19814.40s)
just say dentists
[330:17] (19817.76s)
This is what what should have what I
[330:20] (19820.16s)
have should have done at the beginning.
[330:22] (19822.64s)
Say dentists
[330:24] (19824.56s)
get a dentist
[330:29] (19829.04s)
and replace them. So this makes our code
[330:31] (19831.84s)
a little bit more readable I believe.
[330:34] (19834.32s)
Right after the cart we're going to have
[330:36] (19836.16s)
the cart header component which you can
[330:39] (19839.20s)
grab it from the source code. Let me
[330:41] (19841.36s)
paste and walk you through it. So, we're
[330:43] (19843.44s)
going to import the cart header with a
[330:45] (19845.84s)
div and then the image itself which is
[330:48] (19848.88s)
going to display the dentist image URL.
[330:51] (19851.68s)
Then we can have the card title which is
[330:54] (19854.56s)
going to have the dentist name, the
[330:56] (19856.64s)
description that will have the
[330:58] (19858.56s)
specialtity, the star icon, you know, we
[331:01] (19861.92s)
don't really have any reviews, but just
[331:04] (19864.48s)
to have a nice UI, I'll put five for
[331:07] (19867.68s)
every single doctor. And then the amount
[331:10] (19870.24s)
of appointments. So let's save and see
[331:13] (19873.28s)
it. Do we have any doctors? Yes, we have
[331:16] (19876.64s)
because we have created them under the
[331:19] (19879.36s)
admin page,
[331:22] (19882.16s)
you know, in the previous sections,
[331:24] (19884.32s)
right? So these are the doctors that we
[331:26] (19886.32s)
have. Let's say Jane is going to be
[331:29] (19889.12s)
inactive. And if we go into the
[331:31] (19891.84s)
appointments page, now this is inactive.
[331:34] (19894.48s)
We should not be able to see it, right?
[331:37] (19897.20s)
Okay. Now let's bring this back to be
[331:39] (19899.68s)
active.
[331:44] (19904.00s)
And then we're going to build the card
[331:46] (19906.24s)
content. So right after the cart header,
[331:49] (19909.12s)
let's shrink this. We're going to have
[331:51] (19911.12s)
card content.
[331:53] (19913.20s)
Let's import it. We'll have the map pen
[331:56] (19916.48s)
icon where it says our location is in
[331:59] (19919.04s)
the dent. You can put an actual
[332:01] (19921.12s)
location. I'll ignore it. Let's say
[332:03] (19923.44s)
phone icon and then the badge. So here
[332:07] (19927.12s)
basically we display the dentist bio
[332:09] (19929.84s)
their phone. Um that's it. And then at
[332:13] (19933.28s)
the end we have a batch. Let's save and
[332:15] (19935.76s)
see the output. So this is exactly what
[332:18] (19938.16s)
we have for these doctors in the
[332:20] (19940.32s)
database. And if I try to select one of
[332:22] (19942.80s)
them as you can tell UI updates but we
[332:25] (19945.44s)
would like to have a button at the
[332:27] (19947.20s)
bottom as well. So I'll go here. I think
[332:30] (19950.00s)
right below to this div. Okay. So uh
[332:33] (19953.92s)
just above the very last one I'll say if
[332:36] (19956.32s)
there is a selected dentist then go
[332:38] (19958.96s)
ahead to display this button
[332:41] (19961.60s)
which should look like this
[332:44] (19964.48s)
and once we click to it we're going to
[332:46] (19966.40s)
call a method
[332:48] (19968.80s)
which is the on continue that will take
[332:51] (19971.20s)
us to the next step. So I will click to
[332:54] (19974.40s)
it. Now we are in the next step and
[332:56] (19976.88s)
let's try to build this component now.
[332:59] (19979.28s)
And you know what? Just before we build
[333:01] (19981.36s)
this step, let's go under the
[333:03] (19983.44s)
appointments page. I'll just refresh. So
[333:06] (19986.00s)
here we have a really ugly loading
[333:07] (19987.92s)
state. Let's try to fix it pretty
[333:09] (19989.76s)
quickly. I will go under the components
[333:12] (19992.56s)
under the appointments. I will say
[333:14] (19994.56s)
something like doctor
[333:17] (19997.76s)
cards loading.
[333:20] (20000.24s)
TSX and you can grab this from the
[333:23] (20003.36s)
source code. Basically, this is going to
[333:25] (20005.52s)
be using the shaden
[333:28] (20008.16s)
skeleton component.
[333:30] (20010.72s)
So again, this is something that we are
[333:32] (20012.40s)
not going to build by ourselves, but
[333:34] (20014.64s)
we're just going to use the component
[333:36] (20016.80s)
with some class names.
[333:39] (20019.60s)
Okay, so that's why I'm just going to
[333:41] (20021.76s)
grab it from the source code. Here we
[333:44] (20024.32s)
have doctor cards loading where
[333:46] (20026.80s)
basically we would like to show six
[333:49] (20029.12s)
different skeleton cards. So this is how
[333:51] (20031.68s)
we can do it. Six times we are going to
[333:54] (20034.00s)
call this component which is up here.
[333:57] (20037.84s)
Okay. So UI only no logic at all. For
[334:01] (20041.20s)
that reason we copied it and paste it.
[334:03] (20043.92s)
Let's go here
[334:05] (20045.92s)
and fix the loading state.
[334:09] (20049.44s)
So we are going to return a div that has
[334:11] (20051.92s)
a title that says choose your dentist.
[334:14] (20054.96s)
And then we're going to show the actual
[334:17] (20057.12s)
skeleton. Let's save. And I'll just make
[334:19] (20059.52s)
this to be true so that we can see it
[334:21] (20061.44s)
clearly.
[334:23] (20063.68s)
Okay, if you're in the loading state,
[334:25] (20065.36s)
that's what we're going to see. And if
[334:27] (20067.84s)
you want to update the count, you would
[334:29] (20069.60s)
just go here. Let's say show me only
[334:31] (20071.68s)
three, which is fine,
[334:34] (20074.88s)
but I'll just leave it as six. Okay. And
[334:38] (20078.24s)
then let's put the actual loading state
[334:39] (20079.92s)
and test it out. I'll refresh. We can
[334:42] (20082.48s)
see it for a split second. Once data is
[334:44] (20084.72s)
fetched, um UI is is going to get
[334:47] (20087.28s)
updated. Oh, now it is time to build the
[334:49] (20089.92s)
second step which is to select the time.
[334:53] (20093.04s)
Here is the end result. Basically on the
[334:55] (20095.92s)
left we have a header component where we
[334:58] (20098.40s)
can go back and then we have the
[335:00] (20100.56s)
appointment type selection. Right? So
[335:03] (20103.20s)
once you select one of them the
[335:05] (20105.04s)
available date will be visible and then
[335:07] (20107.76s)
you're going to get the next five days.
[335:10] (20110.00s)
Okay. So whatever the date today is
[335:12] (20112.80s)
you're going to get the next 1 2 3 4 5
[335:16] (20116.56s)
days. Okay, so this is the next five
[335:18] (20118.80s)
days and we'll see how to do it and then
[335:21] (20121.68s)
we're going to get the available times
[335:23] (20123.92s)
which is going to be an array of like
[335:27] (20127.20s)
times right and then if they are booked
[335:30] (20130.00s)
we are going to make them to be
[335:31] (20131.20s)
disabled. So let's try to see it how we
[335:33] (20133.92s)
can make this step by step. First I'll
[335:36] (20136.96s)
give you two different utility functions
[335:39] (20139.76s)
under the utils file. So under the
[335:42] (20142.64s)
source code, find this file. Okay. And
[335:45] (20145.60s)
shrink this. We're going to have two
[335:48] (20148.00s)
more functions. One is to get the next
[335:51] (20151.60s)
five days. So here I'm just pasting it.
[335:54] (20154.96s)
And again I have proudly generated this
[335:58] (20158.00s)
with AI. So I don't really need to
[336:00] (20160.64s)
memorize this logic. I just said give me
[336:03] (20163.04s)
a function that is going to give me the
[336:04] (20164.56s)
next 5 days starting from today. Right?
[336:08] (20168.08s)
Okay. Then the other method which is
[336:10] (20170.48s)
basically returning an array of
[336:14] (20174.32s)
dates, right? That's it.
[336:17] (20177.20s)
Okay. So, go ahead and grab both of
[336:19] (20179.20s)
these functions. We're going to be using
[336:21] (20181.04s)
them. And then we will go under the
[336:23] (20183.36s)
appointments and we're going to create a
[336:25] (20185.92s)
server action. Let's shrink everything
[336:28] (20188.56s)
and go at the very bottom. And here is
[336:31] (20191.12s)
the server action. I'll just walk you
[336:33] (20193.04s)
through it. It is tell of code. We have
[336:35] (20195.44s)
a try and catch block. Under the catch,
[336:38] (20198.16s)
if there is an error, we will just
[336:39] (20199.68s)
return an empty array. But in the try,
[336:42] (20202.32s)
we will just try to successfully fetch
[336:44] (20204.96s)
the appointments where here is the
[336:47] (20207.12s)
doctor that we get as an argument, the
[336:49] (20209.84s)
date and status like it is either
[336:53] (20213.76s)
confirmed or completed, right? And then
[336:56] (20216.32s)
we're going to just select the time in
[336:58] (20218.40s)
the return statement. We are going to
[337:00] (20220.32s)
return the time itself. So this is going
[337:02] (20222.72s)
to give us the booked time slots because
[337:05] (20225.68s)
we passed the date and the specific
[337:07] (20227.92s)
doctor and we don't really care about
[337:10] (20230.32s)
the status if it is confirmed or
[337:12] (20232.72s)
completed. We'll just consider this as
[337:16] (20236.00s)
you know uh booked time slot. Okay. Now
[337:19] (20239.28s)
we would like to call this action under
[337:21] (20241.36s)
our hook which is going to be under the
[337:24] (20244.00s)
use appointments.
[337:27] (20247.04s)
So at this point it's going to be the
[337:28] (20248.88s)
exact same thing as this one. We'll just
[337:31] (20251.60s)
try to return a use query. Here is our
[337:34] (20254.64s)
function. Here is the query key. Let's
[337:36] (20256.96s)
import it. We're going to pass the
[337:38] (20258.96s)
doctor ID as well as the date because
[337:41] (20261.52s)
this is what we expect under the server
[337:43] (20263.68s)
action. And then here there is a field
[337:46] (20266.32s)
that we're just learning for the very
[337:48] (20268.16s)
first time. Uh basically a query would
[337:51] (20271.36s)
run automatically. But here we say only
[337:54] (20274.40s)
run this query only if you know the both
[337:58] (20278.16s)
doctor ID and date are provided. So if
[338:01] (20281.60s)
let's say this is undefined if this is
[338:03] (20283.92s)
not provided this function is not going
[338:06] (20286.96s)
to run. Okay just keep this in mind. Um
[338:09] (20289.92s)
that's one thing that time to time we
[338:12] (20292.48s)
use it with a tst query. Okay, let's
[338:15] (20295.76s)
save this and we should be able to
[338:18] (20298.08s)
consume this hook in our component and
[338:20] (20300.80s)
that component is going to be under the
[338:23] (20303.28s)
page.tsx.
[338:25] (20305.12s)
Let's scroll to the bottom after the
[338:27] (20307.28s)
current step of one, we will say if step
[338:30] (20310.24s)
is equal to two, right? And if there is
[338:33] (20313.52s)
a selected dentist, then we can run this
[338:37] (20317.04s)
component which is going to be equal to
[338:40] (20320.16s)
time selection step. And as you can
[338:43] (20323.92s)
tell, this is going to also get some
[338:46] (20326.00s)
props. And let me provide all of them
[338:48] (20328.32s)
one by one. So we will have the selected
[338:52] (20332.08s)
dentist ID within this component.
[338:54] (20334.40s)
Selected date, time, and the appointment
[338:57] (20337.44s)
type. On the back method, we're going to
[338:59] (20339.92s)
go one step back, right? The current
[339:02] (20342.96s)
step will be equal to one because we are
[339:05] (20345.12s)
in the second step. On continue, we'll
[339:07] (20347.68s)
go to the next step. And on date change,
[339:10] (20350.96s)
time change, and type change, we are
[339:13] (20353.60s)
going to pass the setter methods, which
[339:16] (20356.32s)
are all of these that we have above.
[339:19] (20359.76s)
Okay, so I think that's going to make
[339:21] (20361.28s)
sense. Just don't worry about it for
[339:23] (20363.04s)
now. Copy the method name or component
[339:26] (20366.56s)
name and then paste it under the
[339:29] (20369.52s)
appointments. We'll say tsx.
[339:33] (20373.12s)
Let's generate it,
[339:36] (20376.08s)
import it, and get into the component.
[339:39] (20379.60s)
So, first off, we would like to make our
[339:41] (20381.52s)
code type safe. So, we'll just go at the
[339:43] (20383.68s)
very top as always, paste our interface,
[339:46] (20386.72s)
right? So, these are all the props that
[339:48] (20388.72s)
we are getting as you can tell. And here
[339:51] (20391.44s)
are the specific types. Now, let's say
[339:54] (20394.72s)
we would like to use this interface,
[339:56] (20396.56s)
which is time, selection, steps, props,
[339:59] (20399.84s)
and then import all of them one by one.
[340:07] (20407.60s)
Okay, it's kind of long but that's fine.
[340:10] (20410.00s)
Again, a better option would be use
[340:12] (20412.88s)
something like Zastand, right? This is
[340:15] (20415.36s)
something that I have mentioned. Uh just
[340:17] (20417.60s)
please keep that in mind here. Our goal
[340:19] (20419.84s)
is to actually learn how to build it
[340:22] (20422.72s)
rather than trying to write the perfect
[340:25] (20425.36s)
code that is possible. All right, so
[340:28] (20428.24s)
let's go into this method. We are going
[340:30] (20430.16s)
to call our utility functions which is
[340:32] (20432.64s)
to get the next five days as well as get
[340:35] (20435.76s)
the available time slots. So these are
[340:38] (20438.72s)
only the you know only the string
[340:41] (20441.04s)
values. These are not coming from
[340:42] (20442.64s)
database to be able to get the booked
[340:45] (20445.52s)
time slots. We're going to be using our
[340:47] (20447.76s)
hook which is this one. And we're going
[340:50] (20450.40s)
to pass the selected dentist ID and
[340:53] (20453.28s)
selected date. This is going to give us
[340:55] (20455.44s)
the data back. And before we get into
[340:58] (20458.16s)
the return statement, we can have a
[341:00] (20460.24s)
function called handle date select. So
[341:03] (20463.60s)
what happens when you select a date?
[341:05] (20465.92s)
Here I'd like to pretty quickly show you
[341:08] (20468.32s)
something.
[341:10] (20470.32s)
So whenever you update the date, right,
[341:13] (20473.44s)
the time slots should be reseted. Now
[341:16] (20476.40s)
what I mean, let's say you selected this
[341:18] (20478.48s)
date, right, and this time, but if you
[341:21] (20481.28s)
click to this date now, this should not
[341:24] (20484.24s)
be selected, right? it should be
[341:26] (20486.16s)
reseted. So for that reason here we will
[341:29] (20489.28s)
first let's get the date we'll say it is
[341:32] (20492.32s)
type of string
[341:34] (20494.80s)
we will call on date change pass the
[341:38] (20498.88s)
date and then we would like to do the
[341:41] (20501.20s)
reset we'll say reset time when the date
[341:46] (20506.24s)
changes
[341:47] (20507.76s)
we'll say on time change call this and
[341:50] (20510.80s)
put the time to be nothing the empty
[341:53] (20513.28s)
state all right now we can go under the
[341:55] (20515.84s)
return statement And here let's say we
[341:58] (20518.16s)
will have a div with the class name
[342:01] (20521.28s)
space y of six. And then first we're
[342:04] (20524.16s)
going to have the header with the back
[342:06] (20526.08s)
button. Let's import the button and then
[342:08] (20528.96s)
this icon. Let's save and see the
[342:11] (20531.44s)
output.
[342:14] (20534.72s)
Okay. If you say back, you're in the
[342:16] (20536.72s)
first step.
[342:19] (20539.04s)
And then let's try to build the rest of
[342:21] (20541.12s)
the content. Right after this div we
[342:24] (20544.40s)
will have another one. This will be the
[342:27] (20547.44s)
grid component. Let's say grid larger
[342:31] (20551.60s)
screens and above. This time we can put
[342:36] (20556.08s)
you know grid columns of two and a gap
[342:40] (20560.00s)
of eight.
[342:42] (20562.00s)
Okay. Now let's say appointment
[342:45] (20565.52s)
type
[342:50] (20570.64s)
selection. So that's going to be that
[342:52] (20572.64s)
component here. Let me copy and paste.
[342:55] (20575.28s)
It is 10 lines of code.
[342:58] (20578.80s)
So we will go through the appointment
[343:00] (20580.88s)
types which is an array that I will
[343:02] (20582.72s)
provide. And for each appointment type
[343:05] (20585.44s)
you will show a card.
[343:08] (20588.16s)
So this is basically what we are trying
[343:10] (20590.16s)
to build. This is the first card,
[343:12] (20592.48s)
second, third and fourth. We're going to
[343:15] (20595.44s)
put the uh appointment type, whatever
[343:18] (20598.56s)
the type is, the duration as well as the
[343:21] (20601.76s)
price. So only UI related, no logic at
[343:25] (20605.44s)
all. But once you collect them, we're
[343:27] (20607.76s)
going to call our method which is on
[343:30] (20610.56s)
type change and we're going to pass the
[343:32] (20612.96s)
type ID. And these appointment types can
[343:35] (20615.92s)
be coming from the utils. You can find
[343:38] (20618.72s)
it from the source code. Once again,
[343:40] (20620.80s)
this is just a constant where we have
[343:43] (20623.12s)
checkup, cleaning, consultation, and
[343:45] (20625.28s)
emergency. Here is the ID, name, and
[343:48] (20628.08s)
duration as well as the price for all of
[343:50] (20630.80s)
these. Okay, save,
[343:54] (20634.00s)
import it, get the card, card content,
[343:57] (20637.52s)
and we should be good to go.
[344:02] (20642.56s)
Let's select this doctor. And if it is
[344:06] (20646.24s)
selected, as you can tell, we are
[344:08] (20648.00s)
getting this beautiful outline as well.
[344:11] (20651.20s)
Now we can build the next component
[344:14] (20654.16s)
which is the available dates and then
[344:16] (20656.48s)
the available times.
[344:21] (20661.20s)
So head over to VS Code, shrink the
[344:23] (20663.84s)
appointment type selection, and then
[344:26] (20666.24s)
we'll say this time date and time
[344:30] (20670.24s)
selection.
[344:32] (20672.96s)
Here I'll have a div so that we can give
[344:35] (20675.52s)
some spacing. Let's say class name space
[344:38] (20678.24s)
y of four. Um here first I'll have an h1
[344:42] (20682.40s)
with the class name text large. Then
[344:45] (20685.84s)
I'll have the font of medium. Within
[344:49] (20689.36s)
this h3 we can say available
[344:53] (20693.36s)
dates and then here is the date
[344:56] (20696.80s)
selection.
[344:59] (20699.28s)
So I am adding these comments so that if
[345:01] (20701.68s)
you would like to copy and paste from
[345:03] (20703.60s)
the source code, you can just see the
[345:05] (20705.36s)
comment and copy the actual content
[345:08] (20708.80s)
right below to it. Okay, so we're going
[345:11] (20711.44s)
to get the available dates map through
[345:13] (20713.68s)
it on click you know call our handler
[345:17] (20717.44s)
method at the class name variant is
[345:20] (20720.16s)
going to be updated depending on the
[345:22] (20722.64s)
selected date if it is you know selected
[345:25] (20725.84s)
um and then we're going to show the date
[345:27] (20727.60s)
itself. Let's see and you're going to
[345:29] (20729.76s)
see I mean let's save and you're going
[345:31] (20731.60s)
to see what I mean here. We can see
[345:34] (20734.00s)
looks like we don't really have the gap
[345:36] (20736.08s)
working
[345:38] (20738.40s)
this is the case.
[345:41] (20741.44s)
This should be gap-8.
[345:45] (20745.20s)
Okay. If you select this one
[345:48] (20748.56s)
you know UI updates depending on
[345:50] (20750.72s)
whatever you select. And then once we
[345:53] (20753.12s)
select a date we should be able to see
[345:55] (20755.28s)
the times. So I will go right here under
[345:58] (20758.32s)
my code um shrink this after the date
[346:03] (20763.36s)
selection. I will just go a little bit
[346:05] (20765.52s)
below and I'll say time selection and
[346:08] (20768.80s)
only show this when the date is
[346:10] (20770.72s)
selected. Let's import this icon. Please
[346:14] (20774.40s)
feel free to pause the video and read
[346:16] (20776.32s)
the code. We basically say if there is a
[346:18] (20778.96s)
selected date go ahead display this div
[346:21] (20781.84s)
where we have a heading and then we are
[346:25] (20785.12s)
going to map through the available time
[346:27] (20787.44s)
slots and we're going to check if it is
[346:29] (20789.84s)
booked or not and this is how we can do
[346:32] (20792.08s)
it. So on click we say if it is not
[346:34] (20794.80s)
booked go ahead call this method and the
[346:37] (20797.60s)
rest is pretty easy to understand. If it
[346:40] (20800.72s)
is booked, we're going to put this text.
[346:42] (20802.88s)
Um, it should be disabled. Classes
[346:45] (20805.12s)
should change. Things like that. Let's
[346:47] (20807.84s)
save and see the output.
[346:52] (20812.48s)
I'll select this doctor. Let's say teeth
[346:55] (20815.60s)
cleaning
[346:57] (20817.12s)
tomorrow and this date. Okay. Once we
[347:01] (20821.76s)
select everything, we should be able to
[347:03] (20823.84s)
see a button. So, here um let's go at
[347:06] (20826.88s)
the very end, I think. Yeah, just above
[347:10] (20830.56s)
this div I will paste it here. It says
[347:14] (20834.24s)
if there is selected type, selected date
[347:16] (20836.88s)
and time only then show this button
[347:19] (20839.84s)
which is going to take us to the next
[347:22] (20842.08s)
step.
[347:24] (20844.96s)
Okay, here we can see I'll say remove
[347:26] (20846.80s)
the booking. Now we are in the confirm
[347:29] (20849.52s)
step. That should look like this one,
[347:34] (20854.00s)
right? So it's going to be this
[347:35] (20855.92s)
component. Let's try to build it now. So
[347:38] (20858.80s)
when we say confirm booking basically we
[347:41] (20861.52s)
would like to save something to the
[347:43] (20863.28s)
database and to be able to make that
[347:45] (20865.60s)
work let's visit the appointments server
[347:48] (20868.40s)
action the file itself
[347:51] (20871.84s)
we are going to have uh that method
[347:54] (20874.48s)
where we can actually book an
[347:56] (20876.32s)
appointment so I'll say let me zoom in
[347:58] (20878.80s)
first export async function and I'll say
[348:03] (20883.12s)
book appointment
[348:07] (20887.04s)
we will get an input Let's say type of
[348:09] (20889.20s)
this will be book um appointment
[348:14] (20894.88s)
input
[348:16] (20896.40s)
and we're going to create this then
[348:18] (20898.80s)
let's say we will have a try and catch
[348:21] (20901.04s)
block and let's put this type first I'll
[348:24] (20904.00s)
make this to be an interface and we
[348:26] (20906.00s)
don't need to export it where we'll get
[348:28] (20908.24s)
a doctor ID date and time and maybe
[348:31] (20911.04s)
optionally a reason if you want to
[348:33] (20913.36s)
extend this application once you
[348:35] (20915.52s)
complete the entire project. So under
[348:37] (20917.92s)
the try first I would like to get the
[348:40] (20920.00s)
authenticated user from clerk. Let's
[348:42] (20922.80s)
import the oath. I think we already have
[348:44] (20924.72s)
it. And if user is not there the user ID
[348:48] (20928.08s)
unavailable we'll say you must be logged
[348:50] (20930.96s)
in. And then we can validate the
[348:53] (20933.28s)
required fields. So we will say if any
[348:56] (20936.48s)
of them is equal to undefined we'll say
[348:59] (20939.36s)
all these fields are required. Right?
[349:01] (20941.92s)
And then we can find the user in our
[349:04] (20944.32s)
database
[349:06] (20946.32s)
by passing the clerk ID right and then
[349:09] (20949.44s)
we can actually say if this user is not
[349:13] (20953.04s)
in the database then you should set set
[349:15] (20955.84s)
up your account properly. But if we pass
[349:18] (20958.48s)
all these if checks that means
[349:20] (20960.40s)
everything is successfully done. All we
[349:22] (20962.88s)
have to do is creating the appointment
[349:25] (20965.20s)
in the database. So here I'll say here
[349:28] (20968.00s)
is the new appointment that we create by
[349:30] (20970.72s)
using the create method. Here is the
[349:32] (20972.80s)
data by default status should be equal
[349:35] (20975.20s)
to confirmed. Here are all the details
[349:38] (20978.24s)
like the date, time, user ID, doctor ID,
[349:42] (20982.08s)
etc. And then we're going to include
[349:44] (20984.16s)
these fields under the user and then
[349:47] (20987.28s)
these fields under the doctor. Right?
[349:49] (20989.44s)
We're going to select them in this
[349:51] (20991.44s)
query. Okay. Finally, at the end, we can
[349:54] (20994.40s)
just say return the appointment and we
[349:57] (20997.68s)
can transform it by using our method.
[350:00] (21000.32s)
Let's say transform appointment and pass
[350:02] (21002.72s)
the appointment into it. Um, here under
[350:05] (21005.68s)
the catch, let's handle this with a
[350:08] (21008.00s)
console log and maybe throwing an error.
[350:11] (21011.92s)
So, at this point, this should be pretty
[350:13] (21013.68s)
easy to understand. We are just doing
[350:16] (21016.00s)
the same thing again and again, right?
[350:18] (21018.00s)
Get the user ID. Check if it is
[350:19] (21019.84s)
available. Do some validations. Get the
[350:22] (21022.40s)
user from database. Check if it is
[350:24] (21024.32s)
available. If everything is done
[350:26] (21026.08s)
successfully, we can call our method. We
[350:28] (21028.56s)
can call our method by passing the data
[350:30] (21030.72s)
that we would like to save in the
[350:32] (21032.16s)
database and some select fields for user
[350:35] (21035.28s)
and doctor so that we can have it under
[350:37] (21037.68s)
the appointment itself.
[350:40] (21040.16s)
Okay. So that's the entire thing for the
[350:42] (21042.48s)
server action. Now we would like to call
[350:44] (21044.72s)
this within a query, right?
[350:47] (21047.92s)
um or a mutation I should say because
[350:50] (21050.24s)
this is like a post request where we
[350:52] (21052.88s)
would like to create something we will
[350:55] (21055.36s)
go under the use appointments book this
[350:59] (21059.12s)
file and we are going to create one more
[351:02] (21062.08s)
function which is like around the 10
[351:04] (21064.24s)
lines of code where we say use book
[351:06] (21066.80s)
appointments this is our custom hook
[351:08] (21068.96s)
we're going to get the query client and
[351:11] (21071.52s)
import the mutation here is our action
[351:15] (21075.04s)
that we're going to be calling on
[351:16] (21076.96s)
success case we would like to fetch the
[351:19] (21079.12s)
user appointments again right we would
[351:21] (21081.44s)
like to invalidate that query on error
[351:24] (21084.32s)
you can handle this by showing a toast
[351:26] (21086.88s)
or something I'll leave it as uh like as
[351:29] (21089.84s)
it is with a console error just to keep
[351:32] (21092.88s)
it simple okay so that's the entire
[351:35] (21095.36s)
setup that we need to do creating a
[351:37] (21097.68s)
server action as well as a custom hook
[351:40] (21100.08s)
now we would like to call it within our
[351:42] (21102.08s)
component so here under the page tsx go
[351:46] (21106.40s)
under the states and paste this in where
[351:49] (21109.44s)
we call our hook and get the mutation.
[351:51] (21111.92s)
We're going to be calling this in a
[351:53] (21113.36s)
second. And then we have one method
[351:55] (21115.68s)
called handle book appointment. So we
[351:58] (21118.24s)
are going to build it as well. But first
[352:00] (21120.64s)
let's scroll to the bottom. So that was
[352:02] (21122.72s)
the first step. Second step right after
[352:05] (21125.76s)
this we're going to have the third step.
[352:08] (21128.48s)
Let me paste this in. It is almost the
[352:10] (21130.72s)
exact same thing.
[352:12] (21132.88s)
So we are going to create this component
[352:15] (21135.20s)
and it's going to take all these states.
[352:17] (21137.84s)
Here we have the pending state and then
[352:20] (21140.56s)
three different methods. One to go back,
[352:23] (21143.36s)
the other one to uh modify is basically
[352:26] (21146.32s)
again going back and then the
[352:28] (21148.40s)
confirmation.
[352:30] (21150.00s)
Let's create this component.
[352:39] (21159.84s)
import and then let's pass our
[352:42] (21162.08s)
interface. So here I'll go ahead paste
[352:45] (21165.04s)
this in and then let me get all the
[352:47] (21167.52s)
props
[352:49] (21169.60s)
by making it type safe. Okay. So if you
[352:53] (21173.12s)
delete one of them as you can tell it is
[352:54] (21174.88s)
right here completely type safe. Then we
[352:57] (21177.92s)
can build the return statement. Just
[353:00] (21180.32s)
before that let's find the appointment
[353:02] (21182.48s)
type here. I'll say import the
[353:05] (21185.20s)
appointment types find the selected type
[353:08] (21188.72s)
and set this to be you know set it to be
[353:11] (21191.68s)
this variable because we're going to be
[353:13] (21193.36s)
using it. Now let's say under the return
[353:16] (21196.80s)
statement we are going to have a div
[353:20] (21200.16s)
with the class name being space y of six
[353:24] (21204.40s)
and then we're going to have the header
[353:26] (21206.08s)
with the back button. So I will provide
[353:28] (21208.96s)
this to you. Import the button
[353:32] (21212.48s)
and then the back icon. And then after
[353:35] (21215.52s)
the header, let's shrink this. We're
[353:37] (21217.60s)
going to have a card component.
[353:40] (21220.40s)
Let's import it. Last name will be
[353:43] (21223.04s)
maximum width of 2x large. And then
[353:46] (21226.08s)
we're going to have the card header with
[353:48] (21228.08s)
the title that says appointment summary.
[353:51] (21231.92s)
And this is what we're building by the
[353:53] (21233.44s)
way. Okay, appointment summary. Now
[353:56] (21236.00s)
we're going to put this component called
[353:58] (21238.72s)
doctor info. We're going to fetch the
[354:01] (21241.36s)
doctor's you know profile pick name and
[354:04] (21244.48s)
their specialty. And then we're going to
[354:06] (21246.72s)
get the rest of the details.
[354:10] (21250.16s)
And currently this is what we have.
[354:12] (21252.16s)
Let's see it pretty quickly.
[354:16] (21256.80s)
Okay.
[354:18] (21258.40s)
So right after the cart header I'll say
[354:20] (21260.96s)
card content import this which will take
[354:24] (21264.24s)
a class name. Let's say um I believe
[354:27] (21267.36s)
space y4. Yes. And then we're going to
[354:30] (21270.32s)
have the doctor info in a second. And
[354:33] (21273.04s)
then now we'll have the appointment
[354:35] (21275.36s)
details. Okay. Let's paste this in.
[354:39] (21279.28s)
As you can tell, we have the appointment
[354:41] (21281.36s)
type, duration, you know, the date we're
[354:44] (21284.24s)
going to format it. Um selected time and
[354:47] (21287.84s)
price.
[354:49] (21289.84s)
Okay, let's see. This is exactly what we
[354:53] (21293.36s)
have. that looks the same thing just
[354:56] (21296.24s)
like in the demo right it is this
[354:58] (21298.88s)
section but now we would like to have
[355:01] (21301.04s)
this one and I'm going to call this as a
[355:03] (21303.76s)
separate component
[355:05] (21305.76s)
so here I'll just say doctor info and
[355:09] (21309.68s)
I'm going to pass the doctor ID into it
[355:14] (21314.56s)
under the components appointments let's
[355:17] (21317.76s)
say create this filet tsx
[355:22] (21322.32s)
and import it right here. And this
[355:25] (21325.04s)
component is nothing special actually.
[355:27] (21327.68s)
We're just going to get uh oops, we have
[355:29] (21329.92s)
an error. Um let's save this maybe.
[355:35] (21335.76s)
Okay. So, we're just going to get the
[355:37] (21337.44s)
image of the doctor name as well as
[355:39] (21339.60s)
their specialty and then just display it
[355:42] (21342.00s)
on the UI nicely. So, it is like 20
[355:45] (21345.12s)
lines of code where we get the available
[355:47] (21347.68s)
doctors, find the selected one which is
[355:50] (21350.96s)
we are getting from the prop. Um if it
[355:53] (21353.04s)
is null we'll just return null but
[355:55] (21355.44s)
otherwise we're going to show the image
[355:57] (21357.52s)
you know the name as well as the
[355:59] (21359.52s)
specialty. So super quick component you
[356:02] (21362.88s)
can either type it out or copy and
[356:04] (21364.96s)
paste. I know it is kind of annoying but
[356:07] (21367.92s)
like we are almost about about to end
[356:10] (21370.64s)
the tutorial.
[356:12] (21372.40s)
Okay. So let's go ahead see it one last
[356:14] (21374.96s)
time. I'll select this doctor regular
[356:18] (21378.08s)
checkup tomorrow at 9:00. remove the
[356:21] (21381.68s)
booking. Okay, as you can tell, we can
[356:24] (21384.40s)
see the entire details. And at the
[356:26] (21386.56s)
bottom, let's put the action buttons.
[356:28] (21388.80s)
I'll go ahead scroll to the very bottom
[356:31] (21391.44s)
right after the card. I'll say we're
[356:33] (21393.68s)
going to have two different buttons. And
[356:35] (21395.76s)
here are the on click. Like what's going
[356:38] (21398.24s)
to happen? And if we are in the loading
[356:41] (21401.12s)
state, this button should be disabled.
[356:43] (21403.84s)
And the content should update as well.
[356:46] (21406.56s)
Now, if you save, you can see the
[356:48] (21408.40s)
buttons. But if you say confirm booking,
[356:50] (21410.88s)
nothing is going to happen. It's because
[356:53] (21413.12s)
we don't have the functionality yet.
[356:55] (21415.44s)
Right? On conf on confirm, we are
[356:57] (21417.84s)
calling our handlebook appointment which
[357:00] (21420.56s)
does nothing. So let's go ahead and
[357:02] (21422.88s)
build it. Just to make TypeScript happy,
[357:05] (21425.60s)
first I'll have some let's say
[357:07] (21427.84s)
validation. I'll say if selected dentist
[357:11] (21431.20s)
ID is undefined or any of them is equal
[357:14] (21434.64s)
to undefined, we can throw a toast,
[357:17] (21437.44s)
right? And how can we use this to? Well,
[357:20] (21440.16s)
you can import it from Soner
[357:23] (21443.60s)
um just like this. But to be able to
[357:26] (21446.72s)
make this work, you're going to go under
[357:28] (21448.96s)
the layout. Let's find it source um app
[357:34] (21454.00s)
and layout. You would like to put this
[357:37] (21457.12s)
component at some point. Let's say
[357:39] (21459.36s)
toaster which is coming from ser as
[357:42] (21462.08s)
well.
[357:44] (21464.96s)
Okay, it is this import. And once you
[357:47] (21467.52s)
put that you should be able to see a
[357:49] (21469.36s)
toast uh in this case right here we have
[357:52] (21472.56s)
error but you have like success and I
[357:55] (21475.76s)
believe like info
[357:58] (21478.16s)
you can see all of them from the chaten
[358:00] (21480.40s)
documentation let me pretty quickly
[358:02] (21482.64s)
display it
[358:04] (21484.96s)
instead of skeleton we'll just say toast
[358:08] (21488.64s)
itself
[358:13] (21493.20s)
okay why we cannot see it Um,
[358:18] (21498.72s)
okay. So, this is the type of toast that
[358:21] (21501.20s)
we would like to get and you can
[358:22] (21502.80s)
customize it if you wish. Just keep that
[358:25] (21505.60s)
in mind. Everything is coming from the
[358:27] (21507.36s)
documentation.
[358:29] (21509.20s)
Um, and you don't really need to
[358:30] (21510.80s)
memorize any of them. So, we're going to
[358:33] (21513.28s)
import the appointment types and find
[358:35] (21515.84s)
the selected type, then assign it to
[358:37] (21517.92s)
this variable because we are going to be
[358:40] (21520.24s)
using it. Then we'll say book
[358:42] (21522.64s)
appointment mutation call the mutate
[358:45] (21525.52s)
method with the object where we can pass
[358:48] (21528.16s)
our variables. So we'll say here is the
[358:50] (21530.56s)
doctor ID. Let's say here is the date.
[358:53] (21533.44s)
So date, time and reason. So these are
[358:56] (21536.56s)
all the variables. Then we can add some
[358:58] (21538.96s)
on success and on error case. Here I'll
[359:02] (21542.00s)
open up an object. Let's say on success
[359:04] (21544.48s)
we would like to do something on the
[359:06] (21546.24s)
front end here which is going to be an
[359:08] (21548.80s)
async function. So here I accidentally
[359:11] (21551.84s)
paused the video but I was saying we are
[359:14] (21554.08s)
going to get the appointment as the
[359:15] (21555.92s)
argument and we are going to first store
[359:19] (21559.04s)
it in a uh you know in a in a state if I
[359:23] (21563.28s)
can't talk here is that state that we'll
[359:25] (21565.36s)
be using and I can just paste this in
[359:27] (21567.76s)
we'll say store the appointment details
[359:30] (21570.08s)
to show it in the model which is going
[359:32] (21572.64s)
to be this one right here are all the
[359:35] (21575.04s)
details and we will also send an email
[359:37] (21577.84s)
which is something that we will do in
[359:39] (21579.68s)
The next section for now let's say set
[359:42] (21582.32s)
booked appointment and the value is
[359:46] (21586.00s)
going to be this one that we are getting
[359:48] (21588.24s)
as the result here let's add a to-do
[359:51] (21591.04s)
let's say send email using resend which
[359:55] (21595.28s)
is going to be in the next section then
[359:58] (21598.08s)
um here we can say show the success
[360:02] (21602.00s)
model
[360:03] (21603.52s)
and for this we'll say set show
[360:05] (21605.84s)
confirmation model to be equal to true
[360:08] (21608.96s)
then we can reset the form
[360:13] (21613.04s)
and I am too lazy to type this out here.
[360:16] (21616.24s)
Basically, we're going to get the rest
[360:18] (21618.00s)
of the state and set them with the
[360:20] (21620.40s)
initial states, right? And here I will
[360:23] (21623.76s)
have another object
[360:26] (21626.08s)
I think not another object but right
[360:28] (21628.16s)
after the on success I'll just say on
[360:30] (21630.32s)
error we would like to do something
[360:32] (21632.00s)
else. In this case I'll just show a
[360:34] (21634.40s)
toast.
[360:36] (21636.00s)
So here is how that would look like.
[360:38] (21638.24s)
Now, let's actually save and test it
[360:40] (21640.32s)
out. First, I'll open up my database.
[360:42] (21642.96s)
Let me log in pretty quickly. I believe
[360:45] (21645.76s)
we don't really have any appointments at
[360:47] (21647.92s)
all. Let's select the project under the
[360:50] (21650.40s)
tables under the appointments. We should
[360:53] (21653.60s)
have zero documents, right? We have some
[360:56] (21656.56s)
doctors, users, but zero appointments.
[361:00] (21660.48s)
So, let's go ahead get one appointment
[361:02] (21662.88s)
from Dr. Jane. Regular checkup. Let's
[361:06] (21666.00s)
say tomorrow it should be at 9. Review
[361:09] (21669.68s)
this and confirm.
[361:12] (21672.64s)
So we are in the loading state. Button
[361:14] (21674.48s)
is disabled.
[361:16] (21676.32s)
And once it is done, we are in the home
[361:18] (21678.40s)
screen. That's fine under the
[361:20] (21680.08s)
appointments because um you know the
[361:23] (21683.20s)
state has been resetted. Now the
[361:25] (21685.28s)
important part is if we have the
[361:27] (21687.28s)
appointment or not. Here we go. Once you
[361:30] (21690.16s)
refresh the time, duration status is
[361:33] (21693.84s)
confirmed. And from the admin dashboard
[361:36] (21696.64s)
you can make this to be completed
[361:38] (21698.80s)
regular checkup as you can tell
[361:41] (21701.52s)
everything is right here even with the
[361:43] (21703.60s)
relations.
[361:45] (21705.28s)
So the doctor is a Jane here are all the
[361:48] (21708.48s)
details and the user is I think me.
[361:52] (21712.72s)
Okay that's my profile. Now, let's go
[361:55] (21715.28s)
under the admin page
[362:00] (21720.80s)
and see if we can have the appointment
[362:03] (21723.12s)
at the very bottom.
[362:06] (21726.16s)
Um, looks like we cannot get it. Uh,
[362:08] (21728.88s)
didn't we build it? I thought we build
[362:10] (21730.64s)
it. No. Say admin page
[362:14] (21734.24s)
dashboard client.
[362:16] (21736.72s)
So, we have the doctor's management.
[362:18] (21738.88s)
Okay, that's fine. But we should be also
[362:22] (21742.32s)
we should be able to see the
[362:23] (21743.60s)
appointments as well. So find the admin
[362:26] (21746.16s)
page.
[362:27] (21747.76s)
Okay. So I think that's one thing that
[362:29] (21749.92s)
we are missing. Let's pretty quickly try
[362:32] (21752.24s)
to implement this as well and then
[362:34] (21754.40s)
complete this section. So I just paused
[362:36] (21756.48s)
the video and realized we are missing
[362:38] (21758.64s)
one more thing. So that's the first one.
[362:40] (21760.96s)
We will definitely include this in our
[362:42] (21762.96s)
project. But first we should include
[362:45] (21765.68s)
something else under the appointment
[362:48] (21768.24s)
page, right? So we are missing this
[362:50] (21770.72s)
component
[362:52] (21772.88s)
the upcoming appointments. Currently we
[362:55] (21775.68s)
can see the doctors even though we have
[362:57] (21777.84s)
an appointment we cannot fetch that. So
[363:00] (21780.48s)
let's try to build it under the
[363:05] (21785.60s)
under the appointments page. So right
[363:08] (21788.40s)
after the steps we're going to display
[363:11] (21791.04s)
it. But I think we are missing a hook.
[363:13] (21793.68s)
So I will go under the use appointment
[363:16] (21796.64s)
file and we are going to get one custom
[363:19] (21799.52s)
hook that we will create that is going
[363:21] (21801.92s)
to give us the appointments for a
[363:24] (21804.88s)
specific user. So here I'll just paste
[363:27] (21807.68s)
this in. We will get user specific
[363:30] (21810.24s)
appointments by calling our server
[363:32] (21812.72s)
action.
[363:34] (21814.24s)
This is something that we have already
[363:35] (21815.92s)
created.
[363:37] (21817.52s)
Here we can see this is going to get get
[363:39] (21819.44s)
us all the appointments for this
[363:41] (21821.60s)
specific user.
[363:43] (21823.44s)
and we're going to return it to the
[363:46] (21826.24s)
client. Okay, so that's our result.
[363:49] (21829.12s)
Let's try to call this hook
[363:51] (21831.92s)
under the page.tsx.
[363:54] (21834.88s)
I'll just scroll to the very top right
[363:57] (21837.76s)
after my mutation. I'll just call the
[364:00] (21840.08s)
hook
[364:02] (21842.64s)
and this is going to give us some data
[364:05] (21845.84s)
and I am going to call this as user
[364:08] (21848.32s)
appointments. Okay, so we can leave it
[364:10] (21850.56s)
as data as well. But just to make our
[364:12] (21852.56s)
code a little bit more readable, I'll
[364:14] (21854.64s)
update the variable name. Now let's
[364:17] (21857.28s)
scroll to the bottom. Right after all
[364:19] (21859.76s)
these steps, we have one div. Just go
[364:23] (21863.20s)
outside of it. And we will say
[364:26] (21866.48s)
um maybe something like show existing
[364:30] (21870.56s)
appointments
[364:33] (21873.36s)
for the current user. And you can copy
[364:37] (21877.36s)
this from source code. It is like 20
[364:39] (21879.76s)
lines of code. I promise it is nothing
[364:42] (21882.08s)
complex. We will basically say if the
[364:46] (21886.24s)
user appointments length is greater than
[364:48] (21888.80s)
zero, go ahead render this part where we
[364:51] (21891.68s)
get the array map through it for every
[364:54] (21894.32s)
single one of them display an image and
[364:57] (21897.60s)
the you know doctor name and call the
[365:00] (21900.64s)
format method from data fs.
[365:04] (21904.80s)
Okay, let's see.
[365:07] (21907.52s)
We have only one appointment that should
[365:10] (21910.00s)
be fetched in a second as you can tell
[365:12] (21912.80s)
and you can even include a skeleton for
[365:15] (21915.04s)
it as well. I'll leave it as a challenge
[365:17] (21917.28s)
for you. So with this I think that's
[365:19] (21919.60s)
going to be it for this entire section.
[365:21] (21921.68s)
In the next one we are going to
[365:23] (21923.20s)
implement the emails as well as this
[365:26] (21926.32s)
table under the admin page. So I'm going
[365:28] (21928.96s)
to leave it for the next section. Let's
[365:31] (21931.52s)
go ahead under the VSS code. Close
[365:33] (21933.52s)
everything. toggle the status bar and
[365:36] (21936.56s)
create a new branch called
[365:39] (21939.60s)
appointments page
[365:43] (21943.36s)
and we will stage all of our changes.
[365:46] (21946.32s)
Add a commit message like uh I don't
[365:48] (21948.88s)
know let's say appointments
[365:51] (21951.20s)
page addit pretty classic.
[365:54] (21954.08s)
Publish this and visit the source code
[365:57] (21957.36s)
as always.
[366:01] (21961.36s)
We are going to create the pull request.
[366:03] (21963.92s)
Now we have a lot of changes. I think it
[366:06] (21966.32s)
is almost like 800 lines. We could wait
[366:10] (21970.08s)
for the code rabbit to give us some
[366:12] (21972.24s)
suggestions. That's what I would do if I
[366:14] (21974.80s)
were in your shoes. But since I'm
[366:16] (21976.88s)
teaching, let's not waste any time. I'll
[366:19] (21979.28s)
go ahead merge the poll request. Let's
[366:22] (21982.32s)
say confirm.
[366:26] (21986.40s)
And then I'll uh I'll switch to the
[366:28] (21988.72s)
master.
[366:30] (21990.24s)
Let's get the latest changes. So if you
[366:33] (21993.20s)
want to see what can be improve on our
[366:35] (21995.28s)
code, definitely wait for the
[366:37] (21997.52s)
suggestion. Um it should give you
[366:39] (21999.92s)
everything that you would need just to
[366:42] (22002.00s)
make this code a little bit more clean.
[366:44] (22004.32s)
If you have any, you know, critical
[366:46] (22006.48s)
issues uh with code rabbit you can fix
[366:48] (22008.72s)
all of them. All right, so with that
[366:50] (22010.64s)
that's going to be it for this entire
[366:52] (22012.24s)
section. Hopefully I'll see you in the
[366:54] (22014.08s)
next one. In this section we are going
[366:56] (22016.96s)
to implement sending emails. So this is
[366:59] (22019.68s)
the confirmation model that we're going
[367:01] (22021.60s)
to see once we have an appointment and
[367:05] (22025.04s)
in the background we are going to send
[367:07] (22027.12s)
this template to the user. Okay. So
[367:09] (22029.84s)
we're going to put the doctor name,
[367:11] (22031.60s)
appointment type, date, uh you know the
[367:14] (22034.40s)
time, duration, cost, and then the
[367:17] (22037.28s)
location. All right. So let's try to
[367:19] (22039.76s)
build it. To be able to send the emails,
[367:22] (22042.72s)
we are going to be using resend.com. So
[367:25] (22045.68s)
they have bunch of different SDKs. You
[367:28] (22048.72s)
can implement it in seconds. In this uh
[367:31] (22051.60s)
project we are going to be using Nex.js
[367:34] (22054.24s)
and here is how that work in simple
[367:36] (22056.24s)
terms. We will get into the details. Let
[367:38] (22058.96s)
me zoom in. Okay. So in the API routes
[367:42] (22062.48s)
we are going to create a post method and
[367:44] (22064.96s)
then we are going to call this send
[367:47] (22067.04s)
method. We're going to pass the from
[367:49] (22069.12s)
email to to the user, right? The subject
[367:52] (22072.88s)
and then we are going to create an email
[367:54] (22074.80s)
template that is going to look like
[367:56] (22076.88s)
this. Okay. Now, if you would like to
[367:59] (22079.44s)
get some pre-made templates, you can get
[368:02] (22082.88s)
them from their website. So, they have
[368:04] (22084.96s)
this user welcome email template. Here
[368:07] (22087.68s)
is the code. Here is the output. Right
[368:09] (22089.92s)
here we can see this is basically some
[368:12] (22092.80s)
um you know HTML and CSS but they have
[368:16] (22096.72s)
wrapped it with React components. So as
[368:19] (22099.36s)
you can tell they are importing all of
[368:21] (22101.12s)
these from React email components. So we
[368:24] (22104.88s)
will get into the details but first you
[368:27] (22107.28s)
should log in.
[368:29] (22109.76s)
So I just logged in. We are going to get
[368:31] (22111.76s)
an API key. So this is the one that I
[368:34] (22114.24s)
have generated in the past but let's try
[368:36] (22116.64s)
to create another one from scratch. I'll
[368:39] (22119.28s)
say Dwise
[368:41] (22121.60s)
and then let's say full access. Now, one
[368:44] (22124.08s)
thing that you can do is adding your own
[368:46] (22126.40s)
domain if you have one. But if you don't
[368:48] (22128.64s)
have it, you can use the recent uh free
[368:52] (22132.88s)
uh domain, right? But you should not use
[368:55] (22135.04s)
it in the production only for testing
[368:57] (22137.60s)
purposes. Okay? So, we'll just use it
[368:59] (22139.92s)
for the testing purposes. So, I'll say
[369:02] (22142.08s)
add this API key without selecting my
[369:05] (22145.12s)
domain and go ahead copy this. This is
[369:07] (22147.92s)
your API key and we are going to store
[369:10] (22150.48s)
it right here. So I'll say recent API
[369:15] (22155.12s)
key.
[369:17] (22157.76s)
Then
[369:19] (22159.52s)
I think for now that's it. We can
[369:21] (22161.44s)
install our package. I'll open up the
[369:23] (22163.52s)
terminal. Let's kill this with Ctrl C.
[369:27] (22167.04s)
And then I'll say mpm install recent.
[369:29] (22169.92s)
Let's go with a specific version like
[369:32] (22172.56s)
6.1.0.
[369:34] (22174.88s)
This is the latest version at the time
[369:36] (22176.80s)
that I'm recording this video. So, we
[369:39] (22179.68s)
are going to install this. Once this is
[369:41] (22181.68s)
done, let's import one more package
[369:43] (22183.92s)
which is going to be let's say mpm
[369:45] (22185.84s)
install at react- email/mponents.
[369:54] (22194.00s)
Okay. And for the version, this should
[369:56] (22196.24s)
be 0.5.
[369:59] (22199.84s)
I forgot to type it out, but let's see.
[370:02] (22202.00s)
under the packages.
[370:06] (22206.24s)
So, let's find React email. Okay, 5.5.
[370:09] (22209.52s)
Go ahead, put it at the end if you're
[370:12] (22212.16s)
watching this video in the future,
[370:13] (22213.60s)
right? 0.5
[370:15] (22215.68s)
and just install it. Now, let's say mpm
[370:19] (22219.28s)
rundev.
[370:21] (22221.76s)
We're going to start our application.
[370:24] (22224.08s)
And to be able to implement this, first
[370:26] (22226.48s)
I'll go under the lib. I'll create
[370:28] (22228.88s)
recent.ts Yes, just like in the past for
[370:32] (22232.00s)
any kind of third party services, we're
[370:34] (22234.88s)
going to set them up under the web
[370:37] (22237.60s)
folder. And this is all we have to do is
[370:40] (22240.32s)
basically importing recent, creating an
[370:43] (22243.20s)
instance by passing the API key and
[370:45] (22245.92s)
exporting the recent instance. And just
[370:48] (22248.56s)
make sure that this is matching with
[370:50] (22250.16s)
your environment variable name. So if
[370:53] (22253.04s)
you have a typo like this, it's not
[370:56] (22256.64s)
going to work. Okay, just make sure
[370:58] (22258.16s)
they're matching. Um that was the very
[371:00] (22260.64s)
first step. The other one is to create
[371:03] (22263.52s)
an endpoint. So here is how that will
[371:06] (22266.48s)
work. Under the appointments page
[371:10] (22270.64s)
here we have a method right let me show
[371:12] (22272.80s)
you this. So in this method when we call
[371:15] (22275.68s)
the mutation we would like to call an
[371:18] (22278.16s)
API endpoint. So here we'll just say
[371:21] (22281.68s)
something like fetch a send a post
[371:25] (22285.60s)
method to this endpoint right and this
[371:28] (22288.40s)
is going to be created under the app
[371:30] (22290.48s)
folder under the API folder which is a
[371:33] (22293.68s)
special folder and then our API route.
[371:37] (22297.04s)
So this is going to be a folder let's
[371:38] (22298.96s)
say send appointment-
[371:42] (22302.96s)
email and then the special file
[371:46] (22306.72s)
called route.s s or js.
[371:50] (22310.88s)
So this folder name is special. This one
[371:53] (22313.60s)
is not. You can say anything here. But
[371:56] (22316.00s)
this file name is just like page.tsx. It
[371:59] (22319.12s)
is special file for API routes. And here
[372:02] (22322.80s)
let me show you how to get started with
[372:04] (22324.96s)
this file. First I'm going to delete
[372:07] (22327.12s)
this patch so that we don't have any
[372:08] (22328.80s)
errors. And let me turn this down. Okay.
[372:11] (22331.76s)
So here we'll just say export async
[372:14] (22334.32s)
function. And this is going to be a post
[372:17] (22337.36s)
request right a post method. So we need
[372:19] (22339.84s)
to call this as post. And then this will
[372:22] (22342.72s)
take the request as the first argument.
[372:25] (22345.28s)
Let's say request and type of this is
[372:27] (22347.44s)
equal to request. Um here we can have a
[372:30] (22350.96s)
try and catch block. Under the try first
[372:33] (22353.68s)
we would like to get the body. So this
[372:36] (22356.64s)
is the you know user data that you're
[372:39] (22359.28s)
going to get on the back end. Let's say
[372:41] (22361.28s)
await
[372:42] (22362.80s)
request.json JSON call the method then
[372:45] (22365.84s)
from here we can dstructure couple of
[372:48] (22368.32s)
different values. So from let's say from
[372:52] (22372.16s)
body
[372:53] (22373.76s)
we will get couple of different values
[372:56] (22376.64s)
such as the user email let's say doctor
[373:00] (22380.96s)
name and I'm too lazy to type this out
[373:03] (22383.76s)
let me provide them to you. So from the
[373:06] (22386.88s)
front end we are going to send all these
[373:08] (22388.80s)
values right doctor name appointment
[373:11] (22391.44s)
date time and type duration and price
[373:14] (22394.96s)
because we are going to include these in
[373:17] (22397.36s)
the email right let's go here under the
[373:20] (22400.40s)
email as you can tell we will have all
[373:22] (22402.32s)
these details. So we will fetch them
[373:25] (22405.36s)
like we're going to get it in the back
[373:27] (22407.20s)
end and let's first validate the
[373:29] (22409.68s)
required fields.
[373:31] (22411.84s)
Here I'll say if there is no user email
[373:33] (22413.92s)
or doctor name or any of them we will
[373:36] (22416.72s)
say next response let's import it from
[373:40] (22420.24s)
here we'll just say missing required
[373:43] (22423.12s)
fields and status code is 400 but else
[373:46] (22426.96s)
we can just send the email
[373:50] (22430.80s)
to able to make this work we'll just say
[373:52] (22432.88s)
await resend
[373:55] (22435.76s)
and import it from the lab
[373:59] (22439.12s)
we'll say emails dot send
[374:03] (22443.76s)
which is getting an object right this is
[374:06] (22446.32s)
the payload so just like in the
[374:08] (22448.24s)
documentation we will say from and you
[374:11] (22451.84s)
can put anything I'll just say then wise
[374:14] (22454.08s)
and then the domain so let's say no dash
[374:18] (22458.24s)
reply
[374:20] (22460.56s)
at recent dev and here I'll just warn
[374:25] (22465.04s)
you with a comment I'll say do not use
[374:27] (22467.76s)
this in production
[374:30] (22470.48s)
only for testing purposes
[374:37] (22477.36s)
like in production you should use your
[374:39] (22479.44s)
own domain. Okay, here maybe I can add
[374:42] (22482.24s)
it here. So that's the from field to
[374:46] (22486.16s)
let's say we're going to send it to the
[374:48] (22488.40s)
user email. Um and then we can put a
[374:52] (22492.16s)
subject. I think I'll just say
[374:54] (22494.56s)
appointment confirmation
[374:57] (22497.28s)
just like this. And I'll put the brand
[374:59] (22499.28s)
name which is Dentwise. And then you can
[375:02] (22502.32s)
add like in the documentation I believe
[375:04] (22504.72s)
they were using HTML. Let's pretty
[375:07] (22507.84s)
quickly see it. So they are using HTML
[375:11] (22511.28s)
and putting something super basic, but
[375:13] (22513.68s)
we can use React components just like
[375:16] (22516.48s)
what they have right here, right? So
[375:18] (22518.96s)
we're going to have the email as a React
[375:21] (22521.76s)
component. So they have this field
[375:23] (22523.92s)
called React. And we don't have the
[375:26] (22526.48s)
component. We will create it. Just type
[375:29] (22529.60s)
this out with me. We'll say appointment
[375:33] (22533.76s)
confirmation
[375:35] (22535.36s)
email. And this will take some props
[375:38] (22538.88s)
here. We can say doctor name. And again
[375:41] (22541.52s)
I'll just provide all of them. Let's say
[375:44] (22544.56s)
appointment, date, time, type, duration,
[375:48] (22548.48s)
and then the price. Now we need to
[375:50] (22550.56s)
create this which is something that
[375:52] (22552.16s)
we're going to do in a second. Let's say
[375:54] (22554.40s)
this will give us the data as well as
[375:57] (22557.20s)
the error, right? Okay. If we have any
[376:01] (22561.12s)
errors, we can handle it.
[376:03] (22563.68s)
So here I'll just paste this in. If we
[376:05] (22565.84s)
have any errors, send 500 failed to send
[376:08] (22568.96s)
email. But else 200 email send
[376:11] (22571.84s)
successfully. And here is the email ID.
[376:14] (22574.64s)
And let's handle the catch block as
[376:16] (22576.32s)
well.
[376:18] (22578.88s)
Okay, so that's it. But now we need to
[376:21] (22581.52s)
create this component. So I will go
[376:24] (22584.00s)
under the source components under the
[376:27] (22587.52s)
emails,
[376:29] (22589.52s)
copy this, paste it and I'll say tsx. So
[376:32] (22592.96s)
this is the file name.
[376:36] (22596.00s)
Now this is a function, right? This is a
[376:38] (22598.32s)
react component. So you can call this
[376:40] (22600.64s)
just by having a function call, right?
[376:43] (22603.12s)
So this is something that I see
[376:44] (22604.48s)
beginners can't really understand. But
[376:46] (22606.56s)
dude, take a look at this. This is
[376:48] (22608.72s)
literally a function. You can basically
[376:51] (22611.04s)
call it in this way. So it doesn't have
[376:53] (22613.12s)
to be something like appointment
[376:56] (22616.56s)
confirmation email. Right? So this is
[376:58] (22618.80s)
one of the ways to call it when you're
[377:00] (22620.32s)
rendering but you can also call it in
[377:03] (22623.68s)
this way. This is still rendering that
[377:06] (22626.00s)
um component because at the end of the
[377:08] (22628.00s)
day it is just a function. So TypeScript
[377:11] (22631.44s)
is not happy with us. It says you should
[377:13] (22633.92s)
provide some interface and make it type
[377:16] (22636.64s)
safe. So let's do this exactly.
[377:20] (22640.48s)
I will say here are the props that we're
[377:22] (22642.40s)
getting and here is our interface. Now
[377:24] (22644.96s)
if you take a look at the documentation
[377:27] (22647.68s)
of recent you're going to see they're
[377:29] (22649.44s)
using tailwind which is we can do as
[377:31] (22651.84s)
well but I have already built it with
[377:34] (22654.88s)
you know pure CSS which is something
[377:37] (22657.60s)
that we would be doing in like normally.
[377:41] (22661.20s)
So I will provide all the CSS classes.
[377:43] (22663.92s)
So this is something that I have
[377:45] (22665.44s)
generated completely with AI. I'm not
[377:48] (22668.00s)
going to lie, right? So I'll just be
[377:51] (22671.28s)
100% honest
[377:53] (22673.68s)
here. I have generated all these styles
[377:56] (22676.00s)
with AI, which I don't really mind.
[377:58] (22678.88s)
There is no point for me to type these
[378:01] (22681.52s)
um styling just to get this output,
[378:04] (22684.08s)
right? I'm happy with it. So I'll just
[378:06] (22686.24s)
copy and paste from AI. If you want to
[378:08] (22688.72s)
update it, you can do so. This is just
[378:10] (22690.88s)
some CSS like super basic CSS classes.
[378:15] (22695.04s)
Now we can get into the email itself.
[378:17] (22697.52s)
Here is how that work. You can check
[378:20] (22700.32s)
this out from recent documentation if
[378:22] (22702.72s)
you're interested. But we would like to
[378:25] (22705.04s)
import the HTML element first. We will
[378:28] (22708.40s)
wrap everything with it. And we're going
[378:30] (22710.40s)
to put the head component as well. Let's
[378:33] (22713.44s)
say import this. Leave it as a
[378:36] (22716.56s)
self-closed component. Then we can have
[378:39] (22719.44s)
a preview element. I'll say your dental
[378:42] (22722.80s)
appointment has been confirmed. And we
[378:45] (22725.44s)
are going to get all these components
[378:47] (22727.84s)
from a package that we have already
[378:49] (22729.92s)
installed which is react email um
[378:53] (22733.68s)
components.
[378:56] (22736.08s)
Okay. So as you can tell now whenever
[378:58] (22738.40s)
you want to use a link image heading
[379:02] (22742.32s)
like text section you'll be using these
[379:04] (22744.80s)
components from that package. Okay. So
[379:08] (22748.16s)
now let's get into the body element.
[379:10] (22750.96s)
I'll say here is the body with the
[379:13] (22753.68s)
styles and we will get into it with a
[379:17] (22757.28s)
container component. For the styles I'll
[379:20] (22760.40s)
say style. This is going to get the
[379:22] (22762.72s)
container class which are these style
[379:26] (22766.00s)
styles right the margin padding and
[379:28] (22768.16s)
maximum width. Then within this we can
[379:30] (22770.64s)
get a section. Now if you don't want to
[379:32] (22772.96s)
type this out just copy the entire
[379:35] (22775.12s)
component from uh you know from the
[379:37] (22777.92s)
source code then we will get the heading
[379:41] (22781.20s)
which is this part. So we are starting
[379:44] (22784.56s)
from here and going all the way until
[379:47] (22787.92s)
the end.
[379:51] (22791.60s)
Now just to speed up the process I think
[379:54] (22794.00s)
I will copy the rest of it from source
[379:56] (22796.32s)
code and paste this in because there is
[379:58] (22798.48s)
literally no logic other than some texts
[380:01] (22801.28s)
classes and UI elements. Um there is one
[380:05] (22805.28s)
thing which is this link. If you click
[380:08] (22808.48s)
to it this will take you to the
[380:11] (22811.44s)
appointments page. Here we can see it
[380:13] (22813.76s)
says whatever the application URL is.
[380:17] (22817.92s)
whatever the application URL is just put
[380:20] (22820.48s)
/appointments at the end and let's
[380:22] (22822.96s)
create this environment variable
[380:27] (22827.12s)
so I will paste this in this is the key
[380:29] (22829.92s)
in localhost right in development this
[380:32] (22832.48s)
should be localhost
[380:34] (22834.80s)
I'll say localhost 300 but in deployment
[380:38] (22838.96s)
we're going to put whatever the URL is
[380:41] (22841.28s)
so maybe it would be something like
[380:43] (22843.68s)
tentwise.com
[380:45] (22845.60s)
or anything right so We will update this
[380:48] (22848.48s)
once we deploy the app. But in our local
[380:51] (22851.68s)
MB, this is going to be the value.
[380:54] (22854.80s)
And I think that's the entire component.
[380:56] (22856.72s)
We can save. This is happy with us. We
[380:59] (22859.84s)
built the entire API route. All we have
[381:02] (22862.64s)
to do is calling this API route which is
[381:06] (22866.32s)
send appointment email under the
[381:09] (22869.44s)
page.tsx. So we're going to delete this
[381:12] (22872.08s)
to-do and call that endpoint. So here
[381:16] (22876.08s)
I'll delete the comment and I would like
[381:18] (22878.56s)
to wrap everything with try and catch
[381:20] (22880.88s)
block. The reason is if sending email
[381:23] (22883.76s)
fails we don't really want our entire
[381:26] (22886.24s)
application fail because it is just a
[381:28] (22888.56s)
background job right. So here I'll have
[381:31] (22891.04s)
my try and catch block which is like 20
[381:33] (22893.76s)
lines of code and please let me walk you
[381:36] (22896.08s)
through it. So we are going to call the
[381:38] (22898.32s)
fetch method by passing our endpoint
[381:41] (22901.44s)
which is slash API slash send
[381:45] (22905.04s)
appointment email. Right? Make sure this
[381:47] (22907.84s)
is correct. Then we're going to pass the
[381:50] (22910.24s)
object where we'll say the method is
[381:52] (22912.80s)
post. This is what we're expecting
[381:56] (22916.16s)
the headers which is JSON and then the
[381:59] (22919.20s)
body. So we're going to send the user
[382:01] (22921.20s)
email and the rest of it here it is. um
[382:04] (22924.48s)
if response is not okay, we can put a
[382:06] (22926.88s)
console log for debugging purposes and
[382:09] (22929.52s)
same for the catch. Now let's go ahead
[382:12] (22932.16s)
and test this out. One thing that I want
[382:14] (22934.32s)
to mention
[382:16] (22936.16s)
um like this is the account that you
[382:18] (22938.80s)
signed up, right? You can only send
[382:20] (22940.96s)
emails to this one since you are not
[382:23] (22943.68s)
using your own domain, right? So here we
[382:27] (22947.52s)
don't have any domains. That means we
[382:29] (22949.60s)
can only send emails to this account. In
[382:33] (22953.12s)
your case, this should be your email. So
[382:35] (22955.68s)
go ahead with this email. Log to our
[382:38] (22958.56s)
application. This is what I have done.
[382:40] (22960.72s)
This is the clerk account. The recent
[382:43] (22963.04s)
account, they are the same email. And I
[382:45] (22965.76s)
will have a new appointment. So I'll say
[382:49] (22969.52s)
John, let's select it.
[382:52] (22972.32s)
next week. This time I'll do a
[382:54] (22974.48s)
consultation review booking and confirm
[383:01] (22981.36s)
Okay, so looks like it is done. Let's
[383:03] (22983.36s)
check the database I was going to say,
[383:05] (22985.52s)
but we can see it already. Um, we didn't
[383:08] (22988.88s)
see the confirmation model. We will
[383:10] (22990.96s)
build it in a second. But let's take a
[383:13] (22993.04s)
look at my inbox.
[383:15] (22995.44s)
So, I just tested out and realized the
[383:18] (22998.08s)
sending email didn't actually work. And
[383:20] (23000.64s)
here is the solution that we're going to
[383:22] (23002.56s)
do. Basically, we need to install this
[383:24] (23004.88s)
package. So, this is in my case what I
[383:27] (23007.84s)
have. I'll go ahead say clear. Maybe in
[383:30] (23010.48s)
your case, you don't really have any
[383:32] (23012.00s)
errors, but just in case if you have it,
[383:34] (23014.56s)
say mpm install and install this package
[383:38] (23018.88s)
react email render.
[383:41] (23021.60s)
Okay, let's run this and test this out
[383:43] (23023.92s)
once again.
[383:45] (23025.76s)
So here let's select this doctor
[383:48] (23028.96s)
this date this time and let's go with
[383:52] (23032.48s)
this service.
[383:55] (23035.28s)
Hopefully this time we should be able to
[383:57] (23037.04s)
get an email. If not we can see how we
[384:00] (23040.00s)
can fix that. Okay. So here we can see 0
[384:02] (23042.96s)
minutes ago we got the email just now.
[384:05] (23045.92s)
And if you cannot see it here maybe
[384:07] (23047.92s)
check out your spam um spam folder. It
[384:11] (23051.60s)
should be there. Right. The email is
[384:13] (23053.68s)
actually working in the terminal. We can
[384:16] (23056.16s)
see it is actually done successfully.
[384:20] (23060.80s)
Okay. If you want to change the logo by
[384:22] (23062.96s)
the way, you have to go ahead install it
[384:25] (23065.76s)
to somewhere and
[384:28] (23068.48s)
you know put the URL right here. This is
[384:30] (23070.56s)
the URL that I have because I have
[384:32] (23072.56s)
hosted this image um in this URL so that
[384:36] (23076.32s)
it can work properly here in the email.
[384:42] (23082.72s)
Okay. So that's how we can make the
[384:44] (23084.56s)
emails work successfully. And now we are
[384:47] (23087.04s)
missing this model which is called
[384:49] (23089.36s)
appointment confirmation model. Right.
[384:51] (23091.76s)
It is like a dialogue that we're going
[384:53] (23093.52s)
to get the details inside of that. So
[384:56] (23096.96s)
let's close everything other than our
[384:59] (23099.28s)
page. Scroll to the bottom where we have
[385:01] (23101.76s)
the step of three. Right? Go outside of
[385:05] (23105.60s)
this div and have this code block. So we
[385:09] (23109.20s)
will say if there is a booked
[385:10] (23110.80s)
appointment just show the model copy the
[385:14] (23114.72s)
name under the components
[385:17] (23117.76s)
we are under the appointments page so
[385:19] (23119.92s)
we'll go under this folder paste this in
[385:23] (23123.68s)
oops
[385:25] (23125.52s)
so here we will paste this in and we'll
[385:27] (23127.68s)
just say tsx and the entire um content
[385:32] (23132.32s)
is going to be coming from our source
[385:34] (23134.48s)
code as always. So I'll just go ahead
[385:37] (23137.04s)
copy and paste because there is no logic
[385:39] (23139.60s)
at all here. We can see we don't have
[385:41] (23141.68s)
anything else other than return
[385:43] (23143.52s)
statement. So it is a dialogue. We got
[385:46] (23146.32s)
some props and we are using them within
[385:49] (23149.68s)
this component. So that's the entire
[385:51] (23151.92s)
thing. Let's save and import it and just
[385:55] (23155.84s)
try to see
[385:58] (23158.00s)
in action. So I would like to have
[386:00] (23160.48s)
another appointment. Let's say this time
[386:03] (23163.04s)
this doctor let's have just tomorrow.
[386:06] (23166.00s)
Here we can see this is booked. So we
[386:08] (23168.24s)
should select another date or time and
[386:12] (23172.16s)
let's say confirm this booking.
[386:15] (23175.28s)
Once it is done we are going to get the
[386:17] (23177.84s)
model. And here we can see we have all
[386:20] (23180.16s)
the details. This doesn't have any logic
[386:22] (23182.80s)
at all other than a link which would
[386:26] (23186.00s)
take you to the dashboard appointments
[386:28] (23188.32s)
page. Let me fix it.
[386:30] (23190.88s)
This is wrong. Um, the link is wrong, I
[386:34] (23194.56s)
mean. And when you copy and paste it
[386:36] (23196.88s)
from the source code, you're going to
[386:38] (23198.16s)
get the correct version.
[386:40] (23200.56s)
Okay, so that's the entire thing. I hope
[386:42] (23202.40s)
you didn't mind it that we copied and
[386:44] (23204.88s)
pasted this component. But that's the
[386:47] (23207.84s)
entire gist of it. So that's it for this
[386:50] (23210.48s)
entire section. Let's go ahead close
[386:53] (23213.04s)
everything. Open up the status bar. Um,
[386:57] (23217.04s)
I'll have a new branch. Let's say
[386:59] (23219.68s)
sending emails
[387:02] (23222.56s)
and let's commit our changes.
[387:12] (23232.24s)
Here is my commit message. Publish this
[387:14] (23234.56s)
branch and let's get into the source
[387:17] (23237.60s)
code.
[387:23] (23243.36s)
Okay, so I'll wait for the suggestions
[387:25] (23245.60s)
that are coming from code rabbit and
[387:27] (23247.92s)
then we should be able to merge the pull
[387:30] (23250.32s)
request. So here are all the features
[387:33] (23253.04s)
that we have introduced in this section.
[387:35] (23255.36s)
The files that we have updated with the
[387:37] (23257.76s)
related you know quick summaries, the
[387:40] (23260.56s)
sequence diagram, what is happening in
[387:42] (23262.56s)
the background step by step. You can
[387:44] (23264.72s)
take a look at this. Then let's scroll
[387:46] (23266.80s)
and see some code suggestions. Um here
[387:49] (23269.84s)
it says you should probably and
[387:52] (23272.64s)
definitely uh validate the email format
[387:56] (23276.08s)
because uh currently this user email is
[387:58] (23278.96s)
not validated which would lead to
[388:01] (23281.20s)
injection issues or sending to in
[388:04] (23284.08s)
invalid addresses. So this is something
[388:06] (23286.80s)
definitely that you should be doing
[388:08] (23288.72s)
right you can copy it paste it into the
[388:10] (23290.88s)
source code. Um so have this validate
[388:14] (23294.00s)
email for uh like val email validation I
[388:17] (23297.84s)
would say and then here it says you
[388:20] (23300.00s)
should make the errors a little bit more
[388:22] (23302.88s)
specific. So this is the way to do it.
[388:25] (23305.84s)
If user email is missing you would say
[388:28] (23308.32s)
you're missing the user email. If the
[388:30] (23310.64s)
appointment date is missing you would
[388:32] (23312.32s)
say hey this field is missing right in
[388:35] (23315.52s)
the error case. Um and then let's scroll
[388:38] (23318.24s)
to the bottom here. It says you should
[388:40] (23320.56s)
make this to be an environment variable.
[388:42] (23322.96s)
I absolutely agree, but we are just
[388:45] (23325.52s)
trying to keep it simple. Um, in a
[388:47] (23327.92s)
production grade codebase, this is how
[388:50] (23330.00s)
you would have it coming as an um
[388:52] (23332.88s)
environment variable. Let's scroll to
[388:54] (23334.96s)
the bottom. Here it says you should
[388:57] (23337.36s)
check, you know, if the email sent
[388:59] (23339.12s)
successfully or not. Depending on that,
[389:01] (23341.28s)
you would either show the model or show
[389:04] (23344.00s)
some kind of error, right? The using the
[389:07] (23347.28s)
toast method. So that's one thing that
[389:09] (23349.84s)
you can do as well. Then if you're going
[389:12] (23352.08s)
to be doing it, you should add this
[389:13] (23353.92s)
state to the model.
[389:18] (23358.00s)
Um here it says for the image URL you
[389:21] (23361.28s)
are using external image which could uh
[389:25] (23365.04s)
you know it's not the best practice. It
[389:27] (23367.20s)
could lead some problems things like
[389:30] (23370.40s)
let's say what happens if this service
[389:32] (23372.48s)
is down you are not going to get the
[389:34] (23374.48s)
image or if it is slow down right if the
[389:37] (23377.60s)
service is not working you are not going
[389:39] (23379.60s)
to see the images under the emails
[389:43] (23383.92s)
right because this is hosted in that
[389:46] (23386.00s)
service so this is something that you
[389:48] (23388.16s)
should definitely keep in mind but here
[389:50] (23390.48s)
we're just trying to make it work in
[389:52] (23392.24s)
development that's why I put the URL
[389:56] (23396.08s)
and And here it says in case this is
[389:58] (23398.24s)
undefined, you should put a fallback. I
[390:00] (23400.72s)
don't think this is if like 100%
[390:03] (23403.12s)
necessary because we'll just always make
[390:05] (23405.44s)
sure that we have this environment
[390:07] (23407.12s)
variable. Okay, so these are all the
[390:09] (23409.52s)
things that you could have included. And
[390:11] (23411.92s)
here it says you can also add the if
[390:14] (23414.00s)
check. So these are just some code
[390:16] (23416.32s)
optimizations that you could have done.
[390:18] (23418.56s)
In my case, I'd like to not waste any
[390:21] (23421.04s)
time. So I'll say merge the pull
[390:22] (23422.88s)
request. go into VS Code. This is done
[390:27] (23427.12s)
successfully. Let's go here. Switch to
[390:30] (23430.72s)
the master
[390:32] (23432.88s)
and we're going to get the latest
[390:34] (23434.88s)
changes.
[390:39] (23439.04s)
Okay. So now our code codebase is up to
[390:41] (23441.84s)
date. So that's it for this entire
[390:43] (23443.84s)
section. I'll see you in the next one.
[390:46] (23446.32s)
So in this section we are going to add
[390:48] (23448.64s)
this table under the admin dashboard. So
[390:52] (23452.24s)
currently we are missing that component.
[390:54] (23454.72s)
Let's go ahead and create that. First
[390:57] (23457.12s)
I'll visit the admin dashboard client.
[391:01] (23461.04s)
Let's scroll to the bottom. Right after
[391:03] (23463.12s)
the doctor's management, we will have
[391:05] (23465.20s)
one more component called recent
[391:08] (23468.64s)
appointments.
[391:11] (23471.84s)
So basically admin should be able to see
[391:14] (23474.32s)
all the appointments, right? So I'll
[391:16] (23476.48s)
copy this. Go under the components
[391:20] (23480.88s)
under the admin and create this and say
[391:23] (23483.20s)
tsx.
[391:25] (23485.12s)
So this component is going to be using a
[391:27] (23487.84s)
table which is going to be coming from
[391:30] (23490.16s)
shad cn ui. So it is this entire
[391:33] (23493.44s)
component. So you can just search for it
[391:36] (23496.00s)
by typing the table. This is a similar
[391:39] (23499.12s)
example that we have right. And to be
[391:41] (23501.68s)
able to use it you need to wrap
[391:43] (23503.44s)
everything with the table component.
[391:45] (23505.76s)
Then you can add a caption table header
[391:49] (23509.12s)
row head. As you can tell it is just
[391:51] (23511.60s)
some you know uh elements some
[391:54] (23514.72s)
components. So no logic at all. For that
[391:57] (23517.84s)
reason we can copy and paste from the
[392:00] (23520.24s)
source code. But first we need to have a
[392:03] (23523.28s)
mutation. So basically when you click to
[392:06] (23526.16s)
this badge it's going to toggle the
[392:09] (23529.36s)
state. So if you click to this it would
[392:11] (23531.36s)
be confirmed and if you click to this
[392:13] (23533.60s)
one it would be sorry if you click to
[392:16] (23536.32s)
this one it it would be completed right.
[392:19] (23539.04s)
So either it's going to be completed
[392:22] (23542.64s)
or it would be confirmed
[392:25] (23545.28s)
by clicking to them we can toggle the
[392:27] (23547.44s)
state. So for this let's first create a
[392:30] (23550.48s)
server action. I will go under the
[392:33] (23553.20s)
appointments actions file.
[392:37] (23557.36s)
Let's shrink everything and go at the
[392:39] (23559.60s)
very bottom create one function which is
[392:43] (23563.20s)
going to be called as let's say export
[392:46] (23566.08s)
async function and call this as update
[392:49] (23569.84s)
appointment
[392:51] (23571.68s)
status
[392:53] (23573.44s)
and we will get an input let's put the
[392:56] (23576.08s)
type we're going to get the ID so that
[392:58] (23578.72s)
we know which appointment that we are
[393:00] (23580.80s)
updating let's say string and then we
[393:04] (23584.00s)
can get the status so let's say This is
[393:07] (23587.36s)
going to be either confirmed and or
[393:10] (23590.32s)
completed. And we can use appointment
[393:12] (23592.88s)
status enum for this right. We have this
[393:16] (23596.56s)
under the schema.prisma.
[393:20] (23600.08s)
So it's going to be either one of these.
[393:22] (23602.80s)
Okay. Now let's get into the function.
[393:24] (23604.64s)
It's going to be super easy.
[393:27] (23607.44s)
I'll say await prisma do appointment
[393:31] (23611.36s)
where we would like to update one. Um
[393:34] (23614.32s)
here is the filter. We'll say where the
[393:38] (23618.16s)
ID is equal to input do ID
[393:42] (23622.24s)
and the data that we would like to
[393:44] (23624.00s)
update which is the status itself. So
[393:46] (23626.56s)
we'll say input dot status right that's
[393:50] (23630.88s)
entire thing let's say
[393:53] (23633.68s)
you're going to give us the new
[393:55] (23635.28s)
appointment the updated appointment
[393:58] (23638.88s)
and we can just return this back to the
[394:00] (23640.96s)
client
[394:03] (23643.60s)
and then let's handle the catch blog.
[394:05] (23645.60s)
This is what I'll have. I think that's
[394:07] (23647.68s)
the entire method that we would need.
[394:10] (23650.08s)
Now, we would like to call this under
[394:12] (23652.00s)
our hooks. So, under the use
[394:14] (23654.80s)
appointments, we will go ahead and
[394:16] (23656.96s)
create a custom hook, which is going to
[394:19] (23659.52s)
be like maybe five lines of code that I
[394:22] (23662.72s)
can copy and paste. So, we're getting
[394:24] (23664.80s)
the query client. Um, this is going to
[394:27] (23667.20s)
be a mutation, not a query because we
[394:30] (23670.32s)
are updating something, right? We are
[394:31] (23671.92s)
not fetching data. So we're going to
[394:33] (23673.92s)
call our mutation which was this one and
[394:37] (23677.68s)
then once it is done successfully once
[394:39] (23679.92s)
we update it we will say call this query
[394:43] (23683.52s)
again so that we can see the latest
[394:46] (23686.48s)
changes right we update the cache okay
[394:50] (23690.00s)
so I hope that makes sense now let's go
[394:51] (23691.92s)
into the recent appointment and try to
[394:54] (23694.80s)
get started with it first I would like
[394:57] (23697.20s)
to call my hooks so here I will get all
[395:00] (23700.56s)
the appointments and then I will at my
[395:03] (23703.28s)
mutation because we're going to be using
[395:05] (23705.68s)
those. Then what happens if you click to
[395:09] (23709.92s)
this badge, right? We would like to call
[395:12] (23712.64s)
our mutation by sending the new state.
[395:15] (23715.52s)
So just imagine if we click to this one,
[395:18] (23718.40s)
we would like this to be completed,
[395:20] (23720.56s)
right? So we are going to add the new
[395:23] (23723.28s)
status. Let me create a method. Let's
[395:26] (23726.24s)
say give a space handle toggle
[395:30] (23730.96s)
appointment
[395:33] (23733.52s)
appointment status
[395:36] (23736.96s)
we are going to get the appointment ID
[395:40] (23740.24s)
which is going to be type of string and
[395:42] (23742.72s)
we can go inside first I'll say let's
[395:46] (23746.16s)
find the appointment this should be an
[395:48] (23748.80s)
arrow function by the way
[395:52] (23752.40s)
we'll say appointments dotfind point.
[395:57] (23757.12s)
Let's get each appointment.
[395:59] (23759.52s)
I'll say appointment ID is equal to the
[396:04] (23764.32s)
appointment ID. So that's going to give
[396:06] (23766.72s)
us which appointment that we click.
[396:09] (23769.12s)
Right? And let's create the new status.
[396:12] (23772.64s)
We will say if appointment status is
[396:15] (23775.60s)
equal to confirmed then new status is
[396:19] (23779.20s)
going to be completed.
[396:22] (23782.88s)
Otherwise it would be confirmed.
[396:26] (23786.08s)
So this is how we can toggle the state
[396:28] (23788.24s)
and we'll say call our mutation dot
[396:31] (23791.20s)
mutate
[396:32] (23792.80s)
pass the object here is the ID which is
[396:36] (23796.00s)
the appointment ID and then the status
[396:39] (23799.28s)
which is the new status. So super easy
[396:42] (23802.40s)
nothing complicated. And then we can add
[396:44] (23804.80s)
a batch for the status. Right? If it is
[396:48] (23808.96s)
confirmed, this is the color and the
[396:51] (23811.04s)
text color. And if it is completed,
[396:53] (23813.68s)
we're going to get this green badge.
[396:56] (23816.24s)
Here is the function. So here, let's
[396:59] (23819.12s)
import the badge from UI. If the case is
[397:02] (23822.56s)
confirmed, this is what we're going to
[397:04] (23824.08s)
see. It says confirmed. If it is
[397:06] (23826.40s)
completed, this is what we're going to
[397:07] (23827.84s)
return. And in the default case, we can
[397:10] (23830.16s)
just have something. I think if you
[397:11] (23831.92s)
don't add this, TypeScript might not be
[397:14] (23834.24s)
happy. Well, looks like it's happy. But
[397:17] (23837.12s)
the best practice is always include a
[397:20] (23840.16s)
default case when you're using a switch
[397:22] (23842.48s)
statement. Okay, so with that, I think
[397:25] (23845.60s)
we can just get into the return
[397:27] (23847.28s)
statement. I'll give Ela space. If you
[397:30] (23850.48s)
don't want to type this out, just go
[397:32] (23852.24s)
ahead into the source code, copy the
[397:34] (23854.48s)
entire return statement, which is
[397:36] (23856.40s)
around, let's say, 70 lines of code. So,
[397:39] (23859.60s)
first we're going to get get a card
[397:42] (23862.40s)
element.
[397:45] (23865.04s)
And then we will have the cart header
[397:47] (23867.28s)
with some text. Let me get the calendar
[397:51] (23871.20s)
icon from Lucid React
[397:54] (23874.00s)
and cart description. Let's save and see
[397:56] (23876.72s)
the output.
[398:00] (23880.08s)
Okay, so that's the title. Now it's time
[398:03] (23883.20s)
to build the cart content. So still
[398:06] (23886.40s)
within the cart, we'll say cart content.
[398:11] (23891.60s)
Let's import it. And we are going to
[398:14] (23894.48s)
have a div that will make this to be
[398:17] (23897.52s)
rounded. Let's turn this off. And then
[398:20] (23900.72s)
we're going to have the table itself.
[398:23] (23903.68s)
So this table is going to be coming from
[398:25] (23905.92s)
chaten.
[398:29] (23909.04s)
And then we will have the table header.
[398:31] (23911.76s)
So as you can tell, we need to import
[398:33] (23913.44s)
all these components one by one and just
[398:37] (23917.44s)
see the output.
[398:42] (23922.96s)
Okay. Okay. So, these are the kind of
[398:46] (23926.56s)
like the headers that we have. Now, it's
[398:48] (23928.88s)
time to put the data. We're just going
[398:51] (23931.20s)
to be going under the table body. So,
[398:54] (23934.16s)
instead of typing this out, I'll go
[398:56] (23936.16s)
right after the table header and paste
[398:58] (23938.56s)
this in and walk you through it. So, we
[399:01] (23941.12s)
have table body. Let's import it. Then,
[399:03] (23943.52s)
we map through the appointments. For
[399:05] (23945.76s)
each of them, we are going to return a
[399:09] (23949.84s)
which is going to have a table cell
[399:11] (23951.76s)
within this. Right? Um here we are
[399:14] (23954.24s)
getting the patient email doctor name.
[399:17] (23957.20s)
We have some TypeScript errors. We're
[399:19] (23959.12s)
going to fix it. Uh but let's get the
[399:21] (23961.68s)
button as well.
[399:24] (23964.80s)
Okay. So that's entire thing. Once you
[399:26] (23966.80s)
click to that button, it's going to call
[399:28] (23968.96s)
the method with the appointment ID. Now
[399:32] (23972.40s)
let's see why this is not type save. We
[399:35] (23975.28s)
will go under the appointments.
[399:40] (23980.72s)
So this is what we are returning right
[399:43] (23983.60s)
the appointments. But I think we should
[399:46] (23986.08s)
call the transform appointment with this
[399:48] (23988.40s)
method.
[399:50] (23990.96s)
let's say transform appointment and pass
[399:53] (23993.68s)
the appointments into it because here we
[399:57] (23997.68s)
are passing some fields like patient
[400:00] (24000.24s)
email uh patient name which is like
[400:04] (24004.64s)
which are the things that we are using
[400:07] (24007.68s)
for now I will save this file and looks
[400:10] (24010.56s)
like we have some more errors let's try
[400:12] (24012.88s)
to fix them um I think I just did a
[400:15] (24015.52s)
mistake let's go back this is not how we
[400:17] (24017.92s)
call it instead we say appointments map
[400:21] (24021.60s)
for every single appointment to call the
[400:23] (24023.76s)
transform appointment method. Right?
[400:26] (24026.56s)
That's how we were using it. So that's
[400:29] (24029.44s)
the correct way. Go ahead fix this line
[400:31] (24031.76s)
and you shouldn't have any errors. Now
[400:34] (24034.88s)
let's test it out. So these are all the
[400:37] (24037.92s)
appointments that we that we currently
[400:40] (24040.08s)
have in the database. Let's try to
[400:42] (24042.48s)
update some of them. So I'll say this
[400:44] (24044.80s)
should be completed. I just click to
[400:46] (24046.80s)
add. You can add a loading state. I'll
[400:49] (24049.36s)
just ignore it in this case and it
[400:52] (24052.24s)
should be completed as well. If you
[400:53] (24053.92s)
click to it again, it should be
[400:55] (24055.68s)
confirmed in a second. Here we go. So,
[400:58] (24058.48s)
this is working as expected. There is
[401:00] (24060.56s)
one more thing that I want to fix. So,
[401:02] (24062.72s)
currently if you say this doctor should
[401:04] (24064.96s)
be inactive. Save the change and go back
[401:09] (24069.04s)
to the appointments page. So, this is
[401:11] (24071.20s)
inactive, right? Let's go here
[401:16] (24076.00s)
and let's wait for this to be loaded.
[401:18] (24078.00s)
Okay, now she's inactive, we cannot see
[401:20] (24080.40s)
it. That's good. But if you go back to
[401:22] (24082.88s)
admin and make this to be active
[401:27] (24087.44s)
and go back to the appointments here,
[401:30] (24090.00s)
you can see the cash has not been
[401:31] (24091.76s)
updated. She should be visible because
[401:34] (24094.56s)
we make her account to be active. But it
[401:38] (24098.24s)
is not working. So let's try to fix it.
[401:40] (24100.56s)
We will go under the hook called use
[401:43] (24103.20s)
doctors. And when we update the doctors
[401:47] (24107.12s)
just instead of invalidating this query
[401:50] (24110.48s)
let's do another one I will duplicate
[401:53] (24113.20s)
and I will say we would like to fetch
[401:55] (24115.76s)
the get available doctors query as well
[402:00] (24120.48s)
okay so which is this query just put
[402:04] (24124.40s)
this line and test it out once again it
[402:08] (24128.00s)
will go back to the admin page let's say
[402:11] (24131.12s)
this is inactive
[402:18] (24138.00s)
okay. So, I don't know how to show this,
[402:20] (24140.00s)
but let's say this should be active
[402:24] (24144.24s)
and then go to the appointments page.
[402:26] (24146.48s)
After 1 second, it should be fetched
[402:28] (24148.72s)
again, right? So, this is how we can
[402:31] (24151.28s)
make that query run. I'll say inactive.
[402:35] (24155.36s)
Go back. She should not be here as you
[402:38] (24158.00s)
can tell.
[402:40] (24160.56s)
Okay, I'll just leave it as active. So
[402:43] (24163.44s)
that's the fix that you can add. And
[402:47] (24167.04s)
just one more thing before we end the
[402:49] (24169.20s)
section, let's go into the layout file
[402:52] (24172.32s)
under the app. So we are calling the
[402:55] (24175.52s)
user sync component which is completely
[402:58] (24178.32s)
fine. But I think we can call this
[403:00] (24180.72s)
method in a better place because what
[403:03] (24183.60s)
happens if this doesn't run entirely and
[403:06] (24186.40s)
we have some navigations. So under the
[403:09] (24189.28s)
homepage okay so under the app under the
[403:12] (24192.64s)
homepage just before we take the user to
[403:15] (24195.60s)
the dashboard page we can save them to
[403:18] (24198.08s)
the database if if they are not already.
[403:21] (24201.44s)
So I will say await call the sync user
[403:25] (24205.20s)
action and once this is done once user
[403:29] (24209.44s)
in the database then navigate them to
[403:32] (24212.08s)
the dashboard page. Okay I hope that
[403:34] (24214.80s)
makes sense. This is really important. I
[403:37] (24217.44s)
will leave this as a comment. So I will
[403:41] (24221.92s)
this is so here is the comment. This is
[403:44] (24224.80s)
done in the homepage component, right?
[403:47] (24227.36s)
Instead of calling the component itself,
[403:49] (24229.76s)
we're just going to call the server
[403:51] (24231.52s)
action. Um once again, the best way of
[403:54] (24234.80s)
doing this, the best way of syncing the
[403:59] (24239.04s)
user is basically calling a web hook.
[404:02] (24242.32s)
Okay. So I'll just say webs which is
[404:05] (24245.44s)
something that you can take as a
[404:07] (24247.04s)
challenge and implement once you
[404:09] (24249.20s)
complete the entire course. So with this
[404:11] (24251.36s)
in mind I think we can save this file
[404:13] (24253.76s)
and commit our changes in the next
[404:16] (24256.40s)
section. We can deploy the entire
[404:18] (24258.32s)
project because we are pretty much done
[404:20] (24260.56s)
with it. Instead of having a new branch
[404:23] (24263.44s)
here I'll just commit them to the
[404:25] (24265.28s)
master. So if you wanted to have some
[404:27] (24267.68s)
code suggestions of course you can do a
[404:30] (24270.00s)
pull request and wait the suggestions
[404:32] (24272.80s)
from code ravit but in this case since
[404:35] (24275.84s)
these are some changes some fixes I will
[404:38] (24278.80s)
go ahead and commit them directly to the
[404:40] (24280.96s)
master. Okay so I stage all of my
[404:43] (24283.60s)
changes and I'll say something like
[404:47] (24287.04s)
small fixes
[404:49] (24289.36s)
um commit this and sync up the changes.
[404:53] (24293.68s)
Now if you take a look at the git repo
[404:56] (24296.48s)
we should be able to see something like
[404:58] (24298.48s)
now here we can see we just got the
[405:00] (24300.56s)
small fixes. All right so that's the
[405:02] (24302.96s)
entire section. Hopefully in the next
[405:05] (24305.36s)
one we are going to deploy this entire
[405:08] (24308.32s)
project. So I'll see you there.
[405:11] (24311.60s)
All right. So now that our entire
[405:13] (24313.52s)
application is working successfully it
[405:16] (24316.00s)
is time to deploy it. And for the
[405:18] (24318.24s)
hosting platform, we are going to be
[405:20] (24320.08s)
using Savala, which has one of the best
[405:22] (24322.80s)
developer experience in the market. And
[405:25] (24325.28s)
they are kind enough to give you $50
[405:27] (24327.84s)
free credits if you use the link in the
[405:30] (24330.08s)
description down below. They have
[405:32] (24332.08s)
services like application hosting,
[405:34] (24334.40s)
object storage, database hosting, and
[405:37] (24337.04s)
static site hosting. And in this case,
[405:39] (24339.52s)
we're going to be using application
[405:41] (24341.04s)
hosting to be able to deploy our app.
[405:44] (24344.00s)
There are some really good reviews about
[405:46] (24346.00s)
Savala and honestly I have been using
[405:48] (24348.48s)
them for a while now in the past
[405:50] (24350.80s)
projects as well and I really like this
[405:53] (24353.44s)
service. So that's why I wanted to share
[405:55] (24355.44s)
it uh in the first place. Um here I'm
[405:58] (24358.48s)
already convinced by taking all these
[406:01] (24361.20s)
features uh into account, right? So they
[406:04] (24364.24s)
would give you unlimited users for your
[406:06] (24366.72s)
team which is something that the other
[406:09] (24369.04s)
products would give you in a paid plan,
[406:11] (24371.60s)
right? the more users you would get, the
[406:13] (24373.84s)
more you would pay. But in this case,
[406:15] (24375.76s)
this is not really this is not really
[406:17] (24377.92s)
the case. So, thinking all this, I'll go
[406:20] (24380.56s)
ahead and log in. And again, you can use
[406:22] (24382.72s)
the link in the description to get $50
[406:25] (24385.52s)
free credits. Okay. So, I'll just open
[406:28] (24388.24s)
up that login link. Let me go ahead and
[406:31] (24391.92s)
visit my dashboard.
[406:34] (24394.16s)
All right. So, we are going to deploy an
[406:36] (24396.16s)
application from our GitHub repository,
[406:39] (24399.04s)
which is this one. Now there is one
[406:41] (24401.28s)
thing that we would like to update in
[406:42] (24402.80s)
our code where we'll go under the
[406:44] (24404.80s)
package JSON we have some scripts right
[406:47] (24407.84s)
when we build our script before we uh
[406:50] (24410.72s)
run next build we're going to run this
[406:53] (24413.20s)
which is Prisma generate. So this is
[406:56] (24416.00s)
going to give us all the types that are
[406:58] (24418.24s)
coming from Prisma client so that our
[407:00] (24420.56s)
code doesn't crash in production. Okay.
[407:03] (24423.60s)
So with this let's go ahead add a
[407:05] (24425.36s)
commit. I'll say get add all get
[407:08] (24428.32s)
commit-m. You can also like you could
[407:11] (24431.04s)
have done this from here as well, but
[407:13] (24433.20s)
this time let's do it from the terminal.
[407:15] (24435.44s)
I'll say build script updated and let's
[407:20] (24440.48s)
say get push. We don't really need to
[407:23] (24443.28s)
start a review at this point. It's going
[407:25] (24445.20s)
to take 2 seconds.
[407:28] (24448.56s)
Let's refresh. We should be able to get
[407:30] (24450.80s)
the latest change. Okay. Now, we're
[407:33] (24453.84s)
going to go into the dashboard. And in
[407:35] (24455.76s)
the dashboard, we will just say deploy
[407:37] (24457.76s)
an application. So from here go ahead
[407:40] (24460.40s)
select your repository. In this case
[407:42] (24462.64s)
this is what I have. So I have selected
[407:45] (24465.36s)
it and the default branch is going to be
[407:47] (24467.36s)
master. In your case it could be main.
[407:49] (24469.60s)
Just don't touch it. Leave it as it is.
[407:51] (24471.76s)
Do not select any other branches. And
[407:54] (24474.24s)
I'll say enable automatic deployment on
[407:56] (24476.96s)
commit. Okay. So we can scroll leave
[407:59] (24479.60s)
everything as it is. For the resources I
[408:02] (24482.32s)
will use the $5 a month plan. But you
[408:05] (24485.28s)
don't need to worry about this because
[408:07] (24487.12s)
we have $50 free credits, right? I've
[408:10] (24490.80s)
been using this for like two months
[408:12] (24492.64s)
maybe and I still have $43.
[408:15] (24495.84s)
In your case, this could be 50. I just
[408:18] (24498.40s)
select this and say create. Do not say
[408:20] (24500.88s)
create and deploy because I'll show you
[408:23] (24503.20s)
something. So this is going to create
[408:26] (24506.00s)
the project for us and we would like to
[408:29] (24509.36s)
add some environment variables. Once we
[408:31] (24511.68s)
add it, then we are going to deploy the
[408:33] (24513.60s)
app. So here let's visit the NV file,
[408:37] (24517.12s)
copy everything and maybe just delete
[408:39] (24519.60s)
these codes under the database URL
[408:43] (24523.12s)
because we don't really need them. So we
[408:45] (24525.44s)
have clerk uh secrets, right? Clerk
[408:48] (24528.56s)
environment variables, database URL,
[408:51] (24531.12s)
VPY, admin email, recent and our front
[408:54] (24534.72s)
end application. So I will copy
[408:56] (24536.96s)
everything
[408:58] (24538.56s)
and paste it right here. Now actually
[409:02] (24542.32s)
before we paste it I'd like to show you
[409:04] (24544.08s)
something. So from here under the
[409:06] (24546.32s)
overview let's say visit the
[409:08] (24548.00s)
application. So we got a URL right under
[409:11] (24551.52s)
the environment variables once we paste
[409:14] (24554.32s)
everything instead of local host we are
[409:16] (24556.88s)
going to put this URL because we are in
[409:19] (24559.60s)
deployment there is nothing like local
[409:21] (24561.36s)
host. Instead this is our app URL. But
[409:24] (24564.56s)
just make sure to delete the very last
[409:28] (24568.32s)
slash. Okay. So, we don't need that
[409:30] (24570.32s)
slash. This is how it should be. And
[409:32] (24572.64s)
I'll say save.
[409:35] (24575.12s)
Now, hopefully we should be able to
[409:36] (24576.96s)
deploy this project. So, under the
[409:39] (24579.04s)
deployments, I'll say deploy now,
[409:42] (24582.56s)
which would take around maybe 2 minutes.
[409:45] (24585.44s)
And then we should be good to go. So, in
[409:47] (24587.76s)
my case, it took around 3 minutes. Let's
[409:50] (24590.32s)
say visit the application.
[409:52] (24592.88s)
And here we go. Now, let's wait for the
[409:55] (24595.28s)
images to be loaded. Looks like we
[409:57] (24597.52s)
cannot see it. Let's refresh once again.
[410:00] (24600.24s)
Maybe that's the first time thing.
[410:04] (24604.24s)
But now our our actual application is
[410:06] (24606.48s)
live on the internet. Okay, I think
[410:08] (24608.64s)
images are not loading. And I know the
[410:11] (24611.04s)
solution. Don't worry, we're going to
[410:12] (24612.56s)
fix it in a second. But that's the
[410:14] (24614.72s)
entire landing page. Let's try to login
[410:17] (24617.76s)
with our account. I'll go with my Google
[410:20] (24620.96s)
account
[410:22] (24622.56s)
which was this user. So once we log in,
[410:26] (24626.08s)
we should be navigated to the home
[410:28] (24628.40s)
screen.
[410:31] (24631.04s)
All right. So here we can see everything
[410:33] (24633.28s)
is working as expected. We can schedule
[410:36] (24636.00s)
a new appointment. We can get the data
[410:38] (24638.48s)
from the database. Uh but now the images
[410:41] (24641.44s)
are not really loading, right? Some of
[410:43] (24643.92s)
them loads, some of them doesn't. Now
[410:46] (24646.24s)
this is because we are using the image
[410:48] (24648.48s)
component. Let me show you this.
[410:51] (24651.68s)
We are using the image component and we
[410:53] (24653.84s)
are not deploying this application to
[410:55] (24655.92s)
Nex.js. So there are some stuff
[410:58] (24658.00s)
happening in the background and it
[410:59] (24659.84s)
cannot find it properly like images are
[411:03] (24663.20s)
not loading and the fix is pretty easy.
[411:05] (24665.68s)
We'll go under the next config.ts file.
[411:08] (24668.88s)
Let's close everything here under the
[411:11] (24671.28s)
images. So right after the remote
[411:13] (24673.68s)
patterns I'll say unoptimized to be
[411:16] (24676.24s)
equal to true. Now let's add a commit.
[411:19] (24679.04s)
I'll say clear everything.
[411:21] (24681.92s)
Get add all get commit-m
[411:27] (24687.20s)
let's say oops I don't know why this is
[411:29] (24689.76s)
scrolling down but let's say get
[411:32] (24692.00s)
commit-m
[411:34] (24694.24s)
next
[411:35] (24695.84s)
config yes update it.
[411:41] (24701.12s)
Okay I screw it up.
[411:44] (24704.72s)
Let me actually just add the commit
[411:46] (24706.24s)
message from here.
[411:50] (24710.88s)
commit this and sync the changes. Now
[411:53] (24713.52s)
what is going to happen is that Savala
[411:55] (24715.68s)
is going to see this latest commit.
[411:58] (24718.24s)
Right? So we just had one more commit
[412:01] (24721.36s)
where we updated the next config.ts.
[412:04] (24724.00s)
Savala is going to see it immediately
[412:06] (24726.64s)
and it's going to start a new
[412:08] (24728.08s)
deployment. As you can tell once this is
[412:11] (24731.04s)
done, our application should be live
[412:13] (24733.20s)
with the latest changes. So let's wait
[412:15] (24735.60s)
for this to be completed and we are
[412:17] (24737.36s)
going to test it out in production. And
[412:20] (24740.56s)
while this is deploying let's talk about
[412:22] (24742.80s)
why we are not deploying our application
[412:25] (24745.12s)
to somewhere like Versel. So this is an
[412:27] (24747.84s)
example just one of them. There are
[412:29] (24749.84s)
endless example like these where you
[412:32] (24752.24s)
have like $300 a month every single
[412:36] (24756.08s)
month right? This is your bill and just
[412:38] (24758.56s)
because you update something on your
[412:40] (24760.40s)
code, you end up with extra $3,000.
[412:45] (24765.04s)
So if you're not really an expert, if
[412:46] (24766.80s)
you don't know what you're doing
[412:48] (24768.16s)
exactly, I wouldn't deploy an
[412:50] (24770.32s)
application to pursel on the paid plan
[412:53] (24773.76s)
where on Savala everything is a lot more
[412:56] (24776.64s)
transparent and absolutely cheaper. And
[413:00] (24780.40s)
I think in their homepage they had a
[413:02] (24782.88s)
testimonial where the guy says I have
[413:05] (24785.60s)
gone from 3K to $650 per month which is
[413:10] (24790.40s)
like 78% off. Um so yeah that's one
[413:14] (24794.96s)
thing that I wanted to mention. Now
[413:16] (24796.96s)
let's see if our application is live.
[413:19] (24799.84s)
Looks like yes it is. I'll say visit the
[413:22] (24802.40s)
app. Now we should be able to see all
[413:24] (24804.80s)
the images without any let's say any
[413:29] (24809.68s)
errors right we had some errors let's go
[413:32] (24812.08s)
under the appointments let's see if we
[413:35] (24815.28s)
can see those images here we can see
[413:37] (24817.28s)
they are live here and let's try to sign
[413:40] (24820.00s)
out in the landing page we can also get
[413:43] (24823.52s)
those images now
[413:46] (24826.16s)
okay so as you can tell everything is
[413:48] (24828.08s)
working as expected when it comes to the
[413:50] (24830.48s)
images so let's try to log in with
[413:52] (24832.88s)
Google once again and we are going to
[413:55] (24835.20s)
call our AI agent and test out if it is
[413:58] (24838.64s)
going to work or not.
[414:03] (24843.52s)
Okay. So, I'll go to the voice page.
[414:06] (24846.32s)
Let's scroll to the bottom. We are on
[414:08] (24848.00s)
the paid plan by the way. That's why we
[414:10] (24850.08s)
can access it, right? We are in the $9 a
[414:13] (24853.04s)
month plan.
[414:14] (24854.96s)
I'll start the call.
[414:20] (24860.00s)
Let's give access to the microphone.
[414:25] (24865.60s)
>> Hi there, this is Riley, your dental
[414:27] (24867.92s)
assistant from Dentwise. I'm here to
[414:30] (24870.16s)
help you with all your dental needs. I
[414:32] (24872.24s)
can provide information about our
[414:33] (24873.60s)
service prices, give you immediate tips
[414:36] (24876.00s)
for dental pain or concerns, help you
[414:38] (24878.32s)
understand different treatment options,
[414:40] (24880.88s)
and share oral health prevention advice.
[414:43] (24883.84s)
What can I help you with today?
[414:46] (24886.32s)
>> Oh, yeah. Can you please provide me the
[414:48] (24888.64s)
services and fees that you have?
[414:52] (24892.16s)
>> Of course. I'd be happy to explain our
[414:53] (24893.68s)
service pricing. Here's what we offer.
[414:56] (24896.00s)
Regular dental checkup, $120.
[414:58] (24898.88s)
This includes a comprehensive oral
[415:00] (24900.72s)
examination, basic x-rays, and oral
[415:03] (24903.36s)
health assessment.
[415:04] (24904.48s)
>> All right, so I'll just end the call. As
[415:06] (24906.32s)
you can tell, this is working. Once we
[415:08] (24908.40s)
end the call, u as you can tell now as
[415:10] (24910.88s)
we speak and he doesn't really care
[415:13] (24913.84s)
what's what we say, right? Uh we can go
[415:16] (24916.24s)
under the appointments.
[415:18] (24918.16s)
We can have another appointment. Let's
[415:20] (24920.40s)
say with Dr. Jane. I'll have the teeth
[415:24] (24924.24s)
cleaning tomorrow. Let's say at this
[415:27] (24927.52s)
time.
[415:29] (24929.44s)
Confirm this booking.
[415:32] (24932.64s)
Once it is done, we should have this
[415:35] (24935.04s)
model and we're going to get an email in
[415:37] (24937.44s)
our inbox. And then we can I think visit
[415:41] (24941.44s)
the admin page.
[415:44] (24944.56s)
This is account that we are admin. So we
[415:47] (24947.84s)
are access so we so we can have the
[415:49] (24949.68s)
access to this page. Um here these are
[415:52] (24952.56s)
all the doctors that we have at the
[415:54] (24954.32s)
moment.
[415:55] (24955.84s)
I don't know why the images are not
[415:57] (24957.60s)
loading but they should be loaded
[415:59] (24959.84s)
eventually. I'll just refresh.
[416:04] (24964.40s)
We can add more doctors. We can edit
[416:07] (24967.04s)
them. Um here the images are a bit
[416:11] (24971.36s)
weird. Why it is not loading?
[416:15] (24975.68s)
Is this URL correct? Let's copy it and
[416:19] (24979.12s)
paste it here.
[416:23] (24983.92s)
Okay, so their service might be down.
[416:26] (24986.00s)
That's why it is not loading or my
[416:28] (24988.08s)
internet should be a bit right?
[416:30] (24990.96s)
It's really slow. But here we can see
[416:32] (24992.80s)
they're actually loading. Um, other than
[416:35] (24995.68s)
this, we can say confirmed or completed.
[416:38] (24998.80s)
Once you click to it, it updates the
[416:40] (25000.88s)
state. Let's make it to be confirmed.
[416:44] (25004.32s)
All right. So we can say they could be
[416:46] (25006.24s)
inactive, active.
[416:48] (25008.72s)
Everything is working I believe. So with
[416:51] (25011.12s)
this I think we have completed the
[416:52] (25012.96s)
entire project. I hope you enjoyed it
[416:54] (25014.96s)
from start till the end. We have built a
[416:57] (25017.68s)
really really good landing page. Right?
[417:00] (25020.80s)
Let's see it pretty quickly. Like this
[417:03] (25023.04s)
is one of the best landing pages that I
[417:06] (25026.56s)
think I have coded in a tutorial. It's
[417:09] (25029.52s)
really cool. looks like a modern startup
[417:12] (25032.72s)
and it doesn't really look like if you
[417:14] (25034.72s)
copied it or paste it. Right now I know
[417:17] (25037.76s)
in this tutorial we have some parts that
[417:20] (25040.48s)
just copy paste completely but they
[417:23] (25043.36s)
don't contain any logic at all right
[417:26] (25046.00s)
such as our landing page. So here we
[417:28] (25048.72s)
have all these components where we copy
[417:31] (25051.20s)
and pasted some of them but they don't
[417:34] (25054.16s)
have any logic and I hope you don't
[417:36] (25056.24s)
really mind it. So, with that, let me
[417:38] (25058.80s)
know what you think in the comments, and
[417:40] (25060.56s)
hopefully I'll see you in the next
[417:42] (25062.16s)
project.