A second and third version of this course released using Next 13+, but this course is great if you want to see another example fullstack project. We now recommend you take the Build an AI-Powered Fullstack Next.js App, v3 course.
Transcript from the "ProjectCard Component" Lesson
>> So next if we look at our App, we wanna work on these project cards here. So first of all, make the project card, and then second, we'll figure out where we're gonna get the data, how we're gonna get the data, and then we'll go from there, sound good?
[00:00:15] Okay, so if we go to the project cards section here on the notes, the project card is really not that special. There's some stuff in here that is maybe worth talking about. So we can do that mostly around some of the TypeScript stuff that I'm doing here with Prisma, which is kinda cool.
[00:00:36] So we could talk about that. So let's go into our components, Folder, and make a project card. There we go. I don't know why it did that. Cool, so I'm gonna do a couple things here on the imports. Bring in Prisma in here. Like I said, I'm gonna do some TypeScript stuff with Prisma.
[00:01:11] We're gonna use the card component cuz it's a card and clsx for helping me write classNames without backticks and pluses cuz I hate doing that. So the project card is a component for each one of these cards. So as a prop, you probably imagine it needs to take in a project so it can actually show this information.
[00:01:32] So you can see right now it takes to this date. This date is when it was created at. This is the name of the project. This is how many tasks are completed out of total. So it needs all the tasks, and then it calculates the percentage. So it needs a few amount of some stuff there for sure for projects.
[00:01:49] So we get that for free automatically as far as those types through Prisma, because Prisma creates a Typesafe client for us that's already got the props. We just gotta find a way to translate that to a React prop. So we can actually do that pretty effectively here. So the first thing we wanna do is we wanna create this object here, which is gonna be something we get using Prisma.validator.
[00:02:19] So what we can say is, what did I call it here, project with tasks. And we can say Prisma.validator, like this. And that's gonna take a generic like this. So this generic is gonna be Prisma.ProjectArgs. So let's talk about this. One, a generic is just like an argument for a type.
[00:02:47] That's the best way I can describe it. Some types are dynamic, as in you might have a type for a function that returns something, but its value, the type of what it returns is dependent on the arguments that it's passed. So you need a generic to have that be dynamic.
[00:03:05] So in this case, validator is taking a generic cuz it needs an argument to what's gonna affect what it returns. And that argument is gonna be Prisma.ProjectArgs. ProjectArgs are gonna be the arguments needed for us to make a project. And we know what's needed to make a project, because if we go look at our schema, it's everything that's required, right?
[00:03:28] So it'd be like a name, a ownerId. Yeah, it would just be those, basically, a name, a ownerId. Everything else either has a default or it's already there, or it's not required. So those will be the Args there. So this isn't something built into Prisma, this is something that was generated, ProjectArgs, cuz there's a UserArgs, right?
[00:03:49] And then there's a TaskArgs, right, that's generated from our schema. So ProjectArgs here, And then that's gonna be a function. And that function is basically a higher order function, or we're gonna immediately invoke it, and we're gonna pass in an object here. And we're here, we just need to include the tasks, because by default, oops, by default, A project, when you query it through Prisma, when you query anything through Prisma that has a relationship, for instance, if I were to query a user, it by default would not include the project or the task, it has to mainly include them.
[00:04:31] You can think of it as a join table in SQL, or in Mongo, I think it's called a population, you can think of it like that. So you have to mainly include it. So we're just saying, yeah, we want to make a type that looks like the arguments for a project, but also includes the tasks, because projects could have tasks.
[00:04:53] Right, I could also say include owner or whatever if I had an owner there, but we're not doing that. Okay, so now that I have that, I can use some TypeScript magic here. And I can make a new type, I'm gonna call it ProjectWithTask. But I'm gonna use this type here that also takes an argument, Prisma.ProjectGetPayload.
[00:05:17] So what is the payload of the project? Well, I want it to be the type of whatever ProjectWithTask is. So I'm gonna say type projectWithTask = Prisma.getProjects, or what is it, al the way around, Project., oop, One word, ProjectGetPayload, there we go. That's a type that takes a generic here, and then I'm gonna say typeof, Our projectWithTask.
[00:05:54] So if you didn't know this in TypeScript, you can tell TypeScript what a type is by putting typeof in front of it, Which is really cool. So now we have a new type, projectWithTasks, And it's gonna be this type here. So now we can actually use this for our functional component.
[00:06:21] So let's do that. So we're gonna make our components. We're gonna call this a ProjectCard. We're gonna make it equal to that, and then I'm gonna use FC for functional component here. That takes a generic. What do you want your props to look like? They're gonna look like a projectWithTasks.
[00:06:45] So we did all of that, so we didn't have to just write our own interface like this interface Projects, and then we just put all the stuff that a project has. That's not hard to do, I mean, you could do that. Problem is, if you do that, and then you go change your project schema, in your schema when you generate what it means to be a project, you gotta change this too.
[00:07:05] And you had to keep doing that over and over and over again, cuz this isn't gonna change automatically. But if we rely on the stuff generated from us through Prisma, which is automatically changeable when we do migrations and things like that, then this code won't ever have to be touched.
[00:07:37] So the first thing is, if you go and look at the cards, they have a date, so we're gonna format that date. I think this is where people will pull in Moment, MomentJS, a massive date library, but that takes up half your app, if not more. We don't need that, one, because it's massive, two, there's better things out there.
[00:08:00] I think datef and s is way better. But three, we can just do this ourselves with a date object, it's actually really easy to do. So we'll make a function called formatDate. [COUGH] It takes in a date. And if I can get my notes, it's just going to make a new date out of the date that we get back and use this toLocalDateString, right?
[00:08:23] So we can say date, NewDate from the date .toLocalDateString, or yeah, DateString, not TimeString. That will just give me the time. So that, we can do that. And then from here, we just pass in our locale, which is gonna be English US. And then just the options you wanna use.
[00:08:48] So in this case, we can say we want the weekday to be long, the year to be numeric, the month to be short, and the day to be numeric. So a little date format hack if you've never used that before, it's actually really simple. I don't know why people don't use this more.
[00:09:09] Okay, so we got that. Now, what we wanna do is we need to compute the completed count. So if we look at here, we need to know how many were completed out of total, and if you look at the six script that we've made, it's random. COMPLETED is, where is it, right here.
[00:09:30] So the status, which can be COMPLETED, NOT_STARTED, or STARTED is random. So if you go to, you can see right here, I'm just taking all these and I'm literally doing a random one. So we don't know how many it is, so we can't hardcode it. So we do have to compute it.
[00:09:44] So what we will do is just basically filter over them and just grab the ones that are completed, and then we will do some math to figure out what is the percentage of that. So let's do that, so completedCounts, It's just gonna be, well, we gotta get the project here.
[00:10:09] There we go. It's gonna be project.tasks.map. Where is it, let me export this right quick. There we go. Okay, I would imagine this would give me some type checks. Probably Project is not, wait, I gotta do this, I gotta say, project is that type. There we go. Okay, so now I can say give me the project and then I just wanna get the ones that are project.status==='COMPLETED'.
[00:11:09] And then you can map that to a TypeScript enum if you want, if you really wanna get crazy, but you don't need to, cuz that's basically what this is. So we can say that, and then we can do that .length. So that'll give us how many are completed.
[00:11:23] And then we can just look at the progress, which is just a simple calculation here. We're just basically saying, round down completedCount divided by total. That's gonna give you some percentage decimal, multiply that back to 100 to get it back to a whole number, and that's the progress, so basic math there.
[00:11:46] And then from there, it's mostly just JSX. I'm just gonna copy that. It's nothing special here. I can go through and talk about all the interpolated values. So I'm just gonna rename my function to formats, so formatDate. But basically, all we're doing here is we're putting a date here using our format or formatDate function, passing in project.createdAT, which is a timestamp.
[00:12:13] I know that because it's right here, created as a DateTime. The next one is here, I'm just showing the percentage that was completed. That's the part that's underneath the card or underneath the bar that's showing that right there. I'm sorry, no, that's that part right there. This part is this number right here, so the number completed divided by total, that's that.
[00:12:46] And then the bar itself is actually pretty simple. All it is is just you got one div here, this background div, that's just a different color and rounded. And then you have another div that's literally sitting on top of it. And we're just changing the width of the upper div based off the percentage.
[00:13:05] So if it's 100% wide, it'll go all the way. If it's 20% wide, it'll go 20% of the way. That's it, that's really all we're doing, so you can look at the code here. So you can see here's the rail, the background div. And then here's the upper div, the actual bar, and the width is what's dynamic.
[00:13:22] It's whatever the progress is percentage. That's really it, nothing else crazy about it. The reason I had to make a style tag versus just putting it in here with Tailwind, cuz you can actually do something like this in Tailwind where you say w and you can put whatever number you want here, pixels, percentage, whatever.
[00:13:43] But it wouldn't work because Tailwind statically looks at these classes to determine what it should add. And if it's dynamic, which in this case it would have been dynamic cuz we would need a progress, it would have been something like w-, It would have been something like that.
[00:14:27] Other than that, it's just regular old JSX here, nothing crazy.