All right, so now that we finished our activity, it's time to get to the last part of this tutorial, which is implementing board limits and a subscription. So right now we have this text which says that five are remaining, but it is hard-coded and it's not actually enforcing anything. So let's go ahead and change that first. I want to go back inside of my Prisma schema right here and let's go after our model audit log. Let's create a model organization limit, short org limit.
Let's give it an ID and a default value of UUID. Now let's go ahead and give it an org ID which is a string, Let's give it a count which is an integer and by default it's going to be zero and let's also ensure that this organization ID is unique for every organization limit we want to create and after that let's add the created at field which is going to be a date time and a default of now and we're also going to have the updated at with a date time and a default, sorry updated at and let me just align all of these things. Let's try like this. And now let's go ahead and let's push this into the database. So just make sure you have the ID, make sure you have the organization id which should be unique, make sure you have a count which is an integer with a default value of zero.
Let's go ahead and let's do the following. Npx, well first as always shut down the app, right, that's important and then let's do npx prisma generate so it's locally available and then npx prisma db push like this and I actually just want to clear out the entire database so let's do that as well npx prisma migrate reset like this and confirm that you want to reset the entire database because I want to enforce this count of our boards and after you do the reset you have to run db push again so I should have done that first my apologies but nevertheless let's go ahead and proceed with this. So now that we have the organization limit I want to go ahead and I want to create a constant called wards So let's go inside of our constants folder where we already have the images and let's create a new file boards.ds and let's export const max3boards to be 5. So from here you will be able to change it if you want for your application. Great and now I want to create a helper called organization limit from where we're going to fetch all the necessary information about how many boards there is left, how to increase a board and to check if we can even create a new one for free.
So go inside of the lib folder and create a new file org-limit.ts and let's go ahead and let's import the auth from clark-next.js. Let's go ahead and import the database and I'm just going to change the import to use add slash lib and let's go ahead and import max free boards from constants boards and first let's write a function so we can easily increment the available count so whenever we create a new board we have to run this function so export const increment available count that is going to be an asynchronous function let's go ahead and extract the organization ID from our auth helper. If we don't have the organization ID, we can just break the function. And let's write const organization limit to be await db.organizationlimit.findunique where we have the organization ID. And I actually think it will be better if we threw an error here.
So throw a new error, unauthorized. So we want to break the server action if we are not having any organization ID, right? We need to enforce this. And now that we fetched this organization limit, let's check if we have it at all. So if we have the organization limit, in that case, let's do a WaitDbOrganizationLimitUpdate where we have a matching organization ID and data is going to be count the current organization limit which we fetched just here, dot count plus one.
So we increase the current value by one. But if this is the first time they have created a board, then this object, this model, doesn't not exist. So we have to create it. Await db.orglimit.create data, pass in the matching organization id and count is fixed to 1 because it's the first board they created. Great!
And now we have to create the same thing but to decrease the amount so let's go ahead and copy this function like that and let's rename it to decrease available count and let's go ahead and this stays the same and then we check if we have the organization limit and then we update it and we could technically do just minus one but that could take us into a negative number So instead we're going to check if the current organization limit count is larger than zero then we're going to do organization limit.count minus one otherwise we're going to go back to zero. So this way we ensure that there's no way the new count can be lower than zero. Otherwise it's going to be a count of one and now let's go ahead and create a helper method to help us check whether the user can create a new free board so export const has available count is going to be an asynchronous function where we're going to fetch the organization ID again. If there is no organization ID, throw new error, unauthorized and then let's go ahead and let's fetch the organization limit to be await db organization limit find unique where we have a matching organization ID.
And then we're going to write if there is no organization limit, or if organization limit.count is smaller than max three boards. So if they created less than five boards we return true meaning that it's okay for them to create a new one else we're going to return false and let's go ahead and create the last helper which is just going to be a helper to actually tell us the number of three boards they've used up. So export const getAvailableCount is again an asynchronous function which again uses the organization ID using the out helper and this time if we don't have the organization ID we're just going to return 0 because this is going to be used in our UI helpers right. So let's go ahead and fetch const get organization limit to be await db organization limit find unique where we have a matching organization id like that. If we don't have the let's rename this to organization limit If we don't have the organization limit, we're going to return 0.
Otherwise, we're going to return the organization limit.count. Great, and now that we have this, we can go back inside of our board list so let's go inside of our board list component so that's inside of the app folder platform dashboard organization organization ID components and in here we have the board list. Great. And let's go ahead and let's do the following. So I want to import max3boards from the constants and I want to import getAvailableCount from add slash lib org limit like that.
And now what I'm going to do is after I do this fetching of the boards here I'm going to get the available count So const availableCount is going to be await getAvailableCount like that and then let's go all the way to the bottom here where We write 5 remaining and instead of writing that let's go ahead and let's do the following let's make this dynamic and let's write max 3 boards like that minus the available count remaining like that perfect so let's refresh our localhost and let's actually make sure that it is running first npm run dev So let's refresh this now and let's wait a second for this to reinitialize and it says that we have five remaining great so it's working because Right now what we are doing is we are calculating 5 minus 0 because take a look at our getAvailableCount. Well first it returns 0 if there is no organization ID but also if there was no organization limit found in the database it means that we haven't even created the first board for anything for us to calculate so we are reducing 5 by 0 which in turn is 5.
Now we have to add one of our helpers here which is going to be this one increment the available count inside of our server action where we create the board. So let's go inside, let me close this and let's go inside of actions create board right here and after we successfully create our board let's go ahead and write await incrementAvailableCount and make sure you import incrementAvailableCount from at slash lib org limit like that but I also want to import another thing from here which is hasAvailableCount so make sure you have the increment available count and hasAvailableCount from our newly created org.limit.util and now we're going to go ahead and use this hasAvailableCount right after we check for authentication let's go ahead and write const canCreate to be hasAvailableCount but let's also make sure to await this otherwise it's going to be a promise and if we cannot create meaning that we surpassed our available count we're going to return an error saying you have reached your limit of free boards please upgrade to create more great and let's try that out now so I believe it should already be working let's just confirm that we properly added this await increment available count we did great and let's try it out so I'm going to refresh this now I'm going to create my first board and I'm going to click create and once I'm redirected to the board and I go back there we go now it says four remaining let's try and fill this up now so I'm gonna create another one here let's go back now it says three remaining great let's create another one and let's go back to confirm two remaining great we are getting close to getting our error there we go one remaining and let's go ahead and let's get the last one here one remaining there we go zero remaining And now let's go ahead and attempt to create.
And there we go. We have an error. You have reached the limits of free boards. Please upgrade to create more. And what I want to create now is that if the user deletes one of their free boards we want to decrease the count right so let's go ahead and do that so the only thing we're going to need is that delete is that decrease available count so let's find the delete board action right here and let's go ahead and let's import decrease available count from lib org limit here and let's go all the way down here after we delete the board and await decrease available count.
And let's try that out now so let me just confirm and refresh here so it says 0 remaining and I'm going to remove this board so delete this board I will get redirected back and now again it says one remaining and if I try I should not be getting any errors and I am not. Great, so our frontend and backend is fully in sync and we can reverse that action. So now we found a way to block the user from creating more than five boards and now we have to find a way to enable them to create more if they add a Stripe subscription. So for that we have to head back inside of our Prisma schema. So let's close everything here.
Let's go inside of Prisma schema.prisma and let's create our last model organization subscription. So model org subscription for short is going to have an id which is a string and a type of ID with the default value of UUID. We're going to have an organization ID, which is also a string and unique for every subscription. And we're going to have the stripe customer ID which is going to be a string optional unique and we're also going to map it to a specific value so we can access it differently so give it a name stripe underscore customer underscore ID so exactly what we have here but in a different case like that. So let me just separate this so we can see them more clearly.
And let me just move this here. All right. Besides this, we're going to have the StripeSubscriptionId, which is also an optional string. It's also unique and let's map the name to be Stripe underscore subscription underscore id. And now let's go ahead and create a StripePriceId, which is going to be an optional string as well.
And it's not going to be unique, but it's going to have a name of Stripe underscore Price underscore ID and last one is stripe current period and date time optional and let's map the name to be stripe underscore current underscore period underscore end like that perfect so make sure you have all of those of course you can always visit my github to see the exact code as you can see I made a typo here so it should be period So just double check that you don't have any typos you can always confirm with my source code. And now what we have to do is push this to the database as well. So let's go ahead and shut down our application and run npx prisma db push. Let's ensure that it's up to date with our PlanetScale or wherever you're hosting your MySQL. And then let's do npx prisma generate so we can locally access this.
Perfect. Now let's go ahead and let's run npm install stripe while we are here. Great! And now we don't have to run the app just yet because we're going to be creating a lot of libs now. So the first lib I want to create is the Stripe lib.
So let's go inside of lib and create a new file stripe.ts. Let's import stripe from stripe. Let's export const stripe to be new stripe and then we're going to give it a process.environment.stripe__api__key and put an exclamation point at the end. So we're gonna have to add this in a moment. And now let's add some options here.
So API version, it can be different if you're watching this in the future, but you can just let TypeScript auto-complete it for you. So you can see for me, it's 2023.10.16. For you, it might be something different. For example, my previous tutorials don't use this exact version, right? But it will always be matching to your package, which you install.
So if you have the newest package, which we just did in a second, when you do this, you can see that you have an auto-complete like this. And I'm going to show you another way to confirm which is the newest version. When, once we get to the Stripe dashboard. So don't just, don't worry For now, you can just write it like this. If you have an error, I believe if you type something else, you can see that it has an error.
So if you wrote everything correctly, you're going to have no errors. And let's write TypeScript true. Great. And now what I want to create is I want to create a lib to check our subscription right to confirm that we have an active subscription so let's go ahead inside the lib folder and create a new file check actually let's just call it subscription.ts and in here let's go ahead and let's import the out from clerk nextjs let's import the database and I'm gonna change this to slash lib and let's write a constant const day in milliseconds is gonna be 8640000 like that and I close that my apologies all right and now let's export const check subscription to be an asynchronous function. And let's extract the organization ID from our out service if there is no organization ID we're just gonna say false meaning we don't have any subscription here so we are not ever gonna say true optimistically right So if we are not able to fetch the organization ID, we're just gonna say, okay, no subscription for you.
And now let's attempt to fetch the subscription. So const org subscription is await db org subscription dot find unique where we have a matching organization id and let's select stripe subscription id to be true, stripe current period and end to be true, stripe customer ID to be true and stripe price ID to be true as well. And now let's write if we don't have the subscription return false. Otherwise let's go ahead and check if it's valid. So just because we have it, that's not enough.
We need to check if it's not expired. So const is valid, is going to check if organization subscription dot stripe price ID exists. And if organization subscription dot stripe current period end question mark dot get time and let's just add an exclamation point at the end here so we're gonna compare the time of the period end plus one extra day, right? And it's larger than date.now. And let me just zoom this out so you can see it in one line.
So stripe current period end, we get the time and we add a buffer of one day, right? And we compare if even that is larger than the current date, then it means that it is valid. Great! So just ensure that you wrote that and of course we have to return is valid and let's turn it into a Boolean by adding double exclamation points at the end. So please don't make one, right?
Make sure you put double. This will turn it into a for sure a Boolean. Great! So we have this done and now we have to create our Stripe API key and our webhook and our actions to actually trigger the checkout. So let's go ahead and let's So we just wrapped up this subscription right is valid.
Now let's go ahead and let's head on stripe.com and go ahead and just log in and you should be seeing this dashboard. And what I want you to do is to create a new well store right so you can either use this top left corner and scroll all the way down to where it says new account or if this is your first time using Stripe I think this will be open for you by default. So let's call this Trello tutorial and let's click create an account right here and now we will be able to copy our API key so click on developers right here in the navbar and here we have the API keys tab and in here click reveal the secret key and click again to copy that secret key and now we have to put that right here in our Stripe API key so let's go inside of our .environment file and let's add stripe underscore API underscore key and paste the key inside and double check that your stripe API key matches exactly what you've written in the environment file so now what I want to create is the user interface for an actual you know a popover model which tells the user all right you need to upgrade now so let's go ahead and do that so I'm gonna go inside of my hooks and I will copy the use card model and I'm gonna paste it here and I will rename it to use Pro Model.
And now let's rename this to Pro Model in all instances. We can remove the ID from the store, we can remove the ID from the props here in the on open, we can remove it here as well and change this from setting any ID so both the on close and on open and also remove the ID from here. Great. And now Let's go ahead and let's create the actual Pro Model component. So I'm going to go inside of components, models and just create a new file pro-model.tsx.
Let's go ahead and let's mark it as useClient. Let's export const ProModel here and let's go ahead and start styling this. So I'm going to create a dialog component from ./.UIDialog and I'm immediately going to add the ProModel from useProModel from our hooks right here. And let's go ahead and let's give it a property open on ProModel is open and let's give it on open change to be ProModel on close. Great and inside let's render the dialog content from ./.UI dialog again and let me just change this to use add slash components just ensure you're not accidentally using the Radix version and inside of here let's give this a class name of MaxVMD padding 0 and overflow hidden And now what I want you to do is I want you to add a little image which you can find well anywhere really but you can go inside of my github and just find the hero.svg.
So let's go ahead and download this image and let's place it inside of our public folder. So let me show you, I prepared my public folder here and I will just drag and drop that inside of the public folder. Great. So let me just close this and let's go back inside of our ProModel here and let's go ahead and add a div here with a class name of AspectVideoRelativeFlexItemCenter and JustifyCenter And let's go ahead and render the image from next slash image. So make sure you add this import as well.
And let's go ahead and give it a source of slash hero.svg which we just added. An alt of hero, class name of object-cover and a fill property. Like that. Perfect. And now I think it's time for us to actually see this model.
So let's do the following. Let's go inside of our providers. So in the components we have the providers and we have the model provider and just below the card model add a pro model which you can import from dot dot slash models pro model or slash components pro model and I quickly want to go back to our use pro model hook and just give it the default value of true for is open and make sure that you have the app running and that should be enough for you to finally see the actual Pro model. Let's just wait a second, see if that is true. And there we go, you should be seeing, it should be open by default.
And I think, yeah, so every time you can close it and just refresh and it should be visible again. So it's visible because in the hook use pro model we added it to be is open true by default and we added it to model providers right here so make sure you do that and also in the actual model itself make sure you're using the Pro model here and Assigned is open here. Great, so now let's continue developing on this one so outside of this div let's go ahead and give it a class name TextNeutral700 mxAuto space y6 and padding of 6 like that let's go ahead and write an h2 element upgrade to taskify pro today like that and let's give this h2 a class name of font semi bold and text Excel outside of the h2 element let's create a paragraph which will say explore the best of Taskify and give it a class name of TextExtraSmall FontSemiBold and TextNeutral600 and outside of this paragraph let's create a div with a class name of PL-3 like this and let's create an unordered list with a class name of TextSM and list disk and inside create a list element which is going to say unlimited boards.
Let's also write advanced checklists. So basically, you know, just some promotional text. Admin and security, features and more. Right, so we know that it's actually only unlimited boards but after you finish this part of the tutorial you're going to know how to limit anything else you want in this application. Great!
And now outside of this div I want to go ahead and add a button component so make sure you import the button component from ./.ui-button here components and let's go ahead and let's write upgrade here and give it a class name of WFull and give it a variant of primary like that. There we go. So now we have our nice little model here. And now let's just change its UseProModel here to be closed by default. And now let's go inside of our components form, form popover here.
And let's go ahead and add that model. So const ProModel here, use ProModel. So make sure you import useProModel from hooks useProModel and then let's go ahead and call it if we have an error inside of this createBoard function. So ProModel on Open. And now if I try and create a new board, so let me just refresh and if I try and create a new board, there we go, you can see that I have a pop-up.
Great. Now let's go ahead and create an action that when we click on this upgrade button we actually get redirected to Stripe checkout. So I want to begin by going quickly inside of my lib utils where I have my CN function and let's add another reusable util called export function absolute URL which takes in the path which is the string and it returns a combination of process.environment.next public app url and combines the path. So we're gonna use this as our Stripe checkout redirect path so that's why we need it to be exactly where our app is hosted so now make sure you copy this environment variable go inside of .environment and add it so HTTP localhost 3000 Just make sure you don't put an extra slash at the end. So it needs to be like this.
It doesn't matter what your port is or wherever you're running it. Just make sure it doesn't have the end slash. Great, and now let's go ahead and let's copy one of the existing server actions like copy card here and let's rename it to stripe redirect would be good. So stripe redirect. Let's go inside of Stripe redirect and let's resolve the schema first.
So we're gonna call it Stripe redirect here and let's go ahead and change this to be just an empty object because we don't need to pass anything. And now go to types.ds and change this to Stripe redirect as well and modify it here and the return type is just going to be a string. So we don't need this Prisma client at all. And now let's go inside of the index here and let's import this StripeRedirect from schema. Let's go all the way down and let's put it here and let's change this function's name to be stripe redirect.
Stripe redirect, lowercase like that. Great and now let's go ahead and let's actually do this. So I'm gonna go ahead actually and how about I remove this entire try and catch block. So all the way from here. I don't even want this.
Make sure you only have the authorization here. Now let's get our settings URL. So that's going to be our absolute URL from libutils which we just created. I've added it just here. And let's go ahead and pass in the backticks organization slash the current organization ID.
Now let's write let the URL, which we expect the return is just gonna be an empty string. And now let's open our try and catch block here. Let's go inside of the try and let's write const organization subscription to be await DB organization subscription find unique where we have the organization ID And now let's check if we have the organization subscription and if organization subscription has Stripe customer ID let's go ahead and let's create a Stripe session so we're gonna do that using const Stripe session to be await Stripe dot billing portal oh let's also import Stripe from add slash lib Stripe as I did here so if we already have a subscription we're going to open the billing portal sessions create and passing the customer to be org subscription dot stripe customer ID and the return URL is our settings URL And now let's just return the URL to be the StripeSession.URL. So that's if we already have a subscription. And now let's write an else clause.
And in here let's go ahead and create a new Stripe session so const StripeSession is going to be await Stripe.checkout.sessions.create success URL is going to be the settings URL cancel URL is going to be the settings URL as well, payment method types is just going to be card, you can of course modify this however you want so I'm just giving you my options here. Mode is going to be subscription. We're going to have a billing address to be auto. The customer email is going to be user email. Do we have the user?
We don't have the user. So let's go ahead up here and let's get the user. Await current user from clerk-next.js So just make sure you import current user from here. And let's also check here if we don't have the user in that case also let's throw an error and now we can go back here and let's go ahead and get the user email addresses the first one in the array dot email address let's run lite items here Let's write price data, the currency is US dollars, product data has a name of Taskify Pro and description is unlimited words for your organization. And now let's go ahead and give this product a unit amount of 2000 which represents 20 recurring on an interval of month so monthly interval and let's also give it a quantity of one right here perfect and then outside of this array extremely important to add the metadata where we're going to pass the organization id like that perfect and now let's go ahead inside of this if clause and let's write url is equal to stripe session dot url or an empty string like that perfect in the catch let's go ahead and let's just return error something went wrong great and now let's go ahead and let's revalidate the path slash organization organization ID which we have and let's just return data to be the URL so either the billing portal or the new checkout.
Great. Now let's go back inside of our Pro model, so inside of Components, Models, Pro model right here and let's go ahead and let's add execute and isLoading from useAction, from hooks useAction let's give it a StripeRedirect from actions StripeRedirect right here and let's go ahead and write on success get the data and now we have to redirect the user so window.location.href is now the data which is going to be our URL and onError is going to have an error and it's going to call toast from sonar so import that dot error and pass in the error like that perfect And now let's go ahead and let's write const onClick to be an empty arrow function which calls the execute option like this with empty object inside. And now let's go ahead and let's give this button a disabled prop if it's loading and on click to be on click and great let's try it out now so make sure you are getting an error here make sure you filled up your boards. Let's click upgrade and we should be redirected to the Stripe checkout page and we are! Great, great job!
But we cannot pay just yet. If you try, it's not going to work. So I just wanted to confirm that we get redirected to the Stripe checkout and now we have to create the API webhook So let's go inside of our API folder So we have to do this inside of the API folder because this is not something we are going to call but something that Stripe is going to call so we need to provide them with an API endpoint so let's create an API endpoint called webhook like this and in the webhook create a new file route.ts let's go ahead and import Stripe from Stripe Let's go ahead and import headers from Next headers. Let's go ahead and import Next response from Next slash server and now let's import the database and finally let's import our stripe util from libstripe now let's write export asynchronous function post, which has a request, which is a type of request. Let's get the body, which is await request.txt.
Let's get the signature. Since this is gonna be called by the Stripe, well, dashboard or SDK, we have to confirm that it is them that's calling that. So let's use the headers, get stripe-signature as string. Basically, we want to protect this endpoint from Power Play. Let's define the event here.
And now let's try and get the event using the StripeWebHooks.constructEvent in the combination of our body, the signature which we have and the process dot environment dot stripe underscore webhook underscore secret so we don't yet have this but we're gonna add it and let's just put an exclamation point here at the end. And then a catch here, which is going to say, return new next response webhook error with a status of 400. And if you want to, you can extract the error from here. Otherwise if we successfully created this event it means that this is then well we can continue and actually finish the user's check out. So this happens after the user checks out so after they enter their credit card and everything else so let's get the session from event.data.object as stripe.checkout.session if event.type is checkout.session.completed, then let's create a subscription await.stripe.subscriptions retrieve, so we retrieve using the session.subscription as a string.
If we don't have session.metadata.organizationId which we just passed inside of the StripeRedirectServer action that's why it said it's very important we are going to withdraw a new error so next response org id is required with a status of 400 because if we don't have that metadata we don't know for which organization to create a subscription and finally await the database org subscription create data org ID session metadata.org id stripe subscription id is subscription.id stripe customer id is going to be subscription.customer as string stripe price id is going to be subscription.items the first one in the array dot price dot id so let's just check if this is correct subscription.items.data then the first one in the array, like that. And stripeCurrentPeriodEnd is going to be a new date which takes in the subscription.currentPeriodEnd times 1000. Great, And we handled if the user creates a subscription for the first time and now we have to handle a different type of event. So let's go outside of this if clause and write if event.type is invoice payment succeeded that means that they have renewed their subscription so in here let's get a subscription again using await stripe subscriptions retrieve StripeSubscriptionsRetrieve SessionSubscription as string and then let's do await db.OrganizationSubscriptionUpdate where we have a matching Stripe subscription ID to be subscription.id and let's update the data to the new Stripe price ID which is subscription.items.data, the first one, dot price.id and Stripe current period and it will be updated to new date with subscription current period and times 1000.
Like that. Perfect, so we finished our Stripe webhook and very important at the end of the webhook you have to return new next response null with a status of 200. Great and now we have to find a way to keep this webhook running locally. So let's go ahead inside of the Stripe dashboard here and in here in the developers tab you have webhooks. Go ahead and click on test in a local environment.
You have to download the Stripe CLI so you can click here if you haven't done that already. Once you have that continue with the video and go inside of your terminal here and let's go ahead and open a new terminal and let's run stripe login and now go ahead and click on this link right here and click open and that will guide you to allow this for your computer so click allow access. Then you can go back inside of here and in a couple of seconds you should be seeing the completed sign right here. Perfect. So let me just clear this now.
And now we have to run this command stripe listened forward to. And let me show you exactly where we're going to listen. So stripe listen dash dash forward dash to localhost 3000 slash API slash webhook. Don't make a mistake and forget the API because it's gonna be weird to debug. I see this happen a lot of times.
So let me zoom out so you can see it in one line. Stripe listened forward to localhost 3000 slash API slash webhook and go ahead and press enter. And after you press enter, you're gonna be seeing your webhook signing secret right here so copy everything that is in bold text like this so make sure you copy this and then go ahead and we have to create this Stripe Webhook Secret API environment key. So let's go and do that. I'm going to go inside of my .environment here and I will add Stripe Webhook Secret and paste it here.
So please make sure that you copy that and confirm that your Stripe webhook secret matches. So Stripe webhook secret needs to be exactly the same and again confirm from your terminal. You must not shut this down. This must be open whenever you want to test out Stripe locally. Confirm that you copied from the start of the bold text to the end of the bold text and paste it here and confirm that you don't have any extra white space.
Great and now what we have to do is allow this API endpoint to be visitable without authentication because we're going to be using that signature event to confirm that it is the correct party. So let's go inside of our middleware file. We have a middleware.ts here and besides this being the public route we also need to add the API webhook so let's add slash API slash webhook here like that and let's try this out now so I'm gonna go ahead here and let's refresh this and let's do it like this. I want to make sure that you keep your terminal open so you can see the logs from here. Let's attempt to create a new one and now let's click upgrade here and now I'm gonna go ahead and just enter some fake information.
So basically if this is your first time working with Stripe you need to enter this exact credit card to get a success otherwise you're gonna be getting failure. So this doesn't matter, this doesn't matter, the name doesn't matter, but this is very important. It needs to be like this. You can find more examples in the documentation. And let's click pay and subscribe.
And let's see if we made any mistakes or if this is working as it should. And there we go. You can see I have a bunch of 200 events inside of my terminal here which means that it is working and let's go ahead and confirm that by I'm going to open a new terminal here and run npx prisma studio so I just want to confirm that I now have an organization subscription. So let me check that out and there we go I have an organization subscription model. Perfect I have the Stripe customer ID, price ID and current period end.
So we confirmed that this is definitely working. Now we have to modify this text here to say unlimited and we also need to check in our server action to allow the user to create new boards. So first let's visit our board list component. So I'm gonna close everything here and I'm going back inside of the app, platform, dashboard, organization, organization ID, components, board, list. And besides the available count let's check if is pro using await check subscription which we created when we started developing the Stripe lib.
So we have the check subscription here where we compare with the day buffer and if it is pro we're going to modify that text. So let's go all the way here and let's do the following. If it is Pro, we're going to say unlimited. Otherwise, we're going to do this check. And now when I save, there we go.
This now says unlimited. Perfect. And now we have to modify this. So let me just zoom out so you can see. So isPro unlimited, otherwise we do that calculation.
And now what we have to do is we have to modify our actions, create board right here. So inside of the create board, let's see where it is. So in here we have the can create and now let's add const is pro await check subscription which you can import from lib subscription. So if we cannot create and if we are not pro only then are we gonna throw an error otherwise we can do this freely and let me also do one more thing so in here I don't want to increase the available count if we are pro, so if is not pro like that so This will only matter if the user later decides to turn off their subscription so they're gonna have some weird values I believe. Let's try it out.
It says unlimited. Let's try it out. And there we go. It's working. Amazing, amazing job.
You finished up a Stripe subscription. Now we have to modify that to say pro here in the info component, as well as in this one here. And we also have to create the billing page. So let's go ahead and do that. So let's go and find our info component.
Let me close everything here and find the info component. You can find it in the platform dashboard, organization, organization ID component, so just alongside board list. And in here let's create an interface info props isProBoolean Let's go ahead and assign those props, so info props and let's extract is pro and now all we're gonna do is change this from being hard-coded to free to is pro is going to say pro otherwise free like that and now we have to find all the places where we are using the info component. So let's find info like this. So first one is in the organization ID page.
Let me show you where that is. So it is in the organization ID folder page.vsx right here and let's go ahead and let's get the isPro from await check subscription from s-lib subscription and pass in isPro here. Great and now we have to do the same thing inside of the activity page right here. So let's go ahead and do that. Let's turn this into an asynchronous function.
Let's import check subscription from Lib subscription and pass isPro as a prop here. Great, and I believe now, there we go. It says that we are pro here. If I go to activity, it says that we are pro here if I go to activity it says that we are pro here but if I go to another one it says that it is free and here I have five remaining but here I have unlimited because subscription is only per organization and now we have to create our last page Billing. So let's go back inside of the Organization ID folder and alongside Activity and Settings create a new folder called Billing.
Go ahead and create page.tsx here and let's go ahead and do const billing page and let's return a div with a class name of w-full. Let's make sure this is an asynchronous component. Let's check isPro using await check subscription. And now let's go ahead and let's render the info component from ./.components.info, pass in the isPro prop. Like that, let me just separate my imports.
Let's make sure we do export default billing page. And now you should be having this page available. So when I click on billing, there we go, it's available. If yours is still 404, confirm that your URL is slash billing. If it's not, find the nav item component and modify, let me show you, nav item component and in here you have the routes and your billing should be slash billing.
Make sure it doesn't have a typo and of course make sure that the page, the billing folder doesn't have a typo either and that it is inside of the organization ID folder. Great, now let's add the separator component from components UI separator. So make sure you have that as well. Let's give it a class name of my2. And now we have to create a subscription button component.
So let's just write subscription button component like this and it's going to accept the isPro argument like that. Let's go ahead inside the billing folder and create the underscore components folder and create a new file subscription-button.vsx. Let's go ahead and mark this as use client. Export const subscription button and return a div for now. Now go back to the billing page and you can import the subscription button from .slash components subscription button.
Let me just align my imports. Now let's modify the interface for the subscription button. So subscription button props is pro is going to be a boolean and let's go ahead and assign that. So subscriptionButtonProps and extract isPro from here. And now we're going to render dynamically depending on that.
So get the button component from components UI button and if is pro we're going to say manage subscription otherwise we're going to say upgrade to pro. And let's go ahead and give this a variant of primary like this and now let's go ahead and let's extract execute and is loading from use action from hooks use action and let's use the stripe redirect from actions stripe redirect because remember a stripe redirect can both handle the cases of initial upgrade and also the billing portal so let's give it a disabled prop of is loading and let's go ahead and write some callbacks here so on success we get the data and then with the data we have to handle window.location.href to be the new data if it is an error we have to handle the error inside of the toast from Sonar so just make sure you import the toast from Sonar and now let's go ahead and let's write const on click here via an arrow function which calls if isPro execute but otherwise it's gonna open the pro model so const pro model use pro model which you can import from hooks use pro model So let's go ahead and write else, Pro Model on Open.
And assign the onClick to the onClick here. Great, so let's try that out now. In here it says Manage Subscription. In the boards here in the settings or in the billing it says upgrade to pro. So if I click here I get to upgrade.
But if I try here it's actually not gonna work. We're gonna get an error. And let me show you why. It's very simple to fix. Inside of your terminal you have a text which says inside of your main project here you should be having a text, where is it?
Oh, we're not logging any error. Basically we have to enable the billing portal. So go inside of the dashboard, let's find some normal dashboard here and let's find the billing or let's search billing portal. Oh, so type in billing portal and go to settings, billing customer portal and in here you have to activate the test link so click on this button and now try this again manage subscription and as you can see no more errors and instead I'm redirected to manage my subscription. Amazing, amazing job you finished the entire tutorial.
Great, great job If we have time I'm going to show you how to deploy as well. If not, thank you so much for watching the tutorial.