Let's go ahead and let's create a premium model. The premium model is going to open every time a user clicks on a premium feature while they don't have an active Stripe subscription. Let's go ahead and go inside of our terminal and let's add a package, Sustent. Once we've added that package, we can go ahead and run the app again and now let's go ahead and let's create a controller for our subscription model so I want to be able to open that model from everywhere. Right.
And let me just refresh my localhost. So this is running. So let's go inside of source. Let's go inside of features and let's create a new folder subscriptions. And inside of here, I'm going to go ahead and create a store folder.
And inside, I'm going to add use subscription model.ts Let's import create from 2Stand And now let's add a type SubscriptionModelState Let me just turn off my copilot here So it doesn't mess around with our code So isOpen will be a boolean onOpen will be a method and onClose will be a method as well And let me just fix the capital L which I have here and now let's export const useSubscriptionModel to use that create which we've imported and use the subscription model state type. Now inside of here you're going to get set from the prop of this arrow function and the arrow function will immediately return an object like this. Now inside of here let's add the isOpen to be false by default. OnOpen will be calling the set method and changing the isOpen to true and our onClose method will change the isOpen to false. There we go.
We now have a function which we are going to use to control whether the subscription model is opened or not. So now let's go ahead and do the following. Let's go inside of source, features, subscriptions. Let's go inside and create a new folder which we're going to call components. And inside of here go ahead and create a subscription model.dsx.
Like this. Let's go ahead and let's mark this as use client and let's import everything we need from dialog. So dialog, dialog title, footer, header, content and description from components UI dialog. And now let's export const subscription whoops model and let's go ahead and add a dialog here and let's go ahead and mark it as open by default let's add a dialog content a dialog header and let's just write hello Now this is not visible anywhere because we are not rendering the subscription model anywhere. So now let's go ahead and go inside of our source components and let's create models.dsx.
So inside of here we're gonna put all of our models which will be controlled with hooks like zustand hooks. So let's mark this as used client as well and let's import subscription model from features subscriptions components subscription model. And now let's export const models. And very simply inside of a fragment we're gonna render each of the model here. You're gonna see why we're doing this in a second.
Now that we have the models here, let's go ahead and let's render them anywhere. So we can for example use the providers. Let me go ahead or actually yeah let's keep this for providers and let's instead go inside of app layout and render them on the same level as Toaster so import models from components models like this and there we go now because models is being rendered here and model renders subscription model we now have this visible right here, you can see an open dialog and we can't even close it so what we're gonna do now is we're gonna enable controls for this dialog we're gonna decide when it's opened and when it's not opened and for that we're gonna use our hook So use subscription model from store which I'm going to change to slash features subscription subscriptions store and then use a subscription model. From here we're going to destructure isOpen and onClose and then we're going to change this dialog to be controlled by that. So isOpen and onOpenChange we'll call the onClose method.
There we go. So you can see how now this is not open by default. But if you go ahead and go inside of the new subscription model and change the is open to true then you can see how it is open. There is one issue with this though and that is that controlling this with zustand can cause errors between hydration from the server side and the client side. So that's why we put this inside of our models component and what we're gonna do here is a little trick to ensure that this model's component only runs on client-side.
So useClient does not mean that it's going to skip server-side rendering. UseClient simply means this is not a server component. But this will still handle server-side rendering and that can cause issues with hydration because our use models are controlled by this state here so let's go ahead and do the following let's go ahead and add isMounted, setIsMounted useState false Make sure you add this from React and also add useEffect. So the trick we're gonna do is simply call useEffect and then inside of here with an empty array, we're gonna call setIsMounted to be true and then if it is not mounted we're simply gonna return null. So the trick is that useEffect can only run on the client side So this will not be run on server-side rendering, which means during server-side rendering this Models component will not even exist.
This means it cannot cause those hydration errors regarding on how we control our models here. So it's hard to reproduce so it might be not making a lot of sense but if you don't do that you might have some weird errors here in the corner giving you a hydration mismatch. Great so now we have a way to open our model. So what we have to do now is go back inside of the used subscription model, change the isOpen to false and now instead of that let's actually go ahead and find a way to trigger this model. Well now that I think of it there's actually no reason we should do that right away, right?
Because we do have an option to keep it open and honestly it might be simpler to simply finish the model first. So my apologies, bring the back, bring isOpen back to true and make sure you can see your model. Now let's go ahead and let's actually style the model. So for the dialogue header, we're gonna give it a class name of flex-items-center and space Y4. Now inside of here, we're gonna add an image component.
You can import the image component from next slash image. I like to move my globals import at the top here. Now to the image component itself, we're going to give a source of slash logo dot svg, an alt of logo, a width of 36 and a height of 36 like this. Make sure that inside of your public folder of course you have the logo.svg or however you called your logo in the previous instances of this. Great.
Now below that let's go ahead and add a dialog title. Inside of here we're going to say upgrade to a paid plan. And let's give the dialog title a class name of text center. Now let's go ahead and let's copy and paste this and let's replace this with the dialog description. And inside of here, let's change the text to upgrade to a paid plan to unlock more features.
Now after the dialog header add a separator from components UI separator so just make sure you've added this as well and let me just separate the feature import here like that. Now inside of here, you're gonna open an unordered list with the class name, space Y2. And now we're gonna list some features. So I'm gonna go ahead and add a list element with the class name, flex-item-center and I'm going to use the check-circle-2 option from Lucid React. So I'm going to use this icon here.
Let me move this alongside the next image. Like this. We're going to go ahead and give the check-circle-2 class name Size 5, margin right 2, fill blue 500 and text white. So we have a nice little checkbox here. Below this check circle we're gonna go ahead and in a paragraph render a feature.
You can render anything you want for example unlimited projects whatever your premium features will be. Let's go ahead and add a small text and text muted foreground. And now we're just going to copy and paste these features as many times as you want. So for the second one, for example, let's add unlimited templates. For the third one, let's add AI background removal.
And for the fourth one, let's add AI image generation. Like that. And then outside of the unordered list add a DialogFooter component so when you're adding these components always make sure that all of them are coming from Components UI dialog. Make sure you don't have any imports coming from erratics. Now inside of the dialog footer, let's add a Button component from components UI button and let's give the dialog footer itself a class name of padding top 2, margin top of 4 and gap Y of 2 between the items inside and ensure that you have the components UI button added and go ahead and add an upgrade button here.
We're gonna go ahead and give it a class name of width full. We're gonna go ahead and prepare the onClick and we're also gonna prepare disable to be false so we can remember to make this modular later. There we go. So currently we have a nice little presentational model here and now we have to find a way to make it open every time we click on a specific feature. So we know finished how it looks, it's currently not functional but for example if you have a template and if it is a premium template, once user clicks on this, it should open the premium model.
So let's go ahead and try that out. So for that, let's go ahead and close everything here and let's go inside of source, features, subscriptions, and let's go ahead and create a new folder called hooks and inside of here create a use paywall hook.ts and inside of here let's import use subscription model from our store You can choose however you want to import it. Now let's go ahead and export const usePaywall and inside of here we're gonna get our subscription model controls. Are use subscription model. Let's define a mock should block constant which for now will always be true.
We are later going to change it via fetching from the API, right? And now let's return an object, which will return back whether we are loading, which will also be false to do fetch from react query. Go ahead and add a comma before the comment here. And then we're going to add the shouldBlock method. And then we're going to call a trigger paywall, which will very simply call the subscription model on open method like this so now make sure that inside of your subscriptions store use subscriptions model, your turn back is open to false so now when you refresh here nothing should happen, you should not have a model And now let's go ahead and let's find some of the features where we've mentioned that we want to open the paywall.
So for example, let's go inside of the app folder, inside of dashboard. And inside of here, I think either the template section or the template card I think it's the template section we've added to do on the on click where we create this from a template. Check if template is pro. So let's go ahead and let's prepare our paywall. So const paywall, use paywall.
From features, subscriptions, hooks, use paywall. I'm just gonna move that here. And now that we have the paywall we can go ahead and do the following so if template is pro and if paywall should block in that case return so make sure you do an early return here so it doesn't execute the rest of the method. We're gonna call paywall trigger paywall like this. So let's go ahead and try it out.
So if you don't have any pro templates you can either go to the database or add them, or you can simply remove this if check and just do this for example. So now no matter which one I click at, you can see how it opens a paid plan here. But the proper way would be to only check for template is pro. So right now I have two templates. One of them has a crown so it opens the paid plan and the other one simply creates a new project and I will now get redirected to that project in my editor.
So that's how we're going to use our newly created use paywall hook. You can also choose how you want to use it, right? So if you want to, you can also immediately destructure things from here. For example, the should block and trigger paywall. You can do that and then you're going to have some shorter code here, should block and trigger paywall like this.
So what I want to do now is I want to add this method to all of the places where we have written that we need to add it. So from the home page I'm pretty sure that's the only place where we have to do it. But in here, let's go ahead and let's limit the AI generation so it can only be done for premium users. So for that, let's use the AI sidebar component, which is located inside of source features, editor components, AI sidebar. Let's go ahead and let's add the import.
Use paywall from features subscriptions hooks use paywall. And inside of here, let's add the should block and trigger paywall from use paywall. And in the on submit here before we call the editor add image here, before we actually call the mutation, what we're gonna do is remove this to do block with paywall and we're gonna actually do it. So if shouldBlock which for now is always true. We do an early return and above that we call the triggerPaywall method like this.
So now if I go ahead and try and enter anything inside of this text box right here, There we go. It says upgrade to a paid plan. Perfect. Now that we've forbidden that let's also do one more thing. So let's add a random image.
Let's also block the remove a background feature. Right? So you can use this method to block any feature that you want to block on the front end side, right? So let's go ahead and do this again. I'm just going to copy this usePaywall.
Well, I will actually copy this, right? The instance of the hook. Let's go inside of the remove background sidebar and I'm gonna go ahead and add the import here. So import usePaywall from features, subscriptions, hooks, usePaywall. Then I'm gonna go ahead and add the shouldBlock and triggerPaywall from usePaywall here.
And there we go. On click, before we mutate and add the image we're going to do the same thing. If should block, return and before that trigger paywall. There we go. So let's go ahead and try it out.
When I click on remove background, it says upgrade to paid plan. Great. But if you go ahead and play around inside of this use paywall and change should block to be false, this would simulate a user who has a subscription. So for that user, as you can see, the feature is not blocked. That user can use the app freely.
Perfect. So just ensure that Once you change should block to false, you should be able to, well, use all the AI features or whatever you have protected from here. And I believe there is one more place where we have to change this. So let's change this back to true. And that is the templates sidebar right here.
So let's go inside of the template sidebar. There we go. I have the same to do here. So I'm going to go ahead and import use paywall from feature Subscriptions Use Paywall here. Let me copy the Should Block and the Trigger Paywall method.
I'm going to add them to my Template sidebar. And now inside of this On Click, which loads the template JSON, I'm going to remove the To Do and check again if template is pro and if should block, I'm doing an early return. And before that, I'm calling the trigger paywall. There we go. So Now if I click on this one, it just says, are you sure?
And I can replace. But if I click on this one, it says upgrade to a paid plan. And while we are here, I've noticed that this template doesn't have any indicator that it's premium. So let's go ahead and add that. So we can start by adding from Lucid React the crown icon.
And then let's go make sure you're inside of the template sidebar. And inside of the data iteration here, so data.data.map Inside of the button itself, make sure that you have the class name relative to the button and above the image here, my apologies, below the image, let's open a if template is pro. In that case, we're going to go ahead and render a div, which will render the crown like this. And now let's go ahead and style this. So the div itself will have an absolute position, a top 2 position.
Let me scroll up so we don't get the tooltip. Top 2, right to size of 8, items center, flex, justify center, background black with a 50% opacity and round it full. And the crown itself will have a class name of size 4, fill yellow 500 and text yellow 500. And there we go. Now as you can see this template has the crown, it indicates that it's premium whereas this one does not.
Great! So we finished the front end part of the subscriptions and now we have to actually add Stripe inside of here so we have the back end part of subscriptions and we can then finally enable this billing here.