how many times has that budget travel hack left you broke in a hotel room with no Wi-Fi and a construction site for a view come on admit it planning trips sounds fun until it's not 50 tabs later you're overwhelmed frustrated and somehow booked a two-star stay next to a nightclub perfect but what if you could solve that chaos not just for yourself but for thousands of people with one powerful app hey I'm Adrian and in today's video we're building and deploying a full stack travel agency dashboard not just another static page but a real product
designed for travel businesses to plan and manage trips for their clients with AI and Stripe payment processing and the star of the show the admin dashboard a polished travel agency back office where you can see user insights trending trips and signup analytics browse all generated trips and customer profiles use AI powered trip generation to create new travel plans in seconds and then view edit and manage those trip details on demand and yeah it's fully responsive so you can also manage trips on the go now by becoming JS Mastery Pro member you can even upgrade this
dashboard with a public-f facing site for travelers featuring a beautiful landing page with curated destinations a pageionated list of all trip packages and a full trip details page with day-to-day itineraries images and pricing and of course to make it real a stripe checkout to finalize that trip throughout the project you'll work with modern ReactJS with React Router V7 the latest Tailwind CSS V4 for styling syncfusion UI component suite used by Visa Disney McDonald's Netflix Starbucks IBM and even Apple apprite an open- source backend for all your app needs free Gemini AI for dynamic trip generation
stripe integration for secure payments typescript for clean type- safe code Sentry for air tracking and monitoring and much much more from code architecture to clean codebase structure so if you're ready to go beyond basic tutorials and build something pitchable polished and portfolio worthy this is the video you've been waiting for let's dive right in to get started building our amazing travel agency dashboard application we'll head over to vit.dev the build tool for the modern web see the application you'll build in this video is a dashboard and React excels at dashboards so technically here we don't
have a need for Nex.js so in this case I'll use React and I'll quickly spin it up with VIT for instance service starts and out of the box support for TypeScript and more but with that said if you want to follow along with Angular NextGS Vue or any other technology you want you're completely free to do that i'll show the setup for the technologies I'm using but later on when we're implementing the design if you want to try out some other technologies you're free to do that still in this case I want to make it
work with Vit so right here under getting started scroll down to scaffolding your first V project we'll use mpm and V at latest so I'll copy this command and head over to my IDE throughout this course I'll be using WebStorm a super powerful JavaScript and TypeScript IDE that as of recently became completely free for non-commercial use before you had to pay to use all of these great functionalities but now you can use it for free so throughout the video if you see me use some cool features that pop up and they're maybe not available in
your text editor well that must mean that they're built into WebStorm so if you want to follow along with that as well you can download it and start using it there once you're in feel free to create a new folder already and then jump right into it within it open up a new terminal and paste the Vit command we just copied if you're not already within an empty folder you'll want to add a travel app right here which is the name of the application so it actually creates a new folder but because I already created
a new empty folder I'll just say mpmcreate vit latest slash to set up the application in the current repository and I'll say why to install the V installer now it'll ask us a couple of questions i'll just say ignore files and continue choose our own framework in this case of course we're continuing with React and it's asking us to choose a variant of a React application we have TypeScript JavaScript but we also have this new React Router V7 yep that's right in this course not only will you learn how to build your own React dashboard
but you'll learn how to route it using the newest version of React Router so press enter and install the create react router installer this took just a couple of seconds and it's asking me whether I want to initialize a new git repository and you know what let's go ahead and do that so I'll say yes initialize it install dependencies with mpm yep we could say yes to that as well while our dependencies are getting installed let's quickly set up our app account it's an open- source tool we'll use to manage our back end i'll leave
the link down in the description so go ahead and create a new account as you can see I'm using it for many projects already but now go ahead and create a new project you can call it travel agency dashboard click next choose your region and create perfect and while we're at it you can also set up Sentry i recently partnered with them so they decided to give you 50,000 errors to track and trust me you won't be able to break that much stuff soon so simply create your account right here feel free to enter JS
mastery as the organization name so let's go ahead and create a project with React i'll call it travel agency and click create perfect we'll get back to this page later on there we go get initialize dependencies installed and we are ready to run our application if you didn't have a new folder before you'll have to cd into this travel app you created but in this case we're already in so I have all of my files right here the only thing I have to do is just run mpm rundev which will spin up the application on
localhost 5173 so back into the browser if you open it you should be able to see something that looks like this you can go ahead and open up this React Router docs it's pointing us here because I'm guessing this is something that is new to a lot of people they've made some significant changes from v6 to v7 and I want to make sure that you understand exactly how everything works so you can not only make it work for this application but every future application you're working on we'll dive a bit deeper into this later on
for now just keep it open let's take a second to clean up our file and folder structure currently we have the folder called welcome here which I will completely delete i will also clean up our root home.tsx tsx by simply running rafce within it which will just spin up a new React functional component called home if this shortcut didn't work for you that must mean that you don't have a React plug-in installed so just search for plugins or extensions and search for modern React snippets which will allow you to very quickly spin up new React
components great let's also head over into the app.css CSS and remove everything besides the import of Tailwind CSS we'll definitely need that now since we'll be using Tailwind we also want to add some Tailwind Power Apps so right within a terminal I'll open up the second one i'll call the first one dev environment or we can call it just dev and I'll call the second one terminal so when we need to install some additional packages we can do that right here while we know that our development environment is still running so let me run mpm
install clsx as well as tailwind- merge right here and press enter these packages will allow us to use tailwind a bit more responsibly so we don't go ahead and override some styles without knowing it and it also allows us to very easily add dynamic styles using Tailwind this should leave you with a very simple plain looking homepage but keep in mind that our app is big and I really do mean it throughout this course we'll first build the admin dashboard that'll consist of this nice looking authentication page we'll then switch over to building this dashboard
which has a lot of moving pieces and then we'll continue with all of the other pages as well the ability to add trips see them right here and even generate AI itineraries the dashboard part of the application where you can see the users where you can create new trips and see them on the dashboard will be released completely for free on YouTube but in case you want to take it a step further and also build a public-f facing website that works seamlessly with the dashboard we'll create but this time for the users trying to book
some trips not for the admins trying to generate them you can access it on jsmastery.pro Pro where you'll be able to watch this entire course lesson by lesson and for each lesson have a lecture summary a transcript a branch including only the code for that specific lesson that shows you exactly what has been implemented up to that point and even a quick quiz making sure that you have properly adopted all of the knowledge within that specific lesson or if you maybe didn't and you got stuck you can ask questions directly within that lesson and get
immediate help oh and I almost forgot alongside the public facing website we'll also integrate Stripe with your dashboard so that we can process payments and actually allow people to go on these trips so if that sounds interesting click the link down in the description but with that said even the content you're watching right now is far more than what you can find in other paid courses this admin dashboard you'll develop will be enterprise ready using the tech that the biggest companies in the industry use so I'm super excited to share a bit more about that
later on but with that said let's ensure that we have everything we need in order to develop this application everything from this nice looking background on the O even to the smallest detail like this logout icon i want to make sure that we have everything you need in order to develop this seamlessly and focus on improving your skills rather than searching for different icons online so for that reason I prepared all of the assets you need right within this project's video kit you can find it in the description and then click over on the assets
that'll point you over to Google so you can simply rightclick it and download them once they're downloaded make sure to unzip them and within it you'll find the app and the public folder the public folder will contain all of the assets that we'll need such as the icons and the images so let's go ahead and delete the current public folder and just drag and drop this new one right in make sure to put it at the root of your directory and we'll do the same thing with the app we don't really have anything in there
so let's just delete it and now I'll drag and drop the app right within it and if you check it out you'll notice that it's still almost the same as we left it before it has a home the root where we have the HTML file and just one single route right now but what I've added are some types they're going to make it easier for us to use TypeScript types in the future as well as some fonts in which we've set up the theming of our application so that we don't have to write all of
these styles one by one rather we have them right here but don't worry this contains zero logic it just contains some CSS that's going to make it easier for us to style specific aspects of the application now if you go back and reload you'll still be able to see the same thing that we had just before so with that we've got the setup out of the way which allows us to already in the next lesson dive into the setup of the complete React UI components library called Syncfusion we'll still use Tailwind CSS to style our
application but Syncfusion will provide us with over 90 higherformance lightweight modular responsive components that are perfect for building dashboards and Syncfusion isn't really like material UI they're used by some of the biggest companies in the world such as Visa Apple Disney McDonald's Netflix Starbucks and IBM do I have to go any further hopefully you're excited because in the next lesson we'll set it all up just before we go ahead and start setting up Sync Fusion I want to make sure to push our code to GitHub follow along and commit to your GitHub as well i'll
open up the terminal run git init commit-m app setup this will add all the changes we have implemented so far then head over to github.com/new and create a new travel agency dashboard i'll keep it as public and click create once you do that you'll have to switch over to the main branch so get branch m main get remote add origin and finally get push u origin master or in this case main if you do that in a couple of seconds if you reload you'll be able to see all of the files committed right here what
I typically like to do is remove these releases packages and deployments and give a short description such as a travel agency dashboard we can add the URL later on for now I'll add some kind of a placeholder and I will also say that the topic is React React Router V7 and what else well yeah it's a dashboard right so we can say dashboard and already our repo is looking much better so with that said let's set up Syncfusion first things first I want to tell you that it is completely free okay so click the link
down in the description and within the video kit I'll also leave a link to the documentation where you can see all of the different components that you can use for example charts here you have the getting started guide on how you can use charts and you can notice that the Syncfusion components have been split into multiple different dependencies such as base data charts PDF export and so on so we'll need to install these different packages separately so head over into your application within the terminal and here we'll need to install all of these different packages
just so you don't have to type them all one by one you can find them right here under mpm install Syncfusion where you can just copy it and then you'll have the full installation command super simple right press enter and this will install all of these different components allowing us to implement enterprise ready dashboards for free there we go that was quick now we'll have to update the Vcon config file so head over to vconfig.ts and here we'll need to prevent the list of dependencies from being used for SSR allowing us to use Syncfusion components
without any issues so I'll say SSR no external and here we can just use a regular expression to target at Syncfusion perfect now if you click set up Syncfusion link down in the description you should be able to see a page that looks like this as you can see the free license is valid for 30 days but I'll show you how you'll be able to continue using their components even after that period ends the only thing you need is a free community license i'll use Google here at the second step if you used a personal
email to create your account you'll also need to add a work email which they need for compliancy but don't worry what you can do is just type support atjsmastery.pro you can use this as the company email that way you can proceed to the next step for the company you can also type JS Mastery you can enter your phone number and create an account once you do that you'll be redirected to your Syncfusions dashboard here it says start your free trial for the Essential Studio this will offer you a ton of stuff but don't worry this
doesn't mean that you'll only be able to use your components for 30 days what you need to do instead is head over to the bottom right part and click get your license here you'll be redirected to different types of licenses that exist such as team unlimited but what we're going to go for is the community this allows you to get access to the entire product line for free if you're making less than a million dollars of course or have five or fewer developers in your team so click claim your free license and fill in some
details you can say individual student or hobbyist for the organization name feel free to type in JSMy i don't believe these are necessary but you might want to save whether you want to use this as open source in which case you can also provide a repo and then you can finally provide a LinkedIn profile and say who is going to be owning the code of course that is yourself so submit it and this request can take some time but what matters the most for us is that already we got an email containing the 7-day free
trial that'll include the access to React components so if you head over there and scroll down you'll be able to find your license key which you can copy or you can also get it right here within your dashboard by specifying that you'll use JavaScript and type JSM travel agency and get a license key right here as well perfect once you do that you can head back over into your code create a new file called env.local and within it you can say vit Syncfusion license key and paste the key that you just got then you'll need
to head over into app root.tsx and right at the top above the layout you can import the register license coming from Syncfusion EJ2 base and then you can call it register license and you want to get access to that ENV variable by saying import meta envision license key and that's it we have successfully registered and we can use Syncfusion's enterprise ready components let's focus on routing or in other words let me give you a quick React Router V7 crash course where I'll teach you how to do routing in modern React let's start from bare beginnings
by deleting our current home route because we'll do everything from scratch within the routes folder I'll create another folder which I'll call admin and within admin I'll create a new file called dashboard.tsx instead of which we can run rafce to spin up this new react page now unlike Nex.js this won't work automatically so that if we just go to forward/dashboard you'll be able to see it what you have to do first is head over into this routes.ts ts file which you can think of as the configuration file here you can see that our index route
points to the homepage which no longer exists so instead of having this index we can use two additional methods one is called layout and the other one is called route to start off we can simply create a route which you'll need to import over from react router dev routes so I'll say route right here it requires you to pass a name of that route or in other words the path and then you need to pass a location to the file so that'll be the routes admin dashboard.tsx and notice how WebStorm automatically tells me what each
one of these params means the first one dashboard is the path and the second one is the file pretty cool and if you do that you can see that this dashboard right now coming from here i can even call it dashboard page shows up right here on localhost 5173/dashboard pretty straightforward right but now let me also show you how we can add something known as a layout so within admin I'll create a new file and I'll call it admin- layout.tsx within it i'll run rafce and I'll give this div a class name equal to admin-
layout now this admin layout is coming from index.css the only thing we're doing here is applying a bit of a light background color with some padding on the top for the navbar and we're also making it a flex container so what we can do now is head over into routes and wrap our route with a layout so right here I'll say layout coming from React router at the top it's not a component but a method as the first parameter it requires a path to that file so I'll say forward sloutes/admin/admin-layout dsx and as the second
one it requires an array of children which are different routes within that specific layout so I'll simply put this dashboard route right within if you do it properly without providing the starting forward slash right here you should be able to see just the admin layout in your page even though you're currently on the dashboard route so what you can notice is that by putting something into a layout it doesn't mean that you have to prepend that route with that name like it is in Nex.js you can think of it more like a Next.js GS route
group where the route still remains its original part but the layout allows you to style it a bit further so for example we might want to display a mobile sidebar right here or maybe like a regular sidebar so I'll put an aside give it a class name of W-ful max-W of 270 pixels typically make it hidden but only show it on desktop devices so this is a sidebar for desktop and I'll say sidebar right here so now on mobile we can see the mobile sidebar but if we head over to desktop we can also see
the regular sidebar but now what matters most is showing the pages within that layout they will all have something in common which are the sidebars sure but how do we show the actual page well I'll render another aside which is an HTML 5 semantic element give it a class name of children and within it I will render something known as an outlet coming from React router this outlet is a component that renders the matching child route of a parent route or nothing if no child route matches in simple words it just shows you the page
or the route that we're currently on that is inside of that admin layout so for example here now this admin layout allows us to have this mobile sidebar but also the contents of the dashboard page so if you needed to add a second route you can do that very easily you just create it right here within the same folder and I'll call it all-users.tsx i'll run rafce and this is the page within which we'll display the users table this will of course also be a part of the admin dashboard so now back in the routes
you can just duplicate this route still within the same layout and I'll say all users and pointing to routes admin all users so now if you wanted to you can very easily switch over to all users and both that one and the regular admin dashboard have the mobile sidebar which we'll implement next that's more or less it when it comes to routing it's actually pretty simple but if you wanted to read a bit more about it you can see that React Router is a multistrategy router for React bridging the gap between React 18 and 19
where you can use it maximally as a React framework or as minimally as you want so there are different ways of using it but in the most simple way you can create some routes and then use them as parts of different layouts as I just showed you don't worry we're going to dive into more depth of how we can use it later on such as how you can create dynamic routes as well but more or less it is super intuitive for example if you wanted to nest routes you would be able to do that just
by creating a single route and then adding additional routes as an array to that route but with that in mind you already know not only the basics but also how to put those basic routes within the layout so I'll close all of the currently open files and I'll go ahead and commit this by heading over into the terminal running git addit commit-m and I'll call it routing perfect ready to move onto the next lesson considering that most of our pages will use the mobile or the desktop sidebar what do you say that we focus on
the layout of those sidebars first and then we can focus on the pages that'll be within it so let's start with the sidebar component i'll first create a new folder within the root of our application and I'll call it components and then our first component will be called nav items nav items.tsx tsx and then there we can run rafce to quickly spin it up we don't need this react import so we can always delete it and we can also create a new component within the components folder called index.ts we only need to do this once
because from this one file we will export all of our other components by saying export default as nav items and then from slashnav items this allows us to very easily import all of the components within one line i'll show you how we can import it very soon but for now let's head over into the nav items component and let's implement it first things first I'll focus on the layout so instead of a div I'll turn it into a section and give it a class name of nav dash items within it we of course need to
have a link coming from react router and it'll point to forward slash this will be our primary link and it can also have a class name of link- logo so right within this link we can render an image so that's an img with a source pointing toward/assets/icons/lo.svg with an al tag of logo and we can also give it a class name equal to size of 30 pixels like this and just below it we can render an H1 that'll say tour visto which is the name of our application you can see that WebStorm doesn't recognize this
word so it might think that we misspelled it but I'll tell it no save this to dictionary because we'll be using it much more often later on okay so now where can we see this nav items component well we can use it within our admin layout so head over into the admin layout and right here where it says sidebar within this aside here we want to render that nav items component but we don't want to just render it like this we want to wrap it within a Syncfusion sidebar component so I'll first render a sidebar
component coming from at Syncfusion EJ2 React navigation i'll give it a width of about 270 pixels and a prop of enable gestures equal to false because this will be our desktop sidebar and within it I will then render the nav items coming from data /components like this so you can see if we didn't have that index.ts within components then we would have to import every single component like this in a new line import nav items from components nav items import something else from somewhere else but in this case we can just do this and now
we can add additional components like com 2 com 3 all in a single line okay so let's also self-close these now items and now right on top you can see that this application was built using a free trial of Syncfusion Studio so we'll have to get a valid license key and that's completely free so soon after you register serial license that message will be completely gone if I covered this before in the video that's great if not don't worry even if the message is still right there we can continue developing everything and then we'll remove
it later on but with that said let's actually expand our browser so we can see what's happening on desktop devices there we go right here we start seeing the sidebar perfect and we can see this to Visto logo that basically points us to the homepage which right now doesn't exist so with that in mind we can continue developing the left sidebar right here so-called the nav items component so right below this link let's render a div that'll have a class name equal to container and within it we can render a nav that's an HTML 5
semantic tag for the nav items within which we can map over our sidebar items which you can import over from constants so say sidebar items.m map and then you get each individual item from which we can dstructure the properties such as the ID anref an icon and a label and then for each one of these we can automatically display a new div within which for now we can maybe display just the label so if you do that and save it here you can see dashboard all users and AI trips but hey where are these items
actually coming from well if you commandclick into sidebar items you'll see that this is a simple array of objects that I prepared before each one of these objects has an ID an icon a label and a path that it points to perfect so now for each one of these instead of a div we want to use a react router component called nav link of course you want to automatically import it from react router the two path will be equal to the href or the link and we can also give it a key equal to ID
now what this nav link allows you to do is to open up a dynamic block of code and then have a callback function within it and that callback function looks something like this so you have the parenthesis and then an immediate return within the parenthesis you can dstructure the is active state so this tells you whether the component is active and we can also use typcript here to say that is active is of a type boolean and now within it we can render a div within which we can render the label if we save this
now all of them are exactly the same as before but now we can use that is active state to style it further for example this div can have a class name equal to and now here we want to make it dynamic so we typically want to apply some styles but we also want to change the styles depending on the is active state of that nav link and for that in Tailwind we use something known as CN short for class names so within our file and folder structure create a new folder and let's call it lib
as in library within the lib folder create a new file and call it utils.ts this stands for utility functions functions that we can reuse across the application that deal with specific functionalities the CN util typically comes directly with Tailwind but you can very easily get it right here from the video kit as well so just copy the entire app lib utils and then paste it over into the utils right here here we'll have a couple of simple functions like formatting the date parsing the markdown to JSON so we can use it parsing the trip data
which we'll use later on and some functions that'll help us calculate the percentage later on more on that soon the only thing we needed right now is this one tailwind merge which will take in the static and the dynamic class names you can also notice that this utils file uses dayjs library to manage date and time so I'll open up my terminal and I'll run mpm install dayjs also we can keep this library within the app that way we have everything important stored right within it there we go so now within this class name you
can say CN and then import it automatically from the top from lib utils and pass the styles that it'll always have such as a group and a nav item class name but now as the second parameter to the CN method you can also provide an object and say when these additional class names will be activated for example the bg primary 100 and important text- white will only be active if the is active state is turned on if it's not then they won't be active we can do a similar thing for the image next to the
label so I'll render a self-closing image tag that'll have a source of icon an al tag of label and a class name equal to I'll make it dynamic one more time and I'll make it a template string this time on group hover we want to change the brightness so I'll say brightness to zero typically size of five and on group hover we'll also want to invert the colors you know that effect when you kind of hover over it the background then turns darker but the icon therefore needs to turn lighter in order to be visible
on that darker background so we can open up a dynamic block of code and say if is active is true in that case we can change the brightness to zero and invert but if it's not we can just leave the text color to dark so text-d dark 200 perfect so now if we check this out in action we can see the logo on the top and you can see that the dashboard is the currently active tab but if you click on the all users tab it still has the same layout because remember we implemented this
page before and gave it the admin layout the trips of course will lead to a 404 but we can now nicely switch between those two and as you hover over it you can see that it actually changes the background color indicating that it'll become active so now we can switch between those two pages within our nav items let's also add a footer to this sidebar sharing a bit more info about the currently logged in user for the time being I will hardcode the user information just so we have something to display but later on of
course this will be coming for real from authentication so say const user is equal to I'll give it a name of Adrian an email of contact atjsmastery.pro which is my email address and an image URL of a path pointing to assets images David.webp i found a random David developer online so now that we have this user we can head down below this nav and create a new footer this footer component will have a class name equal to nav-footer and right at the top of that footer we can display an image with a source of user
question mark url or if that doesn't exist we can always point to that David thing so forward/assets/im images/david.webp and we can give it an al tag of username so that's going to be user question mark.name or David maybe in case the user doesn't exist perfect so now if you check it out you'll see this developer right here at the bottom left right below the image we can render an article article is like a div but it says that the pieces of content within it actually are related so within it we can just display an H2
that'll render the user question mark.name and then below it we can render a P tag that'll render the user question mark so if you save it and check it out you can see Adrian and contactjmastery.pro which is the email for you of course it could be something different and finally below this article we need to display a button through which will allow the user to log out so to this button I'll add an on click and I'll just do a callback function and say console.log logout of course later on we'll actually implement the functionality right
here i'll also give this button a class name of cursor-pointer to let people know that it is clickable and then right within it I'll display a self-closing image that'll have a source of forward slashassets slashicons slash logout svg with an al tag of logout and a class name equal to size of six so now if we save this and check it out you can see that we have a nice looking logout button and that concludes our left sidebar but now what happens if we go to a mobile view we cannot really let the sidebar take
that much space so that's why we're hiding it on tablet or smaller devices so now if I actually put this to a mobile view you'll see that this left sidebar entirely disappears and we have this new mobile sidebar that is there instead so let's implement the mobile sidebar too i'll do that by heading over into components and I'll create a new component called mobile sidebar.tsx i'll run rafce to quickly spin it up and we can export it from our components just by changing the name of the export to mobile sidebar and then we can import
it directly within the admin layout instead of the mobile sidebar we can just render the real mobile sidebar coming from components there we go so now if you did that you understand that we have a mobile on mobile we have this one on desktop and then we show the rest of the page content perfect so that looks something like this nothing fancy yet but it'll look much better soon in this case we can actually see both the code and the view at the same time so it's going to be even easier to implement it i'll
start by giving this div a class name equal to mobile sidebar and I'll also give it a class name of wrapper making it act as a flex wrapper within it I'll display a header component and within that header we can have a link coming from react router pointing to home just forward slash within the link we can display the logo as before so I'll say image that'll have a source equal to assets icons logo svg with an al tag of logo a class name of size-30 pixels and there we go you can see this shortened
version of the logo and then right below it we can render an H1 that'll simply say to Visto which is the name of the application of course feel free to use something else if this doesn't look good and then since this menu will open up we actually need a button so below the link render a button component and this button component will have an on click that'll be a callback function that simply needs to collapse our sidebar okay so how do we get access to this sidebar implementing this on our own would be super difficult
sidebars and mobile navbars and whatever opens and closes even though is seemingly super simple it's actually a pain in the ass to implement so it works properly across all devices so that's why here we'll once again use Syncfusion's sidebar component same as we did for desktop so I'll say const sidebar of a type sidebar component coming from Syncfusion EJ2 react navigation and then what we can do is within this button we can simply say sidebar dot toggle which will actually open up that sidebar but instead of a const for now I'll simply set it to
let because for const since you cannot redeclare the variable you have to declare the value immediately but here we really don't we can just set it as undefined at the start you can see that our TypeScript is complaining right here because we're using this variable before being assigned but we'll actually make this functionality work with Syncfusion's component so for now I will simply suppress this warning with a TS ignore which I will add right above this button as a comment perfect so we are toggling this sidebar and we'll have a regular image this is going
to be a menu icon so I'll give it a source of assets icons menu svg with an al tag of menu and a class name equal to a size of 7 so if I save this you should be able to see a menu icon on the right now right below the header component we can actually render this mobile sidebar so I'll render the sidebar component coming from Syncfusion and I'll give it a width of about 270 and of course we'll have to import it from Syncfusion but not just as a type which is what we
have done so far we'll want to import the actual cyber component like this so if you do this you'll see that it'll automatically be opened and it'll hide our logo so we'll need to give it a ref a ref where we have a function of sidebar and then we'll make this sidebar equal to the sidebar object that we created above i'll also add a ts ignore right here just so we don't have any typ warnings next we can give it a created property so once this sidebar component gets created what we want to do is
automatically hide it so I'll say sidebar.hide looks like our TypeScript will complain here a bit as well so what I'll do is remove these TS ignores and just at the top of the file I will add a TS no check directive which will make sure that no TypeScript errors show for this file then we can also say close on document click this is very important to turn on for mobile sidebars because if you click on a specific element in between the sidebar we want it to also automatically close we can also render the show backdrop
to true which will give it a bit of a background right here and then also we can give it a type equal to over because it's showing over the content and now the only thing we have to do is pass over the nav items component we have created before for the regular sidebar and it'll also work here as well so just import nav items coming from nav items and you should be able to see the full desktop sidebar also works incredibly well on mobile but now it also has some behaviors of a mobile sidebar such
as you can click outside to close it still when you click on a specific element it doesn't actually collapse itself so you can see the page you're on even though it actually does the redirect you can see we're in the user table but we cannot really reopen it anyway so there's definitely some more improvements we have to make to it one thing is taking this sidebar toggle outside of this direct click and turning it into a new function so we can pass it over into the nav items themselves so I'll simply copy this sidebar toggle
and call it toggle sidebar so now right at the top I can say const toggle sidebar is equal to a callback function within which we can just toggle that sidebar now we're calling it right here on button click but I'll also pass it as a prop over to the nav items so I'll say on handle click toggle the sidebar so now we can head over into the nav items and we can accept this handle click as a prop which will be of a type handle click a function that returns void meaning nothing and it'll also
be optional so now we can use this handle click when we're clicking something right here so I'll pass it as the on click to this nav item div right here where we have the classes i'll also add an on click and I'll say on click handle the click so what this should do is when you click on a specific link it should actually collapse the dashboard oh I see it doesn't do it just yet i think I know where the issue is here when we give it a reference I should actually be making this sidebar
object coming from Sync Fusion equal to the ref not vice versa so what we need to do is switch it around so the sidebar object will be equal to the sidebar ref if we do this you can notice that now it actually closes and you can reopen it perfect we now have a mobile nav that allows us to navigate in between different pages so the last part of the layout that is shown across all the pages can you see what it is we have the sidebar we have the mobile sidebar in mobile but what is
that other element that also appears across all of the different pages well it's the header see with a title subtitle and potentially an action button allowing us to do something for that specific page almost every single dashboard has this oftentimes it is also called breadcrumbs because it tells you on which part of the dashboard you're on such as admin users or this could be admin trips but in this case since our dashboard is pretty simple we just have a title subtitle and an action button so let's implement it as well by creating a new component
in the components folder which I'll call header.tsx tsx run rafce and for now I'll just add it to the top of the dashboard page so right here we have the page contents but we'll wrap everything in a main so this is the main page with a class name equal to dashboard and we'll also give it a class name of wrapper within it we'll display the header component coming from components header see this is what I was telling you about if you don't export it from this components index then you'll have to have a new import
for every component but if I came in here and just exported it right here as header coming from header in that case you would just be able to import it like this by saying import header from components again doesn't make a big difference now but as you start importing more components into the file it really makes your code that much cleaner this will be a self-closing component and we immediately know that we'll have to pass different props to it such as the title and the description so title for now can be something like dynamic template
string that'll say welcome and we can say maybe if a username exists so I'll create a new user right here const user is equal to an object that has a name of Adrian of course you can put your own name there and we'll say welcome if user question mark.name name exists then use the name else just say guest and we can also render an emoji of a wave right here at the end near the title will also render the description equal to we can do whatever we want to say here but maybe something that explains
that page we're on such as track activity trends and popular destinations in real time perfect now copy this header And we can also add it over to our second page which is the all users page as a matter of fact we need to do the same thing right there give it a main a class name that says dashboard and wrapper then we can render the header right here this time we won't say welcome we can say something like trips page like this and then the description can be something like track users for now of course
we can actually modify it later on or not track users that's a bit too creepy we can say something like check out our current users in real time and of course below that we have the actual page contents so this will be the all users page contents and then on the other one that was the dashboard we'll instead say dashboard page contents perfect so now if we actually head over into our app you'll see that we have the header and we have the dashboard page contents and if I move over into all users we have
the header all users page contents but now we have to dive into the header component and accept those props to show the differences in between those pages so I'll immediately accept the title and the description we can actually define this as props so we can define an interface of props right at the top that'll have a title of a type string and a description of a type string as well so now we can display the header component with a class name equal to header within it we can have an article because it contains two related
pieces of info within it we can have an H1 that'll display the title and below it we'll have the P tag that'll display the description if you do this you can now see trips page check out our current users but if I head over to dashboard you can see welcome Adrian track activity trends and popular destinations in real time great of course the only thing remaining for us to do is to style it a bit better so let's do that by giving this H1 a class name equal to text-d dark 100 and we actually want
to make this a bit more pronounced if we're on the homepage see here it shows up right here at the top but on dashboard we want to make it a bit larger so for that reason I'll wrap this in a dynamic CN class name like this and we need to properly close it as well so the text dark 100 will always be there but if the URL is homepage then we'll apply some different class names and how can we do that with React router V7 how can we figure out the path well it's super simple
and very similar to Nex.js i'll just say con location is equal to use location hook that's it you import it from React Router and you know the URL oh and let's not forget to import the CN as well from utils okay so now next to this default style we can also check if the location.pathname is triple equal to forward slash and if that is the case we can give it a text of 2 Excel on medium devices text of 4 Excel and font-bold but if we're not on the homepage we can give it a text
of Excel on medium text stuff 2XL and font- semi bold of course we have to properly close this now so now if you compare the differences between this header size and this one right here you won't be able to notice the difference that's because all of these are not on the homepage this is dashboard and the other one is all users where these ones need to be the same to have enough consistency in between the pages but I'll also teach you how to reuse this header on our public facing website whereas we want to make
it much much larger so that's where the difference will be but for now we can also copy this H1 and paste it below but change it to a P tag for the description this one will be text gray of 100 and font normal by default but if the path name is forward slash it'll have text-base and on medium devices text-LG and we don't need this font bold and if it's not on the homepage we'll make it a bit smaller and give it a text-s as well as on medium devices text-l and within it instead of
the title we'll display the description so now we have a proper title and description right here so this is already looking great on mobile we have a fully functional mobile sidebar and it's starting to take the shape of the final application of course where dashboards truly shine is on desktop where we have more screen real estate so we can see all of the different stats charts tables and charts so what do you say that we focus on the main dashboard page contents next okay the primary layout is done so let's focus on the content displaying
what matters on the page i'll collapse it for the mobile view and mobile first development is taking over anyway so it's always good to start developing for mobile and then expanding over for desktop although I would say there is one exception to that rule and that is dashboards dashboards are typically meant to be viewed on desktop so prioritize proper desktop views okay so let's focus on creating the UI of these trip cards right here to have something nice to display on the dashboard right off the bat to the user so let's develop a reusable trip
card layout but you know what before that let's start from the top let's focus on the stat card layout the one that has a nicel looking chart and displays some information i'll do that by creating a new component right here in the components folder and let's call it something like statsc card.tsx run rafce and while we're here we can also create another card this one will be the trip card so I'll create a new trip card.tsx also run rafce and just display it right here what we need to do of course is export them both
from the index.tsx so that's going to be the stats card as well as the trip card perfect so now we can head over and import both of them within the dashboard so we can start implementing them i'll first head over into our routes dashboard and right below the header I'll display the trip card coming from trip card components and right on top of it I'll also display a stats card coming from components so now we can see that we have a stats card and a trip card we first have to ask ourselves what will each
one of these stats card have if you already have a design well then you have everything you need take a look at these cards and ask yourself what is different about them and what is the same the regular card layout is the same the background the borders and everything but what is different is the title total users total trips and so on the number of course the value this up and down stat is different value and then we also have the chart itself so these are the different pieces of data that we have to pass
into it so we can start by forming that for now fake data but later on we'll make it real once real users start creating the trips and they start joining the platform so for now I'll say const dashboard stats is equal to and I'll create a stat for total users and actually doing this is a pretty good idea starting to create everything static from regular JavaScript objects so if you do this and maybe define 12,450 users as it says on the design right here technically what you're doing is creating a structure for your future database
you now know that you have to store this field in the database and that it'll be of a type number we can do a similar thing for users joined which in this case can actually be an object where you keep track of the current month maybe like 218 and the last month that way you can compare that data so last month I'll set it maybe to 176 this will allow us to have this nice looking chart right here after that we can render the total trips card maybe 3,210 and then we can also have the
trips created so this will be for the chart where we can have the current month data maybe like 150 and the last month data maybe like 250 let's say there were more that month and finally we need to know how many active users do we have so I will say user role we have a total of about 62 maybe current month maybe let's say there were 25 and last month let's say there were like 15 so we're growing very fast perfect so this is some fake data that we can now pass over into the cards
so let's focus on the stats card first below the header I'll create a section and this section will have a class name equal to flex flex- call and a gap of six so we can display all of them one next to another within this section I'll create a div that'll have a class name of grid so specifically the grid for the stats card where we want to make it have only one column so calls one on medium devices we want to have three columns so this will be on larger devices and then a gap of
six in between the cards and a full width and right within it we can display our first stats card so I'll say stats card self-closing and we can already start figuring out which different types of props do we have to pass into it so I already told you that we'll need to have the total users right so that's the title header title is total users next we can pass the actual amount which I'll call the total so this will be equal to dashboard stats dot total users we'll also need the current month count which will
be equal to dashboard stats dot users joined dot current month and then we can do the same thing for the last month count is equal to dashboard stats dot users joined dot last month and we can maybe dstructure this dashboard stats just so we don't have to repeat ourselves every time so I will dstructure the total users and users joined from the dashboard stats by saying const total users users joined total trips trips created and user ro equal to dashboard stats so now if you head here you don't have to explicitly say dashboard stats for
every single one of them you can just refer to total users and users joined perfect so now we can very easily duplicate this card two more times for the second one instead of total users we'll have total trips so we can pass the total trips and instead of users joined we can refer to the trips created and then for the last one we can talk about just users so we can say user rot total and then here we can say user rotc current month and last month so if you head back over to your application
you'll see that we still have just three empty cards but now we're passing the right data to them so it'll be super simple to take that data and display it the only thing we have to do is head over into the stats card and implement its layout of course starting with dstructuring all the props that we're passing into it such as the header title the total the last month count and the current month count these will be of a type stats card so what you could do is you could either define an interface right here
of stats card and then say what the header title will be what the total will be and so on or you can do what I did right here and that is to define the stats card within a special file called index.d.ds which allows you to declare interfaces which can be used across your entire application without needing to import it so here we're saying that the header title is a string the number is the total last month count and current month count are both numbers and that's it it just immediately knows that the header title is
a string pretty cool right so now that we have that card we have to take the last month and the current month count and get some useful data out of it such as figure out what is the trend is it increasing or is it decreasing this will be useful for us to figure out the color of the chart and then also by how much has it decreased since the last month and this is actually a pretty interesting challenge if you want to work on it what I'll do is say const dstructure something and make it
equal to the call of the calculate trend percentage and to it I'll pass the current month first and then the last month second and this calculate trend percentage will give me exactly what I need which is the trend and the percentage now how does this calculate trend percentage work and where is it coming from well I declared it within the utils function it's a utility function but of course I invite you to implement it on your own this is a great type of function that chat GBT excels at you give it a very small task
such as to give you a trend and a percentage you say what the inputs are such as the count this month count last month and then you tell it what you need to output so in this case it says if count of last month is zero then there is no change so what is it doing is it's taking a look at the difference it's grabbing the percentage of that difference and then if the change is greater than zero it returns a trend of increment and a percentage if change is lower than zero then a decrement
percentage and if there's no change it just returns zero makes sense right so once we get that trend we can then say const is decrement is equal to and that's going to be equal to trend is equal to decrement what we're doing here is we're just checking for equality and then turning that equality into a boolean variable so we can more easily use it within the card so now that we have all the necessary data we need in order to implement the layout of the card let's actually do it cards are typically articles so I'll
use this article and give it a class name of stats dashcard which will automatically give it a white background and a bit of a shadow if at any point you're wondering which styles are being applied when you add a specific class name you can commandclick into it or just search for it across the codebase until you find it within the app.css you will see that we're giving it some padding making it a flex container with a gap in between the items giving it a background a shadow bit of a rounded corners and a dark text
next right within this article we can render an H3 that'll have a class name equal to text-base font- medium and it'll render the dynamic header title so now each one of these says a different thing total users total trips and then total users once again that's because I forgot to change the last one so if I head over into the dashboard yep you can see here I left total users but actually it was supposed to say active users today perfect so now we have three different cards or just active users is fine so now we
are ready to start implementing the rest of the stats card right below the H3 we can start focusing on the main content so I'll wrap it in a div with a class name of content and within that div I'll create another div with a class name of flex flex- call and a gap of four in between the elements within which we can start rendering an h2 that'll render the total number so now we have the number but to make it look a bit better we can give it a class name of text-4xl and font- semibold
because the total number is more important than the title you need to see those numbers very clearly right below this H2 we can render another div that'll have a class name of flex items center and a gap of two and within it we want to render a figure this figure will contain the percentage as well as the arrow pointing up or down so right here I'll give it a class name equal to flex items center and a gap of one and within it I will render an image with a source equal to it has to
be dynamic so I'll open up a template string and point to assets icons and then if it is decrement I will render the arrow down red svg else I will render the arrow up greens SVG and I'll also give it a class name of size of five and an al tag of arrow if you save that you should be able to see up down up which matches our data and right below that image I'll render something known as a fig caption which is basically just a p tag within which I'll render the math.round And within
it I'll pass the percentage and then add the percentage sign right after it so you can see it says 25% up 40% down 67% up or something different for you if you used different numbers let's also style that text a bit by giving it a class name i'll use a dynamic CN class name it'll always have a text small and a font medium but if it is decrement then it'll also have a text red of 500 else it'll have a text success of 700 so now you can see that the number also matches the color
of the arrow and then outside of this figure I'll also add a bit of an explanatory P tag that'll say versus last month so we know what this number is referring to let's make the text a bit smaller by giving it a class name of text-s font- medium and since it's not so important text gray of 100 and truncate so if it's too long we can just remove that part because it's clear what it is referring to great and now we can head over below this B tag and below two more divs and we can
render an image this image will have a source of forward slash assassets slash icons forward slash dynamically we can check if we are in the is decrement and in that case we'll display the decrement.svg else we'll display the increment svg so if you save it you'll be able to see those different charts now a challenge for you and something that we can look into later on would be to actually make these charts functional using a library like charts.js or something similar for now the most important thing for me is to display the value and the
trend and making this real is a beautiful challenge for you to try out so you might want to check out Syncfusion's chart components because they have a lot of functionalities that allow you to very easily display that chart data but for now let's also style it a bit more by giving it a class name of on extra-large devices W of 32 typically W full H full on medium devices H32 on extra-large devices H full and I'll give it an al tag of trend graph so if I save this they're now a bit larger and if
you check out the desktop version this is looking exactly like it does in the design pretty cool right and later on this will be super functional because you can make these cards clickable so it actually points to a page where you can show more details about those users like active users and total users can point to the old users page and total trips can point to the AI trips page where you can show more details on the trips but for now let's focus on implementing the trip card this will bring so much more life into
our dashboard because each one of these trips will have its own thumbnail so let's collapse it so now is the time to implement that trip card before we start implementing the UI we'll have to figure out the data for those components and similarly how in the dashboard we rendered some fake data for the dashboard stats which soon enough will be imported into our real database we need to have the data for the trips so I prepped some fake data for you right here so we can very easily get going it'll be right here under all
trips dummy and you can see that here we have all the trips so let's simply import it and paste it right here at the top it doesn't have to be within the component as a matter of fact I'll also pull these outside of the component because this is just fake static data which later on will be converted over to be real database data and I'll paste it here you can see that each one of these trips has an ID a name image URLs the itinerary with different locations tags travel styles and estimated prices but it's
never a good idea to keep static data right here within the JSX file where we're trying to do some logic or represent some UI so what I'll do instead is I'll copy these trips user and dashboard stats and I'll move them over to the bottom of the constants file so this is the constants index.ts and I'll paste the dashboard stats the old trips and the user data i think later on we'll also have the users data so I'll copy it while we're here and also add it here so we have some fake users this one
uses the format date functionality so make sure to import it from lib utils there we go this is only to help us focus on the UI but very soon we'll be pulling all of this data directly from the database and you can see there's no more snippets to copy everything else in this entire application will be done entirely by us together so let's make sure to add an export statement at the start of each one of these lines and we can import it right here within the dashboard so I'll say import dashboard stats from constants
also the user and also what else do we need i think it's going to be the all trips so we can render the cards for each one see how clean the file is and we can even pull this dstructuring right here so we have nothing in the component we only have the JSX so now below the stats card I'll actually just hide them for a second i'll display a whole another section right below this section that shows the stats cards this one will be another section that has a class name equal to container and it'll
also have an H1 that'll have a class name equal to text- Excel and font- semibold as well as text- dark 100 and it'll say created trips so now right below it we can render a div that'll have a class name equal to trip-grid and keep in mind nothing is showing up right here besides this trip card which we can remove but now within here we can use that dummy trip data by saying all trips coming from constants dots slice which means that we can only get maybe the first four trips and then we can map
over them by getting data for each one of these trips and then for each one of them we can automatically return a new trip card like this so if you save it you'll now see four different trip cards to which we can pass all of this trip data so I'll give it a key since we're mapping over it of trip ID and as a matter of fact we can dstructure those properties from the trip so I'll dstructure the ID the name image URLs itinerary tags and the estimated price all the info that we need and
now we can very easily pass it over as props into the trip card so the key is just ID the ID will be equal to id to string because typically when you store ids in databases they can be of some other data type not necessarily a string we can get a name equal to name image URL equal to image urls zero so we're going to take the first one the location can be equal to itinerary question mark.0ero so if it exists get the first one and then get its location or if it doesn't exist maybe
display an empty string like this for the tags about the trip display the tags and for the price display the estimated price perfect so now we're passing all of these important props into the trips card so what do you say that we go ahead into it and we accept them and we start implementing that card it won't be that tough trust me maybe even easier than the stat card the data is what matters for now it's static but soon users will be able to create it using Gemini so that the trips contain real suggestions on
the trips where your users want to go so I'll dstructure that data such as ID name location image URL tags and the price all of these are of a type trip card props and now we can make each one of these cards a link coming from React Router why because we want to be able to see the trip details once we click on it this link will render an image with a source equal to image URL and then alt tag can be the name of the trip that they're going to if you save this you'll
be able to see four dummy images for me there are four of the same images right here but for you I might change it up a bit and actually put different ones but of course what matters more is that each one of these cards will actually point to a real trip details page so let's actually add a two-part and we have to get access to the current path so right here it's very similar to how we do it in XJS const path is equal to use location coming from react router then we can check whether
we're clicking on this card from the public facing website or from the admin dashboard because depending on that we're going to link them to a different page so I'll say if path.pathname is triple equal to forward slash meaning public facing or path.pathname dot starts with forward sltra that still means that they're on the public facing website in that case we'll point them over to travel and then the ID of that trip else will point them to trips forward slash ID why because for the admin we want to display the details of that trip whereas for
the person we just want to show them the trip that has been created let's close this properly and if you click on it right now you'll get redirected to a 404 so don't do that just yet but with that said let's continue adding more data to this card i'll give this link a class name equal to trip-ashcard which will kind of collapse it a bit next right below this image I will create an article that'll contain most of that cards or most of that trip's data such as the H2 that'll render the name of the
trip maybe also a figure in this figure we'll contain an image that'll have a source of assets icons location mark svg with an al tag of location and a class name of size of four that's much better now why do I use figure well if you have an icon that is related to a specific text then you might want to put them into a figure and put a figure caption like this instead of a paragraph it's just better for screen readers and overall usability so now we can see that this is New York Paris Tokyo
and so on next we can exit out of this figure and the article as well and create a div right below it this div will have a class name equal to margin top of five to divide it a bit from the top a padding left of about 18 pixels like this padding right of about 3.5 and padding bottom of about five so we're creating space to display some tags right here and for the tags I'll use a Syncfusion chip component so I'll just start typing chip list component i'll give it an ID of a travel
chip and within it we can display the chips directive component and these one will look something like this you have different styles like simple choice chips filter chips dynamic chips it is super customizable within it we can map over our tags by saying tags.m map where we get each individual tag and the index of that tag and we'll render a chip directive not chips directive this time without the s as a self-closing component that has a key equal to index it has a text equal to get first word of a tag because tags can contain
multiple words you have to import this from utils basically we just trim it out split it and render the first word and then we can also provide some CSS class to it to style it further and I'll say CN from utils if index is triple equal to 1 then give it a bg 50 and you have to add an exclamation mark in front to make sure that the style is applied as well as text pink 500 else give it a bg success of 50 and a text success of 700 and save it so now you'll
see that we have different colors for those two different tags first one maybe New York is about adventure and culture paris is relaxation culinary and so on finally we can head below this div that's wrapping the chips and render the article that'll contain the information about the price of course we can style it a bit better by giving it a class name of trip card dash pill and this will just make it a pill that's going to show up on top of this image right here on the right looking great also I automatically added some
images right here to match what you're seeing on your screen right here and with that we're done with the trip card but of course it looks even better on desktop where we can see all of these cards later on we'll be able to visit them as well but you get the idea we can see the latest trips that the agents within our travel agency are creating so now we have a beautiful layout but with fake data so the next obvious step is to implement the full functionality for our application including authentication allowing users to add
real trips storing it all within the database and then displaying it right here on the dashboard as we are right now but with the real data to get started adding functionality to our application and some persistent storage we'll use AppRight an open- source platform that allows you to create your entire backend within minutes in this course we'll use it for their databases storage and authentication functionalities so click the link down in the description to be able to follow along and see exactly what I'm seeing and then create your account as you can see I already
have quite a few projects running on AppRight so let's create another one i'll call it JSM Travel Agency dashboard you can do something similar click next choose a region and your project will be created within here we'll need to grab a couple of IDs and store them within our application so I'll collapse my browser so I can see my code editor 2 and I'll head over into myv.local which is where we can add those apprite keys the first one I'll add will be a vit apprite project ID and that'll be equal to this key that
we can copy right from here i'll add a comment that right here these are going to be our apprite keys and then above we have some coming from Syncfusion perfect now here it's asking us how do we want to integrate our application and I'll say that we want to integrate it with the server using an API key so if you click right here you'll be able to choose your API key name and give it access to all the scopes that'll give you your API key secret so copy it and create a second variable of vit
apprite API key and paste the one that you just copied now head over to databases and create a new database you can call it anything but I think travel is suitable right here as soon as you do that you'll be given your database key so also save it i'll call it vit apprite database ID and just paste it right here within it we'll be able to create additional collections within our database so you can create our first collection and call it users which will allow us to authenticate and store those users right within our database
as soon as you create it you'll be able to copy its ID as well so I'll call it vit apprite users collection ID and paste the key that I just copied and we want to repeat the same thing with the trips so I'll create another collection called trips and create it and I'll copy its ID so I'll create another one called vit apprite trips collection ID and you can paste that one as well now each one of these two collections has to have some fields within it so first let's head over into the users collection
and head over to attributes so we can start defining how our database structure will look like so let's ask ourselves what does every single user need to have well they'll need to have a string that is a name so they need to have a name and we can make it a size of well we could do something like 20 characters and make it required alongside the name we also need an email so we can create another field of email i'll call it email and I'll also make it required alongside the name and the email we'll
also need an account ID so create a new string of account ID make sure to put the I in ID uppercased because this will be case sensitive and we can make it about 1,000 characters just to stay on the safe side and I'll also make it required i'll also create another string for the image URL attached to that specific user's profile photo and I'll make it a,000 characters should be enough i won't make it required this time as maybe some users don't have a profile photo and I'll also add a date time of a type
date time and I'll call it joined at so when did this user join and I'll make it required next we'll add a more complex type called an enum so this is basically a string but it can only be made up of a couple of different values i'll call this enum a status so each user can have a status or you can also call it a type but I'll stick with status right now and it can either be user or admin so you can add a comma after each one to actually add it as an element
then I'll pick the user as the default value and I'll create it that should be it and finally head over to settings of the user collection scroll down to permissions click plus and select any and then select all the permissions right here so that everybody has all the permissions just so we don't get blocked because we don't have the necessary update permissions perfect so now we'll be able to create different users that have all of these different fields next let's create some attributes for the trips so head over to trips collection attributes and create a
first attribute i'll call it trip detail we don't need the s at the end i think trip detail will be fine i'll make it a big size like very big i'll go with maybe 10,000 characters just so we can have the entire itinerary details budget and everything right there and I'll make it required after that I'll create another string call it image URLs give it a size of about well let's do 5,000 since these URLs can be large I won't make it required but I will make it an array to indicate that this is an
array i'll also create a dated time like before called created at so we know when specific trip was created and I'll make it required and I'll also add a URL which will be a link to the stripe payment so I'll call it payment link and I won't make it required i'll also add a user ID which will be a string so we can connect the user with a trip i'll give it a size of about a thousand characters perfect so now you should have image URLs which is a string array you should have trip detail
which is a required string you should have a created ad property which is the required date time a payment link of a URL and the user ID which is a string now head over to that collection settings scroll down to permissions select any and give it all permissions perfect that's it for our AppRight setup very soon we'll be able to use all of these functionalities such as the API integration the AppRite project itself as well as the database collections and even authentication but the next step into making all of this work is connecting our authentication
and we'll do that using Google so for that we'll need the app ID and the app secret coming from Google so you'll have to head over to console.cloud.google google.com head over to projects and create a new project i'll name it something like JSM agency dashboard and create it it'll take a few seconds for it to get created and then you'll be able to open that specific project then where you have the search you can search for clients here you'll be able to see Google O platform and before you click get started head over to branding
and then click get started here you'll be able to choose your app name so you can do something like JSM travel agency and you can enter your email for support you can choose external for the audience and then you'll have to provide an additional contact info address and create it once you create the OOTH config you'll have to head over to clients and create a new client select web application as the application type and you can call it something like JSM travel agency and add an authorized JavaScript origin here you can do something like http
col// localhost 5173 and you can click create later on we'll need to update this with the URL of our deployed application what matters most right now are the key and the secret that will just pop up so copy the key first head back over into Apprite and provide this app ID that you just copied as well as the client secret which you can paste in the second step then copy the URL at the bottom of apps card and click update this will say that the Google authentication has been updated and then head back over to
Google click edit on this client and add an authorized redirect URI by pasting the apprite redirect URI that you just copied and then click save now another thing we have to do on Google to be able to retrieve Google profile photos is to search for Google People API it's this one right here and enable it this will allow us to extract the profile photo from every user perfect was a lot of setup i know but now we prepared ourselves to handle authentication and we have also set up everything regarding apprite so we have very seamless
development time from now on so in the next lesson let's implement it within our codebase to get started implementing our backend system I'll head over into our terminal and just run one simple command mpm install appite we'll use it to handle our back end it got installed in a couple of seconds and now I'll head over into ourv just to add one more environment variable which I'll call vit apprite API endpoint we can add it right here at the end and this is something that is the same for every single apprite project so I'll set
it to https col/cloud.apprite.io slashv1 this is where the cloud version of apprite is hosted so our app needs to know it so you can hit those proper endpoints now we'll need to set it up so I'll check out this documentation that is very simple it guides you through everything we have done so far but then it asks you to set it up from within the code so to get to this page you can just head over to Appra documentation quick start for React and scroll down to the part where we have the code perfect so
it says to create a new file under lib apprite.js and add the following code to it but instead of just copy pasting what we have here we're going to write it together from scratch that's what you're watching this video for anyway right to learn how to do things properly from scratch so back within the code let's head over into our file explorer and head into app create a new folder within it called apprite and within the apprite folder create a new client.ts file where we'll set up the app client here we'll first get access to
all of the apprite variables coming from constants and then we'll export them so we can use them from within the rest of our application so export const apprite config is equal to an object where we're going to have the endpoint URL set to import meta.env_apprite_i env_apprite_appi endpoint and now we can duplicate this a couple of times because we'll need to change it for different things the second one will be the project ID and then of course we'll have to change it to vit apprite project ID after that we'll need the API key which will be
vit apprite you can guess it API key after that we have a database ID and then we'll point it to database ID after that we have the user collection ID pointing to V appride users collection ID and finally we have the trip collection ID pointing to V appride trip collection ID let's just make sure that we have called them properly trips trips user user it has to be the same so if you open up your envir users plural as well as trips plural as well so make sure to spell it properly otherwise the app will
not work so you have to do this very carefully what you can do is maybe a global search for the entire thing you can do that with control or commandshift F and it should search it across the entire codebase so I can immediately see that this one matches this one matches as well database ID is here as well i can see it here this one matches as well apprite API key is there project ID is here and API endpoint is here as well i think I should be good now that we have this entire configuration
we can say const client is equal to new client coming from apprite so new app client and on it we can call a method called set endpoint to which we can pass over the app right config endpoint URL and I'll also call the set project to which I'll pass the app config dot project ID so this way this app right client knows exactly which project and endpoint we're working on after that we'll have to set up additional appre functionalities such as const account to manage user accounts we'll be equal to new account make sure to
automatically imported from apprite at the top account and client from apprite to which we'll pass this client that we have above i'll do the same thing for the database so con's database is new databases coming from apprite and to it will pass the client and con storage is equal to new storage also coming from apprite make sure to import it at the top and I'll pass the client so now that we have the general apprite client as well as these three additional functionalities we can just export them from this file so we can use it
within other files client account database and storage perfect so now that this implementation is done the next step will be to implement authentication there are two different approaches we can take right here the first one would be to focus on the layout or the UI first like what we have been doing before and the second one would be to first implement the functionality in this case we have Google O connected to apprite o and then later on we can very easily do the UI so what do you say let's do the harder thing first back
within our codebase I'll create a new file within the apprite folder which I'll call o.t RTS here we'll create all sorts of different methods that allow us to handle authentication starting with of course the login so all of these different methods will start with export const because these functions will be used from within our front end so I'll say export const login with Google it'll be equal to an asynchronous callback function that'll have a try and catch block in the catch we will simply console.log that error and that's going to be it now we'll have
all sorts of different functions that'll follow this same exact structure export const async function with a try and a catch block where we console log something so what do you say that we automatically duplicate it a few times and then rename it for all sorts of different functions that we'll need i'll do it a couple of times and then we can start renaming them so we have login with Google of course we'll also need a log out user to log them out next we'll need to get the user this is to get the user profile
we'll also need one to get the Google picture their profile photo and we'll also need one to store the user data into the database once they first log in so store user data and if I'm not mistaken we'll also need one to get the existing user if they existed before right now how did I know that we'll need all sorts of these different functions or the other question is how can you know when you're just approaching this project the answer is you can't i actually took the time to build this application beforehand so I can
teach it to you properly if you were building this for the first time you would just start with one massive big function and you would start noticing that that function actually is getting too big and it's doing too many different things with proper code one function should have one job so as soon as you start noticing that it's starting to get more jobs you separate it into other functions okay but with this in mind we can start implementing these one by one starting with the login with Google functionality here we want to leverage apps functionalities
that we have exported from the apprite client file such as for login with Google we want to leverage the account functionality coming from apprite client specifically the create oath to session method that accepts a couple of different parameters the first one is the oath provider so I'll say oath provider.google that's going to look like this and as the second parameter we have to provide the success URL and as the third one the failure URL but all of these are optional so I think if we just leave them empty for now we should be good what
provider Google and you might need to import this oad provider coming from apprite perfect believe it or not this is it to log in the user now for each one of these errors in case you want to make them a bit more descriptive you can say before the error itself you can render a string with the same name as the function so you know exactly where that error is coming from great so login with Google is done but once we actually log the user in we might want to focus on getting that user's information okay
so we'll leverage the same account functionalities but this time not to create a new oath session but rather to get that user that we have just created so I'll say con user is equal to await account.get then if we don't have access to a user so if no user I'll simply return a redirect to forward/signin something obviously went wrong but where is this redirect coming from well it's going to come directly from React Router pretty simple way to do it but in case we do actually get back the user we want to extract something out
of it so I'll say const dstructure the documents and say that is equal to await database.list documents and here you're basically telling it hey extract some documents from the database from for me but you have to actually say exactly from where in the database you want to extract them so you have to provide the ID of the database the ID of the collection and then finally the query with which you want to query that database so thankfully we have stored all of those pieces of data within our apprite configuration so as the first parameter I'll
provide a database ID and this is coming from apprite config so make sure to import this app config right here at the top and then we have to provide the collection ID so that's apparate config user collection id and as the third parameter you have to provide the actual query that you want to use to query these documents so I'll use appreite's query functionality which we'll need to import from apprite equal so we want to return documents where the account ID equals to the user dollar sign ID so we're only returning the data for the
user that is currently logged in then I want to run the query select to tell it which fields do we need such as the name email image URL when did the user joined so joined ad and the account ID perfect and of course make sure to import this database coming from app client great so this function should give us back our logged in user so I'll shift it above right above our logout user because we might not need that functionality for some time yet but what we will need is a Google picture so extracting this
seems like a perfect thing for an AI to do as we don't have to necessarily write all the code ourselves especially repetitive or boring code which doesn't really deal with any logic so this might be a perfect chance to give Juny a try see Juny is Jet Brains or WebStorm's new smart coding agent for productivity and I wanted to test it out a bit to see whether it can help us with this i started using it recently and I'll link it in the description in case you want to test it out too but let me
give it a shot and see what it comes up with so I'll open it up by pressing command shiftp and then typing jun which will open it up right here on the right side now I'll give it a task to fetch the profile photo from the Google people API and return its URL okay I really wasn't descriptive enough so let's see if it can actually do it based on this limited amount of information okay it created a plan which it will use to do that it actually checked out the enving the odd file and now
it should actually write some code after some thinking it looks like the get Google picture function has been implemented to fetch the user's profile photo from the Google people API using the OOTH token it's also changing the store user data and get existing user oh it also implemented the logout function this is pretty cool so it seems like it actually did a lot of stuff right here it stored the user data into apprite by fully understanding how apprite actually works creating a new user document and it even fetch the existing user from apprite all of
this is super amazing but did it do what we asked it to do well let's check it out first things first it is getting the current session and it's getting the OAT token from that session then it's checking whether it got that access token and by the way if you don't have Juny you can just pause right here and write out this code as well then it's actually making a request to the Google people API to get the profile photo so it actually scouted the web and found the right endpoint people Google apis.com v1 people
me person fields photos this is exactly how it should be and it passed the headers with the authorization bearer oath token if it failed it console logs it else it extracts the data gets the photo URL and returns it this is pretty crazy this is the exact implementation that I had in mind so I can now collapse it and they just told me get Google picture function was implemented the implementation is error-free and all the related functions were updated accordingly i got to say this is amazing and it even tackled the logout user functionality where
it simply deleted the current session and return true so feel free to pause and implement this as well and if we look into the store user data here it's getting the user it's checking whether the user already exists in the database and by trying to list that document if it does exist we simply return it else we get access to the Google photo and then we create a new user await database.create create document within this database the user collection we make it unique I think here we can do a bit better by saying ID coming
from apprite dot unique so here we can really use a unique ID and then it passed over the account ID the email the name the image URL and the date we joined that finally it returned the new user This is the perfect implementation of this function and I got to say I am amazed it did everything I asked it to and even more it's even checking for the existing user and returning it if it exists but with such a great implementation of the get user function it's even checking for that here so that additional function
might even be redundant okay since Juny coded this for me it's possible that while trying to replicate it you maybe have some typos so for that reason I'll provide this o.ts file within the video kit as well so you can copy it and ensure that you have the same exact implementation that I have even I'll do it right here by deleting it and putting it here just to ensure that everything is exactly the same and with this in mind I'll also push this over to GitHub by running git add dot getit commit-m and I think
I forgot to push for the last couple of lessons so since then we have implemented routing dashboard layout dashboard UI as well so dashboard layout and UI and now even the o functionality so I'll commit it and push it and in the next lesson we can focus on implementing the UI of this great authentication page to get started with Oth UI let's first create a route for the O page so head over to app and notice how currently we have the routes right here for the admin but now we'll create it outside of the admin
layout which means that this page won't share the layout we created for the admin dashboard because it doesn't have the header the left sidebar the navbar or whatever it just has a singular login so I'll create it right here within a new folder called root and within root I'll create a new file called sign-in.tsx within which I'll run rafce now we'll have to head over into routes.ts because this doesn't just work like Nex.js's file-based routing you still have to define it right here so above the current layout I'll add a new independent route pointing to
sign in and its path will be routes root signin.tsx like this so now if you head over into that route we can also try accessing it on the dashboard by heading over to sign in and there it is sign in cannot read properties of null so let's actually implement it if I reload it one more time the error is gone so I believe we should be good i'll start with the UI so I'll turn this div into a main and give it a class name equal to off what this will do is it'll automatically apply
the background photo right here looks okay on mobile but once again it really stands out on desktop if you want to check out how I did it the only thing you have to do is basically apply a full width a full height and then there's this background o background cover and background no repeat and this image is taken over right here from our assets we have just added it as a CSS variable right here within Tailwind so now I'll create a new section within this main and this section will have a class name equal to
size-ool glass morphism flex-c center and a padding x of six this will basically create this glassy feel over the entire background so the card is more visible so within this section I'll create a new div with a class name equal to sign in card and within it I'll create a new header with a class name equal to header within this header we can render a react router link so make sure to import it and we can point it to forward slash meaning just the homepage and within it we can render our logo so I'll render
an image with a source of assets forward/assets/icons/lo.svg with an al tag of logo and a class name equal to size of 30 pixels there we go and below that link we can render an H1 that'll say tour Visto which is the name of the application with a class name of P28 bold and text-d dark 100 thankfully I added the tour visa name to our Webtorm dictionary so now even when I misspelled names I know I misspelled them there we go below this header we can create the article for the rest of the card that'll have
an H2 and this can say something like start your travel journey we can style it a bit by giving it a class name equal to P28 semibold text- dark 100 and text- center and below it I'll also render a P tag that'll say something like let's see what do we have in the design sign in with Google to manage destinations itineraries and user activity with ease so I'll copy that from the design and paste it right here i'll also give it a class name equal to P18 regular text- center text-g gray 100 and exclamation mark
leading dash 7 so this will change how the characters are structured right below this article I want to render a button and this button will come from Syncfusion so I'll say button component coming from Syncfusion right at the top that is Syncfusion EJ2 React buttons and we can render an image right within that button with a source of for/assets/icons/google.svg SVG within it i will also render an icon CSS that'll be E search icon so we style it further with a class name of button class exclamation mark H11 for the height an exclamation mark W full
sometimes you have to add the important style to the class names so that they're actually taken into account and now you'll see this huge Google G so let's instead give it a class name of size-5 to make it smaller and I just noticed that I applied some styles to this image right here but those styles were actually meant to be applied to the button itself so I'll move them up and say this button will be of a type button with the icon CSS a class name and an on click equal to handle sign in so
right at the top I'll declare this handle signin function as an asynchronous basic function for which the logic will implement very soon but now what matters is that we have this button within it we have this image we can also give it an alt of Google and below the image we can render a span that will say sign in with the Google and we can give it a class name of P-18- semibold and text-white so we can better see it on this blue button there we go sign in with Google now the only thing we
have to do is implement this handle signin function which right now will be super simple to do actually believe it or not we have already done it so right here at the top the only thing you have to do here is say await login with Google which basically renders this entire function not needed so instead of declaring this handle signin function what we can do is just head down and call the login with Google right here when the button is clicked but there is one thing that we have to add at the top though and
that is a loader see in React Router a loader is an asynchronous function tied to a route and it runs before the routes component renders and provides the data to that component so that it can use it immediately once it appears this makes the app feel faster and more structured because data fetching happens before the UI shows so to implement this very cool React router loader you can do it right at the top of the sign-in function export async function client loader you can open up a try and catch block in the catch we can
simply console log the error saying something like error fetching user and in the try we'll actually try to get the user data beforehand so we'll say const user is equal to await account.get and then if there is no user dollar sign ID we'll simply return a redirect coming from React Router to the homepage and if we get the user that's great because then we'll already have access to it before and we also have to import this account functionality from apprite client so now if you reload you can see that it loads super nicely and we
can also copy this client loader and also add it to our admin layout but we'll change it up just a tiny bit within the admin layout head over above the current admin layout component and paste this client loader here we'll do something a bit differently keep in mind this will render when we're on the dashboard page so here we want to check apps o for the currently authenticated user and if there is no authentication redirect user to the signin so we'll do account.get if there is no user ID then we'll redirect to sign in so
a bit differently than in the other one but if there is an existing user so const existing user is equal to a wait get existing user coming from apprite o to it we have to pass the user dollar sign id then we can check if existing user question mark status is equal to user in that case we again want to redirect like this to the forward slash why because regular users should not be able to visit the dashboard only admins can so this is another check to see if we need to reroute them somewhere else
finally we'll return existing user question mark dollar sign ID if we have it we'll return the data for the existing user else we'll say await store user data like this here I am missing user and here instead of saying error fetching user I'll say error in client loader and I'll return the redirect to sign in because most likely we have to sign in if we experience the error fetching the user what you can do now is open up your terminal expand it a bit and stop it from running that way all of these changes regarding
routing will be applied so you can then rerun your application by running mpm rundev which will spin it again on localhost 5173 if you do this and if you head over to forward/dashboard you'll notice that it'll automatically redirect you back to signin which means that our redirects and authentication are working now just before we click this signin with Google button let's head back to Apprite to make sure that all of our settings are correct as of recently AppRight is making a lot of improvements to their infrastructure and one of those improvements are the regions right
now Frankfurt is one of the primary ones but in the future there could be more so depending on which region is closest to you you'll need to click on it right here to copy the endpoint and then you'll have to use what you copied over right here as the apprite endpoint since I'm in Frankfurt region for me it's FRA alongside that we can head back over to O head over into settings and look for Google we have already added our app ID and secret before but I think I forgot to enable it so click enable
recheck your app ID and app secret over from Google on Google you can also head over to the client you created and remove the authorized JavaScript origin for 5173 localhost it's not needed since we have this apprite redirect and once again just copy the URI from apprite and paste it over right here to make sure that you have the same exact one as of recently I think they're also adding the region at the start once you do that click save and also head over into your branding and make sure your app is published if your
app is not published somewhere on this page you should see a publish button so once you do that you'll be able to upload the logo of your application to further customize how your authentication behaves so if everything is looking good on the Google side click update right here on Apprite to confirm our setup just like that Google authentication has been enabled so you can head over to our application and click sign in with Google and just like that Google authentication has been implemented in case you want to further customize your workflow you can do that
by implementing your app domain and your app logo so it shows up on Google so that people know what app they're signing into but with that said I'll just sign in if you do that you can notice that we'll be redirected to localhost 5173 and now if you try to be sneaky and manually navigate over to the dashboard to be able to see what admins are seeing check this out you'll automatically be redirected to the public facing homepage which right now is completely blank and if you navigate over to sign in you should be redirected
back to the homepage as you're already signed in but it looks like that redirect is now working so if you head back to your sign-in page it looks like we need to reverse this operation if there is a user already logged in then redirect to homepage so if you save this and now head over to sign in you can notice that since we're logged in we can no longer see the login page perfect but now hey how do we actually continue developing the rest of our dashboard finally that we're logged in since we are now
being redirected well let's give ourselves admin permissions head over to your app right databases travel dashboard users and notice this one single user right here if you look at its data you'll see my name email and and account ID the image URL as well joined ad and then at the end there's a status it's either a user or an admin so I'll switch it over to admin save it and head back over to localhost 5173 and navigate over to dashboard if you do that you can notice that now we can see the dashboard and we're
no longer getting redirected perfect this means that the entire signin and account creation process are working properly let's also hook up the logout functionality so we can test the full circle of logging out and then authenticating one more time to do that we can head over into our nav items component and right at the top of it we can try to access the user data to replace this dummy user i can do that by saying const user is equal to use loader data and this is coming directly from React router so this is a new
functionality but wait how does this use loader data know exactly what data to fetch from where and why did I set it to be equal to the user well let me show you the use loader data gets the data from the loader function of the nearest route now the nav items component itself is not necessarily a route but it is used within our admin layout so if you head over to the admin layout you'll see that here we have a client loader function that returns only one thing what is that the existing user so that's
why we know that we can fetch that user right here pretty cool right alongside the user we can also get access to the navigate functionality coming from the use navigate hook also provided to us by the courtesy of react router which now is working much more similarly to Nex.js which of course I embrace and then here we can handle the logout functionality it'll be equal to an asynchronous function where we want to await the call to the logout user function coming from apprite off and then after that we simply want to navigate over to forward
slash sign in now let's also render the real user information so right here we have the username and user email i think it's already done right because we're now fetching it from the real user instead of the fake user data object that we had before the only thing we have to do is on click instead of this console log we want to call the real handle logout so if I now save this head back over into the dashboard we'll see that we now have a real Adrian name right here with a real email but the
photo is missing so to fix it I Googled Google profile photo not loading in Vit React development so just trying to see what would be the reason that even though we have the access to the actual URL of the photo and I know we have it because I console logged what was coming back from that photo if I then head over into it you'll see that it's actually publicly accessible online but for some reason it wasn't showing within our application so I Google that and then if you head over to the response it'll say that
by adding refer policy of no refer to the image tag you can solve the Google image not showing so just add the refer policy of no referer to this image where we're rendering the image URL if you do that and go back to your local host you should now be able to see this image right here perfect so finally let's test out the logout functionality if I click it I get redirected back to signin now I'll head over to the dashboard manually and you can see that I can't see it i immediately get redirected to
the signin can I maybe go to the local host just the root route well yes because currently there's nothing on there later on we'll implement the redirect from there too but now if I go ahead and sign in okay now we got redirected to the homepage later on we'll implement a great public-f facing website right here but for now let's head back over to our admin dashboard since we have the full privileges to it and one thing that I noticed is that right here even though it says your name it's not the same name that
we have here it's still using the one from constants so to fix this we'll have to use the React router pre-fetching one last time for now so would you know how to do it based on how we implemented it previously if not I'll show you how to do it one more time first we have this additional function that we create export async function client loader within it it allows you to prefetch some data you need on that page the only thing we need is the user so I'll say const user is equal to await get
user like this and this get user is coming from apprite o so make sure to import it from there and then simply return it or in an even simpler way you can just say return await get user or if you want to be really cool you can turn this over into an ES6 arrow function by saying export const client loader is equal to an asynchronous arrow function with an immediate return so you can just skip the return statement and the braces and just say return await get user and with that you can turn this client
loader into a nicel looking oneliner which fetches the user and now you can retrieve it from within your page just right here under params you get access to the full loader data and we also need to assign a type to it this type will be provided to us by react router so I'll say router or rather route component props and we can import this type so saying import type route in curly braces from dot slash plus types slash dashboard if you do that and remove these curly braces because we don't need them as we're not
within an object you can see that now our TypeScript knows that the loader data will contain the data returned from the loader or the client loader which in this case is the user so how do we now accept that user well the only thing we have to say is const user is equal to loader data and we can even assign a type to it by saying as user or null if it maybe doesn't exist now it looks like TypeScript is still complaining saying that this could be unknown but that's not really the case because we
know what we're going to get back from the client loader oh but it looks like I forgot to add an export statement right here if you add it you'll see that it'll no longer complain and it knows exactly that the username is either a string or undefined perfect so with this in mind we are good to test it all out so if you head over to your dashboard you can see that at the bottom left we have the username and now it says welcome Adrianjs Mastery right here at the top which means that we're successfully
gathering the user data and we're successfully signed in so with that we have now implemented the full circle of authentication everything from the UI to functionality to log out and also fetching the user data and storing it within the database so if you head over to apprite you can already see that we have this user that we created but if you also head over to O you'll see that we have created a user over there as well so not only have we implemented the full authentication but since we have the user in the database here
that means that now we're also successfully creating documents within our database collections which means that we can also not only fetch them to show the username on the left sidebar or the dashboard but rather we can render a full table showing all the users that have joined their application so far and display their statuses they can either be users or admins so in the next lesson let's focus on implementing the users table just before we go ahead and set up our user stable what do you say that we make our app enterprise ready or maybe
should I say enterprise secure using Sentry see Sentry is the application monitoring software that's considered not bad by 4 million developers and it'll allow us to track all sorts of different errors when you actually bring your app to production since I've partnered with Sentry they decided to give you 50,000 errors to test out and trust me that's more than you'll need so I'll leave this special link down below so feel free to create your account and then you'll need to create a new React project like what we've done at the start once you're there don't
follow this setup right here where it asks you to install Centry React in the docs I found a special guide for the React router framework v7 for building full stack web apps with React i'll leave the link to it within the video kit so let's go ahead and follow it together you need a Sentry account check a Sentry project check and an application up and running check as well let's choose what we want to use Sentry for i'll turn everything on from air monitoring to tracing session replays and profiling and I'll copy the command given
to me then head over within your terminal and paste the installation command this will take a couple of seconds and it'll get installed next we can run react router reveal which will reveal some files that'll allow us to set up Sentry so I'll just run that command and now you can see two new files created for you right here within the app entry client and entry server next let's do the client side setup the only thing you have to do is just copy this file from the docs i believe Sentry has already taken the DSN
the ids and the pro and everything regarding your projects directly from your application if not make sure that it did that by making sure that you have some ids or links right here at top then copy it and override the client DSX with this new one that you just copied here we enabled the replay integration as well as the browser tracing integration now we'll need to update our root.tsx tsx to report any unhandled errors from our error boundary so copy the import statement and paste it right at the top of your route and then also
add this century capture exception within the else if of our error boundary that'll be right here at the bottom error boundary and here we have the else if so alongside just reporting it we want to allow Sentry to capture it now let's scroll down and let's do the server side setup by copying this instrument server mjs file so let's go ahead and create it within the root of our application i'll call it instrument.server.mjs and simply paste the one that you just copied scroll down and then we'll have to update our entry.server.tsx so head over into
it that's going to be entry server.tsx tsx for this file we'll have to copy the import i'll paste it at the top then you'll have to take this handle request but you can notice that it already exists within our codebase so what we can do is just we can wrap the current handle request that'll look something like this at the bottom of the screen we can just say export default handle request and then we can wrap it within sentry sentry handle request like this and now you can find where we're exporting the handle request in
the first place and just remove it rather just make it a regular function that we're then wrapping with sentry handle request and exporting at the end and then also copy this handle error part right here and paste it right above it you can import the handle error function coming right at the top from React Router and the only thing that bothers me right here is that it says that sentry handle request has been deprecated but thankfully if you scroll just a bit down you'll see that if you need to customize your handle request you can
just get this wrap sentry handle request provide this pipe within it and then finally export it like this so let's just enable this distributed tracing between the client and the server by adding it below this resolve so I'll head right here find the resolve and add it right below so you can see we have the same thing here but now we're wrapping it with this get metatag transformer coming from Sentry and then also at the bottom we're not going to use Sentry handle request we're going to use the wrap sentry handle request perfect docs are
on point so far so let's continue with the updated scripts because React Router is running in ESM mode and we need to use the import command line options to load our serverside instrumentation before the application starts so we'll have to update the start and dev scripts to include that instrumentation file i'll copy these dev and start commands and head over to package JSON and update the dev and start perfect and then we can set up the source maps upload by updating our V config so you can notice that Sentry automatically prepopulated the organization in the
project for me in case you have multiple you can select one right here and then make sure to generate the O token as well i mean the fact that you can do this from the docs directly is super cool now copy this sentry config head over to vit.config.ts and add it right at the top make sure to import this type from sentry options and now we can define this config so let me just copy this entire config part and paste it below our current one you'll notice that we have two now we're going to keep
the one from Sentry but make sure to add Tailwind CSS to it as the first plugin and add TSC config paths right here as the second plugin as well then we have the React router and the Sentry React router and then make sure to add the SSR right below the plugins or in this case below the Sentry config if you do this you can delete the current config and notice what's happening here we're actually exposing the default config and then passing it over into the Sentry React router config alongside the Sentry config make sure to
import all of these from Sentry and make sure that you have your O token right here alongside your project and organization information if you did that correctly you'll want to include the Sentry on build end hook in React Router config head over to the React router config copy this entire build end part as we don't have it and paste it after the SSR and import sentry on build end perfect and that's it everything has been set up i got to give props to Sentry for creating such a wellstructured documentation page i mean I was just
able to go through it and everything worked just seamlessly even though React Router V7 is a super new framework they knew exactly what they were doing sentry showed me how we can expose the entry server and client files so we can change the configurations and they only added the minor changes to all of these files the minimum for what's required for Sentry to work so with that in mind the only thing left for us to do is to verify this snippet of code will include an intentional error so that we can test that everything is
working as soon as we set it up so throw an error in loader to verify that sentry is working after opening this route in your browser you should see two errors one capture from the server and one capture from the client so instead of copying this entire file we can copy this error right here throw a new error thrown from a loader close all the currently open files and head over into our dashboard that'll be right here within app routes admin dashboard and we'll have to add a loader so you know what i'll just copy
the entire loader paste it right here and save it now if you go back to the browser and reload the page you'll be able to see oops some error thrown in loader believe it or not this is exactly what we wanted to see so if you head back over to your Sentry dashboard and reload the page you'll be able to see your error dashboard and you can see that this index increased and that has been one error right here so at the top right click view all issues and there we have it a minute ago
a new error was thrown in a loader and I mean just take a look at the amount of information that we're getting back right here we can see when the error has happened we can see how many users has it happened to on how many occasions we can mark it as resolved or archived and we even have a replay attached to it so you can see exactly what the user was seeing before the error happened in this case it happened as soon as the dashboard loaded but how do we know that it was actually on
the dashboard well believe it or not here you have the breadcrumbs of everything that led to that event you can see that the user was trying to load the dashboard data and then there was an error in the loader this actually tells us a bit more about how React Router V7 works why because we didn't manually make a request to the dashboard url but it looks like when you try to access the dashboard page and you have a loader attached to it it actually tries to fetch the data for that page first and then we
weren't even able to get to the dashboard page so having all of this information is super useful not really right now while we're in development but in production why well because your users will not be able to tell you this right you're going to ask them hey where are you experiencing this error why did it happen which device you're using they will have no clue right but here you can just check it out the user was trying to access the dashboard page and then immediately after we got an error in the loader you can see
when the error happened oh and as I was explaining this to you looks like I got this little tool tip that's going to explain it to me too so we can go through it together so you can see this error in aggregate across all the different users as well so you know whether it's one that you should fix very soon or the one that can wait you can also narrow your focus on when this error has happened you can explore the details of the error like the context around which it happened which is the IP
address the browser the operating system everything that Sentry has access to they'll share it right here then you can compare different examples of errors take action on it by marking it as a priority or not a priority one or maybe assigning it to the team member that you don't really like as you don't want to fix that error and finally you can share the updates or maybe even track it on GitHub or Jira but with that said let's remove this error from the loader and let's also try to throw one not from the loader but
from the client component itself so this will disallow us to see the dashboard in the first place and I'll say some error thrown in a dashboard you'll see the second error appear this is a client side error now so if you head back to the issues you'll see that we have another error thrown within a dashboard and for this one of course you'll get different kinds of information because it's coming from a different context overall Sentry is just the go-to tool for tracking and monitoring errors in production and you can also check out traces which
allows you to see exactly how that specific error has happened by checking out the descriptions of what was happening before it there's profiles that you can implement to find slow code there's replays that allow you to see exactly what was happening on the screen before the error happened and you'll also just get a lot of quality insights like the first Contentful Paint your web vitals and more you can check it all out in the onboarding so with that in mind let's remove this error from here as we don't want to break our application and if
you head back over here it's like nothing ever happened but now we have an enterprise ready error tracking monitoring system put in place so once we actually deploy this application if something happens to it in production we'll know exactly what happened and to how many users it happened so we can act on it quickly to get started working on the all users page first navigate over to it and then open it up right here within our codebase we have already added it to the routes so if you check it right here you can see that
you can access it by going over to all dash users which is exactly where we're on right now and here we'll use Syncfusion's grid component they have this very nice guide that shows you how you can install it and set it up but basically what you have to do is use the grid component pass the data source to it and then render a couple of different columns for the different fields that you want each one of your rows to have so let me show you how we can make it work first things first I'll change
the class name right here from dashboard to all-ash users and then instead of trips page I will say manage users and we can change the description to something like filter sort and access detailed user profiles perfect right below the header I'll render the Syncfusion grid component this grid component will be coming from right here at the top add Syncfusion SLJ to React grids tables are not easy to work with so let me show you what we can do with this grid first things first you have to pass a data source to it so let's say
data source is equal to and now what you need to do is just basically pass an array of different objects into it but instead of declaring it right here for the time being we can use the static data coming from our constants so I'll simply say users and we'll import those users coming from constants index.js it is basically just an array of three different users that I want to render right here on the page and then once we render them we can switch it over to use real data and there we go already just by
doing that you can see a table of course tables are better viewed on desktop devices so if you check it out it's pretty cool right you get the ID the name email image URL they joined itinerary created and a status of either user or an admin now I'll collapse this just a tiny bit so we can see the code but also the rest of the stuff as you can see it is mobile responsive but we can definitely make it so much better so within it I will render a columns directive but make sure that you
spell it columns plural and use the one coming from React grids next right within it you can render each individual column directive so import that one too and then self-close it within it you can specify exactly what you want each field to have and how you want it to look like so I'll give this field a name equal to name i'll give it a header text equal to name with a capital N i'll give it a width of about 200 and I'll even align the text on the left by saying text align is equal to
left and the second one will be just a column directive not columns but rather a column so you'll also have to import it right here at the top and you can see now that we have decided to manually decide how we want to present all of these different columns and rows now we only have one column with names John Jane and John what you can do is also say grid lines and set it to none that way it looks a bit cleaner you do this to the regular grid component now what else can we do
with each one of these fields well we can also define a template that allows us to choose exactly how each one of these will be rendered so for the template I will open up a callback function inside of which we get access to the props which will be of a type user data because we're mapping over the users so for each one of these props we want to automatically return a div and each one of the divs will have a class name equal to flex items center gap of 1.5 and a padding x of four
and within it we want to display an image so instead of simply displaying a name we want to render an image with a source of props image URL with an al tag of user a class name equal to rounded- full size of 8 and an aspect of square if you do this and reload you should be able to see a profile photo for each one of our fake users and below the profile photo you can render a span element that'll render the props.name perfect this is already better than what we had at the start it
really shows how customizable it is now let's also declare another column directive right below the future ones will be even simpler they'll have a field this one will be for the email so that it knows how to match the data see this value right here matches exactly the value of the key in that specific object in the array so the first one was the name and now we want to show email addresses so the field is email the header text will be set to email the width can be maybe set to 150 and text align
can be set to left and you put this column directive properly within the columns not outside of it you should be able to see it appear right here immediately what we can do next is just copy that column directive and maybe duplicate it two more times for the third field I will render the date joined and change the header text to date joined with a width of maybe 120 will be enough so now we see January as well and maybe the next one can be trip created or I think in this case it's itinerary created
we can again say itinerary or maybe trip is shorter created and this will be when the trip was created so about 130 characters is enough so if I save it now you can also see trip created but I need to spell it properly exactly as to how we have it in the user data so if I head over into users it is itinerary created how many of them have we created so this user has created 10 four and eight and finally and most importantly we need to show the status or the type of the user
so I will duplicate this column directive one more time right below and I'll call the field status a header text of maybe here we can say type of the user width of about 100 and for the template we can once again get access to the props of the user data and then for each one we can return an article that we're going to close and within it we can render a div within the div we can render an H3 that will render the props so here you can see that now if you reload you can
see the type of either the user or the admin but let's also style this a bit differently to make it like a chip component within it first I'll style the article by giving it a class name equal to and I'll make it a dynamic CN class name that'll always have a status column class but then depending on the props status which I can just dstructure from the props right here so I'll simply get the status out of it so we don't have to repeat ourselves too much if the status is triple equal to user in
that case we'll return the BG success of 50 else we'll return the BG light of 300 so if you save that you should be able to see a light background on each one of these articles now right below within this div I'll also give it a class name i'll also make it a CN it'll have a size of 1.5 and rounded dash full and if the status is triple equal to user I will then give it a bg success of 500 else we can give it a bg gray of 500 so if you save that
you'll now see that little dot that we have right here and now we can style this H3 within it by giving it a class name of CN font- enter text- extra small and font- medium and finally if a status is triple equal to user in that case text success of 700 else text-g gray of 500 so let me style this properly and if I go ahead and save this you can see that now the text matches the color but it doesn't yet look good and that's because this div should not close the H3 rather the
div should be a self-closing component as it is just a dot that appears right here before the user so you can see how customizable the table is you can show the photo within it you can change how much space each one of these fields takes and you can also change whatever is presented right within each one of these rows perfect and believe it or not that is it a fully functional table where you can actually hover over fields and implementing filtering or sorting and pagionation within it is also super simple i'll show you how we
can do that once we actually get more users but for now I actually want to load up the real users right here not these fake ones so what we can do is head over into apprite o.ds and create a new function called get all users we can do it right at the bottom the goal of this function is to fetch all of the user so let's export a new async function called get all users and make it equal to an asynchronous function like this so export const get all users is equal to an async error
function and we can open up a try and catch block in the catch I will simply console log the error saying error fetching users and in the try we need to list the documents coming from the database so I'll say const dstructure something and make it equal to an await call to database coming from apprite dot list documents as before we have to tell apprite from which database ID to get it and that database ID is stored in the appread config database ID then we have to specify from which collection so I'll say appreconfig this
time it'll be a user collection ID and finally we need to provide a query to fetch this in this query I want to limit the amount of users that we get back by a specific number let's say five for now and then to implement the pagionation I also want to offset the query by a specific number let's say two essentially with the limit and the offset you are implementing the pagionation why because if the limit is 10 and you're offsetting five you're basically dividing this into two pages one that has five and page two that
has the other five hopefully this makes sense so to make this pageionation dynamic as the params into this function we can accept a limit of a type number as well as the offset of a type number two so instead of hard coding them we can just declare them right here limit and offset now the call of this function will give us back the documents and these documents we can rename to user because that's what they are and we also get back the total number of documents what we can say is if total is triple equal
to zero we can just return users equal to an empty array and total equal to the total which in this case will be equal to zero but in case we do get some users we can then return the users equal to users as well as the total equal to the total we're getting hopefully this makes sense so now the only thing we have to do is call this function within the all users table right here at the top instead of declaring it right here we'll use the React router new functionality to fetch it before the
page loads by implementing a loader so above the function I'll say export async function loader or you can use ES6 to say export const loader is equal to an async function and here we can get access to those users and the total number of users by awaiting the get all users coming from apprite o and let's say we want to limit it to 10 and offset by zero at the start because we're in the first page once you do that we can just return an array of users as well as the total number basically what
we have done is we have exposed it right here to within our function so let's dstructure the loader data of a type route coming from react router types and this is going to be coming from all users dot component props and then we can just say const users and dstructure them from the loader data here we have a bit of a warning saying that users does not exist on type get all users let's see if we're properly returning it we are here but we also can return it in the catch so if something goes wrong
we can return the users as an empty array and the total also equal to zero if we do this then it'll know that always there's going to be a return of users and total also let's fix this route import because I can see that it's coming from the wrong file basically it should be coming from just dot slash plus types forward slash in this case we can get it from all users and we have to specify that we're importing a type of route perfect so now we're getting the users and you can immediately see that
instead of having the fake users coming from the constants which we can now completely remove now we're getting a single user from our real database there are a couple of things we have to fix though the name and the profile photo are looking good we can give some more space to the email address so let's scroll down to the email address and maybe give it a width of 200 that'll give it some more space to breathe after that we have the date joined so I'll just rename this to join at because I believe that's we're
saving it into the database and you can see that now we're getting that date joined and we also need to parse it a bit better do you know how to do that well you need to create a template for how you will render it so just create a template have a callback function where you get the props in this case the joint ad date so I'll just dstructured join ad from it and then we can just return what you want to show in this case I'll call the format date function coming from lib utils and
then I'll pass this joint ad right into it if you do that you'll see April 23rd 20 let's also give it some more space to breathe just so we can see it a bit better april 23rd 2025 beautiful did you see how simple that was we can also say that joint ad is of a type string perfect so I was just able to take this piece of data from the database take its field and then nicely present it right here i don't think we'll need the trips created thing so I will just remove it from
now because we're not actually storing it within the database but what we care about is of course the type of the user which in this case is the admin so with that in mind we now have our first and only user and we have a table with which we can check it out we can head over to the dashboard and check out our only user looks pretty cool right what do you say that we go ahead and sign in as the second user as well to see the differences i'll log out we have this great
looking sign-in page and I'll log in with my second JavaScript mastery email address and now if I try to head over to the dashboard you'll see that I'll be redirected back to the homepage so later on when we implement the public facing website I'll be able to check it out but right now as I'm a mere public user I cannot see the admin dashboard which is its whole point so open up inspect element and head over to application and clear your cookies session storage as well and once you clear it just reload your page or
just manually head over to the sign-in i've logged back in with my admin user so if I head over to all users you'll be able to see this second account that says JavaScript mastery i can see that the image is not loading properly but I think we know how to fix that right we have to head over to where we're rendering that image and we have to give it a refer policy of no refer if I save it and reload you can now see that we have two of the same profile photos for you it
should be something different if you used a different profile but what matters most is that the type of these two users is actually different and the email address as well and they're both showing on our table where we can manage the users so that means that our all users table has now been completed so now that we have the basic dashboard the authentication system and our users are actually logged in it's the time that we actually create the trips that's what this app is all about right so let's focus on creating the form that'll make
all of that possible and finally we are ready to create new AI trips well you could say that this is the moment that we've been waiting for we'll do that through the help of a form everything starts with a form of course but then we'll fuse it with functionalities using AI generation but first let's focus on the basics everything starts with some inputs and as Shhatzian likes to say forms are tricky they're one of the most common things you'll build in a web app but also one of the most complex you have to structure them
and they have to be semantically correct they have to be usable and navigable using the keyboard they have to have proper area attributes to be accessible support both client and server validation and they should be wellstyled and consistent with the rest of the application thankfully in this case I'll show you how to do enterprise level forms using Syncfusion so with that in mind let's create a new route that allow us to create trips that we'll be able to navigate to by clicking this AI trips button right now it points us to a four of four
that is available under forward/trips so let's just create it right here under app we have routes and then under routes and admin I'll create a new file and I'll call it create trip.tsx within it I'll run rafce and then to be able to visit it on the page I'll head over to routes.ts i will duplicate one of the last routes within our admin dashboard interface i'll change the path to point create trip and also the path to the file also has to be a create trip path so if we do this what we might want
to do is just change the route to maybe trips/create that will allow us to have proper REST API routes where we're following good naming conventions on this page we'll be able to create the trips but just above it I'll create another table that'll allow us to see all of the trips including the table of trips so I'll call it just trips and I'll point it to admin trips.tsx so let me create that file by heading over to right here under admin and I'll create a new file called trips.tsx i'll run rafce and here we'll be
able to see all the trips 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 so one of the things that we'll have on the trips page will be a button that will allow us to head over to the trips creation page and we'll actually do that through the help of a header we're already using the header in the all users table so let's just go ahead and copy this starting main tag and the header from the
all users page and let's just paste it right here and properly close the main tag 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 AI generated travel plans but here's the kicker 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 so I'll give it two new props a CTA text equal to create a trip and a CTA URL
pointing to the new route we created of forward slashtrips slashcreate and then we can head over into that header and accept those two new props which are going to be optional the CTA text which is optional of a type string and a CTA URL also optional of a type string 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 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 in this case the new create trips route we created and within it we can render a button component coming from shot CN that'll have a type equal to button a class name equal to button-class excl exclamation mark H11 because sometimes when we need to add additional class names to Syncfusion components we need to denote them as important same thing for the W full and on medium devices W of 240 pixels i found that value works the best within that button we can render an image with a source
equal to assets icons plus.svg with an al tag of plus and a class name of size of five this is a very simple plus icon and then on the right side we can also render a span that'll simply render the CTA text and we can give it a class name equal to P16 semibold as well as text-white 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 a trip button which on desktop looks something like
this 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 which is exactly what we wanted so now we can start implementing that create trip form 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 flex dash call a gap of 10 padding bottom of 20 and a wrapper property this will give it some padding and nicely position it
on the screen within it we can also render another header yep we really want to reuse them as much as possible we'll give it a title of add a new trip as well as a description of view and edit AI generated travel plans and we can end the header right there right below it we can open up a section within which our form will go so let's give it a class name and give it a margin top of 2.5 to divide it from the header as well as a wrapper MD what is this wrapper MD
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 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 you just want to be able to see what you need to see and that is nicely structured inputs that don't necessarily need to extend from the left to the right edge rather you need to have them right in front of you 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 and on submit we can call a handle submit function which I will declare right above by saying const handle sububmit is equal to an asynchronous empty function at least for now we'll add the logic very soon now if you save this you should be able to see a cardlook layout so within this form I will now add a div and within that div I'll add a label that'll have an HTML 4 country why country because the first thing you
got to figure out when you're planning a trip is where you want to go to so I'll basically add a label that'll say country right here and then below it we want to implement a component known as a combo box see a combo box is like a select element but it also allows you to start typing within it something like this where you can type and then you can find results faster like selects are pretty good if you have a limited set of options like five two or 10 but if you have more than 100
or 200 countries well you want to make your life easier and start typing the name of that function so you can more easily find it that's exactly what a combo box component is for and in my early development career I remember creating those combo box components on my own and trust me you don't want to do that it takes a lot of time so we're going to make our life a bit easier here and actually use good programming practices which is not to reinvent the wheel and we'll use a shot CN combo box component which
you'll have to import at the top 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 first things first you'll want to pass in the ID and this will be for selecting a country once you have the ID you'll also want to have a data source similar to what we had with our table this can accept an array of maybe different titles like country one country two and so on 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 i can start typing it suggests me option and I can just select one with my keyboard but now we're going to have more countries and we want to display it in a bit of a better way maybe something that looks a bit like this right so to achieve that we'll have to have a list of different countries that we can fetch thankfully there is a simple endpoint called restcountries.com v3.1/all and if you just head over to that URL it'll give
you a JSON formatted list of all of the countries in the world which is exactly what we need so what do you say that at the top of this page we make a simple fetch request to fetch those countries to do that I'll actually use react router loading functionality by saying export const loader is equal to an async function within which we can get access to a response by calling that API so I'll say await fetch and I'll make a call to https col/restcountries.com/v3.1/all then we want to get the data by awaiting the response JSON
as you typically do with fetch requests and then what do you say that we simply console log them so I'll console log the data we're getting back i'll head over into our application and I will just open up the console and reload the page and if you do that you'll get an error that's because we're not returning anything from this loader so let's actually just return this data and if you do that and reload we will be good so instead of console logging it here where we actually need that data is down below so let's
just put that console lock here and we can actually dstructure the loader data which is of a type route dot component props and this route has to be imported right at the top by saying import type in curly braces route from dot slash plus types forward slashcre so it knows exactly what we have within it and we can dstructure the countries from the loader data so I'll say const count countries is equal to loader data as country array perfect 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 so let's just console log the countries and reload the page 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 each having a name and a ton of other details but we don't need those details for every single one of them so instead of returning the full data let's actually map through it and only return some pieces of the data so I'll say return data.m map where we get each
individual country of a type any and for each one we want to automatically return an object you do an automatic return by wrapping this first in parenthesis and then returning an object and we want to take the name we'll make sure that the name is equal to country.fl country.name.com we'll take in the coordinates which will be equal to country.latt LNG latitude and longitude the value will be equal to country.name do common and I'll also take in the open street map which will be equal to country.maps question mark street map so now we're returning only the
data that we'll actually use and we have it right here within our application so you already know how Syncfusion works magically when you have a data source the only thing you have to do is actually provide that data source right here within this combo box component if you do that and reload the page you'll see that we have a list of a lot of something that something will hopefully turn into a lot of countries soon but for now it is just a list of emptiness so let's go ahead and modify this combo box component to
actually show the right fields so I'll say fields is equal to an object where text will be text and value will be value with a placeholder of select a country and a class name equal to combo box if we save this you can see the placeholder but still no countries that's because we have to take this data and we have to modify it a bit 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 so I'll say const country data is equal to
countries.m map where we get each individual country and for each one we can automatically return an object where we take only the text of country.name name and the value of country do value and these values will then match the ones that we are referring to right here text and the value so let's just say countries is equal to country data you can see that the format matches and we immediately get both the flag as well as the text are you wondering how are we getting the flag as well 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 to see it and with that said we have this greatl looking combo box well not that great looking as of now but it will make it better looking but hey you can try to start typing something and it won't yet update we'll also work on that very very soon so what we can do is fix the class name right here which will automatically make it look so much better let me just reload the page
right here check this out 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 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 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 this is a function that
we haven't yet created so let's just create it right above 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 so right here I can already 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 and then we can do something with those values within the function but more on that soon what we want to focus on right now now that we have
the change is also to turn on the allow filtering 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 that's because we have to define what property will we filter by 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 so query is equal to e.ext dot to lowercase so we can actually not mind the case then we want
to call the e update data and we want to take the list of countries that we have and filter them by getting each country and we'll filter it by name so we're going to check whether the countryname to lowercase includes the query that has been typed once we get that list of countries we'll map over them by saying dot 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 so once again make sure that you have a parenthesy right here we'll
automatically return a text of country.name as well as a value of country 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 so let's go for United States there we go we can also immediately search over for India all of it works wonderful so that's it that's how you very easily create your very own combo box and make it fully functional so what other information do you think is important when deciding where you want to go for a trip the country
where you want to go to and the location surely matters a lot right but what else can we do well let's head below this div that is wrapping our combo box and below it I'll create another div within which I'll have a label and it'll have an HTML 4 for the following input and this one will be about the duration of your trip so I'll say duration and below it I'll render a regular HTML 5 input with an ID of duration a name of duration a placeholder equal to enter a number of days for example
5 12 and so on and we can also give it a class name of form input 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 and we can also give it an onchange so I'll say whenever something changes here take that key press event call the handle change function that requires a key in this case we're changing the duration as well as the value but before we pass the value I'll convert it over into
a number so number constructor and then e.target value so we can actually work with it 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 of a string like the URL bar so you have to convert it back into a number great so now we have the duration as well what else do we need well we'll need a couple of different select elements that allow us to choose the type of our travel are we
traveling with a group maybe solo maybe as a couple then we have to choose our travel style our interests and finally a budget estimate select elements are super useful because you're nudging your user in the right direction you can propose a couple of different options that a user can choose for example travel style can be luxurious or maybe adventurous maybe budget can be low it can be high you can tell your user what you need to hear and we're collecting all of this information in order to make the best trip possible for them so now
that we have this second div which holds the duration we can focus on mapping on all of these individual select items below the div I will map over our select items coming from constants see right here I created an array that has these couple of inputs group type travel style interest and budget and we'll actually map over them so we don't have to create each one of these one by one so I'll just take the key and I will automatically return a div for each one of these this div will have a key since we're
mapping over it and the key will be equal to key within it we will render the label and this label will be an HTML for the key right because we're going to have different properties so this is a little lesson on how you can map over the elements so that you don't have to repeat yourself whenever you map over something you have to ask yourself what is consistent across all of these different things we always have a div we always have a label right so these will be there but the contents of it are going
to change so within it we'll render the key so now if you do this and go back you'll see that now four different labels will appear each with its own key i'll call a function called format key and to it I'll pass the key what this will do is that it'll actually capitalize some of the letters so what we're doing here is that we're taking the first letter of every word and then we are uppercasing it 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 that allows agencies to generate trips for themselves trust me developers will not be replaced because imagine an agency owner wanting to create this great software for themselves they can take Vzero Open AI and chat with it for days but as soon as they need to code something they will experience bugs so being able to chat with AI is not enough you have to know how to engineer things and think of ideas perfect so now that we have this label let's also create a new combo box component right below and as a
matter of fact this combo box component will be very similar to the one created above but you know what let's actually create all of its props one more time so you learn how it truly works first we have the ID which will be the key then we take in the data source a very important part of the combo box component and here I'll say combo box items we'll take each key and then we'll map over different items within it 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 so we want them to be the same so if I do this you'll be able to see that now we have solo couple family friends business for the group type we have a travel style with a couple of options interests as well and then finally a budget 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 even though in this case I don't think it's even necessary we can
have a placeholder we have to make it dynamic so I'll say select key that way this will say select travel style select interest and so on 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 lowercased select interest select budget select travel style and so on and then we also need to handle the change this change will be the same as in the previous select 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 so let me copy these parts right here collapse this back again and then paste these additional props to it but of course we have to properly end it so let's see what we have we have the change where we're taking in the value and passing it over to the handle change function but this time we should not be selecting a country to update rather we will dynamically update the key with the value okay next we head over into allow filtering and then we will try to filter it by the query
so we're going to run the eupdate 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 we're going to then filter over each one of these not countries but items i will render the item to lowercase and I'll check whether it includes the query 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 because they are the same thing perfect now I have
to properly close this so let's count the closing curly braces and parenthesis together 1 2 3 4 do we have more we do and we also have to close this one and this one that's a lot of curly braces and also here we're not checking for the item.name rather it'll just be item for countries we had names for items we have just the item itself and by item I mean each one of these values relax luxury adventure or for the group type couple family friends business and so on and at the end of the day
we can also just give it a class name equal to combo box 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 and it'll just make it fit the rest of the UI a bit better and you end up with something that looks a bit like this a much more complete form perfect so now let me collapse this part where we're mapping over these select items and by the way if at the end of this form something is
not working for you it could be possible following along with me while I'm typing these very complicated ends of curly braces might not be ideal if you have a typo that's totally okay it doesn't mean that you don't understand something it just means that you made a typo so for that reason I will leave the entire create trip.tsx page in the video kit below so you can just copy it over and make sure that your app works so now that we have all of these items what do we do next well believe it or not
I want to display some kind of a map but in this application we're focusing on not a very specific location but rather the entire country 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 that's what this app is made for so we don't need to necessarily show a Google map but just show that country on the world map so let's do that by heading over into our app and below these select items I will render a div
within that div I will render a label and within that label we'll have an HTML 4 location and it'll simply say location on the world map and then within it this would be super hard to implement otherwise but now we can just use the maps component coming from Syncfusion within it you can show a layers directive and within layers directive you can show a single layer directive to which you can pass some additional props that defines how you want that map to look like so let's actually head a bit above and like we format the
data for the countries we want to format those countries for the map so I'll say const map data is equal to an array where we have a country we can take this from form data country so we want to figure out which country was selected but as of now we don't yet have access to the form data this is a state that we have to create 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 use state call where it'll be an
object of different values first let's make sure to import use state from React 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 by default we can select countries zero question mark.name or just an empty string if we cannot get it we can also set the travel style to an empty string at the start we can set the interest to be equal to an empty string at the start same thing for the budget as well as the duration and the
same thing for the group type all of them will be empty strings at the start let's fix this typo right here and let's make sure that we actually have access to the countries beforehand so this const countries coming from the loader data should appear above where we're setting the state there we go 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 alongside the country itself we can also choose a color in which we'll color that country
so I'll say hash E A382 E i found this color to look pretty good and then we can also choose the coordinates of that country so I'll say countries.find we want to find C that specific country 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 case we cannot find it and that will form our map data so now we can take that map data and pass it over as data source is equal to map data
but a very important question is how are we going to shape that data so for that reason Syncfusion has prepared a prop called shape data which gets the set data for the maps to render and here I'll say world underscore map i've actually provided this for you right here next to the constants this contains the list of the coordinates of all of the countries in the world so if you pass it in and reload the page you'll be able to see the world you can also change the shape data path to change it by the
name of the country and we can change the shape settings which will be a color value path of color and we can also provide a fill color so I'll say fill will be equal to E5 E5 E5 so now if you save it you can see that everything will be red so if we leave it like this it looks like the entire world has been selected so we have to say shape data path and I'll set it over to country but for the name we'll actually say shape property path 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 and that's because even though the select fields are here they're not actually yet storing the values within our state so what we have to do is we have to implement this handle change function which right now just takes in the data but we have to set that data to the state so I'll say set form data we will actually modify the entire object by spreading the previous form data in order not
to lose any of it 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 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 same thing if we select United States there we go it's pretty big so we can very easily see it on mobile but of course where this shines is on desktop as you can see
it much better as a matter of fact you can see even a such a small country like Croatia which is right here in Europe you can barely see it on the world map so now we have a fully functional map that shows you where you want to travel to 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 going to 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 a class name of BG gray 200 HPX and W full and this will simply create one line saying that we're at the end of the form below it if we have any errors we might want to display them so what do you say that we create a new use state for the error right here at the top i'll do it right below the current use state so where we have the form data I'll create another use state and I'll call it error set error at the start
equal to null and I'll also do another use state called loading set loading at the start equal to false and I'll also specify that the error can be either of a type string or a type null so just because it starts with a null doesn't mean that it cannot get any other value so the way that type inference works with React or Nex.js GS is that if you pass the initial value the value of that variable will automatically be loaded as the type of that value like in this case the boolean so for null it'll
by default be just null 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 perfect 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 and within it a p tag where we'll render that specific error 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 and
within it we can render a button component coming from Syncfusion with a type equal to submit so we wanted 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 also if we're currently loading I'll set the state to disabled that way the user will not be able to click it multiple times finally within the button I'll render an image with a source equal to it'll make it dynamic pointing to assets icons forward slash but then if we are loading I'll render
the loader.svg else I will render a magic star svg you'll soon see how that looks like a magic star I think has become the official icon or the uh official visual representation of AI generation so if I save it you'll be able to see this button right below and the image is not really loading that's because I missed an S right here under icons so now we can see those sparkles the stars you see what I meant right and below that image I'll render a span with a class name equal to P16 semibold and text-white
and right within it I'll check if we're currently loading and if so I'll say generating dot dot dot else I'll say generate trip and we can save it so now we have that final submit button so this was a super long lesson we have implemented the front end of the form but what have we actually done well let's try to summarize everything by console logging all of the values of the form that the user has selected within the handle submit function right here I'll access the event which will be the form click event so I'll
say react dot form specifically of a type HTML form element first things first we want to prevent the default behavior of the browser by saying event.prevent default 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 next we want to start with the loading so I'll say set loading is true and then just before we console log those values 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 so I'll check if the form data country or the form data dot what do we have travel style or the form data interest or the form data budget or the form data dot group type 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 there we go so now if you try to generate a trip it'll say please provide values for all the trips oh looks like we have this huge loading icon right here definitely not what we
want to have so we have to change the class name of this image to make it have a size of five and actually I want to make it spin so I'll render dynamically by rendering a CN property which will always have a size of five but it'll have a class of animate dash spin only if loading is turned on 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 because we have the error please provide fields or values for all the fields
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 not submitting something went wrong so I'll set loading to false and simply return out of this function because there's nothing for us to do here we're missing the values 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 please provide values for all fields now what else do we need alongside these fields
we also need a duration so I'll say if form duration is lower than one or if form data duration is greater than 10 make sure that it says form data here as well in that case we'll also render the error loading in the return statement but the error will say something like duration must be between 1 and 10 days 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 the other things you'll
notice that it'll actually say that the duration must be in between 1 and 10 days perfect finally we should not be able to generate a trip in case our user is not currently logged in so this functionality is only there for the logged in users 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 dollar sign id in that case I will console and say user not authenticated I will set the loading to be false and I will exit out of
the function finally after all of these checks I'll open up a try and catch block in the catch I will just render the error so con error is error generating trip and we'll say what the error is if we have already gotten to this point i'll also add a finally block so whatever happens whether we're successful or not we want to set the loading to false and in the try for now let's just console log the user so we know which user is trying to create a trip and let's also console log the form data
so we know what kind of trip they're trying to create so with all of that in mind let's try to generate a random trip trust me I will just randomly scroll through all of these countries so I was just about to create the trip but my app crashed for some reason so I just reloaded the server oh it looks like we're getting this process fetch failed interesting what is it failing for is it maybe for the countries well if I manually head over to this API this endpoint seems to be working well so it must
be something that we have recently added to the application well looks like we're good again well we'll see whether we can try to replicate that error soon but with that in mind I'll try to create a new trip completely randomly okay I'll be selecting some of the values here for example Virgin Islands maybe a couple of days let's go with a family i want to see some culture let's do museums and let's do a budget of luxury perfect 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 so let me select something else let's go with Bahamas and there we go it's also barely visible but I can see it there so now if I click generate trip and go to inspect and open up the console check this out we are getting back the user information as well as the form data including the budget country duration group type interest and travel style all the information that we need to generate a proper travel advice so now that we have this proper form let's actually use that data in
the next lesson let's use that data to generate the AI trip okay the form is here but my trip to Bahamas is not yet ready why is that well it's because we haven't yet implemented the Gemini AI itinerary generation so let's do that next you can head over to a studio.google.com and consent to sell your soul once you do that you'll see that you can get started with Gemini but you'll have to sign in to get access to the dashboard within the dashboard you'll be able to create your API key so just click create API
key on the top right and you can search for your Google project it automatically finds it right here and you can create it in that existing project once it get generated copy it head back over to your application within yourv and add it as Gemini i'll call it Gemini API key and I'll paste the key that I just got but Gemini is not the only thing we'll use to generate our trips you'll want to head over to unsplash.com/developers unsplash being the largest image provider in the world that offers stock free images so you'll want to
register as a developer by filling out this form once you verify your account via email just go ahead to create your application let's agree to all of these right here and I'll say travel agency and create the app now it'll let you actually create the app so if you scroll down you'll be able to see your key i'll copy the access key and save it to my app as Unsplash and I'll call it Unsplash access key perfect now let's actually put these great APIs to use within our codebase to do that I'll head over within
my terminal and I'll run mpm install at google slg generative AI and press enter to install it then we'll want to head over to routes.ts and we'll add a new route but this time we'll add an API route can you believe that an API backend route within ReactJS no Nex.js just React that's what's possible with React Router V7 so head over here and create a new route with a path of API create trip and the file will be routes API create-trip.ts instead of TSX so if we do this properly and add a comma that now
allows us to create that file so head over to your file explorer head over within the app routes and within routes create a new folder called API and within API create a new file called create trip.ts within it will deal with the logic of creating this trip and all of that will be just a regular function in this case we can even call it an action so I'll say export const action is equal to an asynchronous function that gets access to the request which will be of a type action function args coming from react router
and then we can define exactly what this action will do for example we're going to pass some props to it through the request object so we can automatically dstructure them this is the data coming from the form so what do we have coming from the form we already saw it in that console log so I'll say const and dstructure the country i'll also get the number of days let's also get a travel style interests budget group type and the user ID and that'll be equal to await request.json let me save this and reload the page
i might also need to reload my terminal that is one thing that I noticed needs to be done after you add a new route so if I do this and reload we should be okay I believe but now we're getting cannot read properties of null reading use context but after that we're good so there's still some things that could be improved with the overall developer experience but again being able to use server actions within React pretty crazy right okay so now that we have all of this info or the data from the form how do
we actually pass it to AI to generate it or to make something useful out of it i'll say const genai is equal to new Google generative AI to which we need to pass the key so I'll say process.env.jemini API key and you can add an exclamation mark to let it know that we know that that variable will be there from env we can do the same thing with the unsplash key by saying const unsplash API key is equal to process.env unsplash access key just like this we can then open up a try and catch block
and in the catch we can simply console that error saying error generating travel plan and then we can console log the error that happened but if everything is going well then we need to figure out a prompt that we'll use to generate that trip so say const prompt is equal to a template string and here you can really go all out template strings allow you to split them into multiple lines so you can be very descriptive the more descriptive you are the better it'll be so we'll need to say something like generate a number of
days day itinerary for country based on the following info and then you can pass additional user information such as the budget can be set to well a string of a specific budget you can repeat the same thing for what else do we have maybe interests so we can close the interests right here and then pass over the interests and you can keep repeating that until you pass over all of the information but typically when you're telling AI what it needs to do you also need to be very descriptive of the format in which you want
to receive that data back so if you head over into the video kit you'll see that here I provided the entire create trip prompt so just go ahead and delete the current one that we started typing or you know what type one yourself and then paste the one that we have right here you'll notice that this one looks like this it is exactly the same as we started typing it but it also covers more different pieces of info such as interests travel style group type and then I specify how it should return the itinerary and
specify the lowest estimated price in a clean non-markdown format with the following structure we wanted to have a name a description estimated price duration budget travel style and so on specify the best times to visit share some weather info the location the itinerary and then we specify how the itinerary should look like dayby day the more info you give it the better the result will be finally we are going to call that AI and pass over the prompt and that will result in the text result we can do that by calling await genai.get get generative
model and here you can choose whatever model you want in this case I'll aim for something like let's do Gemini 2.0 flash i think this one is pretty fast so we should get the results very quickly and we can also do generate content based on the prompt like this so now we're passing that prompt into it and then we get back the trip data so const trip is equal to parse markdown to JSON of the text results.ext this parse markdown to JSON is a function that while I was writing this app for the first time
I asked AI to actually write it which basically takes in a markdown text and it turns it into well JSON right the way it works is it simply parses it and then it runs JSON.parse on it so now that we get this trip what do you say that we also get the image of that country from the Unsplash API and then merge it with that result of the itinerary that's what'll make it stand out so I'll say const image response is equal to await and we want to make a fetch request to https col/ slash
api.unsplash.com slarch slphos question mark query is equal to and then we can pass the country and then we can also pass some interests so that way it'll match very closely with what we need alongside the interests we can also pass in the travel style and then we can append the end sign and then pass the client ID client ID is equal to unsplash API key because without it it would not return anything so now that we have this image response let's actually get some image URLs so I'll say const image urls is equal to in
parenthesis await image response.json JSON outside of this parenthesy we want to say dot results dot slice from 0 to three to get the first three and then we want to run the map on it where we get each individual result of a type any and for each one we want to get back the result urls regular or if it doesn't exist we can get the null so once we have that this will actually give us the images and prepare the URLs so we can save them into the database finally we are ready to create a
trip within the database so I'll say const result is equal to a weight database.create document make sure to import this database coming over from apprite and we can pass in the database ID coming from the apprite config as well as appite config dot trip collection ID we need to give it the id of that specific trip so it'll be id dot unique also coming from apprite and let's see why do I not have access to this create document oh no I have access to it but it's saying that I need to pass a few more
props such as the last object which contains the trip detail again I think in the database we call the detail without the s so make sure that you stay consistent and what we'll do here is say JSON.stringify and we'll pass over the entire trip that was generated for us by AI we are using the stringify because it allows us to save the trip detail object as a string in the database and later on safely restore it by using JSON parse alongside the trip details we can also get the created at which will be a new
date to ISO string so we're getting the current date and time the image URLs attached to it as well as the user ID so once we create this new result in the database we'll return the data and pass over the id as result dot dollar sign ID and this data is actually coming from react router because we need a way for us to return the data that is the result of this server action and in this case I have this error issue it is just E perfect so the only thing that remains for us to
do is to call this API endpoint within our codebase that is then calling additional API services from within our form so we can head back over to create trip.tsx and go right here to where we were console logging the form outputs so if I now head back over here we know where we were right here i'll just say const response is equal to await we're going to use a simple fetch to hit our API create trip endpoint and I'll also pass some options to it such as a method of post headers equal to content type
will be set to application JSON because just we want to pass some JSON over and finally and most importantly we want to pass over the body which will be stringified so I'll say JSON.stringify an object that'll have the country coming from form data dot country number of days which is form data dot duration we'll also get access to the travel style which is equal to form data.t Travel style interests equal to form data.inter interests budget equal to form data.budget budget group type equal to form data.group the group type and yeah you definitely could have dstructured
the form data so it'll be much easier to get it that way you wouldn't have to repeat yourself here i believe we stored it without the s just interest and then finally group type and to it we can also pass the user ID as user dollar sign ID and this is making a request to the trip so this response right here should correlate with what we are returning from the actual server action which is basically the generated trip so let's actually extract it right after the response i'll say const result of a type create trip
response will be equal to await response.json JSON and then we can check if result has its own ID then we can navigate over to that trip's detail page so right at the top of this component I will use the use navigate hook so I'll say const navigate is equal to use navigate which is coming over from react router and then right here I'll navigate over toward slash trips slash result ID so we want to go to trip details page and we can also add an else so I'll say else we can simply console say failed
to generate a trip perfect so now we have completed the second piece of the puzzle the first one being the actual Gemini generation and adding Unsplash images and returning that as an output of our own server API endpoint action and then the second part was actually passing the data to it while calling it from our front end and then navigating over to the trip details page so what do you say that we give it a shot i will open up my browser in its full glory enter a country i'm very interested what will it do
for my country in this case you can put yours to see what kind of trip does it generate i'll give it 7 days for the group type let's be romantic and do a couple travel style will be interesting let's do relaxed let's do historical sites for interests and for this person let's say that budget is not really important they want to do luxury perfect so let's generate a trip it'll take some time to actually generate it as you can see because AI right now is thinking it's producing the output and it's sending it over to
our app looks like the generation stopped so either something went right or something went wrong so if we open up the console it looks like we got error generating trip with a syntax error of unexpected token U unexpected is not a valid JSON it's good that we console log the error so it actually comes from error generating trip so that should help us a bit you can see that it's not coming right here as the else statement it's not coming as part of our own API response rather it's failing right here error generating trip if
I open up my dev terminal we can see that we got the issue here as well and it says value is not JSON serializable and we got another error this time from our API saying apprite exception collection with a requested ID could not be found okay interesting so now it's pretty clear where the issue is it is back within our server action where we're trying to generate a trip that is right here so I'm pointing over to apprite config it's saying that collection with the requested ID could not be found so obviously this is the
issue app config trip collection ID let's see trip collection ID importing it from vit apprite trips collection ID and if I head over to my env i'm saying apprite trips collection ID so this is looking good to me so it might be best to crossverify over on AppRight dashboard by heading over to databases and then trips collection i'll recopy this ID and then back in our application I'll paste it here oh looks like I missed the number right there so it's actually highly likely that it worked for you on the first try but while I
was messing with my env well looks like I deleted a number accidentally so if I head back over to my application and click generate trip one more time let's see what happens now it is generating and it looks like it stopped one more time if it didn't stop we would be redirected to another page so if I open up the console looks like it's pointing to the same issue is it maybe I have to reload the terminal now that I reloaded it it might work who knows third time's the charm okay the third time didn't
work it's not the charm so it looks like this time we got a different error saying that our document structure is invalid missing field trip details so it looks like I did actually rename it to details and not detail so if I go right here and change this over to trip details we can crossverify that right here yep it's trip details for you it might be detail you'll need to check out with your own database so let me give it a go maybe it worked for you in the first try and you're just seeing me
fail how does that make you feel okay there we go um I didn't think I would be so happy to see a 404 page but I am uh the fact that we're seeing this 404 page means that we came all the way to the end of our process because if everything goes right and if the new record has been created in the database only then will it redirect us to that trip details page and even though we cannot see the whole thing right away at least we cannot see it in this beautiful layout it doesn't
mean that it doesn't exist if you head over to Apprite go to the trips database and check out documents you'll see that one of our users has created their first trip so if you want to check it out you can just head over to data and check this out we have three separate image URLs matching what a user searched for yep you can actually see those images already by simply navigating over to this URL and you'll see some of the nice photos these are looking good and we also have the created ad field the payment
link which we will add later the user ID that created it and most importantly the trip details generated by AI so here you can see that it actually gave a name to a trip luxury creation history and romance a couple's escape and it gave us a lot of information that we might want to use to nicely display right within our application which is exactly what we'll do in the next lesson but first I'll go ahead and commit the changes we have right now i think I might have forgot to do it in the last lesson
but that's fine we'll do it right now i'll say get add dot get commit-m and I'll call it generate trip using AI and I'll push it so in the next lesson let's focus on generating this beautiful trip details page so we can finally see what our app actually does now that we have spent a lot of time making it happen great work we're past the most complex parts of the app so if you reach this point leave a comment down below and let me know "Hey Adrian I passed the AI trip generation and now I'm
ready to dive into the trip details." See you there once we create a new trip we'll redirect the user to that trip details page but that page doesn't yet exist and we don't yet have the function to retrieve our trips so let's start with that i'll head over into appreite and then I'll create a new file called trips.ts within this trips we can do the full logic for fetching all the trips or fetching just a single trip by ID so I'll start by exporting a new function called get all trips and it'll be equal to
an asynchronous function inside of which we'll try to fetch those trips so I'll say const all trips is equal to await database coming from apprite dot list documents and then you know the drill we have to provide the database ID coming from apprite config database ID as well as the collection ID coming from app config dot trip collection ID and finally we have to query the trips that we want to get so to implement pagionation here as well we can accept the limit of a type number as well as the offset of a type number
into this function and then once we turn on the query coming from apprite we can limit it by a specific number of elements per page and we can query it by a specific offset so if we have already seen the first page we can move off to the second one and finally I'll also add the query.order order descending based on the created ad so we can see the newly created trips at the top finally if all trips total is triple equal to zero in that case we can simply console and say no trips found and
we'll return an object that says all trips is equal to an empty array and the total will be set to zero but if we do have some trips I'll return an object that'll say all trips is equal to all trips and the total number of trips will be set to all trips dot total perfect so this is the function that allow us to fetch all of these different trips and I also want to create a function that allow us to fetch the trip details for a specific trip so I'll say export const get trip by
id and it'll be equal to an asynchronous function that accepts a trip id of a type string and it simply fetches it so I'll say constrip is equal to a wait database get document singular this time and we again have to provide the database ID so app config database ID the collection ID so that'll be app config trip collection ID and finally the ID that we want to fetch it based off of which is going to be the query so just trip ID like this and this will be called trip not trips so once we
get that trip I'll check if there is no trip dollar sign ID in that case we can simply do a console log and say trip not found and we can return null but else if we actually fetch the trip we can return that full trip details perfect so now we have those two functions that allow us to fetch the trip details either a single one or all of the trips together so what do you say that we go ahead to our routes and we create a route for the trip details page we can do that
by creating it like this and then pointing it over toward slashtrips slash colon trip id so this is the first time that we're defining a dynamic route based on the ID and you do that by simply providing a colon right here at the start similar to what you do in express for example and we can point this over to routes/admin/trip-detail.dsx and now we can create that trip details page by heading over to roots admin and we can create a new trip detail dsx where you can run rafce and here we just want to be able
to quickly fetch the details of that specific trip so let's do just that i'll create a new loader at the top by saying export const loader is equal to an async function that gets access to params of a type loader function args now what are these params that I'm trying to fetch right here well the params are going to be whatever is dynamic so in this case remember this ID well that'll be fed into params ID so we can extract that number so in this case I want to say const trip ID and I want
to dstructure it from the params then once I have the trip ID I'll say if there is no trip ID I'll throw a new error saying trip ID is required but if there is a trip then we can fetch it by saying con trip is equal to await get trip by id this is the function that's coming from apprite trips right at the top and to it we have to pass the trip ID of course once you get it we can simply return that trip so if you do this properly you'll be able to fetch
this trip data right here at the top because you already know the drill we're getting it through the loader data of a type route dot component props and this route is coming right at the top by importing the type of route from slash plus types forward slash and then the name of this file so if you have done this properly you should be able to just say const trip data is equal to parse trip data this is coming from lib utils and to it we'll pass the loader data question mark trip so let's see what
exactly this parse trip data is doing basically the only thing it's doing is it's taking that trip and it's parsing it because before remember we passed it over as a stringified version so now we want to extract the actual data so what do you say that now that we get this data we extract the name out of it so I'll say const name and then is equal to trip data or just so it doesn't break if we don't have the trip data try to get it from an empty object so now we can show this
trip details first I'll wrap everything in a main tag give it a class name equal to travel detail and a wrapper class name then within it we want to render a header component coming from components with a title of trip details a description of view and edit AI generated travel plans and we can close it right there below it I'll render a section that'll have a class name equal to container and a wrapper MD so we can see the content in the middle of the screen within which I'll render a header within which I'll render
an H1 where we can render the name of the trip to style it a bit better let's give this H1 a class name equal to P40 semibold index-d dark 100 just so we can see it clearly so if you do this it's possible that the app is not working because we have added an additional route so let me actually rerun it one more time it'll quickly spin up on localhost 5173 which is when we can reload and now if we head over to AI trips we don't really have any trips yet we could go ahead
and create a new trip so we get redirected to it but what we can also do is just head over to trips forward slash and then we can get the ID of the trip from apprite database so head over to appite databases trips and then copy the document ID once you do that you can append it to a URL so we get redirected to the trip details page but now the issue is that we don't yet see the name of the trip so something could be wrong with us fetching the data so first I want
to see what we're getting outside of this loader data by console logging it so if I console log it right here and open up the console it seems that we're getting the object that then contains the trip details within which we finally have that string so what I'll do is I'll try to render the loader data dot trip details instead of a trip and if we do that properly you see that now on the right side we're getting a luxury creation history and romance a couple's escape so that means that we have successfully retrieved the
trip details coming from the database in this case we can just automatically return the trip no need to declare the variable and then return it so I'll just simply say return await get trip by ID perfect so now we're fetching the trip details and now we can slowly start rendering all of the additional information about this trip but before that we'll need one more component to show the trip duration and location to make sure that it looks exactly like it does on the Figma design so let's create a component for these two little chips we
can do that by creating a new component within components and I'll call it info pill.tsx tsx i'll run rafce and I will export it through our components folder as info pill and then we can import it over within our trip details right below the header i'll call it info pill and render it from components now to it we'll have to pass some props maybe such as a text in this case for the duration so I will render a dynamic duration and then we can say how many days so 7 day plan for example but the
question is where is that duration coming from and the answer is that it's coming from the trip data so here we can dstructure alongside the name all of the other properties that we'll have such as the duration the itinerary the travel style the group type the budget interests estimated price as well the description the best time to visit the weather info and the country so we're getting all of that info from the trip data and now we can just render this text and we'll also render the image alongside that info pill which will be forward/assets/icons/cal.svg
so if I now save it we need to head over into this info pill and implement it it'll be super simple we will just accept those two props such as the text and the image which can be of a type info pill props and then we can return a figure with a class name equal to info pill within it we can also render an image that'll have a source equal to image with an al tag of text and below the image we can render the fig caption that'll simply render the text so if you do
this you can now see a nice 7-day plan right here and you can simply duplicate this info pill and for the second one within here we want to render the location or rather that'll be the itinerary so if you try to render it like this you'll notice that it's actually an object that has day location and activities so we have to parse it so we can properly show it so I'll say itinerary question marks slice from 0 to two so we only want to show the first two elements and then we can map over those
items so map where we get each individual item and we only want to extract the location for each item and then we want to join them together by using a comma so join with a comma and a space or if it doesn't exist an empty string so if you do this now we can see split split uh which doesn't stand for JavaScript split function actually it is a name of the city in Croatia for some other one maybe it gets two different cities that actually make some sense and instead of a calendar we can render
the location marks SVG so you can see that this is actually the location perfect so now we have those two pill components right here and that allows us to focus on creating the rest of the layout so I will actually put those pills within the header component right below the name and I actually want to put them one next to another so I'll put them within a div i'll open it here and close it here and I'll give this div a class name equal to flex items center and a gap of five so now they
will appear one next to another and then we can head over below this div below the header but still within this section we can create another section within it this one will be for our gallery the most important point of our trip so I'll say class name of gallery and within it we want to map over the image urls so now the question is where are these image URLs coming from and they are going to be a part of the trip as well but not part of the trip details this is the text that was
generated through AI the image URLs are going to be a separate property under these trips how can we know that because if you go into our apprite trips right you'll notice that when we return the get trip by ID we're returning the full trip and if you remember how this trip has been stored into the database you will know that trip details and image URLs are two separate properties so we have to extract them separately image URLs is equal to loader data question mark image URLs or an empty array if we don't manage to get
any of them so if we do that we now have image URLs and you can see that we're mapping over three of these image URLs right here so instead of simply mapping over them what we can do is say map and we are going to get a URL of a type string as well as an index for I of a type number and for each one of these we want to simply return an image that'll have a source of URL and a key of I if we save this you should be able to see three
images appear right away but they are of different aspect ratios so we want to make sure that it actually still looks good so I'll give it a class name equal to CN and it'll always have a wful a rounded- excel and object- cover so we round up the corners a bit but then if it's the first index so if I is triple equal to zero in that case we'll give it a on medium devices call span 2 on medium devices row span 2 and a bit of a bigger height of 330 pixels but if it's
not the first one on medium devices I want to give it a row span one and a height of only 150 pixels so if we save this you'll see that now the first one takes more space and the second two take less space this is okay on mobile but where it really shines is of course on desktop where you can see that now we have this nicel looking layout that gives you the idea of that trip in a glance you don't have to take a look at three separate pictures you just take a look at
this portion of the screen and you get a general idea of what these places are all about now we can head below this gallery section right here and we can create another section this time for different chips talking about what this travel is all about so what I'll do here is I'll create a section and give it a class name of flex gap of three on medium devices a bit more gap of five items center and a flex- wrap so they wrap nicely on mobile devices within this section I will render a chip list component
coming from Syncfusion with an ID of travel chip and within it I will render a chips directive so we can choose exactly how each one of them looks like and then we have to figure out what we will map over so right above I'll create a new object const pill items which will be equal to an array where we're going to have different objects we're mapping over such as the text where the first one will be we'll talk about the travel style which we're taking from the trip data and we can also provide a background
color to it so we differentiate all four of them maybe something like a BG pink of 50 and uh text of pink of 50 as well and now we can repeat this three more times for the second one we can talk about the group type for the third one we can talk about the budget and for the fourth one we can maybe talk about interests and we can change the color for each one so the second one will be BG and text primary after that we can have BG and text success and finally we can
have maybe BG and text of Navy and in this case I think we'll have to make the text a bit more pronounced so for the primary I'll call it 500 for success I'll do 700 and for navy I'll do 500 as well perfect so now if we do this we'll be able to head down and we can map over these pills right here within chips directive so I'll go right here and map over the pill items and for each one we'll get access to a pill as well as the index and for each one we
want to automatically return a chip directive also coming from Syncfusion which will take in the key of I a text where we can render the pill.ext and also the CSS class which we can use to style it a bit more so I'll make it dynamic and I'll render the background right here so pill.bg BG text-base font-bium and a padding X of four if we do this you'll be able to see four different chips but they're not really that visible are they and it seems like some have multiple words this one doesn't have any text at
all so what we can do is we can style the text a bit more by wrapping this build text into the call of the get first word function what this will do is it will extract the only the first word of that text you can see it trims the words splits it and then returns the first one so now here we can see only historical for example also the colors don't seem to be matching so let's see what's happening with this pink one right here maybe we should do pink 500 in order to be able
to see it better there we go that's a bit better so now on a glance we can immediately see what this trip is all about how long it lasts which cities are we visiting the look and feel of it and then we can see that it's relaxed couple-based luxury and historical what else can we show for this entire trip well let's follow the design right now we have this part done and the last part before we can render the actual AI generated itinerary is going to be the rating so right here we can render the
rating so to do that we'll remain in the current section but we'll go below the chip list component and I will render a ul an unordered list with a class name equal to flex a gap of one and items center and within it we can create a new array of five elements that will fill with no values so null and then we want to map over those values i'll simply put an underscore right here because we don't really care about what the values inside but we do care about the index which is the second parameter
and then for each one of these empty elements in the array we want to return a list item with a key of index and we simply want to render an image that has a source equal to assets icons star svg with an alt tag of star and a class name of size of 18 pixels if you do this you should be able to see five stars appear right here and we can also head below this dynamic part and render one last list item with a class name of ML1 so margin left one to divide it
a bit and we'll render one last chip list component with its chips directive with a single chip directive inside so that will look like this and this one will simply render a text of something like 4.9 out of five we're not collecting the data on the quality of these strips so that might be something that you might want to implement to be dynamic later on and I'll give it a CSS class of bg red 50 and text- red 500 just to see how it looks like there we go we can also maybe make it yellow
just so it matches the stars so if you do this you should be able to see it like this we can even make it a bit darker because yellow is not that visible there we go this is looking good so now we can exit out of this li ul and the section and we can focus on creating the last section the one that matters the most which will be the header of the trip so it'll take the title the description and the budget and then after that we'll be able to render the entire itinerary so
let's do that by giving this section a class name of title and then within it we can render an article within which we can render an H3 that'll say duration dash day country travel style trip so we're combining everything into one 7-day Croatia relaxed trip after that we can render a P tag that'll render the budget the group type as well as interests so if we save this we can also see that this is a luxury couple and historical sites and outside of that article we can also render an H2 that'll render the estimated price
so we're taking all of these different pieces of info and putting them together finally check this out i'll head below this section for the header and I'll render a p tag that'll render the description if you do that immediately you'll have a very short description so we can give it a class name equal to text-sm on med devices-l font-normal and text- dark 400 so we can kind of style it a bit more like a regular description and then below it we're finally ready to map over the important info so I'll head below the P tag
and I'll create a UL an unordered list with a class name equal to itinerary that simply means kind of like steps in the trip like how is the trip going to play out we want to take that itinerary and map over it by using the map and what we get is a day plan so for each part of the itinerary we get one special day plan and we get an index of that day so then for each one of these we can automatically return an LI we have to close it as well properly and I'll
give it a key equal to index let's see if I have closed that properly we have a ul oh looks like I have one extra parenthesy right here now we should be good and within this LI I will also render an H3 that'll say day plan day and then day plan so this way we can specify in which cities we're in within which day so let's see if we can somehow show more of the locations right here maybe instead of uh just uh slicing two we can maybe get more just so we can see that
we see different cities right here there we go i think this is good so now we get the idea that there's going to be more cities but you can see all of them right here right below perfect so let's scroll a bit down and alongside just the title of each day we want to show the activities planned for that day that's what actually matters so I'll create a ul and map over the day plan activities dom map where we get each individual activity and an index for that activity of a type number and for each
one we want to automatically return a list item with a key equal to index and within it we can return a span that will render the activity time and below it we can render a p tag that will render the activity description so if we do this you should be able to see that now we have morning afternoon evening and we have that for each one of the days right here we can style it a bit differently by giving this span a class name of flex-shrink-0 we can also give the P tag a class name
of flex grow so if it needs more space then it'll actually expand to fit that space but already this is looking great on mobile what we can do is maybe just bold this span a bit by giving it a P8 semibold maybe there we go that's a bit better so now it's standing out we see morning afternoon and evening and we have some emojis so we can very quickly browse we just scan through what's happening i won't yet show how it looks like on desktop if you want to you can check it out on your
end but I want to just finish it off by also sharing some information about the city as well as the weather information so below this ul where we have the itinerary I want to map over something new actually I want to map over two different things that I want to combine into one so right at the top below the pill items I'll create another array called visit time and weather info and within it I'll render the title for each one this one will be best time to visit and here we can render the items of
best time to visit and for the second one the title will be something like weather info so we know what clothes to wear and the items will be weather info so if we now save this we can take it over and map over it right here below the ul by saying visit time and weather info map where we get each individual section for each one of these sections we can return a section and let's properly close it as well we can give it a key equal to section.title and a class name of visit and then
within the section we can render a div with an H3 that'll render the section.title so if we save it you should be able to see that we have best time to visit and weather and then below that H3 I will render another ul that'll simply map over the items so section do items do map where we get each individual item and for each one of these we want to return a list item with a key equal to item so let's fix this typo right here that's going to be a key and I'll just add a
question mark here in case we don't have access to the items and we can render a P tag that'll have a class name equal to flex- grow and within it we can render the item itself so if I save this you can see best times to visit April to May June to September September to October or November to March and we can see different temperatures in different parts of the year so if you like summer more it's very warm 25 to 35 degrees or 77 to 95 Fahrenheit or in the winter it's getting pretty cold
depending on where you are in the world this maybe isn't warm and this maybe isn't cold as maybe you're experiencing some very different temperatures so since this is a travel application comment down below and let me know where you're watching from so just drop in the name of your country and maybe a flag emoji just so we can see where you guys are from but with that in mind we can head over below this section basically below all of the sections so I'm going to get down below this one and below this closing one where
we have all the details and I'll create one last section in this one we're going to have an H2 that'll say popular trips so if the user has scrolled to the end of their trip details it means that they like it so much so they're going to go for it or maybe it means that that trip isn't really their favorite so maybe they want to check out some other popular trips on our site so to do that let's give this section a class name equal to flex flex- call and a gap of six and we
can give this H2 a class name of P24 semibold text-d dark 100 so that's going to make it stand out a bit more and right here we need to map over our trips and for that thankfully we have created our server action that does just that it fetches all the trips we have done that just before we have created the fetch trip details function so fetching it will be super simple the only thing you have to do is say const all trips is equal to loader data dotall trips as a trip array or maybe an
empty array if we don't have anything else and now we have to actually call that within the loader so currently we're just returning the trip details so you can see that here it's complaining that loader data does not contain all trips so what we have to do is right below trying to fetch a specific trip we can also try to get all trips so that'll look like this const trips is equal to a weight get all trips and to it we have to pass the limit of trips so maybe we can just limit it to
four of the most popular ones and make sure to import this get all trips from apprite trips and then once you have it over here you can simply return it as well so now we'll actually have to put this into a new variable const trip is equal to await get trip by details so so far we have been only returning a trip but now alongside returning a trip we also have to return the trips so let's actually do it as part of an object where trip will simply be a trip and then all trips will
be trips dot all trips do map and we only want to take what we need from each one we don't need all the info so I will dstructure the dollar sign ID the trip details as well as the image URLs and then we can automatically return like this parenthesis and then curly braces the ID equal to dollar sign ID we can parse all of the details by saying data.parse trip data because remember it's in a string format so we have to first pass it into this parser function and then we can say image urls is
going to be image urls or simply an empty array perfect so now we're returning that from this function you can see we immediately lost all of the trip details because now it'll actually be coming as part of the trip so we have to say loader data urls and trip.trip details if you do that you'll see that now we get this data back if in case it's not there I'll just add a question mark so TypeScript doesn't bother me with this and now we are ready to show the other popular trips we can do that by
just mapping over them so right at the bottom I'll create a div below this h2 that'll have a class name equal to trip-grid and we'll call the all trips and then say map where we get each individual trip and for each trip we want to render a trip card which we have already created as a reusable component that needs to accept all of these different things so let's give it an ID equal to trip ID let's also give it a name and just so we don't repeat ourselves we can dstructure those properties directly from the
props right here so let's do just that i will dstructure the trip into ID name image URLs itinerary interest travel style as well as the estimated price so now we can pass all of them right there id is ID since we're mapping over it we also have to give it a key name will be name after that we have location which will be equal to itinerary question mark.0ero so we're going to take the first location.loation or an empty string in case it doesn't exist image URLs well we'll only take the first image so image URLs
zero for the tags we can pass an array of interests so interest and the second one can be travel style and for the price we can pass the estimated price so if we save this you can now see that all of the details are back and now we have one other popular trip in this case it's the same that we're currently on as it's the only one that we have but later on we'll show different trips right here and how nice it was to just reuse the card we have already created before just before we
admire this trip details page I want to show you a little optimization that actually goes a long way and that is that here we're making two separate asynchronous await requests each one of these takes time maybe about 500 milliseconds so if we try to wait for both of these one by one it's going to take a total of 1,000 milliseconds which is a lot of time if you're trying to fetch the data but the thing is that we don't have to have both of these fetched at the same time they don't depend on one another
we show the trip details right here and then we show other trips at the bottom they can happen at the same time instead of one after another to do that we can use the promise that all functionality that way they will happen at the same time in parallel okay that's the key the way you do it is you say const and then dstructure what you want to get so we first want to get the singular trip and then we want to get the trips then you make that equal to an await promise.all call to which
you pass an array of different calls the first one will be giving us our trip details so I'll paste it here and then the second call will be giving us all of the trips so we can put it here and you don't need a weight because we already have a weight right there so if you do it like this now both of these requests will happen in parallel at the same time and they will reduce the time it takes to load the page in half pretty cool right so always remember that when you have multiple
asynchronous calls that don't depend one on another you can simply put them within the promise that all so with that in mind check this out we're looking at the trip details we have this one trip detail that we can see we can read what it is about at a very first easy glance see the estimated price and then we can start seeing what this trip is all about for example we have a private guided tour first relax at the pool romantic dinner at a fine dining restaurant well this is how life should be lived right
just kidding but yeah in general you have a couple of days of different trips and it's all separated into morning afternoon and evening to know exactly where you have to be at a specific point in time you can get more info on when you might want to visit it based on the season that you like or the weather and I just noticed that on desktop this card looks very narrow so we might want to expand it a bit so if you head right here we have to take the section for the popular trips copy it
and then put it below the section for the details that way we'll allow it to take the full space as it doesn't depend on the details so we can show 1 2 3 4 in the full width right here amazing with this lesson we're finally taking everything we have created so far and displaying it right here but to reach this page we had to manually navigate over by grabbing the ID from apprite which let's be honest regular users won't be able to do and then we came over here but for now if you head over
to trips they're completely empty so what do you say that next we implement the all trips page great work so far so now that we have the trip details page we have to have a page that allows us to visit all of those great trips so let's create it first I'll head over into our routes right here i want to check whether we already have a route for the trips and we do it is the trips.tsx page so let's head over into trips.tsx and currently on there we only have a header but within here we
also want to access all the trips similarly to how we got access to these popular trips at the bottom of the trip details so I'll head over to the trip detail one more time and I'll copy the part where we have the loader that fetches that data so I'll copy this alongside the top part of the trip detail that actually has the loader data so I'll copy it and I'll override the top part right here of course we have to change this over to trips but the loader data will remain the same it's just the
import of that route that'll change as it'll point to slashtypes slash trips instead of trip detail perfect and we'll also make the loader a bit simpler we don't need to fetch the individual trip so we don't need the params to get the trip ID so I can remove that part but instead we'll need a request why because we'll implement the pagionation so we need to know on which page we're on and what is the offset so I'll say that the limit per page is eight and then we have to get access to the current URL
which will be equal to new URL and we'll get it from the request URL once we have that we'll have to figure out on which page we're on so I'll say const page is equal to parse int URL dot search params.get page so similar to nex.js JS this is working in the principle of search params and I'll say if we don't have it we can default it to a string of one and the radics of that parse int can be set to 10 that means that it's a decimal system finally we need to get the
offset to figure out how many pages do we have to skip and that'll be equal to page minus one so we want to take the previous page and multiply it by the limit or in other words the amount of trips we're seeing on that page with that info We can now call the get all trips no longer within the promise all it can be a very simple call to const all trips is equal to await get all trips but now instead of passing the fake limit and offset we can actually pass the limit and the
offset coming from the page params perfect and alongside all trips we're also getting the access to the total number of trips so what are we returning well we'll return trips which will be equal to all trips do map we want to take the ID trip details image URLs we do the same thing as we did before but now as the second parameter we'll also pass over the total number of trips perfect so now we're returning that data from the loader and we can consume it right here within this page not the trip details but rather
trips so it'll be as simple as saying cons trips is equal to loader data trips as a trip array so we know what each one of those has and now we can map over them so I'll head it right here below the header and I'll create a new section that section will have an H1 that'll have a class name equal to P-24 semibold and text-d dark 100 and it'll say manage created trips because we are in the admin dashboard right now then right below the H1 we can render a div that'll have a class name
equal to trip-grid and if you remember correctly we already rendered a trip grid on the details page so head over to trip detail all the way down at the bottom and you can see that here we have a div where we're mapping over all of those trips so copy it and paste it right here trips.m map where we get each individual trip and for each trip we render a card with a trip ID name image URL location tags price and more it is all fitting in very nicely right here and if you do just that
and save you'll immediately be able to see the card for one trip that we have created so far so what do you say that we go ahead and create another trip just to make sure that our app still works let's say that I want to visit Japan and we really want to make it an interesting trip that last about 10 days i'd like to go there with my fiance so I'll say as a couple with a travel style of cultural we can put different interests right here such as historical sites and a mid-range budget you
can see Japan right here in the world map and let's generate a trip we can see that it's loading and the button is disabled so we cannot click on it like monkeys but in a couple of seconds the trip will be generated so let me expand it and it actually seems I wasn't redirected so something must have gone wrong it looks like the trip details has exceeded 5,000 characters that's the limit that we set and since I chose 10 days which is the maximum duration it's highly likely that the itinerary expanded over 5,000 characters so
head over to your dashboard into databases trips attributes and see if we can edit the trip details let's see what is the maximum size that we can set okay I updated it to 10k now if I go back and retry to generate this trip hopefully 10,000 characters is enough to explain what I should visit in Japan for 10 days there we go that's beautiful see how it picked up those very nice photos from Unsplash we have a bit of nature a bit of city and a bit of culture cultural couple mid-range historical it is rated
very well and we have a bit of a description i should arrive at one of these two airports visit the Imperial Palace enjoy a welcome dinner and then go to different places this is amazing I got to say and see the best time to visit definitely is in the spring as there's cherry blossoms there as well but everything is looking great right now as you can see that now the second trip has been generated so if I head over to the AI trips you can see that both of them are right here so what we
could focus on next is the pagionation and let me just add a bit of a margin bottom of like four to this one right here just to create some space and let's implement the pagionation for this we'll have to have a couple of states that'll help us manage it so at the top I will say const search params is equal to use search params once again similar to nex.js but now everything is coming from react router we want to access the initial page by parsing the response of search params.get page so we want to get
access to the page through the URL by default it can be set to one or instead of parse end maybe you can also use just a regular number constructor this might be an even simpler way to convert this into a number so it's search params.get or one then once we have the page we can also store it to the state by setting use state snippet call it current page and set current page at the start equal to the initial page and we'll also need a function to handle the page change so we'll say const handle
page change which accepts a page of a type number and then it simply updates the current page to the new page as well as it updates the search param in the URL to question mark page is equal to the selected page don't forget to import use state from react and once you do that we can actually use a pagionation component coming from Syncfusion right at the bottom so below this div still within the section I'll create a pager component coming from Syncfusion and if you save it you'll immediately see the pages right here at the
bottom but we can style it a bit further by saying total records count is equal to loader data dot total we can also pass in the page size equal to eight so it knows that we can want to show eight elements per page and we can pass the current page equal to current page and we can also pass the click so what happens on click here we get some arguments of what happened within that click and call the handle page change where we pass the args.curren page to it so now if I save this and
reload you can see that we're in the first page so to truly test this out we can first maybe give some margin bottom of four right here so we make some space and also we can give this pager component some margin as well so it looks like it's floating so I'll say MB4 for the class name or I think it's CSS class like this there we go so now it's floating like the cards and it looks like we still cannot navigate because we only have one page right so what if we set the page size
to only one will that work and we also have to update the limit right here so if I save this we will now only fetch one trip per page and take a look at this we can head over to the second page and we can see the second trip and now we're at the second page and all of that is updated right here within the URL so if you want to copy this entire thing and share it over with a friend you can totally do that and you can also head back to the previous page
to see the first trip of course it looks a bit empty because we don't have many trips right now so I'll bring back the limit to eight and also bring back the page size to eight as well so later on we'll be able to show more trips two rows of four because we have reused the trip card component that we created before for the dashboard so the last thing that we have to do is actually update our dashboard to reflect real stats from our database from the stats about the users trips and active users to
the created trips because right now our all users page is dynamic and so is the trips page but the only thing that's static is the dashboard so in the next lesson let's make the dashboard dynamic and finally now that we have all the data we are ready to collect it in one place and show it on this great looking dashboard page we'll make it look like this where we're tracking the amount of total users total trips and active users today to live trips and finally real charts that shows you how many users have created their
accounts over time as well as which trips they're creating looks like recently everybody's been into adventure then you'll see which users created how many trips and you'll see trips based on different interests let's make it happen first things first we have to collect all of this data in one place and that won't be an easy task but we'll do our best so head over to AppRight and create a new file called dashboard.ts and within here we'll have to create many different functions that allow us to fetch all of the different pieces of data that will
display on the dashboard and I really want to end this course on a high note so it won't be easy trust me when you're working with charts and tables you're working with data and tables are easier because you're just presenting that data in a very simple structure but when you want to present data in charts especially charts that have X and Y axes that show different pieces of data on hover and you try to present it in a nice understandable way it's not easy and you have to deal with a lot of native JavaScript reduce
functions date and year processing making sure that you combine all of these arrays and objects into one to be able to calculate it so as I said we'll end this note on the high note because that's what you came into this video for to be able to build a proper dashboard so back within our codebase first things first we'll try to get access to the users and trips stats the ones that we're going to show on the top cards so I'll say export const get users and trips stats i'll make this equal to an asynchronous
function that will return a promise which will be resolved into dashboard stats and we can open up a function block once we need to do at the start is get access to the current date i'll do that by saying cons is equal to new date pretty simple right then we want to get the start of this month and we can do that by saying const start current and that'll be equal to new date d.get full year d.get month and then pass over a1 for the date and then get outside of it and call the two
ISO string on it now duplicate this two times and we'll want to get the prev and for the second one we'll want to get the start of the last month so I'll call it start prev which is equal to new date d.get full year get month minus one so we want to get the previous month to ISO string then the last thing we want to get the end prev which is the end of the last month so here we want to get a new date for the year so here we'll leave the date as zero
okay so now we have access to these couple of dates now as before we're going to get access to all of the users and trips so I'll say const dstructure the users and trips and make it equal to await promise.all all where we're going to call database coming from apprite.list list documents and we want to list them from appate config database ID and then appate config dot user collection ID in this case this shouldn't even be within parenthesis so I'll just leave it like this and then we can duplicate it below and we can do
the same thing to get the trips the only thing we have to change is the trip collection ID okay so now we have different dates we have different trips and users and we want to combine that but I'll first create a function that will help us to do that const filter by date of a type filter by date and this type we can declare above so we know exactly what we're working with type filter by date is equal to a function that accepts the items that we want to filter and this item will be of
a type document array and what is a document well a document is an interface that will have a key of a type string and then it can have any kind of value so we want to keep it open it can be an array it can be a string it can be a value it can be a number we're going to have a key as well of a type string we're going to have a start of a type string and maybe even an end of a type string so we know what to filter and finally we
will return a number when we filter that out so we'll know how many of what has happened on which day so now that we have this we can actually accept all of these params items key start and end and we can call the items.filter so we can filter them what do we want to filter them by well first we get each individual item into the filter function and we want to automatically return the filter command so I'll check if the item key is greater than or equal to start and if not end or item key
oh my god this is getting complicated i remember writing this one time before but being fully honest right now this feels like gibberish to me so what better to do than get some help from Chad GPT it says that little helper counts how many documents in an array fall within a given date range so here's a breakdown we take in an array of objects each is a database document like a trip or a user we take the key which we want to use to join them at then we take the property name such as joined
at or created at and then we go from start to the end of a specific period then here is where things get tricky here we have filtering logic so we say item.filter and we compare the item key to make it greater than or equal to start so we only keep those whose date is on or after the start that makes sense and then for the second part we check if not end or item key is lower than or equal to end which enforces the upper boundary if the end is provided so it makes sure that
we actually count the numbers and return the number of items in that specific day so finally we have to attach the dot length to it in other words filter by date function if you call it with users joined at start current and end previous will tell you how many users have joined between the start current and end previous and this will be super useful for our chart data now let's continue after that we want to filter out users by ro so I'll say con filter users by ro and I'll make it equal to a function
that accepts the role of a type string and returns users dod documents dotf filter where each u is a document and it only returns the ones where u status is equal to that specific role this one is more straightforward and finally now that we have collected all of this data we want to return it from this function so I want to say return a big object where we first share the number of total users equal to users.tal then we share the users joined which will be equal to well we'll get the current month by calling
the filter by date function to which we'll pass what we want to filter it's going to be user documents what do we want to filter it by so which is the key it's the joint ad next what is the start so this is the start of the current month and when do we end it in this case I'll pass undefined as we don't want to end this specific period of time and that'll give us the users joined but the second part of the users joined will be the last month so how many did we get
the last month so we can filter by date and we'll again pass the users.documents documents by joined at will pass the start prev so the start of the previous month as well as the end prev so end of the last month and here I noticed I have to say start current so let me actually expand this into a new line so it's a bit easier to see that we're basically calling the same function two times but for the previous month and the current month there we go and this is for the users joined now what
I'll do is I will duplicate this user joined just below and I'll do it for the user role so this will filter it based on the user or the admin in this case we want to get access to the total in the previous one we just had the current and the last but here within this user role we want to get the total and it'll be equal to filter users by ro the role will be user and we'll simply get the length of all of the users next we get the current month where we'll filter
it by date and we will pass in the amount of users that we want to filter by date so when did they join by filtering users by role of the user so this is now getting very meta or very matrixy where we're calling this filter by ro right here within filter by date but basically what this is trying to do is it's trying to tell us how many users have joined in a specific period of time within this month that have a role of user and the rest will be the same for the last month
we once again we want to take the users the role of the user joined at start end prevent end this is good and one more time we want to do something similar so I will duplicate this user role one more time below but this time we'll do it for the trips so I'll extract this total up above the object and I'll say total trips is equal to trips total next we have trips created this should be technically called trips created over time that's actually what makes it difficult not the amount of total trips created but
trips over time right so I'll say current month filter by date here we want to filter by date and we want to get the trips documents so trips documents by created at and we want to do the same thing right here below trips documents created at and here we're returning all of these important pieces of info users and trip stats that we will use on our dashboard so what do you say that we actually start using them i'll head over into my dashboard.tsx and I'll want to call these pieces of data because everything on the
dashboard right now is entirely fake so we need to start by modifying our client loader to not only call the user info but to also call the dashboard stats so I will expand this function and I will create another await that will fetch the dashboard stats so I'll say await get users and trips stats and we of course have to put all of these into their specific variables or we can once again do a promise all so I'll say const user and then dashboard stats we can call these stats we just created will be equal
to await promise doall and here we can then put both of these calls so we can try to call them at the same time in parallel perfect so now we're getting these dashboard stats and we need to return them right here by saying return user as well as dashboard stats coming from client loader now we can accept that right here from the client loader it'll be now we're not getting only the user the user is only a part of the loader data so it'll be loader data dot user next we have to dstructure the dashboard
stats from the loader data so I'll say const dashboard stats is equal to loader data and now we can display them right here within the stats card so for the total users I will use dashboard stats total users and let's see if it changes from 13,000 to about two yep this makes more sense next we can get the current month count by getting to dashboard stats dot users joined dot current month and now we can see we're down but we also have to change the bottom one so I'll change the last month over to dashboard
stats dot users joined last month so we worked super hard so we can actually get the real stats right here now we can do a similar thing for the total trips so we'll show the dashboard stats dot total trips we can also show the current month by saying dashboard stats dot trips created dot current month and below we can say dashboard stats dot trips created last month so now we can see that we have a total of two trips created and we can check out the active users today by doing the same thing dashboard stats
dot user rotto total and for the current we can do the dashboard stats dot user rot current month and we can repeat it at the bottom dashboard stats dot user rot last month so you can see that we only had one active user today okay that makes sense and now we are seeing these three different cards with the real user data it was hard to create them but we needed to get the previous and last months so we know what the increase is now to get access to other pieces of data that we need such
as the created trips which we have already gotten a couple of times so far both on the all trips page as well as on the trip details page right at the bottom so we have to get it one more time so we can show these live cards but then we'll also have to get the stats for the charts that will show at the bottom these calls will get a bit repetitive we'll have to list the documents from the database do some logic with dates and time and finally return it in a format that our front
end will like so to save us some time I'll provide the full dashboard.ts file within the video kit so just head over to the bottom of the video kit and you can get it right here once you get it you can override the one that we have right here if you check out this file you'll notice we have a couple of different functions so let me collapse all of them so you get the idea the one that we created was the toughest one so we have it right here and we have it right here now
I'll collapse these couple of functions that we have right here but the toughest one was the one that we created so I'll collapse that one too get users and trip stats then we have the get user growth per day which is the data that we'll use for our chart get trips created per day as well and then get trips by travel style so let's take a quick look into this get user growth per day here we are listing the users we are taking the current date and time and we are aggregating the number of users
per day that they have joined and this is a perfect use case for a reduce function whenever you want to count some numbers up in an array such as you have a day one maybe two users have joined then you have a second day and then maybe four users have joined for the third day you have five users when you want to use a map and count something up in that map well that's what the reduce function is perfect for basically what it does is it takes a key in this case the key is going
to be the day that you want to map over and then you're adding a specific count of users in this case to the day that they have joined within it might sound a bit complicated but that's because it is and reduce is not something that we use super often but as I told you with charts you're going to use it a lot and this just reminded me of the complete path to JavaScript mastery that I recently completely re-recorded from scratch it's one of the oldest courses that I had but now I updated it and here
we have all of these different array methods in detail so everything from strings in details to arrays and details and here we have the array reduce and we explain it in a lot of detail so for example if you have a grocery list with all the prices you want to calculate the total price of all the items this is a perfect example to use reduce you could do it with a for each where you're just going to map over each one of these elements and add them together but you use an external variable to keep
track of the total with reduce it does it all together basically you get this variable called accumulator which starts at zero and then you take the current value for each iteration and then you add it together but as you can see here in the course we go super deep into understanding exactly how it works so if you want to polish your JavaScript knowledge a bit go check out complete path to JavaScript mastery by becoming a JS mastery pro member another thing that we cover over there is object entries as well where you can take both
the keys and values of an object and then map them over to an array pretty useful when you want to have a chart that has a day and count attached to each day so essentially you'll end up with an array of objects where each object has a count and a day which will look something like this great so now let's call these functions right within our dashboard right here where we called the get users and trip stats we'll also call the get all trips because we want to render them within the cards i think four
will be enough we also want to get access to the get user growth per day we also want to get trips by travel style and we want to get all users but only four will be enough in this case just to show them within a table of course we have to add a weight in front every single one of these calls and we are calling all of these simultaneously because these pieces of data don't depend one on another now here we're getting all the trips but remember we have to properly parse the details of those
trips and I believe we have already done that within the trips page here we are getting all the trips but in order to get them with all the proper details you need to map over them parse the data and attach the images so you can copy this part right here where it says all trips.m map all the way until the end then right at the bottom you can say const all trips is equal to and you can paste what you just copied but in this case we can call it trips right here and where are
these trips coming from well we are yet to define the variables where all of these functions will spit out their values so let's define them one by one first we have the user then we have the dashboard stats after that we get the trips we then have the user growth the trips by travel style and then all users perfect so now we're getting these trips.m map right here but I think we have to say trips.all trips because trips also contains the total of the trips and all trips is an actual trips array we also want
to map over the users so I'll say const mapped users of a type users itinerary count will be equal to all users do users do map where we get a individual user and for each one we automatically return an object not like this because this opens up a function but rather parentheses and then an object within it which automatically returns an object that's just another pro JavaScript tip within it we can say that we want to get the image URL which is equal to user dot image URL we also want to get the name which
is equal to user name the count which is equal to user itinerary count like this so now that we have collected all of these pieces of data we can return them user dashboard stats all trips user growth trips by travel style and finally all users which will be equal to mapped users we are returning all of this from the client loader i think at this point it is safe to remove this dstructuring of the fake data as well as these fake imports right here at the top this will momentarily break our application but we'll make
it work very soon as we extract real data and bring it into this page so from the loader data I will dstructure the all trips the user growth the trips by travel style the all users and I think that's it immediately this will make our app work with real trip cards and then we can scroll down below the created trips here where we have the trip card and we can make sure that we're passing the right data so we're mapping over the all trips and This time I will simply say all trips.m map and we
get each individual trip for each trip we want to return the trip ID trip name we also want to get the image URLs itinerary tags so I'll simply say trip dot in front of every one of these for the name we know that it'll be there so I'll add an exclamation mark and for the tags tags actually don't exist they are a combination of trip interests as well as the trip.t travel style and for these you'll also have to add an exclamation mark and the same thing for the price because you can see that it's
showing so it has the data but we have to render it properly so now we are ready to focus on the last section of the day it'll come below this section that keeps track of our cards so I'll create a section with a class name equal to grid grid-call-1 on large devices grid-call-2 with a gap of five within it I will render a chart component coming from Syncfusion and within it I will inject again coming from Syncfusion specific services an array of services to be exact specifically the column series the spline area series the category
the data label coming from Syncfusion React Charts and the tool tip coming from React Charts as well i simply copied this over from their docs and you can already see that this gives us some kind of a chart so let's give this chart an id equal to chart 1 a primary xaxis will be equal to user xaxis coming from constants so now you can see that it says day at the bottom and then we'll have a primary yaxis equal to user yaxis once again coming from constants i'll also give it a title equal to user
growth and a tool tip equal to an object where enable will be set to true so now you can kind of hover over it and when we have some real data you'll be able to see what it says but now we actually show the data within it so below the inject I'll render a series collection directive and within it I'll render a series directive which will be a self-closing tag here you actually put a data source of user growth but you have to define exactly how each field will look like so x name will be
day y name will be count the type will be column the name will be column as well and the column width you can choose how long or wide you want to make it i'll do 0.3 and you can go to very small details like choosing a corner radius so for example on the top left I can make it 10 and also on the top right I can also make it 10 so they look a bit rounded like it's the little details right so currently I only have one day that we have been active that I
have been developing this app and you can see that it says that two users have joined today or rather this was yesterday so now if I go ahead and join as another user okay now we have logged in as the new user and we are right here on the homepage the non-admin homepage which right now is completely blank so we might actually want to create this root route right next to the signin i'll call it the travel page so travel page.tsx and I'll run rafce then you'll have to head over to the routes and we
can add another layout right below our previous layout which will be forward slash routes slashroot slpage layout.tsx and inside of an array we can give it its first index route this time coming from react router that'll be routes root travel page.tsx tsx so we are rendering this new page that we have right here when you do this you might want to reload your application just so you can see the changes and before we rerun it you also need to create this page layout so right here within app root you can create a new layout called
page layout.tsx where you can run rafce now if you do this and rerun your application it'll spin it up on localhost 5173 and you'll be able to see the page layout the only reason why I wanted to have this here right now is so we can implement the logout button remember we have already implemented the logout functionality on the nav items so I will simply grab it from there here it is a button to handle the logout so I will copy it paste it over into the page layout and I will also copy the handle
logout functionality alongside the use navigate and paste it at the top of the page layout so here you can see this little log out button right now but before you click it try to navigate over to the dashboard manually and you'll notice that you might be able to see it for a second but then it'll be gone so once you do that you can then log out your account will have been created and you can sign in with your admin account once you come back now you'll be redirected to the homepage so if we head
over to that page layout we could add another button right here maybe this one will just redirect us to the dashboard so I'll say dashboard and on click I will simply call a callback function that will navigate over to forward slashdashboard so now if you click on the dashboard there we go and now we have three users and you can see that our user growth has increased for this new day that we have added another user great now we can continue with the dashboard but now I will add a second series directive i'll do that
by copying this one first right below the data source will still be the user growth by day count but this time I'll give it a type of spline area and a name of wave if you do that you'll kind of be able to see the trend of how many users are joining so we can also give it a fill color equal to RGBA something like 71 132 238 and then most importantly the opacity of 0.3 so it's not actually appearing over the other elements so now we can see the trend of course this is not
super cool when you have two but later on when you have more it'll look much better because you'll be able to see the trend on each one in this case instead of a column width and a corner radius we can give it a border of about width of two and a color of hash 47 8 for e so now it'll have a border as well so you can better track the trend now we can exit out of this chart component and right below it we can create another so what I'll do is I'll simply copy
the entire chart and I will paste it one more time right below it starting from the top I'll call it chart 2 primary x-axis will be trip x-axis and y will be trip yaxis this one will say trip trends will still inject the same things and then it'll have a series directive with a data source of trips by travel style with x name of travel style y name of count type of column name of day column width of 0.3 and the same corner radius and we don't need a second series directive for this chart so
if you save it you'll be able to see it just below to track trip trends so there you have it we have two trips one relaxed and one cultural now I'll head over below this chart component and I want to make sure that both of these are in the same section right here and they need to say grid instead of grits columns so if we do that and if you go to desktop you'll notice that now they both show in the same row while we're here what do you say that we go ahead and create
two more trips to fill out our dashboard a bit more so I'll head over to create a trip i'll select a random country from the list enter a number of days and I'll choose some of these fields at random there we go looks like this will be a fun one so let's go ahead and generate it let's see if there's some nightife and bars in Burundi Africa this one should be quick because it's a 5-day trip only looks like Burundi has its own Las Vegas and some interesting houses and let's do one more i'll pick
another random country let's go with Guatemala a short two-day trip with friends that's going to be an adventure with hiking and a premium budget what's happening at Guatemala there we go it looks like there's going to be a lot of hiking but since we're looking at a luxury trip it's going to be a private transfer to the Volcano National Park perfect so if we head over to the dashboard you'll see that it looks much better and we can see more trip trends right here so the last part that we have to do is create some
very quick references to our users and trip bookings so that we don't necessarily need to go to their respective pages right here and here rather we can see it all from our global dashboard so to do that I'll head below this section and I'll create another section with a class name equal to user trip wrapper within here we have to map over the users and trips both at the same time so what do you say that right here at the top we create a variable called users and trips and we make it an array where
the first object has a title of latest user signups with a data source equal to all users with a field of count and a header text equal to trips created count is also a string and we can duplicate this right below but this time instead of latest user signups we'll have trips based on interests we will pass the trips data with the field of interest and the header text of interests so now we have to make sure that we have access to these trips and in this case we just need the images the names and
the interests nothing more so we can create those trips variable out of the all trips by mapping over them getting each individual trip and automatically returning an object with an image URL of trip image urls of zero so we return just the first one the name of trip.name and the interest of trip interests but make sure that you have properly closed it right here in parenthesy and then a curly brace so now we have access to it and we can map over it so right below I will map over users and trips do map where
we can dstructure the title the data source the field and the header text and outside of it we also get the index so for each one of these we can return a div with a key of index a class name equal to flex flex- call and a gap of five and an h3 within it that'll render the title that'll look like this latest user signups and trips based on interests where we have a class name equal to P20 semibold and text dash dark 100 to make it a bit stand out and then we can render
the grid this grid will be very similar to the grid in all users so head over into all users find where we use the grid component and just copy it you don't have to copy it until the end maybe just take the first thing where we're actually rendering the username and the photo so until the end of the first column directive next right below the H3 paste that grid component but change the data source to data source and make sure to import the grid component from Syncfusion import the columns directive and import the column directive
we'll keep this exact the same because we want to show the users same as before and we'll render just one more column directive this one to render the second field with a header text which will be dynamic header text so either the latest user signups or trips based on interests with a width of 150 and text align of left and we can end it right here make sure to properly close the columns directive as well so in this case we have to close it and then close the grid component itself if you do that you
should be able to see two different tables the first one rendering the users and the second one rendering the interests oh but wait why is the number of trips created non-existent i think we don't have this field stored into the database we would need to do some additional logic to connect the user to the trip but what we can do for now is just head over all the way up to where we have the count of the users and here maybe we can say if it doesn't exist do a math.f floor math.random times 10 this
will give us a random number from 0 to 10 perfect this is looking good so with that in mind if we expand this dashboard so you can see it on full screen you have now developed a fullyfledged dashboard with a real database where you can see different charts the users the trips you can see absolutely everything right here from the type of the user to the trips that we have generated so far with budenation and the ability to generate new trips using Gemini so this is absolutely amazing and I almost forgot about the trip details
page which is maybe the star of the show because here is where you can see the actual trips that we have generated for each user so the last thing we have to do is to deploy this travel agency admin dashboard which we'll do right now but keep in mind that I really wanted to take this course further than just the admin dashboard of course at the same time I wanted to make it complete so right here right now you have successfully developed the admin dashboard with all of these functionalities so the next step will be
waiting for you on JS Mastery Pro we'll take this app a step further by developing a public landing page where different agencies can create their own landing pages with featured destinations and show their handpicked trips then users can view those trips and even book them by paying for them using Stripe checkout after that they'll be redirected to this greatl looking success screen so what do you think if this is something you'd like to explore you can check it out right here within the video kit watch it now by becoming a member whether you decide to
improve this app or not one thing is certain and that is that we have to get it deployed so back within our app I'll stop it from running and I'll run git add dot git commit-m and I'll say final commit and finally run git push once you push it you can head over to Verscell and add a new project you should be able to see it pop up right here and you'll notice that Versell will automatically figure out that we're using the React router framework then you'll be able to add your environment variables right here
so head back over to your application go to your env.local copy these env this will take about a minute but while that is happening you can head over to appite and create a new platform this will be a web app so you can give it a name of travel agency and for host name you can just add a star click next next again we have already done this and you have your endpoint which we have already used before now your app should be accessible from the web so if we head back over to Verscell you'll
see that a new project was just deployed to JSM Pro or to your name in this case so you can continue to its dashboard and click visit this will redirect you to a very simplistic page for now but if you log out you'll be able to see your sign-in page deployed and live on the internet and you can now sign in with Google after which if you log in as admin you'll be able to check out your admin dashboard pretty cool stuff right in this project I purposefully left a couple of places that you can
take to improve it even further for example you can make these trips created dynamic or you can make these headings point to the users and trips tables you can fix a little bug when the user is first time creating an account and they try to visit the dashboard they can actually see it that's a weird edge case that's worth taking a look at but overall congratulations on building such an amazing application that acts both as a dashboard as well as an AI generation tool for all of your and everybody else's future travels with that in
mind if you'd like to polish a bit of your JavaScript check out Complete Path to JavaScript Mastery or if you maybe want to do a bit of Nex.js you can check out the Ultimate Next.js JS course or just become a JSM Pro member to get it all alongside real interview feedback and even the access to building the public version of this website all of that is available on JSM Pro so check it out and I'll see you there have a wonderful