
No Comments Yet
Be the first to share your thoughts and start the conversation.
Be the first to share your thoughts and start the conversation.
How did you manage to remove the blur property and reach here?
Upgrading gives you access to quizzes so you can test your knowledge, track progress, and improve your skills.
By logging in, you'll unlock full access to this and other free tutorials on JSM Pro.
Why? Logging in lets us personalize your learning experience, track your progress, and keep you in the loop with new workshops, coding tips, and platform updates.
You'll also be the first to know about upcoming launches, events, and exclusive discounts.
No spam—just helpful content to level up your skills.
If that sounds fair, go ahead and log in to continue →
Enter your name and email to get instant access
In this lesson, we explore the process of creating a trip management feature using AI-driven forms with Syncfusion. The tutorial guides us through the various components needed to build a robust front-end interface for generating travel plans, emphasizing the significance of form structure, functionality, and user interactions that enhance accessibility and usability. Furthermore, we dive into the practical implementation of RESTful routes, combo box selections for countries, and submit handling to ensure a smooth user experience.
00:00:02 And finally, we are ready to create new AI trips.
00:00:08 Well, you could say that this is the moment that we've been waiting for.
00:00:11 We'll do that through the help of a form.
00:00:14 Everything starts with a form, of course, but then we'll fuse it with functionalities using AI generation.
00:00:20 But first, let's focus on the basics.
00:00:23 Everything starts with some inputs.
00:00:25 And as Schatzian likes to say, forms are tricky.
00:00:29 They're one of the most common things you'll build in a web app, but also one of the most complex.
00:00:35 You have to structure them and they have to be semantically correct.
00:00:38 They have to be usable and navigatable using the keyboard.
00:00:42 They have to have proper area attributes to be accessible.
00:00:46 support both client and server validation, and they should be well-styled and consistent with the rest of the application.
00:00:54 Thankfully, in this case, I'll show you how to do enterprise-level forms using Syncfusion.
00:00:59 So, with that in mind, let's create a new route that'll allow us to create trips that we'll be able to navigate to by clicking this AI Trips button.
00:01:09 Right now, it points us to a 404 that is available under forward slash trips.
00:01:15 So, let's just create it.
00:01:17 Right here, under App, we have Routes, and then under Routes and Admin, I'll create a new file and I'll call it CreateTrip.tsx.
00:01:28 Within it, I'll run RAFCE, and then to be able to visit it on the page, I'll head over to Routes.tsx, I will duplicate one of the last routes within our
00:01:40 admin dashboard interface.
00:01:42 I'll change the path to point create trip and also the path to the file also has to be a create trip.
00:01:52 So if we do this, what we might want to do is just change the route to maybe trips forward slash create.
00:01:58 That will allow us to have proper REST API routes where we're following good naming conventions.
00:02:05 On this page, we'll be able to create the trips.
00:02:08 But just above it, I'll create another table that'll allow us to see all of the trips, including the table of trips.
00:02:16 So I'll call it just trips and I'll point it to admin trips.tsx.
00:02:22 So let me create that file by heading over to right here under admin and I'll create a new file called trips.tsx.
00:02:32 I'll run RAFCE and here we'll be able to see all the trips.
00:02:36 If you now head over to your terminal, stop it from running and then rerun it and reload your page on trips, you should be able to see it.
00:02:45 So one of the things that we'll have on the TRIPS page will be a button that'll allow us to head over to the TRIPS creation page.
00:02:53 And we'll actually do that through the help of a header.
00:02:56 We're already using the header in the All Users table.
00:02:59 So let's just go ahead and copy this starting main tag and the header from the All Users page.
00:03:05 And let's just paste it right here.
00:03:08 and properly close the main tag.
00:03:11 If you do this, of course for now it'll say Manage Users, but here we want to say Trips, and we can give it a description of something like View and Edit.
00:03:22 AI generated travel plans.
00:03:26 But here's the kicker.
00:03:28 In this case, we'll actually give some additional functionalities to the header by adding a call to action button to actually generate a new trip.
00:03:37 So I'll give it two new props.
00:03:39 A CTA text equal to create a trip.
00:03:45 and a CTA URL pointing to the new route we created of forward slash trips, forward slash create.
00:03:54 And then we can head over into that header and accept those two new props which are going to be optional.
00:04:00 the CTA text, which is optional of a type string, and a CTA URL, also optional of a type string.
00:04:10 And we can accept them as props, CTA text and CTA URL, and now we can render them right below this article by first checking if they exist,
00:04:20 so CTA text and CTA URL, and only if those two exist, then we will render a link component, coming from React Router, pointing to that CTA URL.
00:04:33 In this case, the new create-trips route we created.
00:04:37 And within it, we can render a button component coming from ShadCN that'll have a type equal to button, a class name equal to button-class,
00:04:50 exclamation mark H11, because sometimes when we need to add additional class names to Syncfusion components, we need to denote them as important.
00:04:59 Same thing for the W full, and on medium devices, W of 240 pixels.
00:05:06 I found that value works the best.
00:05:09 Within that button, we can render an image with a source equal to assets.
00:05:15 icons, plus.svg with an alt tag of plus and a class name of size of five.
00:05:24 This is a very simple plus icon.
00:05:27 And then on the right side, we can also render a span that'll simply render the CTA text.
00:05:34 And we can give it a class name equal to P16 Semi Bold.
00:05:41 as well as text-white.
00:05:43 So, if you add this, head back over into Trips, and check whether we have properly added the right CTA URL, like this, you should be able to see a Create
00:05:54 a Trip button, which on desktop looks something like this.
00:05:58 So now, right from the page where you can see all the trips, or at least you will be able to see them soon, you can also navigate over to Create a Trip page,
00:06:07 which is exactly what we wanted.
00:06:10 So now, we can start implementing that Create Trip form.
00:06:14 Let's start off by wrapping everything into a main tag to denote that this will be a page with a class name of flex.
00:06:22 Flex dash call a gap of 10, padding bottom of 20, and a wrapper property.
00:06:30 This will give it some padding and nicely position it on the screen.
00:06:33 Within it, we can also render another header.
00:06:37 Yep, we really want to reuse them as much as possible.
00:06:40 We'll give it a title of Add a New Trip, as well as a description of View and Edit AI-generated Travel Plans.
00:06:53 And we can end the header right there.
00:06:56 Right below it, we can open up a section within which our form will go.
00:07:01 So let's give it a class name and give it a margin top of 2.5 divided from the header, as well as a wrapper MD.
00:07:10 What is this wrapper MD?
00:07:12 Well, it is to ensure that the form stays in the middle of the screen and it doesn't extend throughout the entirety of the dashboard.
00:07:19 See, on web screens you have a lot of space, but sometimes you want to use that space, like for the table, And in other times,
00:07:29 you just want to be able to see what you need to see.
00:07:32 And that is nicely structured inputs that don't necessarily need to extend from the left to the right edge.
00:07:38 Rather, you need to have them right in front of you.
00:07:41 So, heading back over within our section, right within it, I will create a form that'll have a class name equal to trip-form.
00:07:53 And on submit, we can call a handleSubmit function, which I will declare right above by saying const handleSubmit is equal to an asynchronous empty function,
00:08:07 at least for now.
00:08:08 We'll add the logic very soon.
00:08:10 Now, if you save this, you should be able to see a card-looking layout.
00:08:14 So, within this form, I will now add a div.
00:08:19 And within that div, I'll add a label that'll have an HTML4 country.
00:08:25 Why country?
00:08:26 Because the first thing you've got to figure out when you're planning a trip is where you want to go to.
00:08:31 So I'll basically add a label that'll say country right here.
00:08:37 And then below it, we want to implement a component known as a combo box.
00:08:41 See, a combo box is like a select element, but it also allows you to start typing within it.
00:08:48 Something like this, where you can type and then you can find results faster.
00:08:54 Like, selects are pretty good if you have a limited set of options like 5, 2, or 10. But if you have more than 100 or 200 countries,
00:09:03 well, you want to make your life easier and start typing the name of that function so you can more easily find it.
00:09:08 That's exactly what a combo box component is for.
00:09:11 And in my early development career, I remember creating those combo box components on my own.
00:09:16 And trust me, you don't want to do that.
00:09:18 It takes a lot of time.
00:09:21 So we're gonna make our life a bit easier here and actually use good programming practices, which is not to reinvent the wheel.
00:09:28 And we'll use a ShadCN combo box component, which we'll have to import at the top.
00:09:35 And then once you import it, coming from Syncfusion, EJ2, React, Dropdowns, you'll have to pass some props to it to actually make it work.
00:09:46 First things first, you'll want to pass in the ID, and this will be for selecting a country.
00:09:52 Once you have the ID, you'll also want to have a data source, similar to what we had with our table.
00:09:59 This can accept an array of maybe different titles, like country1, country2, and so on.
00:10:06 As you can see, it is super intuitive that I didn't even have to take a look at the docs, and I immediately understood how this component works.
00:10:15 I can start typing, it suggests me option, and I can just select one with my keyboard.
00:10:20 But now we're gonna have more countries, and we want to display it in a bit of a better way.
00:10:25 Maybe something that looks a bit like this, right?
00:10:28 So, to achieve that, we'll have to have a list of different countries that we can fetch.
00:10:33 Thankfully, there is a simple endpoint called restcountries.com v3.1 forward slash all, and if you just head over to that URL,
00:10:44 It'll give you a JSON formatted list of all of the countries in the world, which is exactly what we need.
00:10:52 So what do you say that at the top of this page, we make a simple fetch request to fetch those countries.
00:10:59 To do that, I'll actually use React router's loading functionality by saying export const loader is equal to an async function within which we can get
00:11:12 access to a response by calling that API.
00:11:16 So I'll say await fetch.
00:11:19 And I'll make a call to https://restcountries.com forward slash v3.1 forward slash all.
00:11:29 Then we want to get the data by awaiting the response JSON, as you typically do with fetch requests.
00:11:36 And then what do you say that we simply console log them?
00:11:40 So I'll console log the data we're getting back.
00:11:42 I'll head over into our application and I will just open up the console.
00:11:47 and then reload the page.
00:11:49 And if you do that, you'll get an error.
00:11:51 That's because we're not returning anything from this loader.
00:11:55 So let's actually just return this data.
00:11:58 And if you do that and reload, we will be good.
00:12:02 So instead of console logging it here, where we actually need that data is down below.
00:12:07 So let's just put that console log here.
00:12:10 And we can actually destructure the loader data, which is of a type route.component props.
00:12:19 And this route has to be imported right at the top by saying import type in curly braces route.
00:12:28 from dot slash plus types forward slash create dash trip.
00:12:33 So it knows exactly what we have within it.
00:12:36 And we can destructure the countries from the loader data.
00:12:40 So I'll say const countries is equal to loader data as country array.
00:12:48 Perfect.
00:12:49 So now we know that this is an array of one of the countries and later on we'll return the name, coordinates, value, and the open street map view.
00:12:57 So let's just console log the countries and reload the page.
00:13:03 If you do that and inspect the element, you'll be able to see that we get back an object of a ton of different countries,
00:13:12 each having a name and a ton of other details.
00:13:16 But we don't need those details for every single one of them.
00:13:19 So instead of returning the full data, let's actually map through it and only return some pieces of the data.
00:13:27 So I'll say return data.map where we get each individual country of a type any and for each one we want to automatically return an object.
00:13:38 You do an automatic return by wrapping this first in parentheses and then returning an object.
00:13:45 and we want to take the name.
00:13:46 We'll make sure that the name is equal to country.flag plus country.name.common We'll take in the coordinates, which will be equal to country.latlng,
00:14:00 latitude and longitude.
00:14:02 The value will be equal to country.name.common.
00:14:06 And I'll also take in the open street map, which will be equal to country.maps?openstreet.
00:14:15 map.
00:14:16 So now we're returning only the data that we'll actually use.
00:14:21 And we have it right here within our application.
00:14:24 So you already know how Syncfusion works magically when you have a data source.
00:14:29 The only thing you have to do is actually provide that data source right here within this combo box component.
00:14:35 If you do that and reload the page, you'll see that we have a list of a lot of something.
00:14:42 that something will hopefully turn into a lot of countries soon.
00:14:47 But for now, it is just a list of emptiness.
00:14:51 So let's go ahead and modify this ComboBox component to actually show the right fields.
00:14:58 So I'll say fields is equal to an object where text will be text and value will be value.
00:15:06 with a placeholder of select a country and a class name equal to combo box.
00:15:15 If we save this, you can see the placeholder, but still no countries.
00:15:20 That's because we have to take this data and we have to modify it a bit.
00:15:25 So what I'll do is right here where we have the countries, I'll create those countries in a way that we need it.
00:15:32 So I'll say const countryData is equal to countries.map where we get each individual country and for each one we can automatically return an object where
00:15:45 we take only the text of country.name and the value of country.value and these values will then match the ones that we are referring to right here.
00:15:58 Text and the value.
00:16:00 So, let's just say countries is equal to countryData.
00:16:05 You can see that the format matches, and we immediately get both the flag as well as the text.
00:16:12 Are you wondering how are we getting the flag as well?
00:16:15 Well, that's because each one of these flags is basically an emoji, like a set of characters, so you don't necessarily need to render an image to be able
00:16:23 to see it.
00:16:24 And with that said, we have this great looking combo box.
00:16:27 Well, not that great looking as of now, but it will make it better looking.
00:16:31 But hey, you can try to start typing something and it won't yet update.
00:16:36 We'll also work on that very, very soon.
00:16:38 So what we can do is fix the class name right here, which will automatically make it look so much better.
00:16:45 Let me just reload the page right here.
00:16:48 Check this out.
00:16:49 So now I can go here and I can select one of the countries and we'll also have to keep track of the selected value.
00:16:57 So I'll have to add a change property where we'll take a look at the event which has a value which is either of a type string or undefined and then within
00:17:10 it we want to check if a value exists and if so we'll call the handle change function And to it, we'll change the field of country to the value that was selected.
00:17:23 This is a function that we haven't yet created.
00:17:26 So let's just create it right above.
00:17:29 Here where we have the handle submit, I'll also add the const handle change, and it'll be a function that takes in the field and the value.
00:17:40 So right here, I can order the type that it'll take the key, which will be a key of trip form data and a value, which will be either a string or a number.
00:17:55 And then we can do something with those values within the function.
00:17:59 But more on that soon.
00:18:00 What we want to focus on right now, now that we have the change, is also to turn on the allow filtering.
00:18:08 It is a very simple boolean prop, but once you enable it, you can head over here and you think you'll be able to start typing but not yet.
00:18:17 That's because we have to define what property will we filter by.
00:18:22 So I'll turn on the filtering and here we get access to the type event and we'll try to get access to the query that the user has typed.
00:18:32 So query is equal to e.text.toLowerCase so we can actually not mind the case.
00:18:42 Then we want to call the eUpdateData And we want to take the list of countries that we have and filter them by getting each country and we'll filter it
00:18:56 by name.
00:18:56 So we're going to check whether the country.name.toLowerCase includes the query that has been typed once we get that list of countries we'll map over them
00:19:10 by saying .country and then once again for each one of these countries let's do it properly for each one of these countries we will automatically return
00:19:22 so once again make sure that you have a parenthesis right here we'll automatically return a text of country.name, as well as a value of country.value.
00:19:36 So if you do this properly, then you'll be able to start typing and you'll immediately be getting all the countries matching that search term.
00:19:47 So let's go for United States.
00:19:49 There we go.
00:19:50 We can also immediately search for India.
00:19:54 All of it works.
00:19:56 Wonderful.
00:19:57 So that's it.
00:19:58 That's how you very easily create your very own combo box and make it fully functional.
00:20:04 So what other information do you think is important when deciding where you want to go for a trip?
00:20:10 The country where you want to go to and the location surely matters a lot, right?
00:20:15 But what else can we do?
00:20:18 Well, let's head below this div that is wrapping our combo box.
00:20:24 And below it, I'll create another div within which I'll have a label, and it'll have an HTML4 for the following input, and this one will be about the duration
00:20:37 of your trip.
00:20:39 So I'll say duration, and below it, I'll render a regular HTML5 input with an ID of duration, a name of duration, A placeholder equal to enter a number
00:20:56 of days.
00:20:58 For example, 5, 12, and so on.
00:21:02 And we can also give it a class name of form input.
00:21:07 And we'll also change the placeholder color to gray 100. If you do this, you'll be able to see a duration input so people can enter a specific number of days.
00:21:20 And we can also give it an onChange.
00:21:23 So I'll say whenever something changes here, take that keyPress event, call the handleChange function that requires a key.
00:21:34 In this case, we're changing the duration.
00:21:37 as well as the value, but before we pass the value, I'll convert it over into a number.
00:21:43 So number constructor, and then e.target.value, so we can actually work with it.
00:21:49 Because I don't know if you knew, but every single input, no matter if you make it like a type of a number input, it'll still take in the values in form
00:21:59 of a string, like the URL bar.
00:22:02 So you have to convert it back into a number.
00:22:04 Great.
00:22:04 So now we have the duration as well.
00:22:07 What else do we need?
00:22:08 Well, we'll need a couple of different select elements that allow us to choose the type of our travel.
00:22:15 Are we traveling with a group?
00:22:17 Maybe solo, maybe as a couple.
00:22:20 Then we have to choose our travel style, our interests, and finally a budget estimate.
00:22:25 Select elements are super useful because you're nudging your user in the right direction.
00:22:30 You can propose a couple of different options that a user can choose.
00:22:34 For example, travel style can be luxurious or maybe adventurous.
00:22:38 Maybe budget can be low, it can be high.
00:22:41 You can tell your user what you need to hear.
00:22:44 And we're collecting all of this information in order to make the best trip possible for them.
00:22:50 So now that we have this second div, which holds the duration, we can focus on mapping on all of these individual select items.
00:22:58 Below the div, I will map over our select items coming from constants.
00:23:04 See, right here, I created an array that has these couple of inputs, group type, travel style, interest, and budget.
00:23:13 and we'll actually map over them, so we don't have to create each one of these one by one.
00:23:18 So I'll just take the key, and I will automatically return a div for each one of these.
00:23:26 This div will have a key, since we're mapping over it, and the key will be equal to key.
00:23:33 Within it, we will render the label, and this label will be an HTML4, the key, right?
00:23:41 Because we're going to have different properties.
00:23:43 So this is a little lesson on how you can map over the elements so that you don't have to repeat yourself.
00:23:51 Whenever you map over something, you have to ask yourself, what is consistent across all of these different things?
00:23:58 We always have a div, we always have a label, right?
00:24:01 So these will be there.
00:24:03 but the contents of it are going to change.
00:24:05 So within it, we'll render the key.
00:24:08 So now if you do this and go back, you'll see that now four different labels will appear, each with its own key.
00:24:16 I'll call a function called formatKey, and to it, I'll pass the key.
00:24:21 What this will do is that it'll actually capitalize some of the letters.
00:24:25 So what we're doing here is that we're taking the first letter of every word, and then we're uppercasing it.
00:24:32 Another little function that AI could very easily create, but AI can't create entire experiences and ideas, such as this application that we're creating,
00:24:42 that allows agencies to generate trips for themselves.
00:24:46 Trust me, developers will not be replaced.
00:24:48 Because imagine an agency owner wanting to create this great software for themselves.
00:24:54 They can take vZero, OpenAI, and chat with it for days But as soon as they need to code something, they will experience bugs.
00:25:03 So being able to chat with AI is not enough.
00:25:06 You have to know how to engineer things and think of ideas.
00:25:10 Perfect.
00:25:11 So now that we have this label, let's also create a new combo box component right below.
00:25:19 And as a matter of fact, this ComboBox component will be very similar to the one created above.
00:25:25 But you know what?
00:25:26 Let's actually create all of its props one more time, so you'll learn how it truly works.
00:25:32 First, we have the ID, which will be the key.
00:25:35 Then, we take in the data source, a very important part of the ComboBox component.
00:25:41 And here, I'll say combo box items, we'll take each key, and then we'll map over different items within it.
00:25:50 And for each one of these items, I will automatically return a modified object where the text is equal to item, and the value is equal to item as well.
00:26:02 So we want them to be the same.
00:26:03 So if I do this, you'll be able to see that now we have solo, couple, family, friends, business for the group type.
00:26:10 We have a travel style with a couple of options, interests as well, and then finally a budget.
00:26:16 So we could technically select the fields still and say that fields will be text of a type text and value of a type value.
00:26:25 Even though in this case, I don't think it's even necessary.
00:26:28 We can have a placeholder.
00:26:30 We have to make it dynamic, so I'll say select key.
00:26:34 That way, this will say select travel style, select interest, and so on.
00:26:39 And actually, we can say select key, but I'll wrap it with format key, so it looks a bit better, so that it's not lower-cased.
00:26:49 Select interest, select budget, select travel style, and so on.
00:26:54 And then we also need to handle the change.
00:26:57 This change will be the same as in the previous Select.
00:27:01 So I'll copy this part where we're changing and I'll also copy this part where we're filtering and choosing what to filter by.
00:27:11 So let me copy these parts right here.
00:27:15 collapse this back again, and then paste these additional props to it.
00:27:21 But of course we have to properly end it.
00:27:23 So let's see what we have.
00:27:25 We have the change where we're taking in the value and passing it over to the handleChange function.
00:27:32 But this time we should not be selecting a country.
00:27:36 to update, rather, we will dynamically update the key with the value.
00:27:41 Okay?
00:27:43 Next, we head over into allow filtering, and then we will try to filter it by the query.
00:27:50 So we're going to run the e-update data, but we're not going to map over the countries, rather, we're going to map over the combo box items under a specific key.
00:28:00 We're going to then filter over each one of these, not countries, but items.
00:28:07 I will render the item to lowercase, and I'll check whether it includes the query.
00:28:13 If it does, I'll then map over these items, and for each item, I will return the item right here, and then I'll return for the value item as well,
00:28:26 because they are the same thing.
00:28:28 Perfect.
00:28:29 Now I have to properly close this.
00:28:31 So let's count the closing curly braces and parentheses together.
00:28:36 One, two, three, four.
00:28:39 Do we have more?
00:28:40 We do.
00:28:41 And we also have to close this one and this one.
00:28:46 That's a lot of curly braces.
00:28:49 And also here, we're not checking for the item.name.
00:28:52 Rather, it'll just be item.
00:28:54 For countries, we have names.
00:28:56 For items, we have just the item itself.
00:28:59 And by item, I mean each one of these values, relaxed, luxury, adventure, or for the group type, couple, family, friends,
00:29:07 business, and so on.
00:29:09 And at the end of the day, we can also just give it a class name, equal to combo box.
00:29:16 And this style, or this class, will actually apply some paddings, borders, lights, it'll round up the corners, change the text to a bit of a darker color,
00:29:27 and it'll just make it fit the rest of the UI a bit better.
00:29:32 and you end up with something that looks a bit like this.
00:29:35 A much more complete form.
00:29:37 Perfect.
00:29:38 So now, let me collapse this part where we're mapping over these select items.
00:29:44 And by the way, if at the end of this form, something is not working for you, it could be possible.
00:29:49 Following along with me while I'm typing these very complicated ends of curly braces might not be ideal.
00:29:56 If you have a typo, that's totally okay.
00:29:58 It doesn't mean that you don't understand something.
00:30:00 It just means that you made a typo.
00:30:02 So for that reason, I will leave the entire CreateTrip.tsx page in the video kit below so you can just copy it over and make sure that your app works.
00:30:13 So now that we have all of these items, what do we do next?
00:30:18 Well, believe it or not, I want to display some kind of a map.
00:30:21 But in this application, we're focusing on not a very specific location, but rather the entire country.
00:30:28 Let's say you wanted to visit Croatia, a country I'm from, and you wanted to find out about the best places you want to visit within that country.
00:30:37 That's what this app is made for.
00:30:39 So we don't need to necessarily show a Google map, but just show that country on the world map.
00:30:45 So let's do that by heading over into our app, and below these select items, I will render a div.
00:30:53 Within that div, I will render a label, and within that label, we'll have an HTML4 location, and it'll simply say, location on the world map.
00:31:06 And then within it, this would be super hard to implement otherwise, but now we can just use the Maps component coming from Syncfusion.
00:31:14 Within it, you can show a layers directive.
00:31:19 And within layers directive, you can show a single layer directive.
00:31:24 to which you can pass some additional props.
00:31:26 That defines how you want that map to look like.
00:31:29 So let's actually head a bit above.
00:31:32 And like we formatted the data for the countries, we want to format those countries for the map.
00:31:38 So I'll say const mapData is equal to an array where we have a country.
00:31:45 We can take this from formData.country.
00:31:48 So we want to figure out which country was selected.
00:31:52 But as of now, we don't yet have access to the form data.
00:31:56 This is a state that we have to create.
00:31:59 So right at the top, I'll create a new use state snippet, and I'll call it form data, set form data, equal to the useState call,
00:32:11 where it'll be an object of different values.
00:32:16 First, let's make sure to import useState from React.
00:32:20 And we can also define the type of this state by saying that it'll be a trip form data, and it'll have properties such as country.
00:32:31 By default, we can select country's zero, question mark dot name, or just an empty string if we cannot get it.
00:32:39 We can also set the travel style to an empty string at the start.
00:32:43 We can set the interest to be equal to an empty string at the start.
00:32:48 Same thing for the budget, as well as the duration, and the same thing for the group type.
00:32:56 All of them will be empty strings at the start.
00:32:59 Let's fix this typo right here.
00:33:02 And let's make sure that we actually have access to the countries beforehand.
00:33:05 So this constCountries coming from the Loader data should appear above where we're setting the state.
00:33:13 There we go.
00:33:14 So now we have this form data, and in the map data, we're using the one from the country that we have selected right here in the first place.
00:33:23 Alongside the country itself, we can also choose a color in which we'll color that country.
00:33:30 So I'll say hash EA382E.
00:33:35 I found this color to look pretty good.
00:33:38 And then we can also choose the coordinates of that country.
00:33:41 So I'll say countries.find.
00:33:45 We want to find C, that specific country.
00:33:49 And if that country's name matches the one from the form, so form data.country, in that case, we want to take its coordinates or just an empty array in
00:34:01 case we cannot find it.
00:34:03 and that will form our map data.
00:34:05 So now, we can take that map data and pass it over as data source is equal to map data.
00:34:14 But a very important question is, how are we going to shape that data?
00:34:19 So for that reason, Syncfusion has prepared a prop called ShapeData, which gets the set data for the maps to render.
00:34:28 And here I'll say world underscore map.
00:34:32 I've actually provided this for you right here next to the constants.
00:34:37 This contains the list of the coordinates of all of the countries in the world.
00:34:42 So if you pass it in.
00:34:45 and reload the page, you'll be able to see the world.
00:34:48 You can also change the shape data path to change it by the name of the country.
00:34:54 And we can change the shape settings, which will be a color value path of color, and we can also provide a fill color.
00:35:05 So I'll say fill will be equal to E5, E5, E5.
00:35:12 So now if you save it, you can see that everything will be red.
00:35:16 So if we leave it like this, it looks like the entire world has been selected.
00:35:21 So we have to say shape data path, and I'll set it over to country.
00:35:27 But for the name, we'll actually say shape property.
00:35:31 path.
00:35:32 So now if you go ahead and select a country like United States you would hope that it'll all light up like a Christmas tree But that's not really the case.
00:35:42 And that's because even though the select fields are here, they're not actually yet storing the values within our state.
00:35:50 So what we have to do is we have to implement this handleChange function, which right now just takes in the data, but we have to set that data to the state.
00:36:01 So I'll say setFormData.
00:36:05 We will actually modify the entire object.
00:36:09 by spreading the previous form data in order not to lose any of it.
00:36:15 And then the only thing we'll do is we'll dynamically update a key, only the key that we're updating, with the value that we're trying to update it with.
00:36:26 So if we do this, you'll notice that now we should be able to change the value of any kind of these fields, and that it'll automatically light up right here.
00:36:37 Same thing if we select United States.
00:36:39 There we go, it's pretty big, so we can very easily see it on mobile.
00:36:43 But of course, where this shines is on desktop, as you can see it much better.
00:36:48 As a matter of fact, you can see even a such small country like Croatia, which is right here in Europe, you can barely see it on the world map.
00:36:56 So now we have a fully functional map that shows you where you want to travel to.
00:37:02 For extra points, maybe you can take in the country you're flying from and then create some kind of a flying animation to point to the countries you're
00:37:11 going to.
00:37:11 But with that said, let's just head a bit below this map component, which for now I will collapse, and let's create a single self-closing div that'll have
00:37:23 a class name of BGGray200.
00:37:28 HPX and WFull.
00:37:31 And this will simply create one line saying that we're at the end of the form.
00:37:36 Below it, if we have any errors, we might want to display them.
00:37:41 So what do you say that we create a new use state for the error?
00:37:46 Right here at the top.
00:37:48 I'll do it right below the current use state.
00:37:51 So where we have the form data, I'll create another use state.
00:37:57 And I'll call it error set error at the start equal to null.
00:38:03 And I'll also do another use state called loading set loading at the start equal to false.
00:38:13 And I'll also specify that the error can be either of a type string.
00:38:18 or a type null.
00:38:19 So just because it starts with a null doesn't mean that it cannot get any other value.
00:38:23 So the way that type inference works with React or Next.js is that if you pass the initial value, the value of that variable will automatically be loaded
00:38:34 as the type of that value, like in this case the boolean.
00:38:37 So for null, it'll by default be just null.
00:38:40 But we know that later on we want to switch it to string, which is why you have to manually specify which type you want it to be.
00:38:47 Perfect.
00:38:47 So now let's head back down and let's say if there is an error, we will render a new div with a class name of error.
00:38:59 and within it a p tag where we'll render that specific error.
00:39:03 Finally, below the error, we can also render the footer of this form component that'll have a class name equal to padding x of 6, w full,
00:39:16 and within it we can render a button component coming from Syncfusion.
00:39:21 with a type equal to submit.
00:39:24 So we want it to submit the form, and I'll give it a class name of button class, exclamation mark H of 12, and exclamation mark AW of full.
00:39:37 Also, if we're currently loading, I'll set the state to disabled.
00:39:41 That way, the user will not be able to click it multiple times.
00:39:45 Finally, within the button, I'll render an image with a source equal to...
00:39:51 I'll make it dynamic, pointing to assets, icons, forward slash, but then if we are loading, I'll render the loader.svg Else,
00:40:04 I will render a magic star dot SVG.
00:40:08 You'll soon see how that looks like.
00:40:10 A magic star, I think, has become the official icon or the official visual representation of AI generation.
00:40:19 So if I save it, you'll be able to see this button right below.
00:40:23 And the image is not really loading.
00:40:26 That's because I missed an S right here under icons.
00:40:28 So now we can see those sparkles, the stars.
00:40:31 You see what I meant, right?
00:40:33 And below that image, I'll render a span with a class name equal to P16 SemiBold and text dash white.
00:40:43 And right within it, I'll check if we're currently loading.
00:40:46 And if so, I'll say generating dot dot dot, else I'll say generate trip.
00:40:54 And we can save it.
00:40:56 So now we have that final Submit button.
00:40:59 So this was a super long lesson.
00:41:02 We have implemented the front end of the form.
00:41:05 But what have we actually done?
00:41:07 Well, let's try to summarize everything.
00:41:09 by console logging all of the values of the form that the user has selected within the handle submit function.
00:41:16 Right here, I'll access the event which will be the form click event.
00:41:21 So I'll say react.formEvent specifically of a type HTML form element.
00:41:29 First things first, we want to prevent the default behavior of the browser by saying event.preventDefault.
00:41:36 Oftentimes the event is just abbreviated to E, and the default behavior of the browser is to reload the page, which we don't want.
00:41:44 Next, we want to start with the loading, so I'll say setLoadingIsTrue, And then just before we console log those values,
00:41:52 we want to make sure that the user has actually filled in all of the necessary fields because we need them to generate the trip.
00:41:59 So I'll check if the form data dot country or the form data dot, what do we have?
00:42:07 Travel style.
00:42:09 or the form data dot interest, or the form data dot budget, or the form data dot group type.
00:42:20 If any of these don't exist, so if they're empty, in that case, we want to set the error to say, Please provide values for all fields.
00:42:34 There we go.
00:42:35 So now if you try to generate a trip, it'll say, please provide values for all the trips.
00:42:41 Oh, looks like we have this huge loading icon right here.
00:42:44 Definitely not what we want to have.
00:42:46 So we have to change the class name of this image to make it have a size of five.
00:42:55 And actually, I want to make it spin, so I'll render it dynamically by rendering a CN property, which will always have a size of 5, but it'll have a class
00:43:07 of animate-spin only if loading is turned on.
00:43:13 So if I do this, you can now see that it's smaller and it says generating, even though that's not really the case, right?
00:43:20 Because we have the error, please provide fields or values for all the fields.
00:43:24 So let's head back over to the form and alongside setting the error that we have, We also want to just set the loading to false because obviously we're
00:43:35 not submitting something went wrong.
00:43:37 So I'll set loading to false and simply return out of this function because there's nothing for us to do here.
00:43:44 We're missing the values.
00:43:46 So now if I reload the page and click the generate trip button one more time, you'll see that it'll stop loading immediately and it'll say,
00:43:55 please provide values for all fields.
00:43:59 Now what else do we need alongside these fields?
00:44:03 We also need a duration.
00:44:04 So I'll say if form.duration is lower than 1, or if form.data.duration is greater than 10, make sure that it says form.data here as well.
00:44:17 In that case, we'll also render the error loading in the return statement.
00:44:21 But the error will say something like, duration must be between 1 and 10 days.
00:44:30 So I'll save it, and now if I select the duration of 55 days, which is a lot for AI to generate the trip based off of, and when we actually fill in all
00:44:39 the other things, you'll notice that it'll actually say that the duration must be in between 1 and 10 days.
00:44:49 Perfect.
00:44:50 Finally, we should not be able to generate a trip in case our user is not currently logged in.
00:44:56 So this functionality is only there for the logged-in users.
00:45:00 So after this if statement, I'll try to get the user by saying await account coming from apprite.get And then if there is no user.$id,
00:45:15 in that case, I will console that error and say user not authenticated, I will set the loading to be false, and I will exit out of the function.
00:45:26 Finally, after all of these checks, I'll open up a try and catch block.
00:45:33 In the catch, I will just render the error.
00:45:37 So const.error is error generating trip.
00:45:42 And we'll see what the error is if we have already gotten to this point.
00:45:47 I'll also add a finally block.
00:45:49 So whatever happens, whether we're successful or not, we want to set the loading to false.
00:45:55 And in the try, for now, let's just consulog the user so we know which user is trying to create a trip.
00:46:05 And let's also consulog the form data.
00:46:10 So we know what kind of trip they're trying to create.
00:46:13 So with all of that in mind, let's try to generate a random trip.
00:46:17 Trust me, I will just randomly scroll through all of these countries.
00:46:21 So I was just about to create the trip, but my app crashed for some reason, so I just reloaded the server.
00:46:28 Oh, it looks like we're getting this process fetch failed.
00:46:32 Interesting.
00:46:33 What is it failing for?
00:46:35 Is it maybe for the countries?
00:46:38 Well, if I manually head over to this API, this endpoint seems to be working well.
00:46:44 So it must be something that we have recently added to the application.
00:46:49 Now, looks like we're good again.
00:46:51 Well, we'll see whether we can try to replicate that error soon.
00:46:54 But with that in mind, I'll try to create a new trip completely randomly, okay?
00:47:00 I'll be selecting some of the values here.
00:47:02 For example, Virgin Islands.
00:47:04 Maybe a couple of days.
00:47:06 Let's go with a family.
00:47:09 I want to see some culture.
00:47:11 Let's do museums and let's do a budget of luxury.
00:47:15 Perfect.
00:47:15 And I'm not sure whether United States Virgin Islands are so small that we cannot see them, or maybe our map is not working properly.
00:47:23 So let me select something else.
00:47:24 Let's go with Bahamas.
00:47:26 And there we go.
00:47:27 It's also barely visible, but I can see it there.
00:47:30 So now if I click Generate Trip and go to Inspect and open up the console, check this out.
00:47:38 We're getting back the user information as well as the form data, including the budget, country, duration, group type, interest,
00:47:49 and travel style.
00:47:51 All the information that we need to generate a proper travel advice.
00:47:56 So now that we have this proper form, let's actually use that data.
00:48:02 In the next lesson, let's use that data to generate the AI trip.