In order to implement the template system let's prepare the following things. First of all let's run bun run dev and ensure that you're running on localhost 3000 right here. In your second terminal go ahead and run bun Run database studio. So you have your Drizzle Studio running. So in here I see my projects and in here I can see my database.
I want you to go inside of the project column, select everything inside and Let's go and remove everything that we have here. So I want to remove every single project that we have. Let's start from a blank slate here. And I can notice that our empty state is not exactly handled well here. So let's do the following.
Let's go back inside of our projects section here and let's improve this query which shows no projects found. So instead of checking for this we can do the following. We can check for that as well but we can also do this or data pages first inside dot data dot length. There we go. So in case it has the first page but the first page is simply empty then still display no projects found.
Excellent. Now that we've solved that this is what you have to do. You have to create four different templates or how many you want but four will look the best if you want to present this project to someone. And I'm gonna give you a couple of options to do that. The first option is to simply create a template yourself.
And by template I mean just create a random project. So let's wait a second for my editor to compile so I get redirected to the editor page. And now inside of here you can simply go ahead and create template 1 for example. You can do this if that's what you prefer. Right?
But here's another option that I'm going to give you. When you download the source code, inside of the public folder, I'm going to go ahead and prepare some files for you. So I will create a car sale JSON and a car sale PNG, a coming soon JSON and coming soon PNG, flash sale JSON and flash sale PNG and travel json and travel png. So you might already guess what these things are. These are my json files which you can use to simply open inside of your editor.
So let me go ahead and do that. I'm going to go ahead and open the car sale JSON like this and the only thing you have to do now is the following you have to click export and to PNG or you can use this one the car sale PNG basically you need to have a thumbnail for your template, right? So if you've made your own template, just make sure you export in form of PNG. Once you have your PNG, go ahead and go inside of upload thing. So I want you to go inside of your project and select the files option and inside of here we're gonna go ahead and directly upload our file.
So I'm gonna go ahead and now add the car sale PNG here. So let's click upload and let's wait a moment for car sale to upload. Great! I now have car sale PNG inside of my project and when I click here, there we go. That's the image I'm looking for.
So we're gonna use this as our URL for the thumbnail. So now let's prepare that. Just make sure you have this project here. There we go. So this is now my project.
I only have one project because I want to keep it simple for me. Let's go inside of Drizzle Studio here and let's refresh our projects. So right now we're going to enable templates by using a super admin. So we are a super admin. We have database access.
So that's why I told you to remove all of your projects so that you can easily determine which project is which. So inside of here what I'm going to do is I'm going to rename this project directly here. You don't have to do it but it is what I'm gonna do. I'm gonna call this car sale poster like this and I'm gonna click save one change. Now this is named car sale poster so if I refresh here there we go car sale poster And now what I'm gonna do is I'm gonna change this to is template to be true.
And I'm gonna save this change as well. So this now has the true value for is template. And the last thing I'm gonna do is I'm gonna go ahead and select car sale here which I've uploaded and I'm gonna copy the URL and then I'm gonna go and find the thumbnail URL here and I'm gonna paste that URL string here. There we go. So now inside of here this is ready to be rendered as a template.
So if you want to you can now go ahead and create templates in that way. So I just want you to for now create just one template right just make sure that inside of your Drizzle Studio You have one project with the proper name, JSON that you like, which you can just, you know, if you have access to the source code, you can just use the JSON or just create your own version, right? Make sure you've added the thumbnail URL. We did that by directly uploading it into upload thing here and then we copied the URL. Once you have those things you are ready to continue further.
So remember inside of the public file here for you I'm going to add all of these jsons which you can try out and add inside of an editor and it's gonna have this cool looking things like this coming soon poster, the flash sale or the travel poster. Great. Now let's go ahead and let's implement the templates endpoint. Let's head back inside of our projects API. So instead of source, app, API, route, projects right here.
Let's go ahead and let's add get slash templates. Then let's go ahead and let's verify out so only Authorized Users can access the templates here. You can of course change this in the future. And now let's go ahead and let's add zValidator here. And zValidator will validate the query.
So that's gonna be z.object, page, z.curse, number and the same thing is gonna be for limit. So we're gonna have the same pagination as we had for our projects but we're also going to have it for templates right because there can be a lot of them and then simply add an asynchronous controller here so inside what we're going to do now is the following Let's go ahead and let's destructure page and limit from CRequestValidQuery. We don't have to do any explicit Auth check because the middleware here is enough The reason we do it inside of here is because this can be undefined So we have to solve that typescript error with an early return. Otherwise, this would cause an error But in here, we are not exactly using the auth ID for anything, right? We are just gonna load all templates.
We are not gonna load specific user templates. We are loading all templates. So we have the page and the limit here. So all we have to do now is write const data await database select from projects where equals projects is template is true. We're going to add a limit here.
We're gonna add an offset. Using our page variable, so page minus one. Make sure you add this in the parenthesis and then multiply it by the limit. And let's order by the following. Ascending.
So make sure you import ascending from DrizzleRM. So ascending will be projects is premium. Is pro like this. So we want to show the user pro features first. And where is my ascending?
Whoops. Right, so we kind of want to upsell free users by showing them premium templates first so that they have to upgrade if they want those. You can of course not do that but I'm just showing you how you can do that. You prioritize the premium features first. And then let's also add by descending projects updated ads so the newest are shown at the top.
Great! Now that you have that return C.JSON data. There we go. So now you have the templates API. Now let's create a use query for that but we're not going to use the infinite query because in here I just want to load four of them so for that I think it's better to use something simpler for example let's go ahead and let's call use get images and let's copy that and paste it inside of projects API and rename it to use get templates.
Go inside of use get templates and rename it to use get templates here and then inside of here we're gonna do the following. Let's define a type, which will be our request type. And that will be infer request type from Hono, client, sorry, type of client.API projects, templates.get. And a 200, right? So we want the successful result, but do we even need the 200?
Let me just check this. Right, so I don't think we need anything. My apologies, we need to define the query. I completely messed up. This is what we wanted to do.
Okay, so now we have the request type here, great. And now let's go ahead and define our API query parameter here to be a type of request type like this and then we can modify the query key and also change it to templates and in here let's define the page to be apiQuery.page and the limit to be apiQuery.limit. We're gonna see later, perhaps it is a better idea for us to use infinite query here, but this is how I did it originally. So we can easily come back to this and modify this to infinite query if this seems too tedious to maintain. And now let's also...
I think we don't need to do anything else at the moment. So let's just leave it like this and let's make sure that inside of here we do the following. So client API projects templates dot get and inside of here make sure you pass the query which is API query like this and this will be fail to fetch templates like this there we go And now let's go ahead and let's actually use that. So for that I want to go inside of source, app folder, dashboard, page.tsx and above the project section let's add templates section like this and let's create it so templates-section.tsx like this let's mark this as use client let's export const templates section And let's simply return a div templates. Let's go back to the page.tsx and import the templates section.
And now in here you should see the text which says templates. Great. And now let's go ahead and do the following. Let's add our h3 element here which will say start from a template and inside of here we can add a class name of font medium and text excel or LG. Let me go ahead and check what we used in the projects section here.
We used font semi bold and text LG. All right. So now let's go ahead and wrap this inside of a div and let's define a grid here. So we're gonna write grid, grid columns 2, md grid columns 4, margin top of 4, and gap of 4. Like this.
And now we have to fill in the data for the templates so let's go ahead and let's use our use get templates from features projects API use get templates and let's add in the query so the page will be 1 and the limit will be 4 like this, so we are only loading the 4 from the first page and then from here we can extract data isLoading and isError so let's just collapse these there we go let's go ahead and check if it's loading in case it's loading we're gonna return a div with a loader inside as we did many times so far so let's go ahead and do it like this let's copy this Let's add this a class name space Y4 and inside of here let's add a div with a loader from Lucid React so just make sure you've added an import for the loader. Let's give this a class name of FlexItemsCenter. JustifyCenter and a height of 32. Let's give the loader itself a class name of Size6TextMutedForeground and animate spin. And I think that should be it if we're loading.
There we go. You can see how now we're loading those templates. Perfect. And Now let me just copy and paste this for the isError case. So if isError, let's go ahead and add our triangle alert icon from Lucid React.
And we're gonna remove the animate spin class for this. We're gonna give this a flex call and a gap y4 and below that we're going to add a paragraph which will simply say failed to load templates like this and then if we don't have data at all so if you don't have data or if the length is not good we're just not going to render anything at all. Great! So now let's go ahead and let's render data dot map. We get the template and inside of here, we're gonna render a template card like this.
So now we have to create the template card. So let's go ahead and do that in the same folder, template card, DSX, and let's create an interface, template card props. So the template card props will accept an image source, which will be a required string, a title, an onclick, which accepts the title and returns a void, disabled which will be an optional Boolean, description which will be a string, width and height, which are both a required number, and isPremium, which will be Boolean or null. Let's just not misspell a boolean. And now let's export const template card.
Let's go ahead and assign the template card props. And now we can extract the image source, the title, the on click, the disabled, the description, the height, the width, and isPremium. I think we've destructured all of them. Great! And now let's go ahead and let's return a native button element.
We're gonna go ahead and give it an onClick of an empty function for now. Let's go ahead and give it a disabled of disabled. Let's give it a class name, which will be dynamic. So we're gonna use our CNUtil. So make sure you've added that, CN.
First, let's define some default classes, so space Y2, group. Let me scroll up so we can see better. Text left, transition, flex and flex call. And then if it is disabled we're gonna add cursor not allowed and opacity of 75. Otherwise cursor will be pointer.
Now inside of the button let's add a div with a class name which will be the following relative, rounded, extra large aspect, three halves, full height, full width, overflow, hidden, and border. And then inside, we're going to render an image from next image so make sure you've added the import for that. Now let's go ahead and let's give this image a property of fill, a source of image source, an alt of title. And now before we move forward let's go inside of the template section and let's actually import the template card so that we can start seeing what we are actually developing. So let's go ahead and give the template card here all the props it needs.
So the key will be template ID, then we're going to have the title, which will be template.name. Then we're going to add an image source which will be template thumbnail URL And let's fix this typescript error by adding a pipe pipe or an empty string on click here will be an empty method for now. Disabled here will be mutation which we don't yet have so let's go ahead and add false. Description will simply render in a string templateWidth times templateHeight and then the units which are pixels like this and then we're gonna add the width to be template width and the same thing will be for height. And then we're going to have is premium which will be template is pro.
Let's actually rename this to is pro in that case. So let's go inside of the template card is pro. There we go. Great. So we now have, as you can see our image here which we've uploaded and set as the thumbnail URL.
So we can see that here now. So let's now go ahead and style the template card further. So instead of using aspect three halves here, here's what we can do instead. We can leverage the height and the width to create the proper aspect ratio. So let's go ahead and use the style attribute for that because it will not work otherwise due to the just-in-time compiler that Tailwind uses.
So style will have an aspect ratio which will simply be our width divided by our height like this and there we go You can see how perfectly it fits now. Great. So now let's go ahead and give this image a class name of following. So it's going to have an object cover transition transform and on group hover it's going to have a scale of 105 so when we hover over this it kind of zooms in a bit. Great.
And now let's go ahead and let me see. So just below this image, we're gonna add to do, oh, my apologies we can actually do this already so let's add the is pro like this if that is the case we're gonna render a div which will render a crown from Lucid React right here Let's go ahead and go inside of our Drizzle Studio here and let's enable the isPro to be true and let's save it so that we can see the changes. So let's mark this as is pro and let me refresh this. And now we can develop this properly. So I'm going to go ahead and give this crown a class name of size 5 and I'm going to give it a fill yellow 500 and text yellow 500 I'm also gonna go ahead and give the div here a class name of absolute top to right to height of 10 width of 10 flex items center justify center background black with 50% opacity rounded full and the Z index of 10.
So kind of like this. There we go. And now you can see how we have the cool little crown in the corner indicating that this is a premium template. Excellent. And now below this is Pro let's add a div which will simply render a text open in editor and this div will this paragraph will have a class name text white and font medium.
And let's go ahead and give the div the following class name. So this will have an opacity zero on group hover is going to have an opacity 100. Transition absolute inset zero. So it's centered, background black with 50% opacity, flex, items center, justify center, rounded extra large and let me fix the typo in justify center here rounded extra large backdrop filter and backdrop blur SM so now when you hover over well looks like we got something here wrong. Let's see what it is.
Do we have flex defined? We do. But it looks like our text is simply not getting centered. It is because I still have a typo in my justify. So this is justify.
There we go. So now it says open in editor. So also make sure since we're using absolute absolute here, that means this needs to have relative so just make sure you have that. Great! So now we have this amazing effect here and there's one last thing that I just want to add here.
So that will simply be right before our button ends open up a new div with a class name space y1 and inside of here we're gonna go ahead and enter a paragraph and the title of our team of our template right we're gonna give it a text small and font medium and then below that we're gonna open another paragraph where we are going to render the description and inside of here we're gonna give it a class name of text extra small text muted foreground opacity 0 by default but when we hover on the group opacity will become 75 and transition. And now let's try it out. So when I hover there we go you can see how nice of an effect we have here so even when we keep the hover below we have all the effects here. Great! And now what we have to do is we actually have to open the template when we click on this.
Right? And it should create a new project. So let's go ahead and do that now. So we can simply pass in the onClick now so I have not used it so far but we can now pass in the onClick here and we don't have to accept the title right so we can just do an on click like this and then inside of the template section here we're going to modify this on click and this on click will call another on click method. Make sure you just add it's an error because we it doesn't exist yet but we're going to create it in a second so let's go ahead and pass in the entire template here like this and then let's go ahead and create the onClick method here so const onClick like this and then this onClick accepts the template and the template if you want to quickly fix the error you can use any but we can also go inside of the use get templates and we can create a proper type here so export type response type will be infer response type from Hono type of client API projects templates get and 200 there we go So now we have the response type here.
So you can go ahead and import the response type from use get templates. So make sure you choose this as the import because we have a couple of same named ones or yeah you can simply add to your existing import where your use get templates are and then inside of the template section here for the template you can define response type data first in the array that's the type that will get accepted here and as you can see my onclick has no errors with that because it matches exactly what I'm passing here so I'm gonna go ahead and add a to-do check if template is pro Right now there's nothing we can do if it's pro. And let's also add mutation use create project from features projects API use create project. And then after we check whether it's pro or not we're simply gonna call mutation.mutate like this in the first argument we're gonna pass in everything we need to create this so the name of this will be template.name project like this. So it's going to be car sale poster project.
And then we're going to have the JSON which will be template JSON, the width which will be template width and the same thing will be for height. There we go. And then on success here we're going to redirect to the newly created project. So for that we also need the router from useRouter from next navigation. So make sure you just add this import here.
And then inside of here we can structure the data. So this is the new project which is created so we're gonna have the ID so we can simply do router.push open backdex slash editor slash data ID. There we go. Let's go ahead and try this out so we have the on click now. The only thing we also have to do is disable this while mutation is pending.
Let's go ahead and try it out. So right now I only have one project here it's my car sale poster but if I click open in editor, there we go. I have Car Sale Project Poster, Car Sale Poster Project. So this is now my project. This is not a template, right?
This is simply a copy of a template. So if I take a look inside of my Drizzle Studio here and I refresh my projects, you can see that I have two projects, right? But only one of them has a thumbnail URL and is a template and is true. The other one is just a project I'm going to work on. So that's how we're going to handle templates.
Great! So if you want to you can now go ahead and add as many templates as you want inside of your project. We can later in additional chapters create a proper admin dashboard for handling this and inside of the source code you're gonna have the car sale JSON so what you can do is you can now click start creating, click on open and simply create for example travel JSON and you can go ahead and export PNG here and then upload the PNG inside of upload thing here and then instead of Drizzle Studio enable the is template to true and whatever you want to do with is pro. Just make sure you also add the thumbnail URL so it's nicely shown right here on the start from a template. So let me go ahead and do this with you one more time.
So I'm gonna go ahead and let me go ahead and refresh my projects here so I now have three I believe. So I'm gonna go ahead and call this one the travel poster. I will save this change. I will change it to his template to be true. I'm gonna go ahead inside of upload thing and I'm gonna add my Travel PNG which you can either use directly for my public folder or simply from the editor export the travel as a PNG and once this is uploaded you can easily now get the URL for that.
Just make sure it loads. Go inside of Drizzle Studio and change the thumbnail URL for that. There we go. Let's save this. I hope that everything else is saved as well.
Let me just refresh to double check. Travel poster URL. There we go. Let's try in here. Let's refresh.
And there we go. We can see how now I have two templates here. Perfect. Great. Amazing, amazing job.
So now you know how to make templates and what we have to do next is the following. So I want to be able to create a completely empty project, click on the design and select the same templates from here. So we just solved, you know, starting from a template from here. But what if you want to start from a completely empty project, you know, and then change your mind and want to click on the design and select the template from here. That's what we're gonna do in the next chapter.
Great, great job.