Completing the Select Date Page
Completing the Select Date Page
Get the project source code below, and follow along with the lesson material.
Download Project Source CodeTo set up the project on your local machine, please follow the directions provided in the README.md
file. If you run into any issues with running the project source code, then feel free to reach out to the author in the course's Discord channel.
Lesson Transcript
[00:00 - 00:23] So we're starting to reach a point where we have all the fundamental architectural blocks in place. What I'm really hoping to accomplish with this course is to give you a sort of foundation so that you will feel that you have templates or examples for building anything you might need to run into in your product, be that logic or UI or whatever else.
[00:24 - 00:33] But still, let's try and actually make our app do something. And since we're building a booking app, we need to be able to show the user which time slots are available.
[00:34 - 00:43] So an available time slot is one where either one or more providers don't have a booking. So this involves a little bit of set theory.
[00:44 - 00:57] And this logic could reside either in the database, on the backend or on the front end. And since we're not really dealing with very privileged information here or anything, I really don't have much of a preference for where it should reside.
[00:58 - 01:12] I feel like all three, there are cases for all three. What we're going to be doing is actually something of a hybrid because we're going to be doing something in the database and then we're going to be doing a little bit in the front end as well.
[01:13 - 01:29] So on the database, we're going to be creating a series of time slots where we divide each day into an interval of three hours or intervals of three hours. And then we're going for each provider to specify whether or not that time slot has been booked.
[01:30 - 01:53] So we're going to send this information to our front end and then we're going to let the front end decide to show whether a time slot is available or not by reducing this. So since this endpoint involves quite a bit of code, I'm going to write it in a helper function so that we don't put all of it directly into our endpoint because that would take up a little bit too much space.
[01:54 - 02:04] So we're going to create a file here called get booked slots dot TS. And it's going to contain this code.
[02:05 - 02:17] So we're defining a slot to be an object here. And we're using Zod already because these are the things we're going to be returning from our endpoint, so we might as well involve sod already as it is.
[02:18 - 02:22] We are going to need the type as well. So we do that by inferring it like so.
[02:23 - 02:34] So this is a way to get the TypeScript type out of a Zod schema. So these slots contain a time, which is going to be the start time where the time starts.
[02:35 - 02:39] And we're going to just always use intervals of three hours. So we don't need to specify that anywhere.
[02:40 - 02:47] If you create a more complex set up, you might want to do something like that, but for us, this is fine. We then have a provider ID.
[02:48 - 02:57] So this indicates which provider this slot refers to. And then we have a Boolean value that specifies whether or not this slot is booked.
[02:58 - 03:07] So the function that we're creating here is obviously async and then returns a promise of these slots, so an array of them. And let me just go over this.
[03:08 - 03:16] So this is all a bit of SQL query that we're establishing. And you can see that we're sort of bringing next to its knees a little bit.
[03:17 - 03:27] So this is definitely something you might consider either putting into a stored procedure. Or maybe you want to use one of the other database connection tools.
[03:28 - 03:32] So there's a number of them popping up. So next isn't the only thing that exists.
[03:33 - 03:38] And at this point, there are plenty of alternatives. And I don't have a lot of experience with them, so I can't speak to them.
[03:39 - 03:49] But maybe that's worth looking into. But what we're doing here is basically we're creating this rest for result here , which is what we're returning at the end.
[03:50 - 04:01] And we're selecting and then we're specifying explicitly that this is going to be an array of slots. So this is a way to tell next that that is what comes out of this because it would not be able to infer it.
[04:02 - 04:12] I want to make sure that you know that next is able to infer not just the very primitive selects. It does understand some simple joins and so on.
[04:13 - 04:25] But if you get into something that is more complex like this, especially involving raw statements, it obviously doesn't have a chance. So in this case, we need to specify specifically what comes out of it.
[04:26 - 04:34] So the selection that we're doing here, let me try and go through it quickly. So we're calling this Postgres function called generate series.
[04:35 - 04:43] And we give it two timestamps, which is the start and end time. And then we give it an interval which we've specified up here because we're using it in a couple of places.
[04:44 - 04:47] That says three hour. And then we cast it to a time.
[04:48 - 04:53] So this will create a number of rows for us. That is what we want to return.
[04:54 - 05:02] So these are going to be divided into intervals of three. Obviously, that's going to give us nighttime and everything as well.
[05:03 - 05:15] So we're filtering that out just down here, but we'll get back to that later. First of all, I want to state that we're using this particular thing to select the time and then the provider ID.
[05:16 - 05:32] And we're joining here on the provider on true, which means that we just want all of the providers that exist in the database. In a more complex setup, we might also want to specify which providers we are looking into, maybe only the ones that provide the service type that we were asking for or something like that.
[05:33 - 05:41] But we don't have any logic like that. What we're then doing is we're selecting something called booked in each row.
[05:42 - 05:50] And that is going to be true or false based on this, whether something exists in this little question mark here. And that's the case query.
[05:51 - 06:00] The case query is defined up here where we are selecting from the booking table . So we're selecting the ID, we could select anything because we're just looking at whether or not it exists.
[06:01 - 06:24] But we're joining with the provider or making this criteria here. And then we're saying whenever during on this booking overlaps with this TS range that we're creating here, we're creating a timestamp range that goes from whichever our time is and then this time plus our interval of three hours.
[06:25 - 06:43] So basically we're checking here for whether or not any booking overlaps with the interval that we're specifying. In the end, we then go over them and we want to make sure that we're extracting weekends and we also want to extract times that are outside of normal working hours.
[06:44 - 06:55] So this should give us an array for each of our providers that has a slot, whether it's booked or not. So it's a little bit of a complex thing, but I hope that it's reasonably clear what's going on here.
[06:56 - 07:04] So in the end, we get this array of slots out of it. So let's turn this into an endpoint that we can call from our front end.
[07:05 - 07:21] So we'll create an endpoint here called get availability. And it should take an input which should be, let's create a constant for this.
[07:22 - 07:46] So get availability, availability input, and we'll create a const for that up here. And we'll make this an object that takes, let's see, we should have a start date.
[07:47 - 07:55] And then we could either specify an end date, but maybe it would be more convenient to just give this a number of days that we want. It feels to me like that's what we're going to want from the front end.
[07:56 - 08:06] So I'm going to make that the second parameter. And that's then going to be a number.
[08:07 - 08:10] So that's the input. What is it going to return?
[08:11 - 08:27] It is going to return an array of slots. So the slot we already have defined in our file there.
[08:28 - 08:37] So that is going to be the output. There we go.
[08:38 - 08:55] And then what is the query going to look like? We need the context and the input.
[08:56 - 09:09] And then we're going to first create the end date, which we, so let's use date FNS. It has an add, let me import that because I've had trouble for some reason.
[09:10 - 09:23] Auto import doesn't really work with that. So we're going to add and it takes input.start date.
[09:24 - 09:27] And I think it's like this days. Yep.
[09:28 - 09:33] And then number of days. And then we have our end date.
[09:34 - 09:50] So we can now wait to get both slots. And we're going to give it our transaction as the next instance.
[09:51 - 09:57] So next instances can be both just a plain next and it can be a transaction. They have the same API.
[09:58 - 10:05] So you can use one in place for the other. That's a pretty nice feature of it.
[10:06 - 10:17] And we want to specify the two dates and then we want to return this. Right.
[10:18 - 10:29] So let's think a little bit about how we want to use this on the front end. Our design specifies that underneath the calendar should be a list of available slots when the user selects the date.
[10:30 - 10:38] And if there were a lot of slots available then maybe we want to use a grid or something but we are dividing the days into three hour intervals. So it won't be a lot.
[10:39 - 10:45] So a row will probably be just fine. Let's create a component called select slot for these rows.
[10:46 - 10:58] And let's for now assume that things are going to be nicely formatted for it. So let me paste in this code here.
[10:59 - 11:17] This component takes a selected day and a selected slot if there is one because maybe the user hasn't selected anything but maybe they have will let the state be handled further out. So the set selected slot is what will be called when the user clicks on one of these available slots.
[11:18 - 11:49] And we will then give it an array of available slots that are similar to the slot that we returned from our end point except we are not including the booked boolean because we are going to be filtering these so that the ones that are actually booked will not be included in this. Also we will make sure that we only show one even if there is one for multiple providers because we obviously don't want the user to see the same time slot displayed more than once even if they could select it for different providers.
[11:50 - 12:13] We are going to let the system handle that they just choose whatever provider is first available. So this component is basically a flexbox so it is a flex row and it uses the space X with two so to create a little horizontal distance between all the slots.
[12:14 - 12:28] Each slot is then a button and it basically has a class name based on whether or not it is the selected one so it has some flex stuff to center items and so on and it has a white background and a shadow. All that is not very interesting.
[12:29 - 12:50] The border is going to be indigo if it is selected and otherwise we will give it a white border and the reason I am giving it a white border instead of just no border is just to make sure that all the dimensions remain the same and that is a simple way to do that. If the user clicks on this slot then the selected slot will be called for this.
[12:51 - 13:07] The text or the contents of the button is basically the time where it starts and then the time where it starts plus three hours and then I am putting a little arrow icon in the middle of them. I am spacing these with a regular space.
[13:08 - 13:25] You could argue maybe that it should be a non-breaking space or maybe I should even have turned this whole thing into a flex buck and make separate divs for these but I think that is overkill. This will work for what we need.
[13:26 - 13:36] If you recall the booking flow context is where we are storing the user's progress so to speak. This is where we are keeping track of everything they have entered into our form so far.
[13:37 - 13:57] At this point we are ready to allow them to select a time slot so we are going to add a time object into this state here. It is going to be optional just like everything else because initially the user hasn't selected anything but at the point when they have it is going to be a date.
[13:58 - 14:03] Maybe I should note here that we are not putting the selected day in here. This is the selected time slot.
[14:04 - 14:22] The selected day is a local state in the select day page because that doesn't actually specify something that we are going to submit to the back end when we want to create the booking. We just want the selected time slot which will obviously contain the specific date as well.
[14:23 - 14:39] So with that in place we are ready to update this and let me just paste some code in here because there is quite a lot. Before I do that let me just see why this is failing.
[14:40 - 14:42] Type does not exist. Did you mean to write numerous days?
[14:43 - 14:52] No I did not. It seems like I have mistyped in the back end but this is actually a very nice opportunity to show you something.
[14:53 - 14:55] This is the front end code. This is in my front end.
[14:56 - 15:01] I can click on go to that like so. Go to definition.
[15:02 - 15:09] This takes me to my back end to the end point in my back end code. If you can show me any rest setup that works as smoothly as this I would like to see it.
[15:10 - 15:16] At least I haven't seen that myself. Let me just go here and fix this typo up here.
[15:17 - 15:25] That should say number of days and that now works. Right.
[15:26 - 15:31] So let's go over these changes quickly. First of all I create this helper function called unique by.
[15:32 - 15:43] Normally I would never do that because you should just import it from low dash or ram that or whichever utility library you tend to use. But I only needed this one function so I just decided to implement it up here.
[15:44 - 15:56] It should probably be changed but for now it works. If you're not familiar with it it basically takes an array of items and given some predicate it will just filter it so that there are only unique values in it.
[15:57 - 16:03] We're going to need that later on. So there are a few things that have changed here at the top.
[16:04 - 16:20] First of all the selected day is now being set. So if the state.time is defined then the selected day is going to be the start of the day so that day on which the selected time slot sits.
[16:21 - 16:32] Just when we enter the page we want that to be the default value now. And we've also created a helper function here that calls the set selected day but also updates the state.
[16:33 - 16:48] So this is for when we are changing to another day. So not selecting a time slot but selecting a different day then we want the time that we've actually selected to be undefined so that we have no longer selected a specific time slot.
[16:49 - 17:00] This is the way it worked before I think and then down here we're creating our query to our end point. So we're specifying that we want to get availability for this month and then 62 days into the future.
[17:01 - 17:24] This is to make sure that we're covering two months which is what we're showing at a time. Alright we are then creating first of all we're creating available slots which is well it starts out being undefined but when the query has succeeded we're going to first create a helper variable called slots per day.
[17:25 - 17:39] So basically we're taking all the slots that came out of our backend and we are putting them into a map that is split by day. So this is going to help us with the calendar so that we can show the days whether they are available or not.
[17:40 - 17:49] Obviously we don't want the days to know all of their slots. We want this available days that we had before we're now going to create that by reducing these.
[17:50 - 18:04] So basically we say if there are some slots that aren't booked on any given day then that day is available. So that is what we're creating here and that is going to be the available days that we're using down here.
[18:05 - 18:16] We're also looking at if the user has selected a day then we want to show the available time slots underneath. And we're going to figure that out first by figuring out what are the available slots for all providers.
[18:17 - 18:33] And we do that just by looking up the slots per day that we created up here and then filtering by the ones that aren't booked and then we map them so that we don't have that booked item in there. So this is what our one month component expects.
[18:34 - 18:48] But then we call this unique by helper function to make sure that we only have one for each time. So we use the string here to make sure that there's no object equivalence problems.
[18:49 - 19:02] But basically we're making sure that for each time slot we only show one slot. So with this we should now be ready to look at what things look like in the front end.
[19:03 - 19:25] Let's try to start out servers and see what happens. Okay so when we select the date we get the header but there's nothing showing here.
[19:26 - 19:32] And I think that's because we don't have any providers. Let me just go and insert a provider in a table here.
[19:33 - 19:44] So in this. And I believe that was name and email.
[19:45 - 19:55] Let's just call our provider John and say that his email is [email protected]. There we go.
[19:56 - 20:08] So now we have a provider and it should be able to, well I should be able to refresh already. So if I go here and I select tomorrow I now see three time slots here.
[20:09 - 20:20] So obviously there's no bookings so far so every day we'll have those exact three time slots available. But you can see that I can select one here and now that is selected.
[20:21 - 20:28] So if I go here and then back then it has been unselected. That's because we set the state to undefined when changing the date.
[20:29 - 20:51] But if I select this and I go back and then I go front again then the 18th is still selected and this time slot is still selected. So our time slot selection is now being stored in our context and we should be able to submit it when we go to the next page which we have yet to create.
[20:52 - 21:06] But considering that we don't have any bookings yet and we don't have an easy way of creating them then we will just assume that this works but we'll be able to test that as soon as we can actually proceed with the booking. So we'll get to that in a minute.
This lesson preview is part of the Fullstack Typescript with TailwindCSS and tRPC Using Modern Features of PostgreSQL course and can be unlocked immediately with a \newline Pro subscription or a single-time purchase. Already have access to this course? Log in here.
Get unlimited access to Fullstack Typescript with TailwindCSS and tRPC Using Modern Features of PostgreSQL, plus 70+ \newline books, guides and courses with the \newline Pro subscription.
![Thumbnail for the \newline course Fullstack Typescript with TailwindCSS and tRPC Using Modern Features of PostgreSQL](/_next/image?url=https%3A%2F%2Fs3.amazonaws.com%2Fassets.fullstack.io%2Fn%2F20240619184857865_KristianDupont_FullstackTypeScriptwithTailwindCSSandtRPCusingModernFeaturesofPostgreSQL_1450.67x816_CoverImage%2520%25281%2529.png&w=3840&q=75)