
No Comments Yet
Be the first to share your thoughts and start the conversation.
Be the first to share your thoughts and start the conversation.
How did you manage to remove the blur property and reach here?
Upgrading gives you access to quizzes so you can test your knowledge, track progress, and improve your skills.
By logging in, you'll unlock full access to this and other free tutorials on JSM Pro.
Why? Logging in lets us personalize your learning experience, track your progress, and keep you in the loop with new workshops, coding tips, and platform updates.
You'll also be the first to know about upcoming launches, events, and exclusive discounts.
No spam—just helpful content to level up your skills.
If that sounds fair, go ahead and log in to continue →
Enter your name and email to get instant access
In this lesson, the speaker walks through the process of creating a dashboard for displaying trips and statistics using a mobile-first development approach. The focus shifts from layout to creating content-specific cards that present essential data to users, highlighting the importance of both mobile and desktop views.
00:00:02 Okay, the primary layout is done, so let's focus on the content.
00:00:06 Displaying what matters on the page.
00:00:08 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
00:00:17 over for desktop.
00:00:18 Although I would say there is one exception to that rule, and that is dashboards.
00:00:23 Dashboards are typically meant to be viewed on desktop, so prioritize proper desktop views.
00:00:30 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.
00:00:39 So let's develop a reusable trip card layout.
00:00:42 But you know what?
00:00:43 Before that, let's start from the top.
00:00:45 Let's focus on the stat card layout, the one that has a nice looking chart and displays some information.
00:00:52 I'll do that by creating a new component right here in the components folder.
00:00:58 And let's call it something like statscard.tsx.
00:01:03 Run RAFCE.
00:01:06 And while we're here, we can also create another card.
00:01:10 This one will be the trip card.
00:01:12 So I'll create a new trip card.tsx.
00:01:17 Also run RAFCE and just display it right here.
00:01:20 What we need to do, of course, is export them both from the index.tsx.
00:01:25 So that's going to be the stats card, as well as the trip card.
00:01:32 Perfect.
00:01:34 So now we can head over and import both of them within the dashboard so we can start implementing them.
00:01:40 I'll first head over into our routes dashboard and right below the header I'll display the trip card.
00:01:52 coming from trip card components.
00:01:54 And right on top of it, I'll also display a stats card coming from components.
00:02:00 So now we can see that we have a stats card and a trip card.
00:02:04 We first have to ask ourselves, what will each one of these stats card have?
00:02:08 If you already have a design, well, then you have everything you need.
00:02:12 Take a look at these cards and ask yourself what is different about them and what is the same.
00:02:17 The regular card layout is the same, the background, the borders and everything, but what is different is the title, total users,
00:02:27 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.
00:02:36 So these are the different pieces of data that we have to pass into it.
00:02:39 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.
00:02:50 So for now, I'll say const dashboardStats is equal to and I'll create a stat for total users.
00:02:58 And actually doing this is a pretty good idea.
00:03:01 Starting to create everything static from regular JavaScript objects.
00:03:06 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.
00:03:18 You now know that you have to store this field in the database and that it'll be of a type number.
00:03:24 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,
00:03:36 maybe like 218, in the last month.
00:03:40 That way, you can compare that data.
00:03:42 So last month, I'll set it maybe to 176. This will allow us to have this nice looking chart right here.
00:03:50 After that, we can render the total trips card, maybe 3,210. And then we can also have the trips created.
00:03:58 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
00:04:10 more that month.
00:04:11 And finally, we need to know how many active users do we have.
00:04:14 So I will say user role.
00:04:18 We have a total of about 62 maybe.
00:04:21 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.
00:04:31 Perfect.
00:04:32 So this is some fake data that we can now pass over into the cards.
00:04:36 So let's focus on the stats card first.
00:04:39 Below the header, I'll create a section.
00:04:42 And this section will have a class name equal to flex, flex-call, and a gap of six.
00:04:51 So we can display all of them one next to another.
00:04:55 Within this section, I'll create a div that'll have a class name of grid.
00:05:00 So, specifically the grid for the stats card, where we want to make it have only one column, so calls one.
00:05:09 On median devices, we want to have three columns.
00:05:12 So this will be on larger devices and then a gap of six in between the cards and a full width.
00:05:19 And right within it, we can display our first stats card.
00:05:23 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?
00:05:33 So I already told you that we'll need to have the total users, right?
00:05:38 So that's the title.
00:05:40 Header title is total users.
00:05:45 Next, we can pass the actual amount, which I'll call the total.
00:05:49 So this will be equal to dashboardStats.totalUsers.
00:05:55 We'll also need the current month count which will be equal to DashboardStats.UsersJoined.CurrentMonth and then we can do the same thing for the last month
00:06:11 count is equal to DashboardStats.
00:06:14 dot users joined dot last month.
00:06:20 And we can maybe destructure this dashboard stats just so we don't have to repeat ourselves every time.
00:06:25 So I will destructure the total users and users joined from the dashboard stats.
00:06:32 by saying const totalUsers, usersJoined, totalTrips, tripsCreated, and userRole equal to dashboardStats.
00:06:46 So now, if you head here, you don't have to explicitly say dashboardStats for every single one of them.
00:06:52 You can just refer to totalUsers and usersJoined.
00:06:55 Perfect.
00:06:57 So now we can very easily duplicate this card two more times.
00:07:01 For the second one, instead of total users, we'll have total trips.
00:07:05 So we can pass the total trips and instead of users joined, we can refer to the trips created.
00:07:12 And then for the last one, we can talk about just users.
00:07:16 So we can say userRole.total and then here we can say userRole.currentMonth and that last month.
00:07:26 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.
00:07:34 So it'll be super simple to take that data and display it.
00:07:38 The only thing we have to do is head over into the stats card and implement its layout.
00:07:45 Of course, starting with destructuring all the props that we're passing into it, such as the header title, the total, the last month count,
00:07:57 and the current month count.
00:08:00 These will be of a type stats card.
00:08:04 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,
00:08:12 what the total will be, and so on.
00:08:14 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
00:08:28 which can be used across your entire application without needing to import it.
00:08:33 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.
00:08:41 And that's it.
00:08:42 It just immediately knows that the header title is a string.
00:08:45 Pretty cool, right?
00:08:46 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.
00:08:54 such as figure out what is the trend.
00:08:56 Is it increasing or is it decreasing?
00:08:59 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.
00:09:07 This is actually a pretty interesting challenge if you want to work on it.
00:09:10 What I'll do is say const, destructure something, and make it equal to the call of the calculateTrendPercentage.
00:09:19 And to it, I'll pass the current month first and then the last month second.
00:09:24 And this calculateTrendPercentage will give me exactly what I need, which is the trend and the percentage.
00:09:30 Now, how does this calculateTrendPercentage work and where is it coming from?
00:09:34 Well, I declared it within the utils function.
00:09:38 It's a utility function.
00:09:39 But of course, I invite you to implement it on your own.
00:09:43 This is a great type of function that ChatGBT excels at.
00:09:47 You give it a very small task, such as to give you a trend and a percentage.
00:09:53 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.
00:09:59 So in this case, it says if count of last month is zero, then there is no chain.
00:10:05 So what is it doing is it's taking a look at the difference, it's grabbing the percentage of that difference.
00:10:12 And then if the change is greater than zero, it returns a trend of increment and a percentage.
00:10:18 If change is lower than zero, then a decrement percentage.
00:10:22 And if there's no change, it just returns zero.
00:10:26 Makes sense, right?
00:10:27 So once we get that trend, we can then say cost is decrement is equal to, and that's going to be equal to trend is equal to decrement.
00:10:37 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.
00:10:46 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.
00:10:53 Cards are typically articles, so I'll use this article and give it a class name of stats-card, which will automatically give it a white background and
00:11:03 a bit of a shadow.
00:11:05 If at any point you're wondering which styles are being applied when you add a specific class name, you can command click into it or just search for it
00:11:14 across the code base until you find it within the app.css.
00:11:18 You'll see that we're giving it some padding, making it a flex container with a gap in between the items, giving it a background,
00:11:25 a shadow, a bit of a rounded corners, and a dark text.
00:11:29 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.
00:11:43 So now each one of these says a different thing, total users, total trips, and then total users once again.
00:11:50 That's because I forgot to change the last one.
00:11:53 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.
00:12:05 Perfect.
00:12:06 So now we have three different cards.
00:12:09 Or just active users is fine.
00:12:12 So now, we are ready to start implementing the rest of the stats card.
00:12:17 Right below the H3, we can start focusing on the main content.
00:12:21 So I'll wrap it in a div with a class name of content.
00:12:26 And within that div, I'll create another div with a class name of flex, flex-col, and a gap of four in between the elements.
00:12:36 within which we can start rendering an H2 that'll render the total number.
00:12:41 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.
00:12:50 Because the total number is more important than the title, you need to see those numbers very clearly.
00:12:56 Right below this H2, we can render another div, that'll have a class name of flex, items-center, and a gap of 2. And within it,
00:13:09 we want to render a figure.
00:13:10 This figure will contain the percentage as well as the arrow pointing up or down.
00:13:15 So right here, I'll give it a class name equal to flex, items-center, and a gap of 1. And within it, I will render an image With a source equal to,
00:13:31 it has to be dynamic.
00:13:32 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.
00:13:46 else I will render the arrow up green dot svg and I'll also give it a class name of size of five and then I'll tag of arrow if you save that you should
00:13:59 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,
00:14:08 which is basically just a P tag, within which I'll render the math that round, and within it, I'll pass the percentage, and then add the percentage sign
00:14:19 right after it.
00:14:20 So you can see it says 25% up, 40% down, 67% up, or something different for you if you used different numbers.
00:14:28 Let's also style that text a bit by giving it a class name, I'll use a dynamic CN class name.
00:14:36 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.
00:14:52 of 700 so now we 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
00:15:02 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-sm font-medium
00:15:14 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
00:15:25 to great and now we can head over below the speed tag and below two more divs and we can render an image This image will have a source of forward slash
00:15:39 assets forward slash icons forward slash dynamically we can check if we're in the is decrement and in that case we'll display the decrement.svg Else,
00:15:52 we'll display the increment.svg.
00:15:55 So, if you save it, you'll be able to see those different charts.
00:15:58 Now, a challenge for you, and something that we can look into later on, would be to actually make these charts functional,
00:16:06 using a library like charts.js or something similar.
00:16:09 For now, the most important thing for me is to display the value and the trend.
00:16:14 And making this real is a beautiful challenge for you to try out.
00:16:18 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.
00:16:26 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, WFull,
00:16:36 HFull, on medium devices, H32, on extra-large devices, HFull.
00:16:44 And I'll give it an alt tag of trend graph.
00:16:49 So if I save this, they're now a bit larger.
00:16:52 And if you check out the desktop version, this is looking exactly like it does in the design.
00:16:58 Pretty cool, right?
00:17:00 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
00:17:09 about those users.
00:17:10 Like active users and total users can point to the All Users page, and total trips can point to the AI Trips page where you can show more details on the trips.
00:17:19 But for now, let's focus on implementing the trip card.
00:17:22 This will bring so much more life into our dashboard because each one of these trips will have its own thumbnail.
00:17:29 So, let's collapse it.
00:17:31 So now is the time to implement that trip card.
00:17:34 Before we start implementing the UI, we'll have to figure out the data for those components.
00:17:39 And similarly, how in the dashboard?
00:17:42 we rendered some fake data for the dashboard stats, which soon enough will be imported into our real database.
00:17:49 We need to have the data for the trips.
00:17:51 So I prepped some fake data for you right here so we can very easily get going.
00:17:55 It'll be right here under All Trips, Dummy.
00:17:59 And you can see that here we have all the trips.
00:18:01 So let's simply import it and paste it right here at the top.
00:18:06 It doesn't have to be within the component.
00:18:08 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
00:18:18 database data.
00:18:19 And I'll paste it.
00:18:20 Here, you can see that each one of these strips has an ID, a name, image URLs, the itinerary with different locations, tags,
00:18:30 travel styles, and estimated prices.
00:18:32 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.
00:18:41 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.
00:18:53 So this is the constants index.ts.
00:18:56 and I'll paste the dashboard stats, the old trips, and the user data.
00:19:02 I think later on we'll also have the user's data, so I'll copy it while we're here, and also add it here, so we have some fake users.
00:19:11 This one uses the format date functionality, so make sure to import it from LibUtils.
00:19:17 There we go.
00:19:18 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.
00:19:24 And you can see there's no more snippets to copy.
00:19:27 Everything else in this entire application will be done entirely by us together.
00:19:32 So let's make sure to add an export statement at the start of each one of these lines.
00:19:37 And we can import it right here within the dashboard.
00:19:39 So I'll say import dashboard stats from constants.
00:19:43 Also the user.
00:19:45 And also what else do we need?
00:19:47 I think it's going to be the all trips.
00:19:50 So we can render the cards for each one.
00:19:53 See how clean the file is?
00:19:55 And we can even pull this destructuring right here.
00:19:58 So we have nothing in the component, we only have the JSX.
00:20:02 So now below the stats card, I'll actually just hide them for a second.
00:20:07 I'll display a whole another section right below this section that shows the stats cards.
00:20:13 This one will be another section that has a class name equal to container.
00:20:19 And it'll also have an H1 that'll have a class name equal to text-excel and font-semi-bold as well as text-dark 100. And it'll say created trips.
00:20:35 So now right below it, we can render our div.
00:20:39 That'll have a class name equal to trip-grid.
00:20:43 And keep in mind, nothing is showing up right here besides this trip card which we can remove.
00:20:48 But now, within here, we can use that dummy trip data by saying all trips coming from constants.slice, which means that we can only get maybe the first
00:21:00 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
00:21:12 trip card, like this.
00:21:15 So if you save it, you'll now see four different trip cards to which we can pass all of this trip data.
00:21:22 So I'll give it a key, since we're mapping over it, of trip.id.
00:21:27 And as a matter of fact, we can destructure those properties from the trip.
00:21:31 So I'll destructure the ID.
00:21:34 the name, image URLs, itinerary, tags, and the estimated price.
00:21:41 All the info that we need, and now we can very easily pass it over as props into the trip card.
00:21:47 So the key is just ID.
00:21:50 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,
00:21:58 not necessarily a string.
00:22:00 We can get a name equal to name, Image URL equal to image URLs, zero.
00:22:08 So we're going to take the first one.
00:22:10 The location can be equal to itinerary, question mark dot zero.
00:22:14 So if it exists, get the first one and then get its location.
00:22:19 Or if it doesn't exist, maybe display an empty string like this.
00:22:22 For the tags about the trip, display the tags.
00:22:25 And for the price, display the estimated price.
00:22:29 Perfect.
00:22:30 So now we're passing all of these important props into the trips card.
00:22:34 So what do you say that we go ahead into it, and we accept them, and we start implementing that card?
00:22:40 It won't be that tough, trust me.
00:22:41 Maybe even easier than the stat card.
00:22:44 The data is what matters.
00:22:45 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
00:22:56 to go.
00:22:56 So, I'll destructure that data, such as ID, name, location, image URL, tags, and the price.
00:23:05 All of these are of a type trip, card, props.
00:23:10 And now we can make each one of these cards a link coming from React router.
00:23:16 Why?
00:23:17 Because we want to be able to see the trip details once we click on it.
00:23:20 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.
00:23:32 If you save this, you'll be able to see four dummy images.
00:23:36 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.
00:23:42 But of course, what matters more is that each one of these cards will actually point to a real trip details page.
00:23:49 So let's actually add a to part, and we have to get access to the current path.
00:23:55 So right here, it's very similar to how we do it in Next.js.
00:24:00 Const path.
00:24:01 is equal to use location coming from React Router.
00:24:06 Then we can check whether we're clicking on this card from the public-facing website or from the admin dashboard.
00:24:13 Because depending on that, we're going to link them to a different page.
00:24:17 So I'll say if path.pathname is triple equal to forward slash, meaning public-facing, or path.pathname.startswith That still means that they are on the
00:24:35 public-facing website.
00:24:36 In that case, we'll point them over to travel and then the ID of that trip.
00:24:42 Else, we'll point them to trips forward slash ID.
00:24:48 Why?
00:24:48 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.
00:24:55 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.
00:25:02 But with that said, let's continue adding more data to this card.
00:25:06 I'll give this link a class name equal to trip-card, which will kind of collapse it a bit.
00:25:12 Next, right below this image, I will create an article that will contain most of that cards or most of that trips data.
00:25:20 such as the H2 that'll render the name of the trip.
00:25:25 Maybe also a figure in this figure will contain an image that'll have a source of assets, icons, location mark dot SVG.
00:25:39 with an alt tag of location and a class name of size of four.
00:25:45 That's much better.
00:25:46 Now, why do I use figure?
00:25:48 Well, if you have an icon that is related to a specific text, then you might want to put them into figure and put a figure caption like this.
00:25:59 instead of a paragraph.
00:26:01 It's just better for screen readers and overall usability.
00:26:04 So now we can see that this is New York, Paris, Tokyo, and so on.
00:26:08 Next, we can exit out of this figure and the article as well and create a div right below it.
00:26:16 This div will have a class name equal to margin top of 5 to divide it a bit from the top, a padding left of about 18 pixels like this,
00:26:27 padding right of about 3.5, and padding bottom of about 5. So we're creating space to display some tags right here.
00:26:35 And for the tags, I'll use a Syncfusion chip component.
00:26:39 So I'll just start typing chip list component.
00:26:44 I'll give it an ID of a travel chip.
00:26:48 And within it, we can display the chip's directive component.
00:26:52 And this one will look something like this.
00:26:54 You have different styles, like simple, choice chips, filter chips, dynamic chips.
00:26:59 It is super customizable.
00:27:01 Within it, we can map over our tags by saying tags.map, where we get each individual tag and the index of that tag.
00:27:09 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.
00:27:21 It has a text equal to get first word of a tag because tags can contain multiple words.
00:27:29 You have to import this from utils.
00:27:32 Basically, we just trim it out, split it, and render the first word.
00:27:37 And then we can also provide some CSS class to it to style it further.
00:27:43 And I'll say CN from utils.
00:27:47 If index is triple equal to 1, then give it a BG pink 50. And you have to add an exclamation mark in front to make sure that the style is applied.
00:27:59 as well as text pink 500, else give it a BG success of 50 and a text success of 700 and save it.
00:28:12 So now you'll see that we have different colors for those two different tags.
00:28:17 First one, maybe New York is about adventure and culture, Paris is relaxation, culinary, and so on.
00:28:24 Finally, we can head below this div that's wrapping the chips and render the article.
00:28:29 That'll contain the information about the price.
00:28:32 Of course, we can style it a bit better by giving it a class name of trip card dash pill.
00:28:40 And this will just make it a pill that's going to show up on top of this image right here on the right.
00:28:46 Looking great.
00:28:47 Also, I automatically added some images right here to match what you're seeing on your screen right here.
00:28:52 And with that, we're done with the trip card.
00:28:54 But of course, it looks even better on desktop.
00:28:57 where we can see all of these cards.
00:29:00 Later on, we'll be able to visit them as well.
00:29:02 But you get the idea.
00:29:03 We can see the latest trips that the agents within our travel agency are creating.
00:29:08 So now we have a beautiful layout, but with fake data.
00:29:11 So the next obvious step is to implement the full functionality for our application, including authentication, allowing users to add real trips,
00:29:22 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.