Completing Booking
Completing Booking
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:21] So we finally reached a point where we can create the last page that the customer is going to see that we've called enter email. It's basically the page where they will enter their email address and see a summary of their booking and then when they click the submit button, they're going to create this booking in the system.
[00:22 - 00:35] So we'll create an endpoint for that in our back end, but before we do that, let me just introduce you to a little helper that I tend to use in TRPC projects. So I've used this many times and it just always comes in handy.
[00:36 - 00:46] I call it TRPC assert and it looks like this. So if you're familiar with assertion functions, this should look very natural to you.
[00:47 - 01:05] Basically it checks a condition and if this condition isn't truthy, it's going to throw an error and specifically here we're throwing a TRPC error. So this is an exception or error class that comes with the TRPC library that takes an error code and that could be one of the HTTP codes here and then a message.
[01:06 - 01:20] So the one thing that's good to note about this is, well first of all, this function takes these three parameters and the error code is defaulting to internal server error. So you don't have to specify that if that is what you want to create.
[01:21 - 01:36] That means that you can typically call this as a one liner instead of all of these lines so that just makes your code a little bit cleaner. But more important, it asserts the condition that you're checking for and if you're familiar with TypeScript assertion functions, you will know what that means.
[01:37 - 02:05] But basically it means that whatever condition you're specifying here is going to be assumed to be truthy on the line after this call because it will be otherwise the exception will have been thrown. And that means that we can check, say, for something to not be undefined and then further down TypeScript will know that this value is not going to be undefined and so it's not going to complain about missing check or something.
[02:06 - 02:29] So we have this in place and with that let me just update the entire app router because there's quite a few changes as well here now. So we're adding the range class from Postgres range and then as well we're adding this constant here.
[02:30 - 02:53] So this is short for low about inclusive and so the Postgres range class takes some flags when you construct a range and that is what we're going to be doing now because when we create the booking we're going to be creating the range for this booking. And we want to specify that the lower bound should be inclusive though the upper bound is going to be exclusive which is the default.
[02:54 - 03:03] We then have an input object here or Zod schema. So we're going to say okay which service type ID did the user specify?
[03:04 - 03:09] What was the time of their booking? So that's the start time and we will assume three hours after that.
[03:10 - 03:23] What is the description and what is their email address? And then down here we've added this and I should probably turn this into a helper function like we did with the get book slots up here but I will leave that as an exercise to you.
[03:24 - 03:34] Basically we're taking the input that I just specified above and we're just returning an object that has a booking ID in it. We're not actually I think using this but maybe it will come in handy at some point.
[03:35 - 03:44] You could also return the entire booking I don't know. So this mutation because this is a mutation I think this is the first mutation we've created.
[03:45 - 03:54] So mutations differ from queries only in name. A mutation indicates that this is something that will change something significantly.
[03:55 - 04:21] So this is the equivalent to HTTP post or put or patch or delete whereas the query is equivalent to HTTP get. So a query you can expect that that will be something that can be cached whereas a mutation is something that you expect will change something so that is going to have side effects or affect the systems state in one way or another.
[04:22 - 04:39] So that's what we're doing here now because we're creating a booking and creating a booking involves both creating a customer if the customer doesn't exist. So after we've created this range and we do that by specifying the input time so the range constructor takes start time and end time.
[04:40 - 04:53] We're giving that the time and then three hours later and then we're giving this this flag to indicate that the lower bound should be inclusive. When we have this we're going to be using that down here but first of all we're going to make sure that we have the customer in place.
[04:54 - 05:17] So we check to see if the email already exists and if it doesn't then we're inserting a customer here and we're giving them the name of anonymous this should probably be a thing that could enter in the flow as well but I will leave that as an exercise to you as well. So we're creating a customer if it doesn't exist with this email address and then down here we are using the TRPC assert function.
[05:18 - 05:54] So basically we're checking that customer is truthy so if this for whatever reason didn't create a customer this is going to throw it to your PC error internal server error saying could not create customer. So we can be sure down here that customer is defined and that is that's important because up here you can see customer is either a customer or undefined but as soon as we've run this assertion here if I look where is it being used well where is it being used it must be something here we go.
[05:55 - 06:21] Here it is only a customer it cannot be undefined so that's what the type assertion function does. The provider we will look at and we are just going to get the first provider this is obviously something that should be fixed as well because if you have more than one provider you need to figure out which one of them was available and all of that but we haven't made any logic for that so we're going to just get the first provider and for our little demo here that should work as well.
[06:22 - 07:00] So we create this booking initializer here with the customer ID the provider ID and then the range that we have up there you can see this is of type range date but that's what the booking initializer expects and that is what we insert down here into our database and if this works we will get a booking out of it and we're just going to return the ID here. So with this let's create our enter email page.
[07:01 - 07:18] I've pasted in some code here and there are some squiggly lines we'll get back to those but let me just go over quickly what this page here looks like. It should be pretty recognizable because it's very similar to what we've done before but basically this is the last page that says confirm and enter your email address.
[07:19 - 07:49] So we'll show the service that you selected and for that we're using a query so remember that we're only storing the service type ID in our state and here we want to show the name of the service so we actually have up here a query for the service types and once that has succeeded we can find the service type that they selected and we can show the name of it. This may look a little bit excessive to you and maybe it is you could probably argue that this should be in the context instead so that we only do it once or maybe it should be even further out.
[07:50 - 08:39] There's definitely a case to be made for that but I want to stress that React Query is quite good at caching things so depending on how we configured it and I'm not completely sure here but there's a good chance that this will just be reached from the cache and so there's actually no problem even in terms of performance or anything but that is how we show the service type that the user selected then we show them the time that they chose and then we're restating the description that they wrote in the first box. In the end comes an email input where they can enter their email and there's some squiggly lines here as well because we don't have email in the state and then finally there's a handle submit for the primary button because instead of just proceeding we now want to submit this booking.
[08:40 - 09:04] So this here is like it was before on the other pages and the query we've been over but this is our mutation and mutations look a little bit different than queries. So with this query if I were to specify some parameters I would do that it here but for the mutation what I get is something that has a function function called mutate async.
[09:05 - 09:22] There's actually also a synchronous one but here we're using the asynchronous function and we're calling that and this is where you specify the parameters. So we check to see that we have a description and email and everything else and if not then we just return we should obviously validate this and show a proper error message but we'll just return.
[09:23 - 09:40] So this just makes sure that all of these are truthy and then we proceed with the mutation. And obviously our state still doesn't have an email so we should just add that but the other squiggly line you're noticing here is for the service type ID.
[09:41 - 09:55] The reason for that is that in our context here we are storing our service type ID as a number but the mutation here is actually expecting the proper type which is the service type ID with the branded type. So there are two things wrong in here.
[09:56 - 10:20] First let's add the email as a string and then we should change this to actually be a service type ID. And now sadly as we've been over auto import doesn't work well for that so we 're going to do it like so.
[10:21 - 10:28] Oops. Easy booking slash schema that gives us the service type ID.
[10:29 - 10:39] So now our state is using the proper type and that makes the squiggly line thing here disappear. However, now we might have a problem on the first page.
[10:40 - 10:53] Let's just go and take a look at that. So you can see here that when I'm trying to update state to say the service type ID here because this is a number this obviously won't work.
[10:54 - 11:02] So there are a few things we could do here. One is to just cast it to a service type ID and that is definitely what I would otherwise do.
[11:03 - 11:15] But since this is also iterating over the values and we have a service type out here we could just use that instead and just forget about what comes in from the value here. So I think I'm going to do that.
[11:16 - 11:23] So let's see say service type dot ID here. And now we don't need this anymore.
[11:24 - 11:37] And so we can update the state with the service type that comes from up here which is a proper service type ID at this point as well. So everything should be working now.
[11:38 - 12:26] Let's in the end create a little so in the new booking we're going to replace this one with our newly created one. So we'll auto import that and then we'll create let's make a little do like so and we'll show that as the last page when the user has created the booking.
[12:27 - 12:37] So with that it's time to try our system out. Let's start our servers and try this out.
[12:38 - 12:45] So we should now be able to fully complete a booking. Let's say we want help move my couch.
[12:46 - 12:55] So this is a moving job. We're going to specify that we want it tomorrow at 3pm.
[12:56 - 13:05] And so this is the final page and you can see it's fetching the moving service type and specifying that I want it tomorrow at 3pm. Help move my couch.
[13:06 - 13:11] Now let me just enter some email address here and try to confirm the booking. Thank you.
[13:12 - 13:18] Let's see if our server received something. So there is a great booking that returned 200 which is okay.
[13:19 - 13:23] So we have created a booking now. Let's try and create another one.
[13:24 - 13:33] So paint my kitchen and that is just a service type one. And we'll set that for tomorrow.
[13:34 - 13:44] You can see now that there's only two slots available tomorrow now. If I click on the day after tomorrow I can choose the afternoon one but now there's only these two left.
[13:45 - 13:51] Let's book this one as well. We can do that with the same customer to make sure that that works as well.
[13:52 - 14:13] And then I will try to do a final booking and I'm going to stop coming up with things here because this isn't really the interesting part. But let's say that I want to book this slot tomorrow and if some other customer somewhere in the world was doing something else and they too wanted to book this slot which they can hear.
[14:14 - 14:38] So we'll let this guy proceed and then we'll let this guy proceed and we'll say that they are going to confirm their booking. And now this person here is going to try to create a booking in the same time slot and we haven't created any validation for this on the front end but at least our backend should fail with it.
[14:39 - 14:52] And you can see we since our mutation is failing on proceed is never called. We just get an error out here in the console but the error is stemming from this post request that is failing.
[14:53 - 15:06] You can actually see here we get the debug information in our console because this is a developer server. So it says these conflicting values violate exclusion constraint booking provider during ID.
[15:07 - 15:30] So in other words even though we haven't created any validation in the front end or the backend our database is making sure that we can't create two bookings at the same time. So that is nice and let us just try for fun to look at what the day looks like because now tomorrow has no available slots and it turns yellow just like we wanted it to.
[15:31 - 15:47] So there are no slots there but we could select something for the next day. So that is a pretty good place to be and this is a very obviously very scruffy booking system but I do think that the foundation is in place in a quite nice way here.
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)