Build a Calendly Clone – Full Stack Next.js, Typescript, React, Tailwind

freeCodeCamp.org · Beginner ·🌐 Frontend Engineering ·1y ago

Key Takeaways

Build a modern full-stack Calendly clone with Google Calendar integration using Next.js, React, TypeScript, and Tailwind CSS, featuring authentication, event management, and user interface design.

Full Transcript

Learn how to build a modern fullstack Calendarly clone with Google Calendar integration. The course uses Nex.js, React, TypeScript, Neon, and more. Norah Faulry created this course. Have you ever tried to schedule a meeting and ended up in a time zone you didn't know existed? You think you've booked a call for Tuesday, but your calendar actually saves it for next Thursday at 3:00 a.m. during your beach vacation? Yeah, we've all been there. Or maybe you've played the neverending game of email pingpong. Does Tuesday at 2 work for you? How about Wednesday at 4? Actually, how is 2037 looking? Well, the chaos stops here because today you're going to build and deploy your own scheduling app. One that helps people book meetings in just one click and no psyching powers required. Just like Calendarly, the scheduling tool loved by millions around the world. Now imagine building something like Calendarly, but you made it. That's right. Say hello to Calendarra if full stack scheduling app built to help people manage their time, create events, and let others book meetings with ease. And yes, nobody will get meeting reminders while they are in RAM sleep mode. Hello and welcome to our brand new course. I'm newer, a software engineer and the lead instructor at Programming Fluency and I'm super excited to guide you through this journey. In this course, you're going to build and deploy a Calendarly clone using NexJS5. And here is a quick look on what we are going to do. Start off strong with a sleek landing page that doesn't scream template from 2010. Set up authentication using Clerk so users can register, login, and feel like they own the place. Build beautiful nav bars because let's be honest, lost users don't book meetings. Create an events page where users can set up their own events. Build a schedule page so users can define their availability and look very professional. launch public profile pages so users can share links and let anyone in the world book meetings with them. And finally, we're going to add Google calendar integration using Google APIs so meetings actually show up where they should. And we're going to build all of this using TypeScript for clean and type safe code. React 19 for interactivity and seamless user experience. Tailwind CSS version 4 for styling because let's be honest, writing vanilla CSS is like writing code on a typewriter. Chaten UI components for that polished modern feel. Neon as our postgress database and Drizzle OM for type safe queries. By the end of this course, you won't just have another portfolio project. You're going to have real application that solves real problems and showcases your full stack skills. So, if you're ready to level up and build something impressive, come join me and let's build something amazing together. Hey everyone, before we jump into building our calendar app, let's take a quick peek at what we're going to make. This app is all about making it super easy to schedule meetings, keep track of availability, and stay on top of things. No more endless back and forth messages. So, let's check out how it all comes together. If a user is opening our application for the first time ever, they are going to see this landing page. And here they are going to get the option to sign in with Google. Of course, if you're using the calendar app, you have to have a Google account, okay? Because this app will be synchronized with the Google calendar. So, the only option any user will have to sign in or register a new account is using their Google account. Okay. So here I can choose to continue with Google. Sign in with Google or I can choose to sign up whatever. This will take me to the register page or I can sign in also. It's going to take me to the login page. See here this is the login routes and if I click on sign up it's going to take me to the register route. And of course as you can see our application is deployed to the internet. So, I'm going to continue with Google. Verify your human. Then after I sign in, I'm going to be redirected to the events page. Let me go to full screen. Okay, right now I'm viewing the events page. And as you can see, this is the navbar, the logo, those are the nav links. We have my events, my schedule, and public profile. And here we have the user button. If you click on it, you're going to get a user panel like that where you can manage your own account or you can even sign out. Let's sign out and let's log in one more time. All right, then. Now we are in the events page. Of course, we don't have any events yet. So, we have to create our first event to get started. But before we do that, let's take a look at the public profile page. Of course, since we don't have any events yet, it's going to tell me no events available at the moment. So, let's go ahead and create our first event. I'm going to click here on new event. Let's name that event hood and coffee chat. And here we can set the duration of the event for that event. I'm going to set it for 30 minutes. And here we can type any description we want. Let's say a casual hangout or developers. Let's correct that. Okay. Okay. Let's say a casual hangout for developers to talk about code, share ideas and get feedback on projects of course over coffee. So yeah, this is our first event. And the description is going to be optional, by the way. And you can set your event to active or not active. Let's set that event to active and then click save. Of course, you can cancel creating your event from here. And also you can click save to save your event. And there you go. We have our event here on the events page. I can edit the event in any way I want. I can make it not active. I can delete it if I want. Let's delete it. And we are back to not having any events. So right now let's recreate that event and let's click save again. All right. Then let's create some other events here. For example, I want to set that event to 1 hour 1 hour pair programming session. So if here I say 60 minutes and then click save, it's going to be translated to 1 hour here on the events page. And also here as you can see we have that event intro to JavaScript workshop. It's translated to 1 hour 24 minutes and so on. Now I want to create a 15minute intro call for 15 minutes. But let's say that um I want to make that event inactive for a while. Okay, let's say I'm not ready for a 15minute intro call these days, but I'm going to be ready for it in a few days. Okay, so let's click save. And as you can see when we go to the events page we get that event here without the copy link button because this event is not active. Let's add our last one here. This is a podcast guest interview. Going to make it 95 minutes. And this is the description. Click save. And there we go. Those are our events. Of course, we are having an event card for each event. So, right now, if let's say I copy that link, it's going to tell me link copied successfully. And right now, I want to send that link to anyone in order to be able to book that event with me. Okay? I like football, by the way. Let's say that I have some friends who like football as well and they want to book a 45 minutes with me to watch big games together. As I told you, I'm going to copy that link and let's say that I have a friend here. We're going to paste that link in their browser. Click enter. And of course, that friend doesn't have to be a user of our app. As you can see, they are directed to the event page without signing in. And this is, by the way, the navbar for the public view. We're going to have a public navbar which has login and register and a private navbar which we already saw. So right now, as you can see, let's say that my friend want to book that event with me. It's going to take him to the event page. And here it's telling him, "Book that event with Nakri." But at the moment, he's currently booked up. Please check back later or choose a shorter event. Okay. Why is that? Why he's seeing this message? because I didn't set my schedule yet. For now, I don't have any schedule. So, I'm going to go ahead to the my schedule page and let's say that I am available on Tuesdays from 12 p.m. to let's say 1600, which means 400 p.m. And let's say that I'm also available on Fridays. Okay, let's say from 9 to 5. Okay, I'm going to click save and right now I get a schedule saved successfully. So if I go back here to that page and my friend reloads his page, as you can see, my friend here gets a form where he can books that event with me. Here the time zone is going to be the time zone where the user actually in. Of course, I live in Cairo in Egypt. So this is why I'm getting the time zone. If someone lives in the United States, for example, they are going to get the United States time zone and so on. We're going to apply all of that logic ourselves. Okay, I hope you're excited. So here I'm going to pick a date. As you can see, only Tuesdays and Fridays are available. And we have that availability up to one year, by the way. Okay, we're going to make that availability schedule up to one year. So, right now we are in May 2025. We're going to get availabilities until May 2026. Okay. So, let's choose that day, Tuesday, 20th of May. And then here I'm going to select a meeting time. Of course, those are the available time slots that I have set in my schedule. I'm going to choose that time. Whatever. And here, let's say that my friend's name is Leo Messi. Okay. Of course, Messi is my friend. And here I'm going to create a temporary email. And then I'm going to copy that email and paste it here. And here, let's type anything. Let's say that Leo Messi is telling me, "Let's watch the FC Barcelona game together." And then Leo Messi is going to click on book event. He's going to get that interface and then he's going to be redirected to a success page saying successfully booked football match event whatever with me. And then we're going to tell him that he should receive a confirmation email. So let's go back here to our temp mail page. We should receive a confirmation email here. Okay, we're not getting the confirmation email because I'm using a production application and the application is not published yet in the Google API. So, I'm going to use the local project. I'm going to open my old project. By the way, this is the source code for the old project. I'm going to start the development server. I'm going to type mpm rundev. And I opened localhost 3000 here. This is the local development server. I'm going to sign in and I get my events here. So, I'm going to copy the link one more time and then I'm going to go back here, paste the link, but here I'm using the local development server, localhost 3000. There you go. We're getting the event page. Let's book the event one more time. Choose 20. Meeting time anything. Okay. 1:15 again. Name Leo Messi. Copy that email. Paste it here. Notes. Let's watch the FC Barcelona game or match together book event. Okay, hopefully we receive a confirmation email now. All right, then we get the confirmation email and in that confirmation email the event details. Right now, Leo Messi can choose to say yes. He's attending that email and then he's going to get that page from the Google calendar APIs from the from Google itself. And right now, let me go to my calendar. Choose May 20th. As you can see, the event is booked here on May 20th between Leo Messi and me. And this is the event details, football match, blah blah blah blah, whatever. And those are the notes that uh he has written to me. So yeah, it didn't work from the production app before because as I told you, we need to publish that production app in the Google APIs. I'm going to show you how to do it while we're developing the application. But right now, let's continue exploring our application. Here we've already seen the my schedule page. Let's check public profile page. Okay, right now I'm viewing my public profile. I'm going to get that message. This is how people will see your public profile. This is a button to copy the public profile URL. If I click on it, I get that toast message. Those are my public events. If I give that uh public profile link to anyone and they use that link, they're also going to view my public profile and then they can select whatever event they want and then book that event the same way we did before. So yeah, that's it. This is a pretty complex project, I know, but I promise you I'll teach you everything step by step so you don't feel any of these complexities and hopefully everything will be very simple to you. So, enough of me talking and let's get started. And now, let's bring the Calendra app to life. And we're going to kick things off by setting up our project. And since we're using NextJS15 as a full stack framework for Calendarra, the first step is to get Nex.js installed and ready to go. So let's create a new project from scratch. Create a new folder here in your desktop and let's name it Calendra app and then let's open a Visual Studio Code new window. and then open that folder using VS Code and then inside of VS code let's open our terminal and if we go to NexJS official documentation click on get started then choose installation you can see that in order to install a new NexJS project we're going to need to type that command mpx create next app at latest which is going to make sure that you have the latest version of NextJS. Right now the latest version is Nex.js 15 but let's say that you are viewing this video while NexJS 17 or 18 is out there. That command is going to make sure that you get that latest version. Okay. So let's copy that command and let's go back to our terminal and paste that command. But I'm going to add dot forward slash in order to install that project inside this directory. Okay, the calendarra app directory. Then press enter. Then you're going to get that error. There is a conflict here because the project want to create theist directory and we already have a do this directory here that was created automatically. So just delete that folder and then type the command again. Press enter. It's going to ask you several questions. Would you like to use TypeScript? Of course. Say yes. ESLint no. Tailwind CSS, of course. Source directory, no. App router, yes. Turbo pack, yes. Customize the import alias, no. Leave it as it is. And now it's installing the NexJS project. Installing dependencies like TypeScript, React, and so on. And there we go. We have our NextJS project. Now let's start the development server by typing mpm run dev. Press enter. It's telling me that port 3000 is in use. And this is because we have the development server running here in the old project. Let me close it. And then let's go back here. close the server and run it again. And right now it's running on localhost 3000. So let's open the browser, go to localhost 30,000 and this is the Nex.js official starter template. Of course, we're going to change all of that. So let's go here. First of all, let's delete all of these files inside of the public directory. And right now, we have an app directory. This is the homepage, the page.tsx file. This is the page that we're seeing right now. Let's remove everything from here. And for now, let's type Calendra app. Click save. We get Calendra app here. Let's test if Tailwind CSS is working. I'm going to add a class name. Let's say font bold text. Let's say 5x large text center. Click save. There you go. Tailwind CSS is working just fine. This is the layout file. It's going to contain the layout of our application, the global layout of our application. And this is the globals.css file which is going to include all the global styles for our application. And of course, this is the favicon file. We're going to delete it and we're going to create our own favicon later. Okay. So now let's go back quickly to our layout file and let's work on our meta data here which by the way is super important. Okay, if you want your application to be properly recognized and indexed by search engines like Google, Bing and so on. So here in the title I'm going to type Calendra. Click save. I should get it here. Okay, while it's reloading, let's remove that description. And I'm going to add a new description here. Description for search engines like calendarra is a simple and efficient calendar app and so on. And right now the title is reflected here calendarra. And if I inspect the page, go to head element here in the meta, the title is calendarra. And this is the description, the description that we wrote here. This one. And now our project is ready. And we can start working on our logic right away. Since we are developing a sensitive app like calendarra that will handle users meetings and schedule and it will integrate with their Google calendar which means we're going to need access to their Google account. Right? In that case we're going to need to develop a strong authentication logic that will handle all of this. And actually we're going to depend on a very strong service old clerk. So, let's go to the clerk documentation. Go to clerk.com and then sign in with your account or create a new account if you don't have one. I'm going to sign in with Google and there we go. This is the old project. And here I'm going to create a new application. Let's name it Calendarra and choose only to sign in with Google. Okay, that's good. Then click on create application. And then let's go to configure. And then here in the email phone username, we just want to add the ability to add a name here. Yeah, this will allow users to set their first and last name. So click save and let's go back here to overview. Then choose next.js. It's going to give you a guide on how to use Clerk and install it locally. First of all, we need to install Clerk. So, copy that command and let's go back to our terminal. Open a new terminal. And let's paste that command here. Press enter. And now clerk is installed. And now let's go back to the documentation. The second step that we need to do is to set up your clerk API keys. So copy also these environment variables and put it in your file. So let's go here to our project, create a new file and name it im and paste these variables inside of it. Click save. And then step number three is to update or create a middleware.ts file. So copy the name of the file from here and let's go back here, create a new file and name it middleware.ts. Then copy all of that code and paste it here. Click save. But here we're going to need to add some code because we want to protect the private routes and only allow access to public routes. So how are we going to do that? First of all, we're going to create a conest named is public route which is going to include all of our public routes. And we're going to use a method named create route matcher of course coming from clerk. And by the way, if you want to autoimp import anything in Visual Studio Code, just highlight the element or the function that you want to import and then click on your keyboard on control and space together. Then it's going to open a menu for you like that telling you that this method is nested inside that package at clerk slashnext.js forward slash server. Choose it and it's going to autoimp import it for you. And here instead of just saying export default clerk middleware, we're going to add this code to the clerk middleware function. We're going to give it a call back function that takes an oath and request. And inside of that callback function, we're going to say if the request the user is trying to make is not a public route, call oath.protect. Meaning that if this is not a public route, I need you to protect that route. Okay. But what's going to happen when clerk protects your private routes? It's simply going to redirect the user to the login or the register route. Okay. Now click save and close the middleware file. And then here in the M file, let's add these environment variables. The next public clerk signin URL. This is the sign-in URL forward/ login. And this is the signup URL forward slashregister. And here in the signin force redirect URL, we are redirecting the user after sign in to the events page. And we're going to do the same after the user signs up. Okay. Now, click save. And finally, our last step is to wrap our whole application in a clerk provider like that. So copy that and let's go to our global layout and let's wrap everything inside a clerk provider coming from clerk nextjs. There we go. Now click save. And now let's create the oath route. We're going to create it inside of the app directory in a route group like that. We're going to create parenthesis and say O. And if you're not familiar with the route groups, it simply prevents the folder name from being included in the routes URL path. Meaning that when I create a new folder under oath under that oath route group named login, I can go to login right away. I don't need to type O here. Okay, Nex.js is going to ignore the word O and use the route forward/ login right away. So here inside of the login folder, we're going to create another folder and we're going to catch all routes like that by typing square brackets wrapped inside another square brackets like that. and then type three dots like that. And then type login again. And then we're going to create another route for register. And we're also going to create a catch all route inside of it. Two square brackets like that. Then three dots or three periods. And type register. And inside of the login folder, we're going to create a page.tsx file. And also inside of register, we're going to create a page.tsx file. There we go. Now, let's create that login page. We're going to say export default function login page. And this function is going to return a next image like that coming from next image which is going to render our logo. So from where are we getting that logo? I'm going to go here to my old project. Go to public assets. I'm going to copy the assets folder and just paste it here in the public directory. And of course for you, you can find these assets, the logo, and the other icons. You can find them in the source code of this course. And you're going to find the link for the source code in the description of this course. Okay. So again, here in the login page, we are rendering the logo in a next image like that. And here we are rendering a signing component coming from clerk. It's a user interface component that will allow users to sign in to their accounts. And this is the styling for the page. Flex flex call items center padding five gap 10. And we're going to give it animate fade in. Let's create that animation. So we're going to go to our globals.css file. And let's go here and add this animation which is the animate fade in. Now click save. Let's go back to the login page. Click save as well. And now let's open our application. Go to forward slash login. And look at that. We have our login page. This is the logo. And this is the signin component. This is coming from clerk. And as you can see, there is a fade in animation. When I reload the page, this is coming from the animation that we just created. And let's also add that animation to all of our application, which means that we're going to go to the global layout file and add that animation animate fade in. Click save. And now let's go ahead and create the register page. Right now if I go to register, it's going to tell me not found or that error. It's simply because we didn't create that page yet. So let's go to the register page. And by the way, if you want to go to any file in your u in your application in Visual Studio Code, there is a shortcut. You can click control plus the letter P and then type the name of the file like in that case I did type register and then choose that file and then here let's create the register page. We're going to say export default function register page which is going to look exactly like the login page. It's going to render a next image to display the logo but here we're going to render a sign up component not sign in also coming from clerk and everything else is the same. Now click save and reload the register page. And there we go. We have the register page here. If I click here on sign in, it's going to take me to the login page. If I click here on sign up, it's going to take me back to the register page. All right. Now that we've got the foundation set up and authentication ready to go, it's time for the fun part, which is designing our landing page. This is the first thing users will see. So, let's make it clean, welcoming, and visually engaging. Let's bring some personality to Calendarra and make a great first impression. First of all, we're going to go to the homepage. Let's remove that and call this home page. And let's remove that. And also that and here we're going to say if there is no loggedin user return a landing page component. Okay. But from where are we getting that user? How are we going to know if there is a loggedin user or not? Simply here we're going to say connest user equals to await current user coming from clerk nextjs server and this is a function we have to call it and since we are using a wait here we have to make this an asynchronous function and of course we're going to define that landing page in a minute. So again here we're saying if no user render the landing page otherwise we're going to redirect the user to the events page and we're going to use a method from nextjs called redirect and that redirect is going to come from next navigation like that. Now let's create that landing page component. Copy that and let's go here to the root directory. create a new folder and name it components. And inside of it, let's create a new file and name it landing page.tsx. And now let's create our landing page. First of all, we're going to make this file as a client component by typing use client at the top of the file. And then here we're going to say export default function landing page. And here I want to return a main element like that that has these styles flex item center padding of 10 gap 24 animate. And in the small screens, medium screens, and below, we're going to have a flex call. Okay. And then here I'm going to render a section that will have heading, subheading, and an illustration image. Also, it's going to have these styles. And then here we want to render the logo. So we're going to render a next image coming from next image. Its source is going to be the logo file. Now, let's save all of that. Import the landing page in order to see our progress. Save and go to the homepage. There we go. This is our logo. Of course, we are not logged in. This is why we are seeing the landing page component. Then under the image here, we want to render a main heading. Saying your time perfectly planned. Let's save. Okay. Nice. Of course, that heading is going to have these styles. And below it here, we're going to have a subheading displaying the text. Save. Okay. Nice. And here we're going to have our illustration image. This is also coming from our assets. Again, I'm reminding you, you can find these assets in the source code and you can find the link for the source code in the description of this course. Okay, click save. There we go. I'm getting the illustration. And then we're going to go below that section and we want to render the clerk signin component. Of course, that component is coming from clerk next.js. And here we are using a theme from clerk. But we have to install clerk themes first. If you go here to clerk documentation, go to docs and search for themes. You can see that clerk has many themes like dark themes, shades of purple, neo brutalism, and so on. So, in order to be able to use those themes, we're going to need to type that command. So, copy it and let's go here, open the terminal, and paste that command. Press enter. And there we go. Now I can import that theme from clerk themes. Now if I click save, we can see the sign-in component here. And as you can see, it looks different from the components that we render in the sign in and register pages because we're using a clerk theme here. And here we are saying routing hash because we're using the sign-in component in a route that's not a catch all route. Let me show you what I mean. Let's comment that for now. Click save. And we're going to get an error here. See, we have one issue here. It's telling you that the signin component is not configured correctly. And this is because the home route is not a catch all route. Before when we used signing and register components here remember we defined those routes as catch all routes and that's why we were able to use that component without the routing hash property. But here since the home route is not a catch all route we have to say routing equals to hash. And there we go. This is our landing page. Of course, it's displayed as flex call here because this is a small screen. Let's make it large. And we get our landing page here. And now that we've laid the foundation of our app, it's time to set up the database for this project. And we're going to be using Neon as our Postgress provider. and we're going to use Drizzle OM for type safe developer friendly way to work with our database. So first of all let's go to Neon. Log into your account or create a new account and then let's create a new project from here. Let's call it Calendra. Then click create and then click on connect. And then we're going to need that connection string. So copy it and then let's go to our project to the file. Then paste that connection string here and we're going to save it in an environment variable named database URL. Now click save and you're good to go. You've created in your own project very easily. Now let's set up Drizzle. Go to their website, search on Google for driorm or go to orm.driel.team and then click on get started, then choose neon, and then scroll down here until you find the installation commands. And then we're going to need to install these packages. Okay, so first of all, copy that command. Leave out the M. We don't need it. Copy that. And then let's go back to our app and paste that command. Press enter. Now it's installed. And then also let's go here and copy that command and leave out that tsx also. We don't need it. And paste it here as well. Then press enter. And now we are all set up. Let's close all files and let's go here to the home directory and create a new folder and let's name it Drizzle and then inside of that Drizzle folder. We're going to add a new file. Let's name it DB which is short for database.ts and this file is going to be responsible for establishing the connection with our database. Okay, press enter. And then first of all, we're going to need to initialize the neon client using the database URL from our environment variables or from our in file. This is why we're going to say connestql equals to neon. And that neon is going to come from that package at neon database forward slashs serverless. And then after that we're going to need to create and export the drizzlem instance with the neon client and schema for type safe queries. How are we going to do that? We're going to export a conest named db which stands for our database equals to drizzle and we're going to get that drizzle from that package drizzle o neon http. So here we're going to initialize that drizzle instance using the SQL conest that we defined earlier and its second argument is going to be an object taking our schema. Okay. But from where are we getting that schema? This is why we're going to need to create a new file here under drizzle named schema.ts. And before creating our schema, let's import it here in the db.ts file. We're going to say import all as schema from schema forward slash schema. Click save. And now let's create our schema. Inside of this schema, we're going to need to create our tables, our database tables. And obviously, we're going to have several tables, right? So the first table is going to be the events table. It's going to have fields like name, description, and duration. So in order to create an event table, we're going to say export const event table equals to something called PG table. And this is a function. And that PG table stands for postgress table and it's going to come from Drizzle OM Postgress core. And this function is going to take two arguments. The first one is going to be the name of the table, the name of the table in the database which is events. And the second one is going to be an object which is going to contain all of our fields. And the first field is going to be ID. Of course, each item in any table in any database in the world should have a unique ID. And this is why we're using a package called UIU ID or a function actually called YU ID. Also, it's going to come from Drizzle or MPG core. So this function YU ID is going to give a unique ID to each item in the events table. So as you can see here, unique ID with default YU ID. And then we're going to call that function primary key which is going to make that ID the primary key of that table. And then we're going to say default random and that function is going to automatically fills this column with a randomly ID if no value is provided. So again this is our first field. The next field is going to be name which is going to be a text also coming from Drizzle OM Postgress core. Okay, this is the event name and not null means that this field is required. It can't be null and that name means that the field is going to have the name name. Okay. And here we're going to provide a description field which is also going to be a text with the name description and it's going to be an optional description since we didn't say not null. And then we're going to need to define the duration of the meeting in minutes. So it's going to be an integer coming from also drizzle or mpg core. This is going to be its name and also it's going to be required. It can't be null. And then we're going to need the clerk user ID which is the ID of the user who created it from clerk. We're going to take his clerk user ID. It's going to be a text. Its name is clerk user ID not null. So you know the drill. And then we're going to need to know if that event is active or not. So we're going to say is active is a boolean and that boolean also coming from PG core and its default value is true. And then finally we're going to need to know when that event was created or updated. And since we're going to use those two variables a lot, we're going to define them here in the top. We're going to say cost created at equals to a time stamp. It's a time stamp coming from Drizzle or MPG core. It's required and we're gonna default it to now. The default value is going to be set to now. Okay. And then we're going to say conest updated at equals to a timestamp also. Its name is updated at it's required. It's going to be default to now. And on update meaning that whenever it's updated, we're going to return a new date. So it's going to automatically updates to current time whenever an update happens. Okay. Now click save. And then we're going to need to create an index for faster wearing. We're going to need to create an index on that table for faster quering. So we're going to add a third argument here saying take the table and then index it using the table.clerk user ID by using that on function and we're going to give it that name clerk user ID index and then we're going to import this function also from drizzlepg core. And now I think we're done with this event table. And the second table that we're going to need to create is schedules table. We're going to need to define schedules table one per user with a time zone and time stamps. So you know the drill. We're going to say export con schedule table equals to a pg table named scheduled which is going to have ID, time zone, clerk user ID created at and updated at. And then our next table that we're going to need to define is schedule availabilities which is going to store available time slots per day. So also we're going to say export connest schedule availability table equals to a pg table that has ID schedule ID. But that schedule ID is going to be a foreign key from the schedule table. And this is why we're going to need to say dot references the schedule table dot id and then we're going to say ondee cascade meaning that we're going to need to cascade delete when schedule is deleted. So if a schedule is deleted from the schedule table the related schedule availability table item is going to be deleted with it. Okay. And each availability is going to have a start time and an end time which is going to be text and they are going to be required. And then of course we're going to need the day of the week. Okay, it's going to be equals to a schedule day of week in. So let's define that. We're going to go up here and define a postgress in for the days of the week. Of course, this is short for enumeration. And we're going to use a function named PG enum coming from PG core. Drizzle MPG core which is going to have the name day. And here inside of the table, we're going to name it that day of the week. We're going to name it day of week, but the enum itself is going to have the name day. And we're going to get that these values for that enum from a conest or a variable named days of week. But from where are we getting that conest? Actually we're going to create a folder here named constants. And inside of it we're going to create a new file named index.ts. And here we're going to say export const days of week in order. And then we're going to provide an array that contains each day of the week. And we're going to export it as const meaning that each value of these values is going to be a constant itself. Save it. Close that file. And now let's import it. So again here we're creating an enum in our Postgress database that has the name day which only accepts values from that days of week in order. Okay. So it can only take values from this array. It's like telling my database create an enome type called day that only have one of those values. And now if we go back to the schedule table and take a look at it, you can imagine that each schedule table can have many availabilities, right? And this is called a relation. In the world of databases, this is called a relation. And this is why we're going to need to create a relation between these two tables. So let's go here below the schedule table and create that relation. We're going to define a relationship for the schedule table in which a schedule has many availabilities. So we're going to say export conest schedule relations equals to relations. This is a function coming from Drizzle RM which is going to create a relation between the schedule table and the schedule availability table and that relation is going to have the name availabilities and of course it's a one to many relationship and this is why we're saying here many and here also we're going to call the many function. So in short, this creates a one to many relationship between the schedule table and the availabilities table. But is this the only relation that we're going to need to create? Actually, no. We're going to go down here and create the reverse relationship in which each availability belongs to one schedule. And here the fields are going to be schedule availability table dot schedule id which is going to reference the schedule table do id which is the foreign key. So to sum up this schedule relations will create a relation between a schedule and the schedule availability table in which each schedule can have many availabilities. And this schedule availability relations sets up a reverse relation where we can go from a time slot back to the full schedule it belongs to. I hope it's clear by now. Now we're done with our schema. Click save. And finally, we're going to need to go to the package.json file and then go to the scripts and add those three scripts DB generate which is going to generate our schema and then DB migrate which is going to migrate our schema and then that DB studio is going to open an interface to view our database. But before we run these commands, we have final thing to do, one final thing to do, which is to create a Drizzle config file. So let's go to the home directory, create a new file, and name it drizzle.config.ts. And then let's add this code to it. We're going to define our database URL variable from our M file. And then we're going to say if the database URL doesn't exist through a new error. And then this is just a configuration code. We're defining the path to our schema, the directory where Drizzle will output migration files. We're specifying that we're using Postgress SQL. We're enabling strict mode for stricter validation and type safety. Here we are enabling logging. And here we are passing in the database credentials which is the database URL the connection string. Now click save and let's run this command. Copy it. Open the terminal and type mpm run and then type db colon generate. Press enter. And it was generated successfully. As you can see, a new folder inside Drizzle named migrations was created. And now let's copy the migrate command. And type mpm run db migrate which is going to migrate our schema. If you open this file here, you can see that the drizzle code that we wrote has been translated into Postgress SQL here. And finally, we're going to run this command. MPM run db studio mpm run db studio. Press enter. And it's telling you that you can view your database from this URL. Let's click on it. And this is our database. We have three tables. Events which has ID, name, description, and so on. Schedule, availabilities table, and schedule table. And by the way, we can view it also from here in our Neon dashboard. Go to your project and go to tables. And also you can see your tables here. And now our database is ready. Let's move on to the next step. And now that our database is all set up, let's head back to the front end and start by creating two navigation bars. one for the public view for people who are not signed in and another one for authenticated users. First of all, we're going to need to create a route group here named main. Okay, created under the app directory. And that main directory will contain all of our main logic of the app. So first of all let's create a layout file for it. Inside of it let's create a layout dsx. So here let's create that layout export default asynchronous function main layout. And of course since this is a layout it's going to take children as its props. We're going to dstructure the children from the props object. And that children is going to be of type react note. And here let's return a main element which is going to have a class name of relative. And here simply we're going to render the children meaning that that layout is going to render all the pages inside of it. Okay. Okay. But where are we going to render our nav bars? Actually, we're going to render them here on the top of the page. Right. So, here we're going to need to render a private navbar if user exists. Otherwise, render a public nav bar. And this means that we're going to need to get the user instance. So we're going to say connest user equals to current user coming from clerk nex.js server and then here simply we're going to say if the user exists render the private nav bar otherwise render the public nav bar. And here of course Typescript is complaining because we're going to need to say await here. And of course here we're going to need to create those two components, the private and the public nav bars. And let me start by creating the public navbar. Okay, since right now we are signed out, we didn't sign in yet. So let's create the public navbar in order to see it. So copy that and then go to components, create a new file, name it public navbar.tsx. tsx and then here we're going to say export default function public navbar that's going to return a nav element of course since this is a navbar and that nav element is going to have those styles and now since we want to see our public nav bar updated live we're going to create the book crowd But for now, the page is not found. So, let's go here to the main route. And under it, we're going to have a route group named public inside parenthesis like that. And inside of public, let's create another folder named book. And inside of it, let's just create a new file for now named page.tsx. tsx and here let's create anything for now let's say export default page function page that's going to return h1 saying book page and here let's comment that in the layout file and just render the public navbar import it. And now let's save here. So there we go. We have our navbar here. This is the nav element styled like that. As you can see, it has a shadow shadow to x large. It has a gray background, dark gr dark dark gray background, which is gray 300 and so on. So let's put elements inside of it like the logo and the login and register buttons. Here we're going to put the logo which is going to render a next link coming from next link. And here it's going to render a next image coming also from next image. And that image of course is going to render the logo. Now click save. As you can see, this is our logo. And of course, that logo is going to point to the login page here. See? And here we want to render the nav links which will contain the login and register buttons. Here we're going to render a component from clerk named sign in button. This component will point directly to the login page. When the user clicks on that button, it's going to point automatically to the login page. Okay. So, let's import it from clerk. And here we are rendering a button from shad cen. We're going to install chat cnen in a minute. But let's also import the sign up button which will point to the register page. Okay. Now let's install that chat cm button. Let's go to chat UI and let's go to getting started installation next.js and then copy that command. No, sorry, the mpm command. Yeah, copy that command and let's go back to our terminal and paste that command. Press enter. Choose the neutral color or whatever color you want. Then choose use force because here as you can see there is an issue that has been going for a while now because of React 19. We are using React 19 and this will cause a peer dependency issue stuff like that. So in order to fix it just choose to use dash force. Press enter here. And now shad cenne is installed. After you install shad cen, you're going to see that a file named components. I think components um json. Yeah, that file. This file has been added to you by shaden. Okay, leave it as it is. And also you can see that ellip folder is added. Yeah, here that has a utility file and that utilities file will contain this function named CN. This function is created by shad CN that will allow us to add dynamic class names to HTML elements. Okay, we're going to use it a lot in our project. So, let's close that file for now and let's add the shad cm button. Okay, how are we going to do that? Search in the documentation for button. Yeah, this one. This is the button that we want. All we're going to need to do is to type MPX chat at latest add button. So, copy that command and just paste it here and press enter. also use d-force and now the button component has been added under components UI button.tsx. So if we go here to the components folder you can see that a UI folder has been added and inside of it this is our button component. Okay, this is created by chat. So, all we're going to need to do here is to import that button component from UI button. And there you go. This is our public navbar. Oh, it looks like chaten has um messed up some of our styles. Let's see what's going on. Let's see the globals. CSS file. So yeah, I think that Shatn added that Tailwind animate CSS, but we don't have it yet. So let's install it. Copy that. Open the terminal and type mpm install that package. Tailwind animate CSS. And now let's reload the page. And there you go. This is our public navbar. If I click here on the login button, it's going to redirect me to the login page. And if I click on the register button, it's going to redirect me to the register page. Yeah, this one. So yeah, this is our public navbar. And now let's go ahead and create the private navbar. Close that. Also close that. And here we're going to need to create that private nav bar. Let's go to components. Create a new file private navbar.tsx. And here export default function private navbar. And here before we do anything, let's sign in in order to see the private navbar. It's signing in. verify your human. Okay, nice. Now we are signed in and redirected to the events page. Remember here in the f file we said that after sign in force redirect to the events page. So also let's create um a temporary events page for now. Inside of main, we're going to create a new route group named private. And inside of private, we're going to create another folder named events. And inside of events, let's create a page.tsx file. And here, let's say export default function page. That's going to return H1 saying events. Now click save. And there we go. We have the events page here. And now let's continue creating the private navbar. Also this component is going to return a nav element of course which is going to have these class names. And here also we're going to render the logo. Nothing different. Import the next link. Also the image. Click save. And of course here we are we are still seeing the private navbar because here we're going to have to revert back to this logic. So uncomment that. Right now we have a user. So if we import the private navbar, we should see it here. There you go. This is the private navbar. So here below the logo, we're going

Original Description

Build and Deploy a modern, full-stack Calendly clone with Google Calendar integration—manage timezones, events, and meeting links like a pro using Next.js 15, Typescript, React 19, Tailwind CSS v4, Neon, Drizzle, Clerk and much more. This course is brought to you by Noor Fakhry, founder of @Programming-Fluency Code: https://github.com/Programming-Fluency/Calendra-Course Noor's Discord Community: https://discord.com/invite/AgAKRtbXsp ❤️ Try interactive Full Stack courses we love, right in your browser: https://scrimba.com/freeCodeCamp-Fullstack (Made possible by a grant from our friends at Scrimba) Course Sections 0:00:00 - Intro 0:03:33 - A First look at Calendra 0:18:04 - Project Setup 0:24:28 - Authentication 0:35:41 - Landing Page 0:42:33 - Database Setup 1:01:32 - Navigation Bars 1:18:43 - Events Page 1:21:45 - New Events Page 1:24:07 - Event Form 1:40:50 - onSubmit & Events Actions 2:09:47 - Event Card 2:24:51 - Edit Events Page 2:32:54 - Favicon 2:34:26 - Schedule Feature 2:38:20 - Schedule Form 2:55:21 - Schedule onSubmit Function 3:06:01 - book route 3:10:24 - PublicProfile Component 3:18:08 - Google Calendar API 3:27:28 - Booking Events Page 3:29:54 - Get valid times from schedule 3:50:17 - Meeting Form & Actions 4:13:48 - Deployment 4:19:35 - Thank You ❤️ 🎉 Thanks to our Champion and Sponsor supporters: 👾 Drake Milly 👾 Ulises Moralez 👾 Goddard Tan 👾 David MG 👾 Matthew Springman 👾 Claudio 👾 Oscar R. 👾 jedi-or-sith 👾 Nattira Maneerat 👾 Justin Hual -- Learn to code for free and get a developer job: https://www.freecodecamp.org Read hundreds of articles on programming: https://freecodecamp.org/news
Watch on YouTube ↗ (saves to browser)
Sign in to unlock AI tutor explanation · ⚡30

Playlist

Uploads from freeCodeCamp.org · freeCodeCamp.org · 0 of 60

← Previous Next →
1 React: Production Server Setup Part 2 - Live Coding with Jesse
React: Production Server Setup Part 2 - Live Coding with Jesse
freeCodeCamp.org
2 cookies vs localStorage vs sessionStorage - Beau teaches JavaScript
cookies vs localStorage vs sessionStorage - Beau teaches JavaScript
freeCodeCamp.org
3 Browser history tutorial - Beau teaches JavaScript
Browser history tutorial - Beau teaches JavaScript
freeCodeCamp.org
4 Graph Data Structure Intro (inc. adjacency list, adjacency matrix, incidence matrix)
Graph Data Structure Intro (inc. adjacency list, adjacency matrix, incidence matrix)
freeCodeCamp.org
5 React: Parameterized Routing with Next.js - Live Coding with Jesse
React: Parameterized Routing with Next.js - Live Coding with Jesse
freeCodeCamp.org
6 React: Dealing with jQuery Issues - Live Coding with Jesse
React: Dealing with jQuery Issues - Live Coding with Jesse
freeCodeCamp.org
7 setInterval and setTimeout: timing events - Beau teaches JavaScript
setInterval and setTimeout: timing events - Beau teaches JavaScript
freeCodeCamp.org
8 Browser and Device Testing - Live Coding with Jesse
Browser and Device Testing - Live Coding with Jesse
freeCodeCamp.org
9 Last Minute Updates - Live Coding with Jesse
Last Minute Updates - Live Coding with Jesse
freeCodeCamp.org
10 Post Launch Updates - Live Coding with Jesse
Post Launch Updates - Live Coding with Jesse
freeCodeCamp.org
11 React: Setting Up Google Analytics - Live Coding with Jesse
React: Setting Up Google Analytics - Live Coding with Jesse
freeCodeCamp.org
12 React: Masonry Layout - Live Coding with Jesse
React: Masonry Layout - Live Coding with Jesse
freeCodeCamp.org
13 Load Balancing Digital Ocean Droplets - Live Coding with Jesse
Load Balancing Digital Ocean Droplets - Live Coding with Jesse
freeCodeCamp.org
14 try, catch, finally, throw - error handling in JavaScript
try, catch, finally, throw - error handling in JavaScript
freeCodeCamp.org
15 Load Balancing: SSL Passthrough Setup - Live Coding with Jesse
Load Balancing: SSL Passthrough Setup - Live Coding with Jesse
freeCodeCamp.org
16 Graphs: breadth-first search - Beau teaches JavaScript
Graphs: breadth-first search - Beau teaches JavaScript
freeCodeCamp.org
17 React: Masonry Layout Part 2 - Live Coding with Jesse
React: Masonry Layout Part 2 - Live Coding with Jesse
freeCodeCamp.org
18 React: WordPress API Live Search - Live Coding with Jesse
React: WordPress API Live Search - Live Coding with Jesse
freeCodeCamp.org
19 Creating WordPress Custom Post Types - Live Coding With Jesse
Creating WordPress Custom Post Types - Live Coding With Jesse
freeCodeCamp.org
20 Dates - Beau teaches JavaScript
Dates - Beau teaches JavaScript
freeCodeCamp.org
21 Miscellaneous Front End Updates - Live Coding with Jesse
Miscellaneous Front End Updates - Live Coding with Jesse
freeCodeCamp.org
22 Merging a Pull Request from GitHub - Live Coding with Jesse
Merging a Pull Request from GitHub - Live Coding with Jesse
freeCodeCamp.org
23 React + Prettier + Standard JS - Live Coding with Jesse
React + Prettier + Standard JS - Live Coding with Jesse
freeCodeCamp.org
24 React: Sortable Responsive Table - Live Coding with Jesse
React: Sortable Responsive Table - Live Coding with Jesse
freeCodeCamp.org
25 Geolocation Sorting by Distance - Live Coding with Jesse
Geolocation Sorting by Distance - Live Coding with Jesse
freeCodeCamp.org
26 Tradeoff Matrix - Agile Software Development
Tradeoff Matrix - Agile Software Development
freeCodeCamp.org
27 The Definition of Ready - Agile Software Development
The Definition of Ready - Agile Software Development
freeCodeCamp.org
28 Getting first React job without experience - Ask Preethi
Getting first React job without experience - Ask Preethi
freeCodeCamp.org
29 React: Google Analytics Click Tracking - Live Coding with Jesse
React: Google Analytics Click Tracking - Live Coding with Jesse
freeCodeCamp.org
30 Submitting a PR to an Open Source Project - Live Coding with Jesse
Submitting a PR to an Open Source Project - Live Coding with Jesse
freeCodeCamp.org
31 Should I go back to school to get CS degree? - Ask Preethi
Should I go back to school to get CS degree? - Ask Preethi
freeCodeCamp.org
32 Hero Section CSS Changes - Live Coding with Jesse
Hero Section CSS Changes - Live Coding with Jesse
freeCodeCamp.org
33 Working Agreement - Agile Software Development
Working Agreement - Agile Software Development
freeCodeCamp.org
34 A day at Pennybox with Co-Founder Reji Eapen
A day at Pennybox with Co-Founder Reji Eapen
freeCodeCamp.org
35 React: Sorting and Filtering Data - Live Coding with Jesse
React: Sorting and Filtering Data - Live Coding with Jesse
freeCodeCamp.org
36 React: Sorting and Filtering Data Part 2 - Live Coding with Jesse
React: Sorting and Filtering Data Part 2 - Live Coding with Jesse
freeCodeCamp.org
37 React: Building a New UI - Live Coding with Jesse
React: Building a New UI - Live Coding with Jesse
freeCodeCamp.org
38 Definition of Done - Agile Software Development
Definition of Done - Agile Software Development
freeCodeCamp.org
39 Getting started with jQuery (tutorial) - Beau teaches JavaScript
Getting started with jQuery (tutorial) - Beau teaches JavaScript
freeCodeCamp.org
40 Making a React Blog with WordPress Content - Live Coding with Jesse
Making a React Blog with WordPress Content - Live Coding with Jesse
freeCodeCamp.org
41 React, NextJS, CSS - Live Coding with Jesse
React, NextJS, CSS - Live Coding with Jesse
freeCodeCamp.org
42 jQuery events - Beau teaches JavaScript
jQuery events - Beau teaches JavaScript
freeCodeCamp.org
43 React/NextJS Routing and WordPress API Custom Types - Live Coding with Jesse
React/NextJS Routing and WordPress API Custom Types - Live Coding with Jesse
freeCodeCamp.org
44 React: Working with API Data - Live Coding with Jesse
React: Working with API Data - Live Coding with Jesse
freeCodeCamp.org
45 React: Refactoring Components - Live Streaming with Jesse
React: Refactoring Components - Live Streaming with Jesse
freeCodeCamp.org
46 jQuery effects - Beau teaches JavaScript
jQuery effects - Beau teaches JavaScript
freeCodeCamp.org
47 More React Refactoring - Live Coding with Jesse
More React Refactoring - Live Coding with Jesse
freeCodeCamp.org
48 animate in jQuery - Beau teaches JavaScript
animate in jQuery - Beau teaches JavaScript
freeCodeCamp.org
49 "Finishing" My React Site - Live Coding with Jesse
"Finishing" My React Site - Live Coding with Jesse
freeCodeCamp.org
50 Starting a New React Project (P2D1) - Live Coding with Jesse
Starting a New React Project (P2D1) - Live Coding with Jesse
freeCodeCamp.org
51 React Project 2 Day 2: Learning Material UI - Live Coding with Jesse
React Project 2 Day 2: Learning Material UI - Live Coding with Jesse
freeCodeCamp.org
52 The Agile Manifesto - Agile Software Development
The Agile Manifesto - Agile Software Development
freeCodeCamp.org
53 jQuery: get and set with http, text, val, and attr - Beau teaches JavaScript
jQuery: get and set with http, text, val, and attr - Beau teaches JavaScript
freeCodeCamp.org
54 React Project 2 Day 3 - Live Coding with Jesse
React Project 2 Day 3 - Live Coding with Jesse
freeCodeCamp.org
55 The INVEST approach to product backlog items
The INVEST approach to product backlog items
freeCodeCamp.org
56 React Project 2 Day 4 - Live Coding with Jesse
React Project 2 Day 4 - Live Coding with Jesse
freeCodeCamp.org
57 Chickens and Pigs - Agile Software Development
Chickens and Pigs - Agile Software Development
freeCodeCamp.org
58 React Project 2 Day 5 - Live Coding with Jesse
React Project 2 Day 5 - Live Coding with Jesse
freeCodeCamp.org
59 jQuery: add and remove DOM elements - Beau teaches JavaScript
jQuery: add and remove DOM elements - Beau teaches JavaScript
freeCodeCamp.org
60 React Project 2 Day 6 - Live Coding with Jesse
React Project 2 Day 6 - Live Coding with Jesse
freeCodeCamp.org

Build a modern full-stack Calendly clone with Google Calendar integration using Next.js, React, TypeScript, and Tailwind CSS. Learn how to implement authentication, event management, and user interface design.

Key Takeaways
  1. Create a new Next.js project with TypeScript and React
  2. Set up authentication using Clerk
  3. Implement event management with Drizzle and Postgres
  4. Design a user interface with Tailwind CSS
  5. Deploy the application
💡 Using Clerk for authentication and Drizzle for type-safe database queries can simplify the development process and improve the overall security of the application.

Related Reads

Chapters (25)

Intro
3:33 A First look at Calendra
18:04 Project Setup
24:28 Authentication
35:41 Landing Page
42:33 Database Setup
1:01:32 Navigation Bars
1:18:43 Events Page
1:21:45 New Events Page
1:24:07 Event Form
1:40:50 onSubmit & Events Actions
2:09:47 Event Card
2:24:51 Edit Events Page
2:32:54 Favicon
2:34:26 Schedule Feature
2:38:20 Schedule Form
2:55:21 Schedule onSubmit Function
3:06:01 book route
3:10:24 PublicProfile Component
3:18:08 Google Calendar API
3:27:28 Booking Events Page
3:29:54 Get valid times from schedule
3:50:17 Meeting Form & Actions
4:13:48 Deployment
4:19:35 Thank You ❤️
Up next
How to Speed Up Your WordPress Website with WP Rocket ⚡Tutorial 2026
Matt Tutorials
Watch →