LunaNotes

Download Subtitles for FastAPI React B2B SaaS Project Tutorial

FastAPI + React B2B SaaS Full Project Build - Orgs, Users, Billing, Roles & More...

FastAPI + React B2B SaaS Full Project Build - Orgs, Users, Billing, Roles & More...

Tech With Tim

3348 segments EN

SRT - Most compatible format for video players (VLC, media players, video editors)

VTT - Web Video Text Tracks for HTML5 video and browsers

TXT - Plain text with timestamps for easy reading and editing

Subtitle Preview

Scroll to view all subtitles

[00:00]

In this video, I'll give you a full tutorial on how to build a B2B

[00:04]

SaaS application that includes organizations, multiple team members,

[00:09]

billing, subscriptions, permissions, roles,

[00:12]

and everything you would need to create a SaaS app.

[00:16]

We're going to be doing this using Python specifically with fast API

[00:19]

for the back end, and then with react and JavaScript for the front end.

[00:24]

We're also going to be using Clarke for handling all of the complex billing and organization

[00:28]

related features, as well as the authentication like signing in, signing out, etc..

[00:33]

This will be a long project with a lot of coding, so for that reason I've included

[00:37]

all of the code that's written in this video linked in the description.

[00:40]

You can find the GitHub repository

[00:42]

in case you need to copy anything, or you prefer to follow along that way.

[00:46]

Also worth noting that this video is not designed for beginners.

[00:49]

Well, I will explain to you everything that I'm doing and the reason for the decisions that I have.

[00:54]

I'm not going to be going over the fundamentals of the frameworks or the language,

[00:57]

so I won't be teaching you fast API or teaching you react.

[01:01]

This is more about how you assemble all of the things you know into a large,

[01:04]

complex project that will act as a really solid base.

[01:08]

If you wanted to build something like a B2B SaaS.

[01:11]

Anyways, with that in mind, that's all I had to go through in the intro.

[01:14]

What I'm going to do now is give you a quick demo of the project, and then we'll get into coding it.

[01:19]

So let's get started.

[01:20]

All right.

[01:20]

So let's get started here with a quick demo of the finished application.

[01:24]

Now we're going to be building a team based task force.

[01:27]

So think of it something like notion or a sauna obviously with some simplified features.

[01:32]

So you can see I have a simple kind of landing page here.

[01:34]

And I have the ability to view the pricing, which requires me to sign in or to sign in or sign up.

[01:39]

So what I'll do now is I'll sign in.

[01:41]

You'll notice that what we're using to sign in to this is Clarke.

[01:44]

Now, Clarke has kindly sponsored this video, but they are completely free.

[01:48]

You do not need to pay to use them.

[01:49]

And they're the easiest way to set up authentication as well as things like organization, billing,

[01:55]

all of that stuff that you're going to see in this video,

[01:57]

which is precisely why I partnered with them and why we're going to use them.

[02:00]

So you can see it gives me a nice email address field. Here.

[02:02]

Let me sign in and then I'll be right back and show you what the app looks like.

[02:06]

Okay. So I was able to sign in here.

[02:07]

And you'll notice right away that the first thing I see is the current organization that I'm inside.

[02:12]

So I have this new org test.

[02:14]

I also have this hello world organization.

[02:17]

And I have the ability to create another new organization by simply pressing this button right

[02:21]

here.

[02:22]

Now, if I go to my dashboard, you'll notice that's going to start loading my different tasks.

[02:26]

Right now we don't have any shows.

[02:27]

Maybe we have two members in this organization.

[02:30]

If I want to make a new task, I can go like test, you know, this or

[02:33]

something, put it in the To-Do category and you can see that it's here.

[02:36]

And then if I want to change its category, I can simply just swap it.

[02:39]

And you see that it moves right over.

[02:41]

Now the interesting thing is that if I change over to my hello, world organization,

[02:45]

you'll notice that I now see some different tasks popping up in this board,

[02:48]

because the tasks that I have are defined per organization.

[02:52]

And what I can also do is click on the pricing.

[02:55]

You'll see that we have some pricing tiers here.

[02:57]

So we have a free tier. And then we have a pro tier.

[03:00]

And the way that I've set this up is that if you want to have more than two members

[03:03]

in your organization, you can use the pro tier, which I just put out $1 per month.

[03:08]

But you can set this to any price that you want.

[03:10]

And if you have less of that sub two or less members, then you can use the free tier.

[03:14]

Now the way that you manage the members is you go to manage with the inside of your organization.

[03:18]

You go to members

[03:19]

and you can see the various members that appear here, and you can set the roles for those members.

[03:23]

So for example, you can make them editor, a member and admin.

[03:27]

I'll talk about all of that in this video, but you can see that this really is kind of

[03:31]

like a B2B SaaS setup where you have billing, you have members, you have organizations,

[03:35]

you have subscriptions, right?

[03:36]

$1 per month.

[03:37]

We have kind of data that is shared between different teams.

[03:41]

We have different roles for members.

[03:43]

That's what I'm trying to show you in this video.

[03:45]

With that said, let's hop over to the code editor,

[03:48]

start writing some code and building this project.

[03:51]

So I'm inside my code editor here,

[03:52]

and what I've done is I've just opened a new folder called B2B SaaS on my desktop.

[03:57]

So open any folder in any code editor that you want.

[03:59]

And you'll notice that for this video I'm going to be using PyCharm.

[04:02]

Now like I said, you can use whatever you like.

[04:04]

But typically I recommend PyCharm for large Python based applications.

[04:08]

In this case a lot of the work is going to be in fast API.

[04:12]

PyCharm is specifically designed for working with frameworks like that gives us

[04:15]

really good typings as well as recommendations with a lot of great features,

[04:19]

so I would recommend using it.

[04:21]

Now, if you do want to use PyCharm and you want to access their Pro subscription,

[04:24]

try it for free and see if you like it.

[04:26]

You can do that from the link in the description.

[04:28]

I do actually have a long term partnership with them.

[04:30]

So again, if you click that link below,

[04:32]

you'll be able to test it out and use it completely for free and see if you like it.

[04:36]

Anyways, with that said, let's get into the setup here.

[04:38]

What I'm going to start doing is setting up two folders, one for my back end and one for my front.

[04:43]

So I'm going to open up my terminal here.

[04:45]

I'm going to go into my B2B SaaS application.

[04:47]

And I'm just going to make one folder here.

[04:49]

So let's go new folder.

[04:51]

Let's simply call that back end.

[04:53]

Then let's make another folder.

[04:55]

Actually we'll do that just from the command line.

[04:57]

That will be our front end.

[04:58]

So let's create our front end.

[04:59]

In order to do that you're going to need Node.js installed on your computer.

[05:02]

So make sure you have that installed because you're going to be using react.

[05:05]

And we're going to say npm create vite

[05:10]

at latest like that.

[05:12]

And then we're just going to simply put front it.

[05:15]

Now when we do that it should create a react application for us

[05:17]

if we select the correct framework.

[05:19]

So I'm going to select react here

[05:21]

I'm just going to use JavaScript I'm not going to use TypeScript for this particular project.

[05:25]

But you could if you want to.

[05:26]

I'm going to go know for roll down field.

[05:28]

And I'm going to say install with npm and start now.

[05:31]

Yes. And it's going to start installing and creating this front end folder for me.

[05:34]

So this is the setup for the front end.

[05:37]

Then we're going to set up the back end.

[05:38]

We're actually going to write the entire backend.

[05:40]

First we're going to understand the architecture of our application.

[05:44]

And then we're going to start building the front end the UI and kind of connecting

[05:47]

all of the pieces together. Okay. So you can see our front end is running.

[05:50]

If I click on it, you know,

[05:51]

this is kind of the default dummy front end that you get when you create the react application.

[05:56]

I'm just going to shut this down by hitting Ctrl C, I'm going to CD

[05:59]

into my back end directory and I'm going to start setting up the code that I did here.

[06:03]

So for this project I'm going to use UV.

[06:06]

So I'm going to type uv init and then dot create a new UV project inside of this back end directory.

[06:11]

You can see when I do that it creates five files.

[06:14]

Here we can delete this main file because we don't need this one for right now.

[06:18]

If you're unfamiliar with UV, I'll leave a video on screen

[06:20]

that explains how it works, but essentially it's a better version of Pip.

[06:23]

You can install it and then you can just use the UV commands like I am here.

[06:27]

All right.

[06:27]

So for our particular back end

[06:29]

project here, we're going to need a few different Python backend frameworks right.

[06:33]

Or Python modules.

[06:34]

So what we're going to do is type UV add.

[06:37]

And we're going to add the fast API module.

[06:39]

Because of course we're using fast API for our back end API we're going to install unicorn.

[06:45]

This is the web server that will run our backend API.

[06:48]

We're going to install SQL alchemy.

[06:50]

This is what's going to be used for connecting to the database.

[06:53]

Then we're going to use Python dot env.

[06:55]

This is for loading environment variable kind of configurations.

[06:59]

Then we're going to use pi JWT.

[07:01]

This is for verifying JWT tokens.

[07:04]

We're going to bring in the Clarke backend API.

[07:06]

This is the Python package for Clarke that allows us to use

[07:10]

Clarke on the backend for handling all of our authentication.

[07:13]

We'll talk more about that later.

[07:15]

And we're going to bring in SBX.

[07:17]

This is what we're going to use

[07:18]

to actually validate a webhook that we're going to get from Clarke later on.

[07:22]

But for now let's install all of this okay.

[07:24]

So you're going to see that that then creates the virtual environment for us.

[07:27]

Installs everything. We get this dot venv folder.

[07:30]

And now we're good to go and start setting up our packet.

[07:33]

So what I'm going to do to begin. So I'll make a new file.

[07:36]

And I'm going to call this dot env.

[07:38]

This is where we're going to put some environment variables that we need for the project.

[07:42]

Now for the env file I'm just going to put in the variables.

[07:46]

And then we'll grab the values that we need later.

[07:48]

So first things first we're going to type Clarke underscore secret underscore key.

[07:54]

We're going to type Clarke underscore publishable underscore key.

[07:58]

Then we're going to type Clarke under underscore sorry

[08:01]

UK s underscore URL.

[08:05]

Then we're going to have the database underscore URL which we can actually fill in right now.

[08:10]

And for the database we're going to use a local SQLite database.

[08:13]

But you could use anything that you want.

[08:15]

So I'm going to type SQLite like that.

[08:18]

And then colon three forward slashes dot slash.

[08:22]

And then task board dot db which is going to create

[08:25]

a database called Task Board for us where we store all of the information.

[08:29]

We then are going to have the front end underscore URL.

[08:32]

And we're going to make this equal to Http colon slash slash localhost.

[08:37]

And then port 5173, which is where our react application is going to run.

[08:41]

And we're going to have the Clarke underscore webhook underscore secret.

[08:47]

And we're actually going to put this up top because that is one of the Clarke values.

[08:52]

We might as well just keep them all together okay.

[08:54]

So we're going to look at all these values later on.

[08:56]

But like I mentioned for all of the authentication, all of the log in all of the organization billing

[09:01]

subscriptions, we're going to use a free framework or a free tool I guess called Clarke okay.

[09:07]

So that's why I'm putting in the variables here.

[09:08]

We're going to create that Clarke.

[09:10]

We're going to create that Clarke account.

[09:11]

And one minute after I start stubbing the API.

[09:14]

And then you'll see why we're using this and why we need this, these particular values.

[09:18]

Sorry okay.

[09:19]

So for our API what I want to start by doing is just creating the different directories and files

[09:24]

that we're going to need.

[09:25]

So we can start to kind of understand the architecture and how things are going to be set up.

[09:29]

Then after that, we'll start actually writing the code and going into the individual files.

[09:33]

So for now, inside of our back end folder, we're going to make a new file.

[09:37]

And we're going to call this start dot pi.

[09:40]

This is going to be the entry point for our application.

[09:42]

The file that we run to effectively run the app.

[09:45]

We're then going to make a folder here.

[09:47]

So a new directory called app.

[09:48]

And this is where all of the code for our application is going to live.

[09:52]

Now inside of app we're going to make a few new folders.

[09:54]

So we're going to have one folder.

[09:56]

And this is going to be called API.

[09:58]

This is for all of the API routes.

[10:01]

We're then going to have another folder.

[10:02]

This is going to be called core for the core functionality.

[10:06]

Essentially another new directory.

[10:08]

This is going to be called models for our database models.

[10:11]

And then another directory.

[10:13]

And this is going to be schemas.

[10:15]

Now the schemas are going to be used to essentially validate data.

[10:18]

Then inside of the app directory we're going to make a new file.

[10:22]

This file is going to be called main.py okay.

[10:25]

And then we'll just go ahead and press cancel there.

[10:27]

So what we have now is app.

[10:29]

We have four folders API core model schema.

[10:32]

We have our Main.py.

[10:34]

And now what we're going to do is start writing the different files.

[10:36]

And you're going to go inside of these folders.

[10:38]

Now I'm not going to write all of the code, but we're just going to write the file names

[10:41]

so that at least we know what we're going to have and how the app is kind of going to be structured.

[10:46]

So from API we're going to make a new Python file.

[10:49]

And this is going to be tasks.py

[10:53]

for handling all of the API routes related to our tasks.

[10:57]

We're then going to make another new file.

[10:59]

And this one is going to be called webhooks.py.

[11:02]

Later we'll discuss what a webhook is.

[11:03]

But essentially this is going to be for all of the endpoints to receive data from clerks.

[11:08]

So when some event happens, like we create a new organization or we subscribe to a particular

[11:13]

billing plan, we can get that information and handle it inside of our app.

[11:17]

We're then going to have core.

[11:18]

So inside of core we're going to make a few new files.

[11:21]

So here we're going to have a file called off API for handling authentication.

[11:26]

We're going to have another new file.

[11:27]

This is going to be called clerks Pi okay.

[11:30]

That's going to be a lot of files.

[11:31]

Just bear with me and then we'll start actually writing them out.

[11:34]

We're going to have another one called configure Pi for all of the settings for our application.

[11:38]

And we're going to have another one called database dot Pi for, you guessed it, handling the database.

[11:45]

Now we're going to go inside of models for our database models.

[11:48]

These are going to be pretty easy.

[11:49]

We're just going to have one called task dot pi.

[11:52]

And then we'll write the model directly inside of there.

[11:54]

And then for our schema same thing we're going to make a new file.

[11:57]

And this is going to be called task dot pi okay.

[12:01]

So quickly pause the video.

[12:02]

Have a look at all of the files that we've created.

[12:04]

Make sure that your app looks the same.

[12:06]

And this is just going to make your life easier.

[12:08]

As we continue that all of the files are already great.

[12:12]

Okay, so now that all of those files are created, we're going to start grabbing

[12:15]

some of the credentials that we need to store inside of this dot environment variable file.

[12:19]

Okay. So we're going to go to our browser.

[12:21]

And I'm going to quickly explain to you why we're using Clarke and how to create an account here.

[12:25]

So like I said Clark allows you to essentially manage all of the users

[12:29]

authentication organizations, billing, subscription, etc.

[12:33]

for your application so it has pre-built components like the ones you saw right here for things

[12:38]

like sign up, sign in, user button, user profile, all of the ones that you actually already saw

[12:42]

in the app that I download to you, things like switching the organization, you know,

[12:46]

creating the organization, looking at a profile of the organization.

[12:49]

So it just makes our life as a developer significantly easier.

[12:52]

And again, the best part is this is free and it's actually very widely used.

[12:56]

You probably seen these UI's in a lot of applications and large sources.

[13:01]

Okay.

[13:01]

So in order to use Clarke we need to do is just create a free account here.

[13:05]

I'll leave a link in the description where you can make that account.

[13:08]

Once you do that, simply go to your dashboard.

[13:11]

And we're going to create a new Clarke project.

[13:14]

So if you just go into your workspace you go here and you just go create application.

[13:19]

It will allow you to give the application a name.

[13:21]

So in this case I'm just going to call this, you know, be to be

[13:26]

task manager or something.

[13:28]

Or let's just go like SAS task manager maybe.

[13:31]

Okay.

[13:31]

And you'll notice that you can enable or disable a ton of different sign in methods.

[13:35]

So I'm just going to leave email for now.

[13:37]

But you could enable Google Sign-In Facebook Sign-In.

[13:40]

You can enable, you know, all of these sign ins at once.

[13:43]

Right.

[13:43]

And this is the benefit of using Clarke

[13:45]

is that if you ever want to change the authentication methods, you just directly do it from this nice UI.

[13:49]

You don't have to do a ton of setup. It's very easy to do it.

[13:52]

So you've been like you can sign in with notion okay.

[13:54]

So we're going to go with just email for now.

[13:57]

We're going to press on create Application.

[13:59]

And this is going to make a new Clarke app for us.

[14:02]

Now there's going to be a lot of instructions here that explain to you how to add this

[14:05]

to your front end.

[14:06]

We will do that later when we start setting up the front end.

[14:09]

But for now, what we're going to do is go to this configure button and start

[14:13]

grabbing some of the keys that we need for our environment variable file.

[14:17]

And you'll also notice that there's a ton of settings here that you can mess with and play with.

[14:21]

For now, we'll just leave it as it is okay.

[14:23]

So we're going to go down here to where it says API keys from the settings.

[14:27]

And we're going to grab a few of the keys that are here.

[14:29]

So first one is the publishable key otherwise known as the public key.

[14:33]

We're just going to copy that.

[14:34]

And we're going to put that into our environment variable file where it says publishable key.

[14:38]

So let's paste that.

[14:39]

The next one is the secret key.

[14:42]

So I'm just going to copy that.

[14:43]

And same thing on a paste that into the Clarke secret key variable.

[14:48]

We then need the WCS URL.

[14:50]

We're going to find that from the right hand side here where it says WCS URL.

[14:54]

Same thing.

[14:55]

We're going to copy that and just paste that right there.

[14:58]

And then lastly we have the Clarke webhook secret.

[15:00]

We're not going to have that right now.

[15:01]

So we'll just put an equal sign and we'll fill that value in later when we get to the webhook okay.

[15:06]

So now we have the values that we need in our environment.

[15:09]

Variable file. Again we'll go back to Clarke later on.

[15:12]

But for now we're going to start filling in some of the Python files that you see here.

[15:16]

So for now where we'll start is inside of this core folder we're going to go into config.py.

[15:22]

And we're going to start writing

[15:23]

the code to load in essentially all of the settings that we need for this app.

[15:27]

So we're going to start by saying import OS.

[15:29]

We're then going to say from dot env

[15:32]

import load underscore dot env.

[15:35]

And I'm just going to configure my interpreter here so we don't get those errors okay.

[15:38]

So now we're going to type load envy.

[15:41]

What this is going to do is load this dot environment variable file for us.

[15:44]

So we can access some of the values inside of it.

[15:46]

We're then going to create a class called config.

[15:49]

This is standard and fast API to store all of your settings and variables in one class.

[15:54]

And we're simply going to write a mapping from our environment variable files or variable.

[15:59]

Sorry two variables inside of the class.

[16:01]

So we're going to say Clarke underscore secret key.

[16:04]

We're going to say this is of type string is equal to OS dot get env.

[16:08]

And then this is going to be the Clarke secret key or an empty string.

[16:12]

If for some reason that doesn't exist.

[16:14]

Next we're going to get the Clarke publishable key.

[16:16]

So we're going to say Clarke if we can type this in capitals underscore publishable

[16:22]

okay.

[16:23]

Underscore key.

[16:24]

And then this is of type string and lowercase.

[16:27]

And this is going to be equal to Oscar get env.

[16:29]

And then the Clarke publishable key.

[16:31]

And again an empty string.

[16:33]

We then are going to have the Clarke underscore

[16:36]

webhook underscore secret.

[16:38]

And this is going to be a string which is equal to the Oscar get env.

[16:44]

And then the Clarke webhook secret and again empty string.

[16:47]

We then are going to have the Clarke underscore J wcs underscore URL.

[16:51]

Same thing string is equal to Oscar get env

[16:54]

same variable Clarke wcs underscore url.

[16:57]

We then are going to have the database

[17:00]

underscore URL which is a string.

[17:03]

Same thing equal to Oscar.

[17:05]

Get env database url.

[17:07]

We then have the front end URL.

[17:09]

So front end underscore URL string

[17:12]

is equal to a stock at env front end url.

[17:15]

And then we're going to have two variables that we're going to define ourselves.

[17:18]

The first is going to be the free tier underscore

[17:21]

membership underscore limit.

[17:24]

This time it's going to be an int which is equal to two.

[17:28]

And then we're going to have the pro underscore tier underscore membership underscore limit

[17:33]

which is going to be equal to zero which stands for unlimited.

[17:38]

Now what I'm doing is just putting two values here that we'll need to use later

[17:41]

to essentially define, okay, how many members can you have in a free plan?

[17:44]

How many can you have in a pro plan that is stored in here?

[17:47]

And we're not hardcoding it multiple places in our app.

[17:50]

We're then going to go and say settings

[17:52]

is equal to config like that,

[17:55]

so that we have access to all of the effectively the settings for our application.

[18:00]

Okay. So that is it for the config.py file.

[18:03]

Now we're going to go and just write the database connection.

[18:06]

So from database we're going to say from SQL alchemy import

[18:11]

create engine we're going to say from SQL alchemy.

[18:15]

And then this is going to be Jaume import

[18:18]

the session maker and the declarative base.

[18:22]

We're then going to say from app score config

[18:26]

we're going to import the settings like so okay

[18:31]

we're then going to continue and we're going to say engine is equal to create underscore engine.

[18:37]

And we're going to say settings dot.

[18:40]

And this is going to be the database underscore URL is going to be essentially

[18:45]

how we're creating the database from what the URL is we're using for that particular database.

[18:50]

And then we're going to say the connect underscore arguments is equal to a dictionary.

[18:54]

And we're going to say check same thread and then is equal to false.

[18:59]

I'm not going to explain this too much in depth.

[19:00]

We're just going to make sure that we're able to run the database successfully.

[19:04]

We don't have any problem with it running inside of the same thread or something else.

[19:08]

We're then going to say session local is equal to the session maker,

[19:12]

and we're going to say auto commit is equal to false.

[19:17]

We're going to say auto flush

[19:20]

is equal to false as well.

[19:22]

And we're going to bind this to our engine.

[19:25]

Now I know a lot of this will look a little bit confusing, but this is very standard

[19:28]

when working with fast API to be able to connect to a SQL database,

[19:33]

we're then going to say base is equal to declarative base.

[19:36]

And what we're going to do is say define get database.

[19:40]

And we're going to say database is equal to session local.

[19:43]

When we call this it's essentially going to bind to the database engine.

[19:47]

It's going to create the database if it doesn't already exist.

[19:50]

And if it does exists it's simply just going to yield it to us.

[19:53]

We're then going to say try and we're going to say like this yield.

[19:58]

And we got to spell yield correctly.

[20:00]

The database.

[20:02]

And we're going to say finally.

[20:04]

So why is this not working like this.

[20:07]

Finally we are going to say DB dot close

[20:12]

okay.

[20:12]

So this is all we need for the database.

[20:14]

What this is doing is essentially just creating a database session.

[20:17]

And then we have this function where when we want to access the database, we just call it

[20:21]

it yields the database to us.

[20:23]

And if there's any issues then it just closes the database and make sure that it saves successfully

[20:27]

okay.

[20:28]

So let's continue here.

[20:29]

And let's now go into the clerk.py file.

[20:32]

And let's write this to connect to clerk.

[20:34]

This is super straightforward.

[20:35]

What we're going to do is say from clerk underscore backend underscore API import clerk.

[20:41]

And we're then going to say from app score

[20:44]

dot config okay.

[20:48]

If we spelled that correctly import settings.

[20:51]

And then we're going to say clerk is equal to clerk.

[20:54]

And we're going to say the bearer underscore auth

[20:57]

is equal to settings dot clerk

[21:01]

okay.

[21:01]

Let me just spell this correctly.

[21:02]

Clerk underscore secret underscore key.

[21:05]

Now the reason why we need to set up some stuff related to clerk here in the back end is the

[21:10]

clerk is going to handle all of our authentication automatically for us in the front end.

[21:15]

Essentially we can just bring in a clerk component and it will allow the user to sign

[21:19]

in, sign out, handle the organizations, and it will just do all of these.

[21:23]

However, we only want authenticated users to be able to send requests to our Python backend

[21:29]

because our backend is going to handle things like creating the tasks,

[21:32]

setting where the tasks are maybe deleting a task.

[21:35]

So what we want to do is ensure that only users

[21:38]

that have the right permissions to handle those tasks are able to do so.

[21:43]

So what we're going to do is from our front end, we're going to include the clerk

[21:47]

user data to a request to our back end.

[21:50]

And our back end is going to verify that this users

[21:53]

who they say they are by using this clerk package.

[21:56]

So essentially we're going to go to clerk and we're going to say hey clerk,

[21:58]

is this actually one of the users who signed into our app?

[22:01]

Clerk is going to say yes or no.

[22:03]

And then based on that information,

[22:04]

we can kind of gate what that user is able to do in our backend application.

[22:08]

It'll make more sense later on.

[22:10]

But that's kind of the basics of why we're setting up clerk like we are.

[22:13]

So with that in mind, let's actually go back to clerk, and I'm going to start setting up

[22:17]

some of the things that we need in order to make this authentication work.

[22:20]

So from clerk we're going to go to organizations here.

[22:24]

And we're going to go to settings.

[22:25]

Now what we're going to do is enable organizations for our app.

[22:28]

We're going to turn off allow personal accounts and just enable this.

[22:32]

Now effectively what we're doing here when we enable organizations

[22:36]

is we're allowing users to create organizations and invite other members to them.

[22:41]

This is very common in something like a B2B software as a service where one person creates

[22:46]

the org, they invite different members and maybe you pay per member, right?

[22:49]

Or you have like a certain number of seats and you upgrade to another plan to unlock more seats.

[22:54]

Clarke allows you to do that.

[22:55]

And that's kind of what we're enabling right now.

[22:57]

So what we're going to do is we're going to set the limited membership to two

[23:01]

right now and make that saved.

[23:04]

The reason for that is that I want to limit any new organizations or three organizations

[23:09]

to just have two members and then force them to pay

[23:12]

to upgrade their organization in order to have unlimited seats.

[23:15]

Okay.

[23:16]

Now we can scroll down here.

[23:17]

There's a few other things that we can set.

[23:19]

For example, the default role for new members, the creator's

[23:22]

initial role in the organization allow members to delete organizations.

[23:26]

Right.

[23:26]

There's a bunch of stuff that we can do here, and for now, we'll kind of leave it as is.

[23:30]

We also could turn off, allow user created organization so that you had to manually

[23:35]

create an organization yourself as the app creator or admin in order for someone to join that org.

[23:41]

All right.

[23:41]

So next thing though, we're going to go to roles and permissions.

[23:44]

Within organizations.

[23:45]

You can have various different roles right view or editor admin.

[23:49]

You know content manager sales manager.

[23:51]

You've probably seen that before if you've used any kind of sets.

[23:54]

So notice that we have advent currently. Right.

[23:57]

We can just kind of describe what the admin can do.

[24:00]

We have member. Right.

[24:02]

And then we can create our own rule.

[24:04]

So for this particular app because we're going to have kind of this task board,

[24:07]

I want to create a new role here called editor.

[24:11]

So I'm going to give this role editor.

[24:13]

See the keys editor I'm going to say a user who can edit tasks.

[24:18]

Cool.

[24:19]

And I'm going to go ahead and I'm going to save that.

[24:21]

Now with this role we can just check if a user is admin, editor or member,

[24:25]

but we can even get more specific and we can add particular permissions per each role.

[24:31]

So what I can do here is go to features

[24:34]

from features I can press add feature.

[24:37]

This particular feature I'm going to call is task okay or let's call it tasks actually.

[24:42]

And I'm going to say task in the task board.

[24:47]

And what I'm going to do is

[24:48]

start setting some permissions that I want to be able to set for particular users.

[24:52]

So I'm going to go create permission for the first permission.

[24:54]

Let's call this create.

[24:57]

We're going to say can create tasks.

[25:00]

And we're going to go ahead and create that.

[25:02]

You can see this one permission has now been created.

[25:04]

Let's make another one.

[25:05]

We're going to call delete can delete tasks.

[25:09]

Let's make another one.

[25:10]

Let's call this view can view tasks okay.

[25:15]

And let's make another one and call it edit and say can edit tasks okay.

[25:20]

So now we have four permissions create delete, view and edit.

[25:24]

Those are within the feature of tasks.

[25:26]

We're going to go ahead and save that.

[25:28]

Go out of here.

[25:30]

And you'll see now that we have this tasks feature created.

[25:33]

So now if we go back to our organizations we get to roles and permissions.

[25:39]

We can go to the admin for example.

[25:41]

And you'll see the permissions are now appearing.

[25:43]

And we can enable all of them for the admin okay.

[25:47]

Let's go ahead and save that.

[25:48]

And let's go back to the editor.

[25:50]

Same thing for the editor.

[25:51]

Let's allow them to do all of this okay.

[25:54]

So save and for the member let's just allow them to view.

[25:58]

So they cannot edit, delete or create.

[26:00]

But they can view the tasks in the task board.

[26:03]

Cool.

[26:04]

So there you go. We just added a few different roles.

[26:07]

We set up the permissions here, and now we can start using these directly from our code.

[26:11]

Whenever someone signs in with their clerk account so we know what permissions

[26:15]

they actually have access to.

[26:16]

However, before we do that, I want to start setting up the billing and the subscription as well.

[26:20]

While we're on this page.

[26:22]

So I'm going to go to billing and I'm going to go to settings.

[26:25]

Now from here what I'm going to do is enable organization billing and press on save.

[26:29]

Now when I do that, it's going to give me a few other settings like allow me to connect my stripe account

[26:33]

or to directly use the clerk payment gateway.

[26:36]

Now if you were in production, this would become more important for now

[26:39]

because we're just testing in development, it doesn't really matter which one you use here,

[26:43]

and we'll be able to just test with kind of like a fake credit card, which we'll see in a second

[26:47]

when we talk about these kind of different plans and upgrading to pro and all of that kind of stuff.

[26:52]

Now, what you also see here is that it shows me plans.

[26:55]

So it says create plan.

[26:56]

I can also just go to subscription plans here and I can start creating various

[27:00]

different plans for organizations or for individual users.

[27:05]

So what I'm going to do for now is I'm going to create a new organization plan.

[27:08]

I'm going to call this pro tier like that,

[27:12]

and I'm just going to write a description and say on limited

[27:18]

seats in the organization.

[27:22]

Okay. Like that.

[27:24]

And then for the fee I'll just make it $1.

[27:26]

You can make it literally anything that you want.

[27:28]

You could enable a free trial, make it publicly available, add various features

[27:31]

to this particular plan.

[27:33]

For example, you could add, you know, the tasks feature.

[27:35]

So maybe you can actually unlock different features with different plans.

[27:40]

In my case, I'm not doing that.

[27:41]

But maybe we have some you know, I feature something

[27:44]

that you only get access to if you're in the Pro plant,

[27:47]

then you could make that a feature, enable it directly in Clark, and then have it.

[27:51]

Actually one of the things associated with this plan.

[27:54]

And your code can then check that and see if the user or the organization has that particular feature.

[27:59]

This is super powerful.

[28:00]

That's why I'm using it.

[28:01]

But you can see for now I just created this new plan.

[28:04]

Now we have a free plan sorry.

[28:05]

And the pro tier plan.

[28:07]

And later I'll show you how we can display that.

[28:09]

And notice it's actually kind of showing it to us already by using this pricing

[28:13]

table component in react and using this method and kind of protect you protect certain features

[28:19]

based on what plan the organization or the user has.

[28:22]

Okay.

[28:23]

So that's pretty much all I wanted to do.

[28:25]

For now, I will quickly mention to you that everything you see inside of Clarke,

[28:28]

you can customize, you can change all kinds of other stuff you can look at here.

[28:32]

For now, though, we'll leave this as is, and we're going to go back to our code and we're

[28:36]

going to start writing this off module, which now hopefully should make a little bit more sense

[28:41]

because we just enable those kind of permissions inside of Clarke.

[28:45]

All right.

[28:45]

So what we're going to do for off here is we're going to start by importing http x.

[28:50]

We're then going to say from fast API import depends

[28:55]

Http exception.

[28:58]

If we can spell exception correctly request

[29:03]

okay and

[29:04]

status, we're then going to say from the Clarke

[29:07]

back end API dot security

[29:10]

import the authentication request options.

[29:13]

We're then going to say from app score dot

[29:16]

config import settings.

[29:19]

And we're going to say from Abc4 dot Clarke

[29:23]

import Clarke okay.

[29:25]

All right. So now we have the imports that we need.

[29:28]

And in this particular file we're going to handle all of the authentication.

[29:31]

And essentially understanding if a particular user has

[29:34]

the ability to edit a task, delete a task etc..

[29:38]

Now effectively what's going to happen is when someone signs into our front end,

[29:42]

like I mentioned, they're going to be using Clarke from our front end.

[29:46]

We're going to include the user's Clarke account details, which includes a JWT token,

[29:52]

which is essentially their identity as the user in our app.

[29:55]

And it's going to be sent to our back end.

[29:57]

So front end is going to send request to the back end okay.

[30:01]

And this is going to include a JWT token from click.

[30:06]

Now what we're going to do on the back end is the back end is going to authenticate this token.

[30:11]

When we do that, we're going to get the user details

[30:15]

from Clarke and check the user's permissions okay.

[30:20]

So this is effectively what this file is going to do.

[30:22]

We're receiving a request from the front end that includes some details about the signed and user.

[30:27]

Our backend is going to authenticate.

[30:29]

These details are valid.

[30:30]

We're going to get the details from Clarke about this particular user.

[30:33]

And we're going to check their permissions to see okay.

[30:36]

Are they a member. Are they an editor. Are they an admin.

[30:38]

What do they actually have the ability to do.

[30:41]

So I'm going to make a class here called auth user.

[30:44]

Inside

[30:45]

of here I'm going to say define underscore underscore init.

[30:48]

And I'm going to take in self a user underscore id

[30:53]

which is a string, and org id which is also a string

[30:57]

and org underscore permissions like this which is a list.

[31:03]

Then what I'm going to do is say self that user id is user

[31:05]

id, self.org id is org i.d.

[31:09]

and self.org permissions

[31:11]

won't spell this correctly or org permissions.

[31:15]

Then I'm going to say define has underscore permission.

[31:19]

We're going to take in self and then a permission

[31:24]

which is a string.

[31:25]

And we're going to return a boolean.

[31:27]

We're going to say return

[31:30]

permission in self-talk permissions.

[31:33]

Now this is simply just going to check if this user has a particular

[31:37]

permission inside of this permissions list.

[31:40]

That's because whenever we check the user in Clarke,

[31:44]

it's going to give us a list of permissions that user has.

[31:47]

Those permissions are going to include the ones that we just created.

[31:50]

Like can they edit a task, can they delete a task, etc..

[31:53]

So we're going to check those essentially.

[31:55]

And if we have them then we're going to tell the back end or whatever function okay.

[31:59]

Yes they can actually do that particular task.

[32:02]

So now I'm going to create a few properties.

[32:05]

So I'm going to say app property define can underscore view

[32:10]

okay.

[32:10]

This is going to take in self.

[32:12]

It's going to return a boolean.

[32:14]

And we're going to return self. Dot has permission.

[32:17]

And then we're going to say org colon tasks colon view.

[32:23]

So the way that the permissions are set up inside of Clarke is that the particular permission

[32:27]

that we create it's like view create delete edit is associated with some feature.

[32:33]

In this case the feature is tasks.

[32:35]

And that feature is associated with an organization.

[32:38]

So we're saying org tasks view.

[32:40]

We're checking in this particular organization.

[32:42]

In the tasks feature do we have the view permission.

[32:46]

So I'm going to copy this now this method.

[32:49]

And we're going to write it for all of the different properties that we have.

[32:52]

So as well as can view we're going to have can create.

[32:55]

And we just change this to create.

[32:59]

Same thing for can

[33:01]

delete change this to delete.

[33:05]

And then what is the last one that we have.

[33:06]

So we have you create delete.

[33:07]

And then we're going to have edit.

[33:09]

And then simply change this to edit.

[33:11]

And that's it.

[33:12]

We now have these four different properties in this class.

[33:15]

And I can use this auth class to determine if a user has a particular permission okay.

[33:20]

So now we're going to write a few functions that essentially takes the data that are front

[33:24]

end sends to the backend

[33:25]

and sends it to Clarke to validate this data and make sure the users who they say they are.

[33:30]

So we're going to write a function called convert underscore

[33:33]

to underscore Http underscore request okay.

[33:38]

Now inside of here what we're going to do is have a fast API

[33:41]

underscore request which is of type request.

[33:45]

And this is going to return an Http dot request.

[33:50]

Now inside of this function I'm going to go return

[33:53]

http dot request.

[33:57]

And I'm going to have the method which is equal

[33:59]

to fast API request dot method.

[34:03]

I want to have the URL which is equal to fast API request URL.

[34:06]

And we're just going to convert this into a string.

[34:11]

And we're going to have headers which is equal to a dictionary.

[34:15]

And this is going to be the fast API request dot headers.

[34:19]

Now effectively all we're doing here is we're just taking the fast API request object.

[34:23]

And we're converting this into an Http request object.

[34:26]

Just a different request object that's going to be used directly with the clerk module.

[34:31]

Now we're going to have another function that's going to be async define.

[34:34]

And this is going to be get underscore current underscore user.

[34:38]

This is going to take in a request which is a fast API request.

[34:42]

And it's going to return an authenticated user okay.

[34:46]

And let's spell request correctly here.

[34:50]

Awesome.

[34:51]

So from this function what we're going to do is we're going to say Http underscore request is equal.

[34:56]

To convert to Http request.

[34:59]

And then we're going to pass this fast API request.

[35:02]

So we just take the request convert it into this Http one.

[35:05]

We're then going to say the request underscore state

[35:08]

is equal to clerk dot authenticate request.

[35:11]

And we're going to pass the Http request okay.

[35:15]

As well as the authenticate request options.

[35:18]

And we're going to say the authorized parties is equal to.

[35:22]

And this is going to be settings dot front end underscore URL.

[35:27]

All right.

[35:28]

So effectively what this is saying is okay get the data from this Http request.

[35:34]

Authenticate it with clerk.

[35:35]

And also just make sure that we're only going to authenticate once they come from our front end URL.

[35:41]

We're going to say if not request

[35:44]

underscore state dot is

[35:47]

signed in okay.

[35:50]

Because what's going to happen

[35:51]

now is this request state is going to give us a bunch of information about the user.

[35:54]

What's their username. Are they signed in.

[35:56]

Are they signed out.

[35:58]

Have they create an account etc. etc..

[36:00]

So if they're not signed in then we're going to raise an Http exception and we're going to say

[36:05]

the status underscore code is equal to statuses

[36:08]

Http underscore 401 underscore unauthorized.

[36:11]

And we're gonna say detail is equal

[36:14]

to not authenticated

[36:17]

okay.

[36:17]

So that's going to check if the particular user is signed in.

[36:21]

And then we're going to say claims are equal

[36:24]

to request underscore state dot payload.

[36:28]

We're going to say the user ID

[36:31]

is equal to claims dot get and then sub.

[36:35]

Now what's going to happen effectively is that when clerk authenticates this request right.

[36:41]

And it kind of pulls out the information from this JWT token.

[36:44]

It's going to include something called a payload.

[36:47]

Now in the payload

[36:48]

we're going to have some information like the sub which is actually the user's ID in clerk

[36:53]

as well as the organization ID that they're inside of and things like their permissions.

[36:57]

So we're going to get that information.

[36:58]

So we can then use that from our backend.

[37:00]

So we're going to say the org ID is equal to claims dot get.

[37:03]

And then org underscore id we're going to say

[37:07]

let's get out of this parentheses here okay.

[37:10]

Go back that the org underscore

[37:15]

permissions are equal to claims dot get.

[37:19]

And we're going to get permissions.

[37:22]

Or we're going to get claims dot get.

[37:26]

And then org underscore permissions.

[37:30]

Or that's because it could be stored under permissions org permissions.

[37:34]

Or if we don't have either of those then we're just going to have an empty list

[37:36]

because we have no permissions.

[37:38]

We're going to say if not user underscore ID then same thing.

[37:43]

We're just going to raise this error.

[37:45]

Because if we don't have a user ID that means there's some error and the user's probably not signed in.

[37:50]

We're also going to say if not and this is going to be org ID.

[37:55]

So if they're not a part of some kind of organization,

[37:57]

then what we're going to do is we're going to return a 400.

[38:00]

So we're going to say 400 bad request, and we're going to say no organization selected

[38:07]

okay.

[38:08]

Because they need to select some particular org organization.

[38:10]

Sorry.

[38:11]

And then if all of that is good we're going to return an off user.

[38:15]

And we're going to pass to the auth user the user ID which is equal to the user

[38:19]

ID, the org id which is equal to the org id

[38:25]

and the org underscore permissions, which is equal to the org permissions.

[38:29]

So effectively you're saying this function will get us the current user from Clarke.

[38:33]

We can convert the request.

[38:35]

We're going to authenticate with Clarke.

[38:36]

We're going to check if they're signed in.

[38:38]

If they are we're going to get information like their user ID, the org ID and the permissions they have.

[38:42]

If for some reason they don't have that data, there's some kind of error.

[38:45]

So we'll return that.

[38:46]

Otherwise we'll return the authorized user.

[38:49]

Now just from this file we're going to create a few more functions as well, which are going to act

[38:53]

as helper functions to essentially get access to particular functionality.

[38:58]

Bear with me. These are going to be pretty simple.

[38:59]

Once we write the first one, the first one is going to be called require view.

[39:03]

This is a function we're going to use to require the view permission from a particular user.

[39:07]

So we're going to say user.

[39:09]

And then this is going to be off user equals depends.

[39:13]

And then this is going to depend on the function.

[39:14]

Get current user okay.

[39:17]

We're not going to call the function. We're just going to write its name.

[39:19]

We're then going to say this will return and auth user.

[39:22]

And what we're going to do from here is we're going to say if not

[39:25]

and then user scan underscore view,

[39:28]

then we're going to raise an Http exception.

[39:31]

And inside of here we're going to have a status code which is equal to status

[39:36]

dot Http underscore 403 okay.

[39:41]

Underscore forbidden saying you don't have this permission.

[39:44]

We're going to say detail.

[39:45]

And we're going to say the view

[39:48]

permission required okay.

[39:51]

Pretty straightforward.

[39:52]

Then down here we're going to return the user.

[39:55]

And we're going to copy this function.

[39:57]

And we're going to have this for every permission.

[39:59]

So rather than now require view we're going to have require create.

[40:03]

We're going to change this to can create.

[40:06]

We're then going to have creates permission required

[40:11]

okay.

[40:12]

And then we're going to do the same thing

[40:13]

except rather than require creates we're going to say required delete.

[40:17]

We're going to change this to be delete.

[40:20]

We're going to change this to say

[40:22]

delete permission required okay.

[40:26]

Now let's go down here rather than require view.

[40:28]

This is going to be require edit.

[40:31]

We're going to change this to say can edit.

[40:33]

And then same thing edit permission required okay.

[40:37]

That's all the we need.

[40:38]

The reason why I'm writing these four functions is we're going to use this is what's

[40:41]

called a dependency injection in a minute and fast API.

[40:45]

So if a user wants to, for example, create a new task, we just call this function.

[40:50]

It will then get the current user and see if the user is actually able to create the task or not.

[40:56]

That's why we have that inside of this off file.

[40:58]

So I know that was kind of complicated thing. That was a lot of code.

[41:01]

But now we've handled the core work for our API and we're going to start working on the database.

[41:06]

Then we'll write the back end API route.

[41:08]

And then we're done with the API.

[41:10]

We can test it and then we can move on to the front end.

[41:12]

So let's close all of this for now.

[41:14]

Again we have kind of the auth stuff handle which is the most complicated.

[41:18]

And what we're going to do is start writing our database models.

[41:20]

So the only database model that we're going to have here is simply to represent a task.

[41:25]

Right.

[41:25]

And a task can be in different stages like pending started, completed.

[41:30]

That's kind of how we'll set it up.

[41:31]

But you can make this more complicated if you want.

[41:33]

So from this task file inside of models, I'm going to define what a task looks like.

[41:39]

So I'm going to import Uuid which will allow us to create a unique ID

[41:43]

I'm going to say from date time

[41:46]

import date time

[41:50]

I'm going to say from SQL alchemy okay.

[41:54]

And then import

[41:56]

column string,

[41:59]

text datetime and enum.

[42:03]

And by the way, if you're getting lost here, all of this code will be available

[42:06]

from the link in the description. I should have mentioned that earlier.

[42:09]

Now let's continue.

[42:09]

I'm going to say imports enum and I'm going to say from App

[42:14]

score dot database

[42:17]

import base okay.

[42:20]

We're then going to say class task status.

[42:24]

And this is going to be string comma enum dot enum.

[42:29]

And we're just going to make a simple enum in Python.

[42:31]

So we're going to have pending which is equal to pending and lowercase.

[42:36]

We're then going to have started

[42:38]

which is equal to started in lowercase.

[42:42]

And we're going to have completed

[42:46]

okay which is equal to completed in lowercase as well.

[42:50]

So this is just be status for a particular task.

[42:52]

You can have more. We're just going to pending started complete.

[42:55]

Then we're going to have class task.

[42:58]

This is going to inherit from base.

[43:00]

And what we're going to do is just define

[43:02]

the table names underscore underscore table name underscore underscore okay.

[43:05]

And this is going to be equal to tasks.

[43:08]

Then we're going to define all of the different data that's going to be stored on this task.

[43:13]

So the first is going to be the ID.

[43:15]

So we're going to have ID column.

[43:17]

This is going to be string primary key true.

[43:20]

And then the default is going to be lambda and then string uuid uuid.

[43:24]

For what this is going to do is just by default create a new unique

[43:27]

ID for all of the tasks automatically for us.

[43:30]

Next we're going to have a title of the task.

[43:32]

This is also going to be column.

[43:34]

It's going to be of type string.

[43:36]

Now we're going to put 255 to limit the length to 255 characters.

[43:40]

And we're going to say knowable is equal to false meaning.

[43:42]

This cannot be empty.

[43:43]

We're then going to say description.

[43:45]

This is going to be column.

[43:47]

We'll just make it text because it can be long.

[43:49]

But this time we're going to say knowable is equal to true because it can be empty.

[43:54]

We're then going to say status is equal to column.

[43:56]

This is going to be of type enum with the task status.

[43:59]

The default is going to be task status.

[44:01]

Dot pending and knowable is going to be false.

[44:03]

This cannot be empty.

[44:05]

Then we're going to have the org ID because every task needs to belong to some org.

[44:10]

So we're going to say column

[44:12]

string.

[44:13]

And then we're going to say nullable is equal to false.

[44:17]

And we're going to say index is equal to true.

[44:19]

So that we can look up values based on this column very quickly

[44:22]

to find all of the tasks for a particular organization

[44:26]

we're then going to have created by and this is going to be the user who created this.

[44:30]

So this is going to be column and then string and then nullable

[44:36]

is equal to false.

[44:37]

We're then going to have created Add.

[44:39]

And this is going to be column datetime.

[44:42]

And we're going to say noble is equal to false.

[44:44]

Or actually we don't need that.

[44:45]

We're just going to say default okay.

[44:48]

Default is equal to date time dot UTC.

[44:52]

Now and then we're going to have updated add.

[44:56]

And this is going to be

[44:59]

column and same thing date time.

[45:02]

And then default is going to be DateTime dot UTC.

[45:06]

No okay. Perfect.

[45:08]

So what this is going to do is it is going to essentially just define

[45:11]

the table schema for our task.

[45:14]

So we have an ID title description status, the org.

[45:17]

This task belongs to who it was created by, when it was created out and when it was updated.

[45:21]

That's all of the information that we need to store about any given task.

[45:26]

Now that we have that we're going to go into schemas and we're going to write

[45:29]

essentially the data validation for creating a task, updating a task,

[45:33]

and getting a task so that we have the correct types in our fast API application.

[45:38]

So what we're going to do is we're going to say from date time import date time,

[45:43]

we're going to say from pedantic import

[45:47]

the base model, we're going to say from typing

[45:51]

import optional okay.

[45:54]

And we're going to say from app dot models dot task okay.

[45:59]

The when we just created imports the task steps.

[46:03]

Now what we're about to create and let's go back to here is something called schemas.

[46:07]

Schemas are essentially Python classes that use pedantic

[46:10]

to help us validate information that gets sent to or returned from our API.

[46:15]

So if someone wants to create a task, for example, they need to pass a title, a description, a status.

[46:20]

That's what we're defining here.

[46:22]

And then we'll use in just one minute.

[46:24]

So we're going to say class task create.

[46:27]

This is going to be the schema that's going to be used

[46:28]

when someone wants to create a task it's going to inherit from the base model.

[46:32]

We're going to say title string.

[46:34]

We're going to say description.

[46:37]

And this is going to be optional

[46:40]

okay.

[46:40]

And then string we're going to say status.

[46:44]

And this is going to be task status.

[46:46]

And then is equal to task status.

[46:49]

Dot pending okay.

[46:52]

So that's kind of the default when they create a task is that it will go and set a pending.

[46:56]

So now if a user wants to create a task

[46:58]

they need to pass this information effectively which we'll use in a minute.

[47:02]

Next we're going to have the task update.

[47:04]

Now this is going to be

[47:05]

pretty much the same thing as the other one, except all of the fields are going to be optional.

[47:10]

So our essay title and then optional string okay is equal to none.

[47:16]

Then we're going to say description and same thing.

[47:20]

This is an optional string is equal to none.

[47:23]

And the status here optional task status equal to not.

[47:27]

So if you want to update the task you can pass any or none of these different combinations of values.

[47:32]

And we'll update based on the ones that you pass.

[47:34]

Right.

[47:35]

Then we're going to have class task status update okay.

[47:40]

So if this is just for updating the status

[47:43]

now we're going to take in the status and the task status like so.

[47:47]

And then lastly we're going to have the class task response.

[47:51]

Now the reason why we write a task response model or schema is because

[47:54]

sometimes we don't want to include all of the information that's in our database

[47:58]

when we return information about data in the database.

[48:02]

So in this case, we're kind of writing a slim version of a task that we'll return to the front end

[48:07]

that might hide any of the sensitive data that we don't want to return.

[48:11]

So we're going to say ID int

[48:14]

we're going to say title string.

[48:16]

We're going to say description

[48:19]

okay.

[48:19]

And this is going to be optional string.

[48:21]

We don't need to make this equal none just optional.

[48:24]

Then we're going to say status is going to be the task status.

[48:26]

We're going to say the org underscore ID is a string.

[48:31]

We're going to say created underscore by is a string.

[48:34]

We're going to say created add is a date time object.

[48:39]

Let's go created and spell that correctly by the way.

[48:42]

And let's go update it at updated underscore at

[48:47]

date time.

[48:49]

And then we're going to say class config.

[48:51]

And we're going to say from underscore attributes equals true.

[48:56]

Which means this will automatically create this response class

[48:59]

based on the attributes of another class which you will see in a minute.

[49:02]

Okay. So that's the schema.

[49:04]

Again we're going to use this in a second where it will make a little bit more sense.

[49:07]

Now what we're going to do is we're going to go to our tasks.py file inside of the API.

[49:13]

And we're going to start using all of the code that we just wrote to define.

[49:16]

The endpoints are kind of the root of our API

[49:19]

for the different operations related to our tasks.

[49:23]

So we're going to say from fast API imports, the API router,

[49:30]

the depends

[49:32]

the Http exception and the status.

[49:36]

We're then going to say from SQL alchemy forum import session,

[49:41]

we're going to say from typing import

[49:44]

the list, we're going to say from app

[49:47]

score dot database

[49:50]

okay.

[49:51]

We're going to import the get database.

[49:53]

We're going to say from app score off imports.

[49:57]

The get underscore current underscore user

[50:01]

we're going to import require view

[50:03]

require create.

[50:05]

So all these functions that we wrote require delete

[50:09]

and require edit okay.

[50:12]

Now you can't see that but I wrote require Edit.

[50:14]

All right then we're going to say from app dot models

[50:18]

dot task import the task model.

[50:22]

And we're going to say from app dot schemas dot task

[50:27]

import the task, create the task,

[50:31]

update the task status update,

[50:35]

and the task response.

[50:38]

Okay. So that's all of our imports.

[50:40]

Now what we're going to do is we're going to create our router.

[50:42]

So we're going to say router is equal to API router.

[50:44]

The prefix is going to be slash API slash tasks.

[50:49]

And then we're going to say tags is equal to tasks.

[50:52]

What we're effectively doing here is saying okay all of the endpoints I'm about to write

[50:56]

here are going to be prefixed with API slash tasks.

[51:00]

So if I have an API here like slash create

[51:03]

I would go to slash API slash task slash create it.

[51:07]

Also let me just quickly fix this because this needs to be inside of a list for the tags.

[51:12]

All right.

[51:12]

So now we're going to say at router dot

[51:15]

get we're going to put just an empty string for now.

[51:18]

Which means if you go to this exact route what we're going to do

[51:22]

is we're going to specify the response underscore model,

[51:25]

which is going to be equal to a list of task response.

[51:29]

And what we're going to do is return all of the tasks that are in the current user's organization.

[51:34]

So we're going to say define.

[51:35]

And this is going to be the list underscore tasks we're going to take in the user.

[51:40]

So we're going to say user of user

[51:43]

equals depends and then require view.

[51:47]

So what's going to happen now is we're saying okay and we need to make this a capital sorry.

[51:51]

We're going to depend on this require view function.

[51:54]

So we're going to call the required view function.

[51:57]

And if that doesn't give us back in authenticated user

[51:59]

it means we don't have the ability to actually view these tasks.

[52:03]

So then we will just raise an exception. Right.

[52:05]

If we do get the authenticated user then we're good to go and we can view them.

[52:09]

We're then going to say DB session is equal to depends and then get database.

[52:15]

So we need the database as well as the permission to view in order to run this function

[52:20]

we're then going to say tasks is equal to db dot query.

[52:25]

We're going to query

[52:26]

the task and we're going to say dot filter.

[52:29]

Then we're gonna say task.org underscore id

[52:33]

is equal to user.org underscore id okay.

[52:37]

And then dot all.

[52:39]

So what this is going to do is it's just going to filter it.

[52:41]

It's going to make sure that the user's organization ID matches the tasks, organization ID, if it does.

[52:47]

And we will return those particular tasks

[52:49]

because those are the tasks that this user is currently in the organization us.

[52:53]

I think that makes sense.

[52:54]

So I said that, but hopefully you get the idea. Okay.

[52:57]

Next we're going to write something very similar, except this time it's going to be to create a task.

[53:03]

So I'm going to copy this and I'm just going to start changing some things.

[53:06]

So rather than row toget we're going to change this to rounded up post,

[53:10]

which means you need to send a Post request to this particular endpoint.

[53:13]

Now for this endpoint we don't need to change it.

[53:15]

We can actually just leave it as this.

[53:17]

Because if you send a Get we'll list the tasks.

[53:19]

If you send a post, we will create the task now rather than the response model being a list.

[53:24]

This time it's just going to be one individual task.

[53:27]

And similarly to before, we're going to take in the user,

[53:30]

except this time it's going to be require create.

[53:33]

We're going to take in the session because we need the database.

[53:36]

But before that we're also going to take in the task underscore data

[53:40]

which is going to be from task create schema that we wrote earlier.

[53:44]

Now let's actually just clean up this function a little bit so it's easier to read.

[53:47]

So let's put our dependencies on separate lines and then go

[53:51]

like this and start writing the rest of the function.

[53:54]

Okay.

[53:55]

So similarly to before again we're getting the user.

[53:57]

We're getting the database.

[53:58]

But now this time we're taking the data from this schema.

[54:00]

Let's have a look at it. Right.

[54:01]

So we need the title description and status in order to create the task.

[54:06]

What we're going to do now is just create the task.

[54:08]

So we're going to say task is equal to task.

[54:12]

We're going to say title is equal to the task data dot title.

[54:16]

We're going to say the description is equal to the task data dot description.

[54:20]

We're going to say the status is the task data dot status.

[54:24]

We're going to say the org ID is the user org ID and created by

[54:29]

is going to be equal to.

[54:31]

And this is the user dot user underscore ID

[54:35]

okay. So that now creates a task.

[54:37]

And by the way the reason why I don't need to check if any of these values exist is because fast API

[54:42]

automatically does that for us by using that schema we created.

[54:45]

So when I put this in here as a requirement for this endpoint, fast API checks okay.

[54:51]

The schema. Did you pass all of this correct data.

[54:54]

And if you didn't it automatically returns an error message to the user okay.

[54:59]

So let's continue here.

[55:00]

We're now going to say db dot add the task.

[55:03]

So after you create the task you need to add it to kind of the staging area for the database.

[55:07]

We're going to say db dot commit.

[55:10]

When we say db commit this is actually going to save it to the database.

[55:13]

And then we can type db, refresh

[55:16]

this task and return the task.

[55:19]

And what refreshing does is it checks the database and kind of re

[55:23]

populates this object with any new values that were created.

[55:27]

So for example, when I create this task here, I didn't set the ID for it.

[55:30]

I didn't set the created at time or the update at time.

[55:33]

But when I refresh here because I added that to the database

[55:37]

and the database will automatically make that for us, it adds those values back to this object,

[55:42]

and then we can return it with all of the populated kind of hydrated values.

[55:47]

Hopefully that makes sense.

[55:48]

Now let's go and start writing a few more endpoints that we need.

[55:51]

So the next one we need is going to be to get a particular task.

[55:54]

So we're going to say router dot get and type slash and then task underscore ID.

[56:00]

Because this is going to be a dynamic path.

[56:02]

We're going to say the response underscore model

[56:06]

is equal to the task response.

[56:09]

We're then going to say define get underscore task.

[56:12]

We're going to take in the task underscore ID

[56:15]

which is a string which is going to match with what's in this path parameter right here.

[56:20]

We're then going to take in the user right which is the author user equals depends.

[56:24]

And then this time it's going to be require view because we're viewing a particular task.

[56:28]

And then same thing DB session okay.

[56:31]

Autocomplete come command is equal to depends get database.

[56:35]

And let's clean this up again like we did before so that we can read these values a little bit easier

[56:40]

okay. And now let's start writing this function.

[56:43]

So what we're going to do to get a particular task is we're going to say

[56:46]

db query task okay dot filter.

[56:50]

And we're going to filter the task.id

[56:53]

is equal to task underscore ID.

[56:56]

And the task dawg id is equal to the user.org ID.

[57:00]

To make sure that both these things match.

[57:02]

Because sure you can know the task ID,

[57:04]

but it needs to be the task that's in this particular organization.

[57:07]

Now we do dot first, which is gives us the first response.

[57:10]

If it exists.

[57:11]

Now, if it doesn't exist, we're going to say if not task, then we can just return a 400 form.

[57:16]

So we can say res http exception.

[57:19]

Okay. Now let's go back here.

[57:22]

This is going to be status.

[57:23]

Underscore code is equal to status

[57:26]

dot http underscore 404 not found.

[57:30]

And then we can say detail is equal to task not found.

[57:35]

And then if that's not the case.

[57:36]

So if we did find the task we can simply return it okay.

[57:39]

And let's add a space there.

[57:40]

So we get rid of that highlight okay.

[57:43]

So that is getting one particular task.

[57:45]

Now let's go to updating a task okay.

[57:48]

So to update the task we're going to say at router

[57:51]

dot put we're going to go slash.

[57:54]

And then same thing.

[57:55]

We're going to take the task ID and the response model will be the task

[57:58]

response I'm doing dot put because that means we're updating.

[58:01]

If you send a put request and we're going to call this define update underscore task.

[58:06]

Now for the parameters we're going to take in the task ID which matches this.

[58:10]

We're also going to take in the task data which is going to be the task update schema.

[58:16]

We're then going to have the user right.

[58:17]

And we're also going to have the database like so okay.

[58:21]

So now that we have all of those we're going to try to find the task right.

[58:25]

And we're also going to make sure sorry that we have the required edit permission.

[58:28]

So let's make sure that it says require edit in the defense right there okay.

[58:32]

So first things first let's try to find the task.

[58:34]

So let's just copy what we found here.

[58:36]

The task needs to exist in order for us to edit it.

[58:39]

So that's the first thing that we're doing now similarly to before we can actually

[58:42]

just copy this right here and paste it.

[58:46]

Because if the task doesn't exist, well, we cannot edit it.

[58:49]

Now, what we're going to do is say if the task underscore data dot

[58:53]

title is not none, then what we're going to do is say task dot

[58:58]

title is equal to task data dot title.

[59:01]

Now we're going to do the exact same thing for the description.

[59:04]

So we're going to save the task data dot description. It's not none.

[59:07]

Then task description is equal to the task data dot description.

[59:11]

And we're to say if the task underscore status

[59:15]

okay we're sorry task data dot status is not none.

[59:19]

Then same thing.

[59:20]

The task status will be equal to the task data dot status.

[59:23]

So we're essentially checking the update data saying okay well if the update data is not none

[59:27]

then we will actually update this current task with that information.

[59:31]

And then we can just type db dot commit which will save that information.

[59:35]

We can then refresh this right from the database.

[59:38]

We can refresh the task and we can return the task.

[59:41]

And that will be the update function.

[59:43]

Now the last function we need is simply to delete a task.

[59:46]

So it's going to be very similar to what we did before.

[59:48]

In fact let's copy everything we have here.

[59:51]

Let's come down.

[59:52]

Let's paste that.

[59:53]

Let's change this to router dot delete.

[59:56]

Let's make sure they have the required delete permission.

[59:59]

We'll remove the task data. We don't need that.

[60:02]

We'll try to find the task.

[60:04]

We'll make sure the task exists.

[60:05]

And if it does we can just delete it.

[60:08]

So DB delete task.

[60:11]

And then we can say TV dot commit to save that in the database.

[60:16]

And then we can just return none.

[60:18]

And for the response model we're just going to change this to actually remove it.

[60:23]

And we're going to say status underscore code is equal

[60:26]

to status dot Http underscore 204 underscore no content.

[60:29]

Because when you delete something well there's nothing really for us to return.

[60:32]

So we're just returning none right.

[60:34]

Find the task exists.

[60:36]

Delete it.

[60:36]

If we have the right permission to do that and then we're good to go.

[60:40]

Perfect.

[60:41]

So that is it for the tasks API okay.

[60:44]

This allows us to get the task.

[60:45]

I also realized that sorry this needs to be called create task.

[60:49]

So it doesn't have the same function name as list task, but okay, get all the tasks,

[60:53]

create a task, get one individual task and then same thing here and delete a task.

[60:59]

I forgot to change the name

[61:00]

and now we have pretty much all of the functionality that we need for handling the various

[61:04]

different tasks.

[61:05]

The last thing we need to do is essentially hook this up to actually start running

[61:08]

the API, add kind of all of the configuration, and then we can test it.

[61:12]

So let's quickly write that so we can go to this main.py file.

[61:15]

Now that we've written all of these individual files and we're going to say from fast

[61:19]

API import with some capitalization, fast API,

[61:24]

we're also going to say from fast API, dot middleware, dot course

[61:29]

import the course middleware.

[61:31]

We're then going to say from app score

[61:35]

dot config import.

[61:38]

And this is going to be settings.

[61:40]

We're then going to say from app dot core dot database

[61:44]

import the engine and the base.

[61:48]

And we're going to say from app dot API

[61:51]

import tasks like that.

[61:53]

Now what we're going to do is we're going to say base dot metadata okay.

[61:58]

Meta data dot create underscore all.

[62:02]

And we're going to say bind is equal to engine.

[62:05]

What this is going to do

[62:06]

is it's going to look for all of the different models that we've defined in our database and create them.

[62:11]

If they don't already exist in the database, we're then going to say

[62:14]

app is equal to fast API.

[62:18]

We're going to say the title of the app is equal to.

[62:21]

And let's go to Task Board API.

[62:24]

We can give it a description

[62:27]

so we can just say, you know be to be task board app

[62:32]

okay.

[62:33]

And then we can go version.

[62:36]

And we don't need to put this here.

[62:36]

But I'm just going to put version 1.0.0 okay.

[62:42]

And then I'm going to say app dot.

[62:44]

And this is going to be add underscore middleware.

[62:47]

And I'm going to add the course middleware.

[62:50]

Now core stands for cross origin Resource sharing.

[62:53]

This is going to allow us to have the API be called

[62:56]

from a different domain or a different resource essentially or origin story.

[63:00]

So I'm going to allow this to be called from our front end.

[63:03]

So I'm going to say allow underscore origins

[63:07]

equal to settings dot front end underscore URL.

[63:13]

To make sure our front end is able to actually call this.

[63:16]

And I'm going to say allow credentials.

[63:19]

True.

[63:19]

So we can pass our authorization tokens I'm going to say allow method star.

[63:24]

So allow everything and allow headers star to allow all of the various headers

[63:29]

okay I'm going to say at app Dot include router.

[63:33]

And I'm going to include the tasks

[63:35]

dot router which is the API router that we just wrote here.

[63:39]

So we just connecting this to our main fast API application.

[63:43]

And then I'm going to say actually I think that's it.

[63:47]

That's all that we need from this particular file.

[63:49]

Okay.

[63:50]

Now I'm going to quickly go to my starts.py file because this is the entry point of my app.

[63:55]

I'm going to import unicorn.

[63:58]

And I'm going to say if underscore underscore name underscore underscore equals

[64:02]

and it's going to be underscore underscore main underscore underscore.

[64:05]

Then you Richaun

[64:08]

dot run.

[64:09]

And I'm going to run like this

[64:12]

app dot main colon app okay.

[64:16]

And say host is equal to 0.0.0.0.

[64:19]

And port is equal to 8000.

[64:22]

And reload is equal to true.

[64:24]

Because we're doing this in debug mode.

[64:26]

Now what this is going to do is it's going to look for this app folder.

[64:29]

It's going to look for this main file.

[64:30]

It's going to look inside the main file for an app called app.

[64:33]

And it's going to run it using unicorn.

[64:35]

It's going to run it on localhost which is this right here on port 8000.

[64:39]

So now we should be good to actually run the API and see if it works.

[64:43]

So what we can do is make sure we're in the back end folder.

[64:46]

We can type you've run starts.py and we should see that the API starts running.

[64:52]

If the API is running that's good.

[64:54]

However it's giving me some error saying no module named backend.

[64:59]

So let me quickly check why I'm getting that.

[65:02]

Okay, so I just had to make a fix here.

[65:03]

Inside this file I had from back end app score dot off.

[65:07]

I seem to change this say from app dot cordon off.

[65:10]

But actually I think that I should have just put this import up here.

[65:13]

So off user like that.

[65:16]

So I'm just going to remove this from here.

[65:18]

Have the off user imported from that.

[65:20]

And now if I save this and run it it looks like we're all good.

[65:23]

The application is started up and we're no longer getting that error.

[65:26]

So the API should be functioning.

[65:28]

Now we're not able to really test this until we actually have a clear account.

[65:32]

And we've signed into our front end.

[65:33]

Fortunately, that's pretty easy to do.

[65:35]

So now what we'll do

[65:36]

is we'll leave the back end running because pretty much all of the functionality is done,

[65:40]

and we can start working on the front end, where we actually sign in.

[65:43]

Users write the UI and then send the request to our back end where they're authenticated by click.

[65:49]

So we're now moving on to the front end.

[65:51]

Now for the front end, we're going to change directories in our terminal

[65:54]

into the front end directory.

[65:56]

You'll see it looks something like this.

[65:58]

What we're going to do from here is just install a few npm packages that we need

[66:02]

specifically for Clarke again for handling all of the authentication,

[66:06]

and then for react router Dom for routing between different pages.

[66:09]

So we're going to type npm I.

[66:12]

And then this is going to be at Clarke

[66:15]

slash Clarke and then dash react.

[66:19]

And then beside this we're going to install react dash router dash dot.

[66:25]

So let's go ahead and install that and wait for that to finish okay.

[66:28]

So all of that has been installed I'm now going to close the terminal.

[66:31]

And I'm going to go into my front end directory.

[66:33]

Now for this project, I am not going to write all of the CSS completely from scratch,

[66:38]

because there is a ton of it, and it's just a huge waste of time for me to do that on the video.

[66:43]

You know, truthfully speaking, most of the CSS was just generated with IE.

[66:47]

Anyways, so what I'm going to allow you guys to do is just download all of the CSS

[66:51]

and bring it into your project, and then write all of the components manually.

[66:55]

If you want to follow along with me so that you don't have to go through

[66:58]

all of this tedious, you know, hundreds actually probably close to a thousand lines of CSS.

[67:03]

So what I'm doing is I'm linking all of the code in the description via GitHub.

[67:07]

If you go to that repo, you go to front end, you go to source, you go to styles.

[67:12]

You'll see all of the styles are organized by pages.

[67:15]

So badges, cards, Kanban layout, all of this kind of stuff.

[67:18]

So you can click into them and you can view all of the different CSS styles

[67:21]

and just drag them right into your project, which is where I'm starting.

[67:25]

All of the CSS is already here.

[67:27]

Again, I'm not going to write any of it, it's just already inside of the project.

[67:32]

Now, same thing with this index dot CSS file.

[67:34]

What I've done is I've just imported all of the individual styles

[67:37]

from this styles folder inside of this indexed on CSS file.

[67:42]

So just make sure you were aware of them.

[67:44]

Now what we're going to do now do we have all of the styles

[67:47]

is we're going to actually start setting up the Clarke authentication.

[67:51]

Then we're going to start writing all of the pages that we need.

[67:54]

So for now I'm just going to delete this assets folder because I don't need it.

[67:57]

So let's get rid of this okay.

[67:59]

And we're gonna go back to Clarke.

[68:00]

And I'm going to show you how we can set this up on the front end.

[68:04]

All right.

[68:04]

So from Clarke we're going to go to overview.

[68:07]

We're going to select react.

[68:09]

And it's going to show us here exactly how we can set up Clarke.

[68:13]

So what we need to do is install the react package, which we already did

[68:16]

and need to create a dot env file where we put in this Clarke v publishable key.

[68:21]

Read Clarke publishable key.

[68:23]

So we're going to go back to our code.

[68:25]

We're going to go inside of front end.

[68:26]

We're going to make a new file called dot env.

[68:30]

And we're just going to paste this in here V2 Clarke publishable key okay.

[68:34]

Perfect. So we've got that in. Now let's go to the next step.

[68:36]

And it's telling us that what we need to do is need to import this inside of our main

[68:40]

dot TSX file where we have the Clarke publishable key.

[68:44]

So let me copy these two variables right here.

[68:47]

And let's go ahead and do that okay.

[68:49]

So we're going to go to main dot Json.

[68:52]

Now from here I'm just going to copy in these two variables.

[68:55]

So the publishable key like that.

[68:57]

And I'm just going to change a few things here

[68:59]

because I need to essentially wrap my app in the Clarke provider component.

[69:03]

But I also need to wrap it in my browser router so I can do the, what do you call it?

[69:09]

Kind of routing or page routing.

[69:11]

So from here I'm going to say import.

[69:15]

And then this is going to be the Clarke provider.

[69:18]

Okay.

[69:19]

From and this is going to be at Clarke slash Clarke dash react

[69:24]

I'm then going to import the browser router

[69:30]

from.

[69:30]

And this is going to be react dash router Dom okay.

[69:35]

Now for my app I'm going to wrap this in the Clarke provider.

[69:39]

For the Clarke provider

[69:40]

we're going to provide our publishable key which is stored in the variable publishable key okay.

[69:45]

And then we're going to put our app directly inside of here.

[69:49]

Now around that we're also going to wrap.

[69:52]

Let's just close this the browser router.

[69:54]

So we're going to say browser router like that okay.

[69:57]

And again inside of the browser router we're going to put our app okay.

[70:02]

So now we should be good from the main.js.

[70:05]

So what we're doing is we're just wrapping the app in the Clarke provider in the browser router.

[70:09]

So we get access to the Clarke settings or the Clarke configuration

[70:13]

as well as the React Router Dom features.

[70:16]

Okay.

[70:17]

Now what we're going to do is go inside of app dot JSX.

[70:21]

From here we're just going to clear everything that is inside of this app component.

[70:27]

We're also going to remove the import of APKs because I've deleted that.

[70:31]

We're going to remove the imports of the React's logo and the Veet logo.

[70:34]

And even same thing with use state.

[70:36]

We actually don't need that.

[70:37]

And we're going to start setting this up to actually handle the routing for our application.

[70:42]

Now, before I can do all of the routing, I do need to have the different pages that I will be routing to.

[70:47]

So what I'm going to do is just stub those pages in my front end.

[70:50]

So from source I'm going to make a new folder.

[70:52]

So let's go new directory like this.

[70:55]

And we're going to call it pages.

[70:57]

Now inside of pages we're just going to create some empty pages that we can route to.

[71:02]

So the first page we're going to have so let's make a new file is going to be the dashboard

[71:07]

okay.

[71:07]

And then page dot JSX.

[71:10]

Now for this all we're going to do is we are just going to write a really simple component.

[71:15]

So we're just going to say function dashboard like so.

[71:19]

And actually this can be dashboard page.

[71:22]

And then we're just going to return an empty fragment okay.

[71:25]

And then we're going to say export default

[71:28]

the dashboard page like so okay.

[71:31]

Now we can just copy that and let's go to the next page.

[71:33]

Now for the next page this is going to be the home page.

[71:37]

So let's just go home page dot JSX okay.

[71:39]

And then we can just change this from dashboard page to say home page

[71:44]

like so now let's have another page.

[71:47]

So let's go new file.

[71:48]

This is going to be the pricing page.

[71:52]

So pricing pages same thing.

[71:55]

Just change this to say pricing and pricing.

[72:01]

Perfect.

[72:02]

Now we have two more pages I believe we're going to have the sign in and the sign up page.

[72:06]

So we're going to say sign up dot JSX.

[72:09]

Same thing.

[72:09]

Just change this to be the sign up page

[72:13]

and the sign up page.

[72:16]

And then let's go a new one. So new page.

[72:18]

And this is going to be this sign in pages.

[72:23]

And from here sign in like that and sign in

[72:30]

okay.

[72:30]

So those are our five pages.

[72:32]

Let me just close all of those.

[72:34]

Now what we're going to do is go to app.

[72:35]

And we're going to start setting up the router

[72:37]

to route between these different pages that once we write them, they're already showing up.

[72:41]

So at the top of our file we're going to say import

[72:45]

and then routes and router

[72:48]

or sorry not router route from.

[72:50]

And this is going to be react dash router dash Dom okay.

[72:56]

That's the first import.

[72:57]

And then we're going to say import signed in signed out

[73:02]

and redirect to sign in from act Clark Clark dash react.

[73:07]

This allows us to essentially gate pages that if you're signed in we show you something.

[73:11]

If we're signed out, we show you something else.

[73:13]

We're then going to import the home page.

[73:16]

Okay.

[73:16]

We are then going to import the sign in page okay.

[73:21]

So let's do that.

[73:22]

Then we're going to import the sign up page.

[73:27]

Then we're going to import the dashboard page.

[73:30]

And then I think the last one we need is the pricing page.

[73:33]

Let's import the pricing page like that.

[73:35]

Now we're going to create a very simple protected route function.

[73:39]

So we're going to say function.

[73:40]

And this is going to be protected routes like so.

[73:44]

And we're just going to take in some children.

[73:47]

And what we're going to do is we're going to return a react fragment.

[73:51]

And inside here we are going to say, if you are signed in,

[73:56]

then we will simply show the children of the protected route.

[74:00]

Otherwise, if you are signed out,

[74:04]

then what we're going to show is the redirect to sign in.

[74:08]

Okay.

[74:09]

So all this is going to do is redirect to the sign in page.

[74:12]

If you are not signed in. That's it. Very easy.

[74:15]

Now from our app what we're going to do is we're just going to define the different routes.

[74:18]

So if you've ever used React Router Dom all we're doing here is we're saying

[74:21]

okay, these are all of the different pages that you could access.

[74:24]

For example sign in slash sign out.

[74:27]

That's all we're putting here.

[74:28]

And then when you go to one of those routes it will just render the component that we wrote,

[74:32]

which is one of these pages. So we're going to say routes.

[74:35]

Now we're going to make a route here for the first route.

[74:38]

We're just going to say path is equal to slash okay.

[74:42]

So that's it.

[74:43]

So that's it for that.

[74:44]

Now inside of this routes we're going to have a bunch of other nested routes.

[74:48]

So we're going to say route index.

[74:50]

Index is the default one.

[74:51]

And we're going to say element is equal to.

[74:53]

And then this is going to be the home page

[74:57]

okay.

[74:57]

So let's close the component like that.

[74:59]

And then for the route we actually don't need to close the row.

[75:01]

We can just make it self enclosed.

[75:03]

Then we're going to have another route.

[75:05]

Now for this route we're going to say path is equal

[75:09]

to sign in slash and then Asterix.

[75:12]

And then we're going to say the element is equal to.

[75:15]

And this is going to be the sign in page

[75:19]

like so then we're going to have another route okay.

[75:24]

So let's just do actually the exact same thing that we did here.

[75:28]

And let's change this to be a sign up.

[75:31]

And then you guessed it this is just going to change to sign up.

[75:34]

Then let's copy that again for the next route.

[75:36]

Let's change this to be slash pricing okay.

[75:39]

So let's give be the pricing page for the elements.

[75:41]

This is going to be the pricing page.

[75:44]

And then we can have the dashboard.

[75:47]

So for the dashboard we're going to say route.

[75:49]

We're going to say the path is equal to dashboard.

[75:53]

Then beneath here we're going to say the element is equal to.

[75:57]

And for the element this time it's going to be the protected route okay.

[76:02]

So protected route.

[76:04]

And inside of the protected route we are going to have the dashboard page.

[76:09]

So essentially what we're saying is okay if you are signed in will allow you to access dashboard.

[76:13]

If you're not signed in we're not going to let you access the dashboard.

[76:15]

So that's why we're using this protected route component for all of the other pages.

[76:19]

You could access them even if you're not signed in.

[76:21]

That's fine. Okay.

[76:23]

Now that's it for kind of handling the routing.

[76:26]

Now if we run this and we go to these different pages,

[76:28]

you'll see that it will actually just render those pages.

[76:30]

Although they won't look any different because we don't have anything different in those components.

[76:34]

But we can actually test it for now because we can test, for example, the protected route.

[76:38]

So let's open this up.

[76:40]

Let's go npm run dev okay.

[76:42]

It should run that for us.

[76:43]

Let's open up our front end okay.

[76:45]

Notice this is kind of the home page.

[76:47]

And if I go to slash dashboard for example you'll see that it should redirect me.

[76:53]

And you see it redirects me to the sign in page here with Clarke, where it asks me

[76:56]

to sign into my account because I am not already signed in.

[76:59]

Okay, let's go back now.

[77:01]

Let's try to go to slash sign up or something.

[77:04]

And you can see we can just go to that route.

[77:05]

But if we try to go to the protected one, then it brings me to the sign in page.

[77:10]

Awesome. So that is running.

[77:12]

Now what we're going to do is we're going to start building kind of like that nav bar,

[77:16]

which allows us to sign in and sign out, see if we're signed in, navigate

[77:20]

between the different pages, and then we'll start building the various other components.

[77:24]

So we're now going to make another folder inside of src.

[77:26]

So new folder call this components.

[77:30]

And for the components we're going to start by creating a layout component

[77:33]

which is going to always be on the screen which is going to act kind of like a nav bar.

[77:37]

So I'm going to say layout dot JS.

[77:40]

Now inside of here I'm going to import

[77:43]

outlet as well as link from okay.

[77:47]

And this is going to be React Router Dom.

[77:50]

Then I'm going to say import.

[77:52]

And this is going to be signed in

[77:55]

signed out user button.

[77:58]

The user button is kind of like the profile button.

[78:01]

Then we're going to have the organization

[78:04]

if we can spell this correctly switcher

[78:07]

as well as use organization

[78:11]

from at Clark Clark Dash react.

[78:14]

And for some reason it's giving me an error here.

[78:16]

It says it's defined but never used.

[78:18]

Okay. That's fine. So we will use it later.

[78:20]

Then we are going to define the function.

[78:23]

So we're going to say function layout like so

[78:27]

we're going to say const

[78:30]

organization okay.

[78:32]

And this is going to be equal to use organization.

[78:37]

Now we're just going to put this inside of braces so we can grab the organization.

[78:42]

We're then going to say return.

[78:44]

And we're going to are building this layout component again.

[78:47]

The way that the layout works is that we're effectively just going to have this

[78:51]

always be on the screen, where we're kind of showing this like nav bar at the top of the page.

[78:55]

So for this div we're going to have class name equal to layout.

[79:00]

We're then going to have another div.

[79:02]

So let's add this here we're going to say

[79:06]

class name is equal to and this is going to be nav.

[79:10]

We're then going to have another div.

[79:11]

If you can't tell right now we're building the nav bar.

[79:13]

We're going to say class name is equal to nav dash container.

[79:19]

We're then going to have a link.

[79:20]

So we're going to say link.

[79:22]

And then this is going to be two equals slash.

[79:24]

So to the homepage and we're going to say class name is equal to nav dash logo.

[79:30]

And for this we're going to put the names.

[79:32]

We're going to say task board.

[79:33]

If you press this it will just bring you to the homepage.

[79:36]

Now beneath that we're going to have another div.

[79:39]

This is going to be class name equal to nav dash links

[79:42]

where we'll have the different links that you can navigate between.

[79:45]

So the first link let's put this here is going to go to.

[79:49]

And we're going to go to the pricing page okay.

[79:53]

So this is going to be slash pricing yet needs to be slash pricing.

[79:56]

The class name will be equal to nav dash link.

[80:01]

And then here we can simply say pricing okay.

[80:05]

And now we're going to have some dynamic things.

[80:07]

So we're going to say is signed out.

[80:09]

So if you're signed out then we'll show you the ability to sign in or to sign up.

[80:14]

So we're going to have a link okay.

[80:16]

We're going to say two is equal to. And then same thing.

[80:19]

This is going to be slash sign dash in.

[80:22]

Then we're going to have class name

[80:25]

is equal to nav dash link.

[80:28]

And you guess what this one is going to say.

[80:30]

It is sign in.

[80:31]

Then let's copy it.

[80:32]

And directly below that we're going to have the sign up

[80:36]

and just change this to sign up okay.

[80:38]

That's it. So if you're signed out we'll show you this.

[80:41]

However if you are signed in then we're going to show you

[80:44]

your organization as well as your profile.

[80:47]

So to show the organization we're going to say organization switcher.

[80:51]

This is a component that comes directly from clerk.

[80:54]

And there's a bunch of settings

[80:55]

that we can pass here to change the styling, which is what I'm going to do.

[80:58]

So first I'm going to say hide personal.

[81:01]

And then I'm going to say after

[81:03]

okay.

[81:03]

And this is going to be create organization

[81:08]

URL is equal to dashboard.

[81:12]

Then I am going to say after select

[81:17]

okay expose correctly organization

[81:20]

URL is equal to the dashboard as well.

[81:25]

Then I'm going to say create

[81:28]

organization mode is equal to modal.

[81:32]

So this means it's going to pop up as like a pop up on screen rather than be a separate page.

[81:37]

Then we navigate to

[81:38]

and then we can override the appearance, which I'm just going to quickly change

[81:41]

because we need to change it from kind of light mode to dark mode.

[81:44]

So we're going to say elements.

[81:46]

And then here we're going to say use or not use user

[81:50]

preview main identifier text underscore underscore

[81:57]

personal workspace I know this is a lot of code, but this is how I change.

[82:02]

The color is going to be color and then white like that okay.

[82:08]

And it looks like we spelt identifier correctly. So incorrectly.

[82:10]

So so let me just fix that okay.

[82:12]

Now after that one we're going to have organization

[82:16]

preview main identifier

[82:21]

underscore underscore organization switcher

[82:25]

trigger is and let's spell trigger correctly.

[82:29]

And then same thing I always keep spelling identifier

[82:32]

incorrectly is going to be color dash white okay.

[82:36]

Again I just need to change this from light mode effectively

[82:39]

where the text is going to be black to dark mode where the text will be white.

[82:42]

Okay, so that's it for the organization switcher.

[82:45]

Kind of annoying to do that override, but that is just one thing we need to do.

[82:48]

And then I'm going to say, okay, if you're currently in an organization.

[82:51]

So let's spell organization correctly.

[82:54]

So I'm can say organization.

[82:55]

And and then we're going to have a link.

[82:58]

And the link is going to say two equals.

[83:00]

And then slash dashboard okay.

[83:03]

And class name

[83:06]

is equal to nav dash link.

[83:09]

And then here we're just going to say dashboard like that okay.

[83:13]

So the idea here is that a user might be signed in but they might not yet be a part of an organization.

[83:18]

So if they're not a part of an organization, well,

[83:20]

I can't show them the dashboard because the dashboard only exists per organization.

[83:24]

So that's kind of what we're talking about here in this sign in component.

[83:27]

Now, if they're not signed in, of course they're not in a what do you call organization

[83:31]

because, well, they're not signed in.

[83:32]

So we'll just show them this to sign in or to sign up.

[83:36]

Okay.

[83:36]

Then we need to export this.

[83:37]

So export default layouts.

[83:40]

And I think we are all good.

[83:42]

Now the only thing is that we need to render this outlet.

[83:46]

And we also need to display the user button which I forgot.

[83:48]

So let's quickly do that. So out or so inside.

[83:51]

But at the end of this sign in container we're going to put the user button

[83:56]

which is just going to show the user's profile.

[83:58]

And then down here in the end of this div we're going to say main.

[84:03]

And we're going to put outlets

[84:05]

okay.

[84:05]

Now what this is going to do is it's going to take whatever should be showing in the page

[84:10]

that we're navigating to and just put it inside of here.

[84:13]

So effectively, the way the layout works is that we're just rendering the page inside of this outlet.

[84:18]

Everything else is just static and always stays on screen.

[84:22]

So this layout is always visible based on what's happening here.

[84:25]

And then we show whatever else kind of the content that the current screen

[84:28]

is showing inside of this out.

[84:31]

Hopefully that makes sense.

[84:33]

So now what we need to do is just import layout from here.

[84:35]

So we're just going to say import layout from.

[84:39]

And this is going to be component.

[84:42]

So I think it's dot slash components slash layout like that.

[84:46]

And then we're going to go to our route like this.

[84:49]

And we're going to say the element is equal to

[84:56]

layout.

[84:57]

Cool.

[84:58]

So now if we do that and we go back here, we should see the nav bar popping up.

[85:03]

And you can see that we have pricing, sign in, sign up.

[85:05]

And for some reason the styling is not working 100%.

[85:08]

So I'll have a look at that in one second.

[85:10]

But that is a good start.

[85:12]

Okay, so I was just having a look at the styling and I noticed some issues,

[85:15]

right, in terms of how the styling is here.

[85:17]

So I just realized I made a few errors when I was building this layout

[85:21]

where first for this link, I actually needed to remove the nav link

[85:26]

that was here before and change it to this btn btn dash primary.

[85:31]

And I need to take all of this and just put it one div up.

[85:36]

So essentially after the pricing it goes inside of here

[85:40]

okay.

[85:40]

So then it's in this nav links div which it wasn't in before okay.

[85:45]

And let's just fix this a bit so that it is all inside of here correctly.

[85:50]

Let's fix the indentation.

[85:52]

And you can see now this inside of this nav links div.

[85:54]

So now if I come back you can see that

[85:56]

it's properly on the right hand side of the screen which is what I was looking for before.

[86:00]

Okay.

[86:01]

So now the layout component is finished.

[86:04]

And what I want to do is start writing the sign in and sign up page.

[86:08]

So we actually have the ability to create an account and sign in to our application.

[86:12]

So let's start with the sign up page.

[86:14]

Now for the signup page.

[86:16]

This is very easy.

[86:17]

All we need to do is just say import sign up.

[86:21]

And then this is going to be from the add

[86:25]

Clark Clark react package.

[86:27]

And then what we can do is just return a simple div.

[86:30]

For the div.

[86:31]

We can say class name is equal to off dash container.

[86:35]

And then inside the div we can put a sign up component.

[86:39]

We can say routing is equal to and this is going to be path.

[86:43]

And then we can say path is equal to slash.

[86:46]

Sign up. So it knows that it goes back to this page.

[86:50]

And then we're going to say the sign in URL is equal

[86:53]

to slash sign dash in okay.

[86:56]

Then we can actually just self enclose this component.

[87:00]

And that is the sign up component from Clark.

[87:02]

We just use it and it will work like this.

[87:05]

Now let's do the same thing for the sign in page.

[87:08]

So for the sign in page it's going to be literally the exact same thing except sign in.

[87:13]

So we'll just copy what we have here okay.

[87:15]

So we're going to say from a we're going to say import sorry.

[87:18]

And this is going to be sign in okay.

[87:21]

From at Clark Clark react for the return.

[87:24]

Make it the same thing except this is going to be sign in.

[87:29]

This is going to be sign in.

[87:30]

And this is going to be sign up.

[87:33]

And this is going to be the sign up URL.

[87:35]

So just swapping those around.

[87:37]

And now let's remove this sign up component.

[87:40]

If we go back here we refresh and we press sign up for example.

[87:44]

You see that.

[87:45]

It brings us to the sign up page where we see the sign up modal.

[87:49]

If you go to sign in brings us to this sign in page.

[87:52]

So what we can do now is try to make an account.

[87:55]

Let me create one.

[87:56]

Press continue and we'll go from there okay.

[87:58]

So I'm just going to make an account here with my email.

[88:01]

Let's close that.

[88:02]

Okay I'm going to need a new password.

[88:04]

So let's set a new one.

[88:07]

Go continue.

[88:08]

And then what it's going to do probably is ask me to verify email.

[88:10]

So yes, you can see I have to verify the email.

[88:12]

Let me do that and I'll be right back okay.

[88:14]

So this is the code I got in my email.

[88:16]

And now I press enter and I get brought back to the page.

[88:20]

Looks like there must have been some error or something.

[88:22]

I don't think it's signed me in.

[88:24]

Maybe so I signed up, but it didn't sign me in.

[88:27]

So let me try to sign in to that account now.

[88:29]

Oh. Now it's saying set up your organization to continue.

[88:32]

Okay. That's interesting.

[88:34]

So let's just create an organization, I guess, like Tim or something.

[88:38]

Okay. And let's see if it works.

[88:40]

And there we go. Okay.

[88:41]

Now we get brought into the dashboard where it looks like we are all good.

[88:45]

So it's a little bit buggy

[88:46]

when we're creating the new account where we need to actually create the organization first.

[88:51]

Otherwise it doesn't let us access this page.

[88:53]

I also still need to change the color, so I think I might have just had a spelling mistake

[88:56]

when I was adjusting the parameters here, but you can see I'm inside an organization.

[89:00]

I can now manage this information from this kind of clerk dashboard here.

[89:04]

And I have the ability if I want to invite someone to my organization, I can view the dashboard.

[89:10]

I can go to the pricing page even though there's nothing there.

[89:13]

And then if I press on my little user account guy, you can see that I have the account again

[89:17]

because of Clarke.

[89:18]

And I can, for example, just go here and go sign out and then the UI will update and show me this.

[89:23]

And if I go back to Clarke now and I refresh it, it should start showing me my users.

[89:29]

And you can see that I have a user here, Tim, a tech with Tim Dunn, that I can view

[89:32]

all the information about the user when they signed in their password.

[89:35]

Well, actually, I don't think I can view their password,

[89:37]

but I can change or reset their password, etc.

[89:40]

etc. etc.

[89:40]

and I can even see their organizations which one

[89:43]

they belong to, invite them to an organization, mess with all of their settings.

[89:46]

You get the idea.

[89:48]

Okay. Super cool. So the author's working.

[89:50]

We're able to sign in, we're able to sign up, create a new account, and create organizations.

[89:54]

Now what we want to do is start handling kind of the task board

[89:56]

and then get into some more of the advanced stuff where we're inviting

[89:59]

different users, having the billing, the subscriptions, the pricing, all of that.

[90:03]

All right, so now that we finish the login functionality, what

[90:06]

we're going to start working on is the actual task management.

[90:09]

So having that kind of Kanban style board like you saw creating tasks

[90:13]

deleting tasks, and then of course the pricing and upgrading the organization.

[90:17]

So let's actually start by going to the home page and let's code this out for now

[90:21]

so that we have kind of some styling.

[90:22]

And we have like a nice landing page people can go to.

[90:25]

Then we can build the dashboard where you're going to be to actually see

[90:28]

the different tasks and move those around. Okay.

[90:31]

So from the homepage we're going to start by importing a link

[90:36]

from React Router Dom.

[90:39]

Then we're going to import

[90:42]

signed in

[90:43]

and then signed out use organization

[90:47]

which comes from Clarke as well as create organization okay.

[90:51]

All right.

[90:51]

So now we are going to go and inside

[90:54]

of the home page component we're going to check the organization the users in.

[90:57]

So we're going to say const organization is equal to use organization okay.

[91:03]

So let's pull in that react hook.

[91:05]

And from here let's start creating the UI.

[91:09]

So we're going to return a div.

[91:11]

For the div we're going to have class name is equal to.

[91:14]

And this is going to be the home dash container okay.

[91:20]

Let's spell container correctly like that.

[91:25]

Now inside of this div we're going to have an h1.

[91:27]

The h1 is just going to be the title.

[91:29]

So we're going to have class name equal to home dash title.

[91:34]

Here we're going to say team task management.

[91:37]

Like that.

[91:38]

And then we're going to put a span.

[91:40]

We're going to say class name is equal to home.

[91:44]

And then dash title and then dash accent.

[91:47]

And we're just going to have made simple

[91:50]

just to add some kind of nice styling here to the header.

[91:53]

Then we're going to have a paragraph tag.

[91:54]

We're going to say class name

[91:57]

is equal to.

[91:58]

And for the class name this is going to be the home dash subtitle.

[92:04]

Inside the subtitle we're going to say organize

[92:07]

your team's work with powerful.

[92:11]

And let's spell this correctly with powerful task boards.

[92:16]

Then we're gonna say create, assign

[92:19]

and track tasks across

[92:24]

your organization.

[92:27]

Okay.

[92:27]

Then we're going to have a signed out container for the users that are signed out.

[92:31]

We're essentially just going to tell them, hey, you know, go and like create a new account.

[92:35]

So we're going to have a div.

[92:37]

We're going to have class name is equal to the home dash buttons.

[92:42]

Then inside the div we're going to have two links.

[92:44]

So for the first link we're going to say two is equal to sign up.

[92:48]

And this is going to be slash sign up.

[92:50]

And then for the class name we're going to say this is equal to btn

[92:55]

btn dash primary btn dash lg.

[92:59]

For large.

[93:00]

And inside the link we're going to say get started for free okay.

[93:05]

Then we're going to have another link for this one.

[93:07]

We're going to say two.

[93:08]

And this is going to be slash sign in.

[93:11]

And then we're going to say the class name is equal to.

[93:16]

So btn btn dash outline btn dash large again.

[93:22]

And then this is just going to say sign in

[93:24]

okay.

[93:24]

So that's it.

[93:25]

If you are signed out now if you are signed in the UI is going to look a little bit different.

[93:31]

So first we're going to check if you're currently in an organization or inside organization.

[93:35]

Question mark okay.

[93:37]

Now if you are in an organization then we're just going to have a link.

[93:40]

And the link is going to go to the dashboard

[93:43]

and just telling you, okay, you know, go manage your task from the dashboard.

[93:46]

So here we're going to say the class name for this link is going to be equal

[93:50]

to the n btn dash primary and then btn dash lg okay.

[93:56]

And we're going to say go to dashboard then.

[93:59]

Otherwise if you do not have an organization we're going to have a div.

[94:04]

So let's create the div here.

[94:05]

The div is going to have a class name which is equal to home dash.

[94:10]

Create dash org.

[94:13]

And then we're going to put create organization like this.

[94:17]

So this is essentially a form that is rendered by Clarke.

[94:20]

So we want to show them hey you need to create an organization.

[94:23]

So we're just going

[94:24]

to show this directly on the home page so that they can see it and they can create it.

[94:27]

And we're going to have after create

[94:31]

organization URL

[94:34]

is equal to slash dashboard

[94:38]

okay.

[94:38]

So after they create an organization will redirect them to the dashboard okay.

[94:42]

So that's going to be it for the home page.

[94:45]

Effectively what we're saying is all right if the user is signed out

[94:48]

tell them to sign in or sign up if they're signed in, check

[94:51]

if they have an organization, if they do, allow them to go to the dashboard.

[94:54]

If they don't have an organization, then simply tell them to create one.

[94:58]

Okay, so that's the home page.

[94:59]

Let's test it out. Let's go here.

[95:01]

And there we go. Nice team.

[95:03]

Task management made simple I think we're going to have to add

[95:05]

like a line break there because looks like it's on the same thing.

[95:09]

So let's just add a PR like that. There we go.

[95:12]

It looks good.

[95:13]

And we can see we have this nice UI okay.

[95:16]

So let's continue from here. And the next thing we're going to do

[95:19]

is start coding out some of the components related to the tasks.

[95:23]

So actually I'm just going to close all of these windows for right now.

[95:26]

I'm going to make a new folder here.

[95:28]

And I'm just going to call this one services.

[95:31]

And inside of services.

[95:33]

What I'm going to do is I'm going to write a file called API dot js.

[95:36]

We're going to put all the calls to our backend APIs.

[95:39]

We're going to say API dot js.

[95:40]

Here we're going to make a variable.

[95:42]

We're going to say const API underscore URL is equal to import

[95:47]

dot meta dot e and v dot

[95:49]

Veet underscore api underscore URL.

[95:53]

Or that this is going to be http colon slash slash

[95:57]

okay localhost port 8000.

[96:01]

Now if we want to define this as a variable in the environment variable file, we can.

[96:05]

Otherwise it'll just load this by default.

[96:08]

If we don't have anything there then we're going to have a function.

[96:11]

So we're going to say export async function.

[96:14]

And this is going to be fetch with us.

[96:17]

What this is going to do is this is going to send a request

[96:20]

to our back end with the clerk authentication.

[96:24]

So what I'm doing in this file is I'm writing all of the requests that will be sent to our back

[96:28]

end. So we can simply just call the functions from our front end code.

[96:31]

Now here we need to make sure that we include the correct headers, which includes

[96:35]

the clerk JWT token, which is essentially how clerk identifies are signed in users.

[96:41]

We're going to send that to our back end along with every single request.

[96:44]

So that's what this kind of helper function is going to do.

[96:47]

So we're saying export async function.

[96:49]

And this is fetch with all we're going to take in some endpoint.

[96:52]

We're going to take in get token and then options

[96:56]

which is equal to an empty object.

[96:59]

We're going to say const.

[97:01]

And this is going to be token is equal to await get token.

[97:05]

This is a function that we're going to call from clerk

[97:07]

which is going to give us the token that we need to use.

[97:10]

Then what we're going to do is we are going to say const

[97:14]

response is equal to await fetch.

[97:19]

What we're going to do is we're going to fetch the following URL.

[97:21]

So this is going to be inside of a dollar sign because we're putting a variable.

[97:26]

The API underscore URL.

[97:28]

And then we're going to have another dollar sign.

[97:30]

And then we're going to put the endpoint that was passed to this function.

[97:35]

So that's the URL that we're calling now.

[97:37]

The options that we're going to pass is dot dot dot options.

[97:40]

So this right here one of our parameters as well as we're

[97:44]

going to pass the content dash type.

[97:47]

And this is going to be application slash Json.

[97:52]

And then we're going to have auth.

[97:56]

And then we're going to have

[97:57]

authorization is bearer.

[98:01]

And then we're going to put our token in here.

[98:04]

This identifies the user.

[98:06]

And then we're going to say dot dot

[98:09]

dot options dot headers.

[98:12]

And this reminds me that I need to actually fix something because I need to put headers like this.

[98:16]

So I need to say headers.

[98:17]

And then all of this is going to be a header that we're passing with our options okay.

[98:22]

So let's just quickly fix that as we did. Perfect.

[98:24]

So now after the response we're going to say if not response dot okay.

[98:32]

So response dot okay.

[98:34]

Then what we're going to do is say const error is equal to await

[98:39]

response dot Json.

[98:41]

Let's spell response correctly

[98:44]

dot catch.

[98:45]

And for the catch we're just going to put something empty here

[98:48]

because we're not going to handle that right now.

[98:49]

Then we're going to say throw new

[98:53]

error.

[98:54]

And we're just going to throw the error dot detail

[98:58]

or we're going to throw request failed like that.

[99:02]

Okay.

[99:02]

Then we're going to say if the response dot

[99:06]

status is equal to 204.

[99:10]

So it has no content, we're just going to return null.

[99:14]

Otherwise we're going to return

[99:16]

response dot Json which is essentially the response right.

[99:20]

And whatever we got back from the API okay.

[99:23]

So this is the function that we're going to call

[99:25]

when we want to send a request to the back end with our clerk authentication.

[99:29]

So now what we're going to do is we're going to write the individual functions for the operations

[99:32]

that we want to perform in the backend.

[99:34]

So the first one it's going to be export async function.

[99:37]

And this is going to be get tasks.

[99:39]

And this is going to get all the tasks from our backend.

[99:41]

So same thing we're going to take in this get token function.

[99:44]

And we're going to return fetch with us

[99:49]

okay.

[99:50]

And when we fetch with auth we're going to fetch the API slash tasks.

[99:54]

Sorry wrote.

[99:56]

And we're going to pass the get token function okay.

[99:59]

Now let's copy the same thing.

[100:01]

And for the next one rather than get tasks we're going to say create task.

[100:06]

This time we're going to take an get token and we're going to take in a task.

[100:09]

And then we're going to call API slash tasks.

[100:12]

But this time it's going to be a Post request.

[100:14]

So what we're going to do is we're going to add in some options here.

[100:18]

For the option we're going to say method is equal to in all capitals post.

[100:23]

And we're going to say the body is equal to Json

[100:26]

dot Stringify Stringify like so.

[100:30]

And we're going to pass in the task okay.

[100:33]

So that is now how you create the task.

[100:35]

Now we'll paste this again.

[100:36]

And now we're going to do update task.

[100:39]

So we're going to say update task okay.

[100:41]

We're going to take in get token.

[100:43]

We're going to take in task ID as well as the task we're going to fetch with auth.

[100:48]

Same thing API slash tasks.

[100:50]

And now this time we're going to say

[100:52]

the method is equal to put because that's the method we use when we want to update.

[100:56]

Then we're going to say the body

[100:59]

is Json

[101:00]

dot stringify and we're going to stringify at the task.

[101:03]

And actually one small change, we need to include the task ID in the URL.

[101:08]

So we're just going to do that like this by changing these to backticks okay.

[101:11]

It already is Backticks perfect. That's great.

[101:14]

So slash API slash tasks

[101:16]

slash the task ID of the task that we want to update.

[101:20]

And then in the body we just include the new task data that we want to use when we're updating.

[101:25]

Okay.

[101:25]

And then the last thing we need to do is delete a task.

[101:28]

So we're going to change this to say delete task.

[101:31]

Same thing. We're going to take an get token.

[101:33]

And this time the task ID then we're going to change this to be a backtick

[101:39]

okay.

[101:40]

And we are going to include the task ID we want to delete.

[101:44]

So let's include that.

[101:45]

And we're going to change the method to be delete.

[101:47]

So we're going to say method

[101:51]

delete in all capitals like that.

[101:53]

And now we have the for function.

[101:55]

So get task create task update task and delete task.

[101:59]

And now we can use those to call the back end API okay.

[102:02]

So with that in mind let's start writing the components that we need

[102:04]

for representing the tasks.

[102:06]

So we're going to go into components directory.

[102:08]

And the first component that we're going to work on is just going to be our task card.

[102:11]

So we're going to say task card dot JSX.

[102:15]

And inside here we're just going to write a card that represents one individual task.

[102:18]

So we're going to say function task card.

[102:22]

And we're going to take in as values here the task on edit.

[102:27]

So function to call if we're ending the task and a function to call.

[102:30]

If we are deleting the task then we're going to say const we're going to say can edit

[102:35]

is equal to exclamation mark exclamation mark on edit.

[102:39]

So essentially did we pass that function.

[102:41]

Then we're going to say const can delete okay.

[102:44]

And this is going to be equal to exclamation point exclamation point.

[102:46]

And then on delete

[102:49]

okay.

[102:49]

Then we're going to return the UI.

[102:51]

So we're going to return a div right.

[102:53]

I said the class name is equal to.

[102:55]

And this is going to actually be in Backticks.

[102:58]

And we're going to say task dash card.

[103:00]

And then we're going to have a dynamic name based on if we can edit the task or not.

[103:04]

So we're going to say can edit question mark.

[103:06]

And then if we can't edit we're going to have the following task dash card dash clickable.

[103:12]

So you can click the task to edit it.

[103:15]

Otherwise we're just going to have an empty string.

[103:16]

So if you can't edit it we're not going to let you click on the task.

[103:19]

Or at least we're not going to show that with this class name.

[103:22]

Next, what we're going to do is have the onClick handler.

[103:25]

So that's going to be in the div as well.

[103:26]

So let's fix this here.

[103:28]

So we're just going to fix the styling okay.

[103:31]

And we're going to say

[103:33]

like this onClick is equal to same thing.

[103:37]

Or we're going to say can edit question mark if we can edit.

[103:41]

So we're going to call the on edit function.

[103:44]

So on edit with the task.

[103:47]

Otherwise we're going to say undefined okay.

[103:50]

Then we're going to have another div inside of here.

[103:53]

For the div we're going to have class name equal to.

[103:56]

And we're going to start with the header.

[103:57]

So we're going to say task card header.

[104:00]

Inside this div we're going to have an h h4.

[104:04]

So let's write that for the H4.

[104:05]

We're just going to put the task name.

[104:07]

So we're going to say class name is equal to task card dash title.

[104:13]

And then inside of here we're going to have task dot title

[104:18]

okay.

[104:18]

Now underneath the H4 we're going to have a can delete.

[104:21]

So if we can delete. So we're going to show a delete button.

[104:23]

So we're gonna say can delete.

[104:24]

And and and then we're going to put a set of parentheses.

[104:28]

We're going to put a button for the button.

[104:30]

We're going to say the class name is equal to.

[104:35]

And this is going to be task dash card dash btn.

[104:38]

And then task dash card dash btn dash delete.

[104:44]

Okay.

[104:45]

Now let's just fix the classes a little bit here.

[104:48]

Beneath this we're going to have the onclick.

[104:50]

The onclick is going to be the following.

[104:52]

So we're going to have e okay.

[104:54]

Then we're going to go into a function.

[104:56]

Now inside of the function we're going to say E dot stop.

[104:59]

And this is propagation okay.

[105:01]

Then after that so we're going to call the on delete function

[105:05]

that's passed into the parent with the task.id to delete it okay.

[105:11]

Then we're going to continue and we're going to have title.

[105:14]

And the title is going to be delete

[105:17]

task like that for this particular button.

[105:20]

And then we're just going to simply have an X inside of the button.

[105:23]

That's what we're going to render on screen okay.

[105:25]

So if we can delete we're going to show that.

[105:27]

And then beneath this we're going to say task dot

[105:30]

description just to make sure it does have a description.

[105:33]

If it does then we're going to show the description.

[105:35]

And for that we'll just have a paragraph tag.

[105:38]

We'll say class name

[105:40]

is equal to.

[105:41]

And this can be task dash car dash

[105:44]

description like that.

[105:47]

And then for the description we can just show the task dot description okay.

[105:52]

So that should be it for the task card.

[105:54]

Then what we can do is export this.

[105:56]

So exports default okay.

[105:59]

And then task card like so.

[106:02]

And then we can start using the task card okay.

[106:05]

So as well as the task card now we're going to create the task column

[106:08]

because we're going have three columns full of task cards.

[106:11]

So we're going to say task column dot J asks for the task column.

[106:16]

This is just going to display the various task cards.

[106:18]

So we're going to say import task card from there okay.

[106:22]

We're then going to say const status underscore labels is equal to.

[106:28]

And then we're going to have the label.

[106:29]

So we're going to have pending is equal to to do

[106:35]

we're going to have started

[106:37]

which is equal to in progress.

[106:41]

And then we're going to have completed which is equal to done.

[106:45]

Okay.

[106:45]

Then we're going to have function task column.

[106:49]

This is the component we're going to take in status.

[106:51]

We're going to take in the tasks.

[106:53]

We're going to take an on edit and on delete as those functions.

[106:58]

And then we're going to return the UI Sorento, return a div

[107:01]

the class name

[107:03]

is going to be for this first div,

[107:06]

the Kanban dash column okay.

[107:09]

Now inside of this div we're going to have another div.

[107:12]

So for the first one we're going to have class name.

[107:15]

And this is going to be inside of Backticks because it's going to be dynamic.

[107:18]

It's going to be Kanban dash

[107:21]

column dash header.

[107:25]

And then Kanban dash column.

[107:28]

Let's spell column correctly dash header dash.

[107:32]

And then we're going to put the status.

[107:34]

So whatever the actual status is we're going to use that in the class name.

[107:37]

So that we can show a different color. What do you call a board.

[107:39]

Kind of based on what the status is.

[107:42]

Then we're going to have an H3 tag for the name of the column.

[107:45]

So we're going to have a class name for the H3.

[107:48]

And the class name is going to be the Kanban

[107:51]

dash column dash title.

[107:54]

And what we're going to do is we're going to use the status labels,

[107:58]

and we're going to pass in the status that we get kind of the string representation.

[108:01]

And we actually want to show in the header.

[108:04]

And then we're going to have a span that's just going to show the number of tasks that are here.

[108:08]

So we're going to have class name equal to Kanban

[108:12]

dash column dash count.

[108:16]

And inside of here we're going to have the tasks dot length okay.

[108:21]

Now beneath that we're going to have a div.

[108:24]

We're going to say class name is equal to Kanban

[108:27]

dash column dash body.

[108:31]

This is where we're actually going to display the tasks.

[108:33]

And we're effectively just going to take all the tasks.

[108:35]

And we're going to map them to their component.

[108:37]

So we're going to say task okay.

[108:38]

And then this is going to map to the task card.

[108:41]

So we're going to say task card

[108:43]

like that.

[108:44]

Now for the task card we need to write all of the values.

[108:47]

So let's actually just put this in parentheses okay.

[108:52]

Put another set of parentheses and put this down here so that we can actually write it properly.

[108:57]

So we're going to say key is equal to task.id.

[109:02]

Then we're going to say the task is equal to the task.

[109:06]

We're going to say on edit is equal to on edit.

[109:09]

And we're going to say on delete is equal to on delete okay.

[109:15]

And that's going to render the tasks. And then that's it.

[109:17]

We can just export default the task column

[109:21]

from this component or from this file. Perfect.

[109:24]

So now we have the task column.

[109:25]

The task column renders the task cards.

[109:27]

Now we need essentially like a Kanban kind of layout.

[109:31]

So we can render the Kanban layout which renders the columns, which renders all of the tasks.

[109:36]

So let's write another component.

[109:39]

This time it's going to be called the can.

[109:41]

Then board dot JSX.

[109:45]

And inside of here we're going to start writing all of the kind of complex logic

[109:48]

with the functions to actually pull the tasks, display them in the correct columns, etc..

[109:53]

So we're going to say import.

[109:55]

You state from, and then we're going to go to react.

[110:00]

Then we're going to say imports

[110:03]

use organization

[110:06]

from at clerk slash clerk react.

[110:10]

Then we're going to import the task column from

[110:16]

dot slash task column okay.

[110:19]

And I don't need the JSX there.

[110:21]

Then I'm going to import the create task

[110:26]

update task okay.

[110:28]

As well as delete task.

[110:31]

Let's write this from the services okay.

[110:35]

So from dot dot slash services slash API

[110:38]

because that's where we wrote these different functions also.

[110:41]

Now we're also going to need to have a form for creating the tasks.

[110:45]

But we will write that in one second.

[110:47]

And then we're going to have const and in capital we're going to have status.

[110:50]

Is is going to be equal to pending

[110:54]

started

[110:56]

and completed as it matches up with our backend.

[110:59]

Now we're going to have the components.

[111:00]

We're going to say function Kanban board

[111:03]

here.

[111:04]

We're going to have the tasks.

[111:06]

We're going to have set tasks.

[111:08]

And we're going to have get token as the functions that we need.

[111:13]

Now inside of here we're going to say const

[111:15]

membership is equal to use organization.

[111:20]

And it's going to tell us what our role is within the organization.

[111:23]

So if you want to get the role of the particular user you can use this use organization hook from clerk.

[111:28]

And then you can pull membership.

[111:30]

So previously we were looking at the organization itself here.

[111:33]

However we can also get the membership.

[111:35]

And that will tell us if they're a member and had been an editor.

[111:38]

Whatever their role in the org is as you've defined in clerk, when you set it up,

[111:43]

we're also going to have const

[111:45]

show form set, show form.

[111:47]

This is going to be for showing the creation form, which we will write in a second.

[111:51]

So we're gonna say use state.

[111:53]

And that's going to be false.

[111:54]

And we're then going to have const editing task

[111:58]

okay.

[111:59]

Set editing task.

[112:03]

And this is going to be equal to use state and false.

[112:07]

And then let's get rid of that.

[112:09]

We're going to get the roles.

[112:10]

So we're going to say const role is equal to membership question mark dot role.

[112:14]

So this is going to give us their role in the organization.

[112:16]

We're going to say const can manage.

[112:19]

So essentially can they edit is equal to role equal equal to.

[112:23]

And then we're going to say org colon admin.

[112:25]

So this is the role if they're an admin in the organization or role

[112:30]

is equal to org colon editor which is the other role that we created.

[112:35]

If you add your own custom roles, all you would do is just change what you see here after the colon

[112:40]

colon.

[112:40]

Sorry to that custom roles name right as you saw in Clarke.

[112:44]

Okay then we're going to have function get tasks by status.

[112:50]

We're going to take in a status.

[112:52]

And we're going to return tasks like this dot filter.

[112:57]

And then task.

[112:59]

And we're going to go task dot

[113:01]

status is equal to the status.

[113:04]

So this is just going to filter all the tasks in our task list and give us

[113:08]

the ones that are a particular status, so we can put them in the correct column.

[113:11]

Then we're going to have a function.

[113:13]

And this is going to be handle edit.

[113:17]

We're going to have a task.

[113:19]

And inside of here we're going to say set editing task.

[113:22]

And we're going to put the correct tasks that we're currently editing.

[113:26]

We're going to say set show form to true okay.

[113:29]

So actually I need to change this state to say no.

[113:32]

So essentially the task that we're currently editing, we're going to store in this variable right here.

[113:37]

The reason we need that is because in a second we're going to pop up a form.

[113:41]

It's going to show the information for the tasks that we either want to create or edit.

[113:45]

So we have two variables right? One okay.

[113:47]

Should we show the form on screen. Yes or no.

[113:49]

And if we are showing the form do we have a task we're currently editing.

[113:53]

If we do, we'll put that inside of the form.

[113:55]

So we can edit that particular task.

[113:57]

Okay. Now a few other functions that we're going to need.

[113:59]

So for example like deleting the task etc..

[114:02]

So what I'm going to do here is say function.

[114:05]

And this is going to be actually an async function because we're going to call our back end.

[114:09]

So we're going to say async function handle delete

[114:13]

this is going to take in a task ID to delete.

[114:18]

And what

[114:18]

we're going to do here is we're going to say if not confirm

[114:22]

and we're going to say are you sure you want

[114:26]

to delete this task to work?

[114:31]

Okay.

[114:31]

And we're just going to return.

[114:33]

So if they didn't confirm this because this is just going

[114:36]

to pop up on the window, essentially ask them, hey, like do you want to delete this or not?

[114:39]

So if they don't confirm it then we will not delete the task and we will return.

[114:43]

Otherwise what we'll do is we'll say const task to delete

[114:47]

is equal to tasks

[114:50]

dot find.

[114:52]

And we're going to find the task with the particular id tid

[114:56]

is equal to the task id like.

[114:59]

So we're going to say set tasks.

[115:02]

And then we're going to say previous.

[115:03]

And we're just going to remove this task from the screen so that it goes away right away.

[115:07]

We're going to say previous dot filter.

[115:09]

And then t t.id does not equal the task id.

[115:13]

So effectively what we're doing is we're just finding the task that we want to delete.

[115:17]

And then we are just removing that task from the task list

[115:20]

immediately so that it no longer shows on screen.

[115:24]

So we're then going to say try await

[115:28]

and then delete task like so

[115:30]

when we delete the task, we're going to pass in the get token function.

[115:34]

And the get token function is right here because we're passing it to this component.

[115:38]

We're also going to pass in the task ID that we want to delete.

[115:42]

We then are going to catch any errors here.

[115:45]

So we're going to say catch error.

[115:47]

And we're going to say set tasks.

[115:50]

So if there was an error

[115:51]

and we're going to add this task back on the screen, we're going to say data dot previous.

[115:55]

And then the task that we originally were going to delete.

[115:58]

And we're going to say console dot error.

[116:01]

And we're just going to show the error like that okay.

[116:05]

So this is the handle delete function which I think is all good.

[116:09]

Now what we're going to do is handle the submit function,

[116:12]

which is how they're going to update a task or create a task for us.

[116:16]

Okay.

[116:16]

So we're going to say async function handle submit.

[116:20]

Again this is going to be for editing or deleting a person on deleting creating a task.

[116:24]

Sorry.

[116:24]

We're going to take in some task data okay.

[116:28]

What we're going to do here is we're going to say if we are currently editing a task.

[116:32]

So if we have one that we're editing, then what we're going to do is we're going to say

[116:36]

const updated task is equal to and we're going to say dot

[116:40]

dot dot editing task and then dot dot dot.

[116:44]

The new task data that we have.

[116:46]

So we're going to merge those two together to effectively update the task

[116:50]

that's currently on our UI.

[116:52]

We're then going to say it's set tasks.

[116:53]

We're going to take the previous list of tasks.

[116:56]

And we're going to say previous dot map.

[116:59]

And then we're going to do is say t okay standing for task

[117:02]

are going to say t.id

[117:06]

and this is going to be equal to editing task.id question mark.

[117:11]

If it is, we're going to put the updated task.

[117:13]

Otherwise we're just going to put the original task.

[117:16]

And that's going to update the tasks on screen for us.

[117:18]

We're then going to say set show form false and set

[117:23]

editing task to no.

[117:26]

Okay.

[117:26]

Now inside of this

[117:27]

if statement, we're then going to send the request to our back in to actually delete it.

[117:31]

So effectively what we're doing is we're updating the UI so it looks like it happens right away.

[117:35]

And then we're actually deleting the task by sending the request to the back end.

[117:40]

So we're going to say try await.

[117:43]

And this is going to be update task okay.

[117:46]

So this is calling that back end function right that we imported here.

[117:49]

So update task.

[117:51]

Now when we call sorry update not updated.

[117:54]

When we call update task we're going to pass in the get token.

[117:58]

The editing task.id

[118:01]

as well as the task data that we want to update it with.

[118:05]

Then we're going to catch any errors to fix this here.

[118:08]

So we're going to say catch error.

[118:10]

And for this we're going to say set tasks.

[118:13]

And then same thing.

[118:13]

We're going to restore the previous task if there was an error.

[118:16]

So we're going to say previous previous dot map t t.id

[118:23]

is equal to adding task.id question mark

[118:27]

adding task colon and then t okay.

[118:29]

So essentially we check is the current task.

[118:32]

So we're looking at the one that we were editing.

[118:34]

If it was okay just put the editing task back.

[118:36]

Otherwise just put the original task okay.

[118:39]

We're then going to say console error.

[118:41]

And we're just going to log the error so we can see the message okay.

[118:45]

Now if that's not the case.

[118:46]

So if we're not updating the task then that must mean that we're creating a new one.

[118:50]

So what we're going to do is say try const

[118:53]

new task is equal to await create task.

[118:57]

To create the task we're going to pass again that get token function as well as the task data.

[119:03]

We're then going to say set tasks.

[119:04]

And we're going to add this new task. So we're going to say previous.

[119:07]

And then this is going to be a list or say dot to dot previous.

[119:10]

And then the new task like that.

[119:13]

And then we're gonna say set show form

[119:16]

false like that to no longer show the form.

[119:19]

And then we can catch any errors.

[119:22]

And if we have any errors, we can just console dot error.

[119:27]

The error. Right okay.

[119:28]

So let's spell error correctly. Perfect.

[119:30]

So that should be good for essentially handling these submit.

[119:33]

So this is when they submit the form.

[119:35]

But we still need to have a few other functions.

[119:37]

So we're going to have the function.

[119:39]

Sorry to handle cancel.

[119:41]

So this is if they're canceling it edit or a creation.

[119:44]

So to handle cancel we're going to say set show form

[119:48]

is false.

[119:49]

And set editing task to no okay.

[119:53]

And then we're going to have a function.

[119:54]

And this is going to be handle add task.

[119:58]

Let's spell handle correctly.

[120:00]

What we're going to do here is we're going to say set editing task to null.

[120:03]

So if they're adding one that we're no longer editing one.

[120:06]

And then we're going to say set show form

[120:08]

to true to show the form that's going to allow us to start editing.

[120:12]

Now, I know we don't have that form yet.

[120:14]

We're going to put that on the screen in just one second.

[120:16]

For now, though, let's return kind of that empty Kanban board so we can at least have a look at it.

[120:21]

And then we can create that creation form.

[120:23]

So we're going to have div class name here.

[120:26]

This is going to be the Kanban dash wrapper.

[120:29]

Then we're going to have another div.

[120:31]

Inside this div we're going to have a class name which is equal

[120:34]

to the Kanban dash header.

[120:37]

Then inside of here we're going to have an H2.

[120:40]

For this. It's going to be called tasks.

[120:43]

And then we're going to have the class name equal to the Kanban dash title.

[120:49]

For this particular H2.

[120:50]

We're then going to have a can manage variables okay.

[120:53]

If we are able to actually manage the tasks that means we can create one.

[120:57]

So what we're going to show is the ability to create a task by adding a button

[121:02]

with the class name, which is equal

[121:04]

to btn btn dash primary.

[121:07]

Then we're going to have onclick which is equal to and this is going to be sorry

[121:13]

the skin handle add task like so.

[121:17]

And then for this we're going to say plus add task.

[121:19]

So essentially if you have the ability to edit.

[121:22]

So you can create a new task, then we will show you this button where when you press the button

[121:26]

effectively we're going to call this where it will show the form and let you make a new task okay.

[121:31]

So let me go to that div because that's the header.

[121:32]

And let's make another div.

[121:34]

This time we're going to say class name is equal to.

[121:37]

And this is going to be the Kanban board okay.

[121:40]

For the Kanban board what we're going to do is show three columns.

[121:44]

So we're going to say statuses right.

[121:46]

Dot map I think we called it statuses up here.

[121:49]

Let's check yes statuses dot map.

[121:52]

We're going to take the status and we're going to map

[121:54]

this to a task column component.

[121:57]

So here let's make sure the correct.

[122:00]

And we're going to put task

[122:02]

column which I believe we imported already for the task column.

[122:07]

Let's go down here.

[122:08]

We are going to have a key.

[122:10]

The key will be equal to the status.

[122:13]

Then we can have the status which is of course equal to the status.

[122:17]

Then the tasks for the task.

[122:19]

This is going to be the function get tasks by status.

[122:22]

And then we'll pass the status there to get all the tasks.

[122:27]

Then we're going to have on edits which is going to be equal to can

[122:30]

manage question mark, handle, edit.

[122:33]

Otherwise no.

[122:35]

And then we're going to have on delete which same thing is going to be equal

[122:38]

to can manage question mark handle.

[122:41]

And this is going to be delete.

[122:43]

And then otherwise no okay.

[122:46]

So only if we're able to edit are we going to have the ability to delete.

[122:49]

Or of course we'll edit the tasks.

[122:51]

And then down here we're going to have show form.

[122:54]

And and for now I'm going to write no.

[122:56]

And in a second this is where we're actually going to show the form that allows us to create new tasks.

[123:01]

Then we're going to say export default, the Kanban board okay.

[123:06]

So what we're missing here is the form to be able to create the tasks.

[123:11]

However, before we put that there I want to first show this Kanban board on screen.

[123:16]

In order to do that, I need to go to my pages and I need to go to my dashboard page,

[123:21]

and I need to quickly write the dashboard page

[123:22]

to display the Kanban board and also load all of our tasks.

[123:27]

So let's do that.

[123:29]

So we're going to go to the top for our Kanban or our dashboard sorry.

[123:32]

And we're going to say imports use state imports

[123:36]

use effect import

[123:39]

use call back from react.

[123:43]

We're then going to say imports

[123:45]

use of

[123:47]

use organization

[123:50]

as well as create organization from Clark Clark react.

[123:54]

Then we're going to say import get tasks okay.

[123:59]

And this is going to be from dot dot slash services.

[124:03]

So the file we write before slash API.

[124:05]

And we're going to import the Kanban board from the components file.

[124:10]

From the dashboard page.

[124:11]

There's a few things that we need to first we need that get token function.

[124:15]

So we can get the token from clerk.

[124:17]

So we're going to say const get token

[124:21]

equals use of.

[124:22]

Now this is how it works in Clarke.

[124:24]

You can use this hook called use auth which will give you access to a get token function.

[124:28]

We can call the get token function which will then give us the Clarke authorization token or JWT token,

[124:34]

which we can then pass to the back end to authenticate the request.

[124:38]

So that's where I'm getting it right from this particular hook then,

[124:41]

same as before I can say const organization

[124:45]

membership

[124:47]

okay is going to be equal to use organization.

[124:50]

And for this what I'm going to do is I'm going to say membership okay.

[124:56]

And then

[124:57]

infinite

[125:00]

is equal to true.

[125:01]

Now what this is going to allow me to do is get the membership status as well as the organization's.

[125:06]

Okay.

[125:07]

When I say memberships infinite true, what that's going to do is just allow us to display

[125:11]

all of the memberships that a particular user has, which is what we're looking for.

[125:15]

Okay.

[125:16]

So now what we're going to do is say const and then we're going to say tasks.

[125:20]

Set tasks is equal to use state.

[125:24]

And this is going to be an empty list for now because we haven't loaded the tasks.

[125:28]

Then we're going to say const loading. Set the loading

[125:33]

is equal to use state.

[125:35]

And to start the loading state is going to be true.

[125:39]

Then we're going to have const error.

[125:43]

And then we're going to say set

[125:45]

error is equal to use state.

[125:48]

And then for now this is going to be null.

[125:50]

Then we're going to say const member.

[125:54]

And this is going to be count is equal to membership.

[125:58]

Question mark dot count or zero.

[126:01]

So we can figure out how many members are in the particular organization.

[126:05]

Because that's what we want to display on this page.

[126:08]

Then what we're going to do is we're going to say const

[126:11]

or guid is equal to organization question mark.id.

[126:16]

And now we're going to start writing some functions to load the task.

[126:19]

So we're going to say const load tasks okay this is a function.

[126:24]

So we're going to say use callback.

[126:28]

And this is going to be async.

[126:30]

And then inside of here for this particular function we're going to say try.

[126:34]

We're going to say set loading is equal to true.

[126:38]

We're going to say set error okay.

[126:41]

It's errors will correctly.

[126:42]

Yes it is too. No.

[126:44]

Then we're going to say const data is equal to await

[126:48]

get tasks which is the function we're calling from that file we wrote before.

[126:52]

Now we're passing that get token function.

[126:54]

So we can actually get the clerk token.

[126:57]

We're going to catch on error.

[126:59]

And we're going to say set error.

[127:01]

And this is going to be error dot message.

[127:05]

And then we are going to go here to a finally block.

[127:09]

And for the finally we're going to say set loading equal to false.

[127:12]

Because no matter what we want to stop that loading state.

[127:15]

Then what we're going to do is just remember to set the tasks with the data, because we need to do that.

[127:20]

Otherwise this isn't going to update anything for us.

[127:22]

And we're going to put in the dependency array of this callback the get token function,

[127:27]

so that if that update that we will do this again to get the new tasks for this new token,

[127:32]

which will be based on the organization that the user is currently active inside of,

[127:37]

as well as which user is signed in, then we need a really quick use affect hooks

[127:41]

so that we can run this automatically when the component mounts.

[127:45]

So in the dependency array we're going to put the org ID

[127:47]

as well as the load tasks function.

[127:52]

And then what we're going to do here

[127:54]

is we're going to say if org id.

[127:57]

So if we do have an organization load tasks

[128:02]

okay.

[128:03]

Otherwise we're going to say set loading to false okay.

[128:09]

And load tasks.

[128:10]

Is this function right here. Perfect.

[128:12]

So we're essentially doing a user effect. We're saying all right right.

[128:14]

When it loads.

[128:15]

If we have an organization ID we'll try to load the tasks.

[128:18]

If we don't we don't need to load anything

[128:20]

because there's no task to load unless we're inside of an organization.

[128:23]

Now quickly we're going to say, if you are not in any organization,

[128:30]

then what we need to do is return just a quick little UI to show you something.

[128:34]

So we're going to say class name is equal to dashboard

[128:39]

dash container.

[128:41]

Then we're going to have another div with class name equal

[128:44]

to no dash org dash container.

[128:48]

Then we're going to have an H1.

[128:50]

This is going to have a class name which is going to be equal

[128:52]

to no dash org dash title.

[128:56]

And then we're going to say welcome to task board which is going to be the name of the app.

[129:01]

Right.

[129:02]

Then we'll put a paragraph tag and we're going to say class

[129:05]

name is equal to no dash org dash text.

[129:11]

And then inside of here we're going to say create

[129:13]

or join an organization

[129:17]

to start managing tasks with your team okay.

[129:24]

And let's spell managing correctly.

[129:26]

Now after that paragraph tag we're just going to show the create

[129:30]

organization form

[129:33]

from clerk, which I think was right there.

[129:36]

Perfect. So create organization.

[129:38]

And after create organization

[129:41]

URL is going to be equal to slash dashboard which I know is the page we're currently on.

[129:47]

But it will refresh the page okay perfect.

[129:49]

So that's if you don't have an organization.

[129:51]

Now if you do have an organization, we're just going to simply show the dashboard with the Kanban page.

[129:56]

So we're going to say div class name is equal

[129:59]

to the dashboard dash container.

[130:03]

Then inside of this div we're going to have another div for the header.

[130:07]

So we're going to say div class name is equal to

[130:11]

and this is going to be the dash board dash header.

[130:15]

Then we're going to have a div with no class name.

[130:17]

And here we're going to have an h1 for the h1.

[130:20]

We're going to put the name of the organization.

[130:22]

So we're going to say organization dot name.

[130:25]

And this is going to have a class name.

[130:27]

The class name is going to be equal to dashboard dash title.

[130:32]

Then we're going to have a paragraph tag.

[130:35]

This is going to be class name equal to the org dash members.

[130:40]

Then we're going to show the member counts.

[130:42]

We're going to say member count.

[130:45]

And then we're going to say member.

[130:48]

And then we're going to put the member count does not okay.

[130:53]

So it does not equal one question mark.

[130:55]

We're going to have an S.

[130:56]

Otherwise we're going to have an empty string.

[130:58]

So we're going to say like one member or two members or three members.

[131:01]

So plural or not plural depending on if we have one or more.

[131:05]

Okay.

[131:05]

So this is our little header component right here.

[131:08]

Now beneath that we're going to say loading question mark.

[131:11]

So if we are loading we're going to quickly show let's do this.

[131:15]

We're going to quickly show a loading paragraph tag.

[131:18]

So we're going to say a paragraph class name

[131:22]

is equal to text dash muted.

[131:25]

And we're just going to say loading tasks dot dot dot.

[131:30]

Otherwise we're going to say is there an error.

[131:33]

If there is an error there were quickly going to show what that error is.

[131:36]

So we're going to put a div.

[131:38]

We're going to say class name is equal to.

[131:41]

And this is going to be card dash error.

[131:44]

And then we're going to have a not div but actually a paragraph tag

[131:48]

with class name equal to text dash error.

[131:52]

And then this is inside of here text dash error dash title.

[131:59]

And we're going to say error

[132:00]

like this loading tasks.

[132:04]

And then we're going to show what the error actually is.

[132:06]

So we're going to do another paragraph tag and same thing.

[132:09]

We're going to have class name is equal to text dash error.

[132:14]

And then this is going to be text dash error dash message.

[132:18]

And we're going to put this particular error okay.

[132:22]

Now if that's not the case.

[132:24]

So if it's not loading and we don't have an error we're finally going to show the Kanban board.

[132:29]

So we're going to show Kanban board.

[132:31]

For the Kanban board we need to pass in the information.

[132:34]

So we're going to pass in the tasks which is equal to tasks.

[132:39]

We're going to pass in set tasks which is equal to the set tasks state.

[132:44]

And we're gonna pass in the get token which is equal to that get token function okay.

[132:50]

So that should now load all of the tasks for us and display them in the dashboard.

[132:55]

Let's try it out by signing into our account and seeing if it works.

[132:59]

Okay. So I just signed into my account.

[133:00]

I also just created a new organization called test, and you can see that

[133:04]

we now have the Kanban state we have to do in progress and done.

[133:07]

It's all showing up properly for us.

[133:09]

And if we decide to maybe make a new organization, test one, two, three or something,

[133:14]

create organization and notice that this changes.

[133:17]

Now right in the background to test one, two, three.

[133:20]

And we can change between the different organizations.

[133:22]

And the dashboard page will update accordingly.

[133:24]

Now we need to make the form to create a new task.

[133:27]

Let's go ahead and do that.

[133:28]

So let's go to components.

[133:30]

Let's make a new file.

[133:31]

And this is going to be called the task form dot Json.

[133:35]

Now for the task form we're going to start by importing what we need.

[133:39]

So we're going to say import use state

[133:42]

and use effect from react.

[133:46]

Or I'm going to say function

[133:49]

task form.

[133:50]

And we're going to have the if we can write this correctly

[133:54]

task on submit and on cancel.

[134:00]

Now what we're going to do here is say const and we're going to say title

[134:05]

set title is equal to use state.

[134:08]

So we're going to start putting in all of the fields.

[134:10]

So we need to create a new task.

[134:11]

We need a title a description and a status.

[134:13]

So we have the title.

[134:14]

Now we're going to do the description. So const

[134:18]

description set description is equal to use state.

[134:23]

And then same thing empty string.

[134:25]

Then we're going to have const status.

[134:28]

Set status is equal to use state.

[134:31]

And by default we'll just start it in the pending status.

[134:35]

Then we're going to have const is

[134:38]

editing is equal to not not task okay.

[134:42]

So do we already have a task.

[134:44]

If we do then we're editing.

[134:45]

If we don't have a task we are not editing.

[134:48]

We're then going to have a use effect.

[134:51]

And we're essentially going to check, okay, are we currently editing the task

[134:54]

if we are currently editing the task.

[134:55]

So we need that in the dependency array, then we need to update the state

[134:59]

here with what is passed from this task.

[135:02]

So we're going to say if

[135:05]

task okay then what we're going to do is say set title.

[135:09]

And this is going to be tasked title.

[135:12]

We're going to say set description.

[135:14]

And this is going to be task dot description okay.

[135:19]

Or and then this is an empty string.

[135:22]

Then we're going to say set status.

[135:25]

And this is going to be task like oops let's go fix this task

[135:31]

dot status like so okay.

[135:33]

We're then going to have an else in the else we're going to say set title to an empty string.

[135:38]

So this is if we're not editing then we just want to make sure that it's empty.

[135:41]

We're going to say set description to an empty string.

[135:44]

And we're going to say set status equal to pending.

[135:49]

Okay.

[135:50]

So this is the beginning of our form again just handling if we're editing or not.

[135:53]

Because we could be pass some data in this task variable here.

[135:57]

That is what we want to populate the form with.

[135:59]

To start.

[136:00]

Then we're going to have a function handle

[136:03]

submit on handle submit we're going to have e.

[136:07]

What we're going to do is say e dot prevent default.

[136:10]

So we don't refresh the page.

[136:12]

We're going to say if not title dot trim then return.

[136:17]

We need to have a title to be able to create a task.

[136:21]

And we're going to say on submit, which is the function that's passed here.

[136:26]

And we're going to pass to this the data.

[136:28]

Sorry, I say title is titled trim.

[136:32]

We're going to say description is description.

[136:35]

So let's go. Here is description dot trim

[136:39]

or no.

[136:41]

And then we're going to pass the status okay.

[136:44]

So that's our on submit.

[136:46]

Now we need to render the actual form.

[136:47]

So we're going to say return div for the first div.

[136:51]

This is going to be a modal.

[136:53]

So what I'm going to do is say class name

[136:56]

is equal to modal dash overlay okay.

[137:00]

Let's spell that correctly.

[137:01]

We're going to have onclick is equal to on cancel.

[137:06]

So if you click outside of the modal that's what this is going to be.

[137:09]

Then it will close it.

[137:10]

We're then going to have a div.

[137:13]

We're going to have the class name for this div to be the actual modal itself.

[137:16]

So essentially we have like the modal background and then the modal.

[137:19]

If you click outside of the modal which is this then we'll close the modal.

[137:23]

Now we're going to have an onclick in the modal.

[137:25]

So we're going to say onclick is equal to and

[137:28]

this is going to be sorry e.

[137:31]

And then this is going to be E dot stop propagation

[137:35]

okay.

[137:36]

Now we're going to go inside of this div.

[137:38]

We're going to have another div.

[137:40]

We're going to have class name is equal to.

[137:43]

And then this is going to be the modal dash header.

[137:47]

Then we're going to have the H2.

[137:49]

We're going to have class name is equal to.

[137:52]

And this is going to be the modal title.

[137:54]

And what we're going to do is say okay well if we are editing.

[137:58]

So if is editing then we're going to put edit task.

[138:02]

Otherwise we're going to put new task as the title for this modal.

[138:07]

Now beneath that we're going to have a button.

[138:10]

This button is going to have a class name which is equal to the modal dash.

[138:15]

Close to close the modal we're going to have an onClick

[138:19]

which is going to be equal to on cancel.

[138:24]

And then we're going to have an X okay.

[138:26]

So that's the x button in the header.

[138:28]

Now beneath that we're going to have a form.

[138:30]

The form is going to have an on submit.

[138:33]

The on submit is going to be the handle submit function that we already wrote inside of the form.

[138:37]

We're going to have some form groups.

[138:39]

So we're going to have a first one.

[138:40]

So we're going to have div class name is equal to form dash group.

[138:44]

Then for the first form group

[138:47]

we are going to put the title okay.

[138:49]

So let's put that here. We're going to say label.

[138:53]

And this is going to be class name is equal to form dash label.

[138:58]

We're going to say HTML for.

[139:00]

So HTML for

[139:02]

is equal to label or is equal to title.

[139:05]

Sorry the label is going to say title.

[139:08]

And then beneath that we're going to have an input box.

[139:11]

So we're going to say input.

[139:13]

And for the input we're going to say ID

[139:16]

is equal to title.

[139:18]

We're going to say it type is equal to text.

[139:22]

We're going to say class name is equal to the form dash input.

[139:26]

We're going to say the value

[139:29]

is equal to the title.

[139:33]

So let's do that there.

[139:34]

We're going to say on change

[139:37]

is equal to and this is going to be E.

[139:40]

And then this is set title E dot

[139:43]

target dot value okay.

[139:47]

Then we're going to have the placeholder which is equal to enter

[139:53]

task title.

[139:55]

And then we're going to have autofocus true

[139:59]

okay.

[140:00]

So this is the first input that we need.

[140:02]

Now we're going to copy this entire one.

[140:03]

And we're going to go to the second input which is the description.

[140:06]

So same thing form group form label.

[140:08]

This time we're going to write

[140:10]

description.

[140:12]

Then we're going to put description here.

[140:14]

So description and just spelled description

[140:19]

correctly okay.

[140:21]

Now rather than the ID being title this is going to be

[140:25]

description.

[140:26]

Now this time rather than input it's going to be a text area.

[140:30]

The ID is description. We can remove the type.

[140:32]

The class name is going to be format dash text area.

[140:37]

The value is going to be the description.

[140:41]

Again I keep spelling description incorrectly.

[140:43]

Rather than enter the task title, we're going to say enter

[140:47]

description

[140:50]

okay.

[140:50]

And then this is going to be optional.

[140:53]

Let's spell description correctly.

[140:55]

And rather than set title this is going to be set description okay.

[140:59]

So that's the description.

[141:00]

And then lastly we need to handle the status.

[141:03]

So same thing.

[141:04]

Let's copy this just to give us a head start.

[141:07]

Let's paste it.

[141:08]

We'll have the label again.

[141:09]

And this time we just change it to say status.

[141:13]

So status.

[141:15]

And here this will say

[141:18]

status now rather than a text area.

[141:21]

I'm actually just going to delete all of this because we're going to use a select box.

[141:24]

So we're going to say select.

[141:26]

And then we're going to say inside of the select.

[141:28]

Here the ID is equal to status.

[141:32]

We're going to say the class name is equal to a form dash.

[141:37]

Select.

[141:38]

We're then going to say the

[141:40]

value is equal to the status.

[141:44]

And we're going to say onchange is equal to same as before E.

[141:48]

And then we're going to say set status E dot target okay.

[141:53]

Target dot value.

[141:57]

And then inside of the select we're going to put these select options.

[142:00]

So we're going to say option value is equal to pending

[142:07]

as the first value.

[142:08]

And then for the text we'll just put to do

[142:11]

we can copy this two times and then put the other options.

[142:15]

So rather than just pending we're going to have started

[142:19]

and we're going to have completed.

[142:21]

And then for this we'll have

[142:24]

in progress and we'll have done.

[142:27]

And then lastly we just need one more div here to actually, what do you call it.

[142:31]

Complete the action.

[142:33]

So we're going to say div class name is equal to form actions.

[142:37]

Because this is where we're going to put the submit button.

[142:40]

We're then going to put a button for the button.

[142:43]

We're going to say type is equal to button.

[142:48]

We're then

[142:48]

going to say class name is equal to btn btn dash outline.

[142:54]

We're going to say onclick is equal to and then this is going to be on cancel.

[142:59]

This button is going to say cancel.

[143:01]

And then we're going to have one more button.

[143:02]

Let's copy this one here.

[143:04]

This one is going to say the following.

[143:06]

So if we're editing so we're going to say is editing Questionmark.

[143:12]

Then we're going to say save changes.

[143:15]

Otherwise we're going to say create task.

[143:18]

And then rather than the outline this is going to be BTN btn primary.

[143:22]

And we don't need an onclick because this will be handled in the form submission

[143:26]

because we're going to change the type Tuesday submit.

[143:29]

And that should complete this form.

[143:32]

All right.

[143:32]

So now that the task form is created what we're going to do is just export this.

[143:36]

So export default task form and we're going to bring it in to our

[143:41]

I think is the Kanban page where we want to display it. Yes.

[143:44]

So we're going to first import it from the Kanban page.

[143:46]

So let's go import task form from task form.

[143:52]

And we're going to go down here where we say show form.

[143:55]

And we're going to show the task form.

[143:58]

And pass the props that we need.

[144:00]

So we're going to say task is equal to and this is going to be editing task.

[144:04]

Then we're going to have the on submit.

[144:07]

So on submit is equal to handle submit.

[144:11]

And then on cancel is going to be equal to handle cancel.

[144:16]

And let's fix the formatting a little bit so that we can actually read this.

[144:20]

We're not going to call these functions.

[144:22]

We're just going to pass the function names.

[144:24]

And there we go.

[144:25]

We now have the form.

[144:26]

So let's test this out and see if the form works okay.

[144:29]

It looks a little bit messed up.

[144:31]

So I think I probably messed up some of the CSS.

[144:33]

Let me see what I did wrong there and I'll be right back.

[144:36]

Okay.

[144:36]

So it looks like I have this whole form not actually inside the modal.

[144:40]

So I need to essentially copy this whole form.

[144:44]

And we're just going to delete it from there and place it inside of the modal div.

[144:49]

So it gets moved inside there okay.

[144:51]

So that's good.

[144:52]

Now if we go back and we go add task, you can see that shows up as a proper modal okay.

[144:57]

So let's try to add a task.

[144:59]

Let's go.

[144:59]

Hello world new task and create it.

[145:04]

And we can see that nothing is happening okay.

[145:06]

So let's check our console and see if we're getting any errors here.

[145:10]

And it says failed to fetch.

[145:11]

You know there's a cause error that's been blocked.

[145:15]

Okay.

[145:16]

Let me have a look at this and I'll be right back.

[145:18]

Okay. So I just found the error here.

[145:19]

Essentially in our back end in our task schema we have an ID

[145:24]

which is an int which actually needs to be a string.

[145:28]

So if we change this to a string it should fix it.

[145:31]

We were getting an internal server error that was saying

[145:34]

unable to pass in as string.

[145:37]

So we just need to fix that, which I just did.

[145:39]

Again go to backend, go to app schemas task, and then simply switch the ID

[145:44]

in the task response from an end to a string.

[145:48]

Now if we go back here, let's just refresh and try it again.

[145:51]

And you can see these tasks get created and actually were created.

[145:55]

They simply, were not being shown on the screen because we were getting an error.

[145:58]

Now we also have an error with this X button that I want to fix.

[146:01]

So I'm going to look at the styling for that.

[146:03]

Let's try to delete it.

[146:05]

Looks like it deletes successfully.

[146:07]

Let's try to edit.

[146:08]

It looks like we can edit it as well. Perfect.

[146:10]

So it's working.

[146:11]

And let's just try to add another task.

[146:13]

So let's go test okay.

[146:16]

Hello.

[146:17]

Create this task and we have hello.

[146:20]

Okay.

[146:22]

Let's just refresh here and.

[146:24]

Yeah it looks like we are good.

[146:25]

Yes. Test. Hello. Like that.

[146:27]

Perfect.

[146:28]

Okay, so let's now fix the styling because this is a little bit messed up on the card with the X button.

[146:32]

And the description kind of showing here.

[146:34]

And then we'll go from there okay.

[146:35]

So the issue is just that we have the description inside of the wrong div in our task card.

[146:40]

So we're just going to move it out here like that.

[146:44]

Now in order to fix this just go to the task card,

[146:48]

scroll down to where you see the description and just move it out.

[146:50]

One div.

[146:51]

So we had it inside of this div the header. We don't want it in the header.

[146:54]

We just want it outside of that.

[146:56]

And then we should be good to go. And if we come back here

[146:58]

you can see now the description is below and the X button is in the top right.

[147:01]

Which is exactly what we're looking for.

[147:04]

So at this point the tasks are working.

[147:06]

Now this member account doesn't seem to be working which we can have a look at.

[147:09]

But the next thing we need to do is have a look at actually increasing the organization member limits

[147:15]

and the pricing so we can have subscriptions and billings, etc..

[147:19]

So let's change over to this org.

[147:21]

And I just want to show you that if I go into an organization here

[147:24]

and I go to manage for example, and members, I can invite members.

[147:28]

So let me invite some more members and show you what happens when we try to invite more than two.

[147:32]

Okay, so I just invited Tim.

[147:33]

AD dev launched us one of my other emails, and when I went to this email, you can see

[147:37]

this is the email I received right where it says, hey, development invitation to join test.

[147:42]

I'm going to accept that invitation.

[147:44]

When I accept the invitation, it should actually redirect me back to my front end.

[147:47]

And you can see that it does and I am.

[147:50]

Let's have a look at it here. Signed in with the other account.

[147:52]

So let me just sign out and sign in with this new account that I accepted the invite from.

[147:58]

Okay.

[147:59]

So I actually just accepted the invite again because since I was already signed in, it gave me an error.

[148:03]

But when I accepted the invite when I wasn't signing so I just signed out.

[148:06]

You can see you just asked me to fill in a password, so I'll just use that password.

[148:10]

That's fine. Press continue.

[148:12]

And now you can see that I'm signed in as Tim at Dev launched us.

[148:16]

And the only organization I'm in is test in which I am a member.

[148:20]

So let me now sign out of this account.

[148:21]

Or actually let's go to the dashboard so we can see the same task board as we saw before.

[148:26]

But notice that I can't do anything.

[148:28]

I can't edit or delete or add a task because I'm just a member.

[148:32]

I'm not an editor.

[148:33]

So let me sign out of this.

[148:35]

Let me sign in with my other account.

[148:38]

Now you'll see that this Tim ad tech with Tim dot net.

[148:40]

If I go to the dashboard now,

[148:42]

I can actually change stuff because I am an editor or in this case an ad bin.

[148:46]

And what I'm going to do is try to invite another member.

[148:49]

So you could see Tim at Dev launch is invited.

[148:51]

And when I press invite and I go, for example, you know, Kenny at dev

[148:55]

launch us and I want to send that invitation.

[148:58]

It says I cannot because I reached a limit of two organization memberships.

[149:02]

So that's exactly what we want.

[149:04]

But what we want now is that we allow the user to actually create a subscription

[149:09]

where they buy our pro tier, which then allows them to invite more members.

[149:14]

So let's start setting that up inside of Clarke.

[149:16]

And I will fix this member account issue as well.

[149:19]

Okay.

[149:19]

So we're going to go back to Clarke.

[149:21]

We're going to go to configure.

[149:23]

And we're going to go to the building.

[149:25]

Now from the billing we have the subscription plans right.

[149:28]

So we created the pro tier.

[149:29]

Just make sure you have that.

[149:31]

Now all we need to do is actually use this pricing table component

[149:35]

directly inside of our website

[149:39]

to be able to display the different tiers that we have here.

[149:42]

And then a user will be able to purchase it.

[149:44]

So what I can do is I can go to pages, go to my pricing page,

[149:49]

and I can write a really simple component that just displays the different pricing options.

[149:54]

So in order to do that, I'm going to say import.

[149:57]

And then I'm going to say

[149:59]

use organization from Clark's Clarke React.

[150:03]

I'm also going to import the pricing table

[150:07]

as well as the create organization view from Clark Clarke react okay.

[150:12]

And actually all of this can just be together.

[150:13]

But I think we'll just leave it in two weeks.

[150:15]

It's a little bit easier to read.

[150:16]

We now have the pricing page.

[150:18]

From the pricing page I'm going to say const.

[150:20]

And this is going to be organization.

[150:23]

And then membership like this

[150:26]

is equal to use organization

[150:30]

okay.

[150:31]

And what we're going to do is we're going to make sure that this member

[150:33]

is an admin before we allow them to view this page.

[150:37]

Because only an admin should be able to manage the subscription of billing of the organization.

[150:42]

So we're going to say const is admin is equal to membership.

[150:45]

Question mark dot role is equal to org and colon admin

[150:51]

okay.

[150:52]

Now we're going to say if there is no organization because it's possible the user is not a part of any,

[150:58]

then what we're going to do is we're going to say return div.

[151:01]

We're going to say class name is equal to pricing dash container.

[151:07]

We're then going to say div

[151:10]

class name is equal to know dash

[151:13]

org dash container we're going to have an H1.

[151:18]

It's going to say view pricing.

[151:21]

The class name is going to be equal

[151:23]

to no org dash title.

[151:27]

Underneath the H1 we're going to have a p.

[151:30]

And we're going to say class name is equal.

[151:32]

To know it dash org dash text.

[151:35]

And the text here is going to say create or join an organization okay.

[151:41]

To view pricing plans

[151:45]

call and let's spell join correctly.

[151:47]

Then underneath this we're going to put the create organization modal

[151:53]

and we're going to say after create organization

[151:57]

URL is equal to slash pricing.

[152:03]

Cool.

[152:04]

So we have that now.

[152:05]

So if there's no organization it will show them this.

[152:07]

Otherwise we need to show them the pricing table.

[152:09]

So we're going to go here and we're going to say div we're going to say

[152:13]

class name is equal to the pricing dash container.

[152:20]

Then we're going to do a div.

[152:22]

We're going to say class name is equal

[152:25]

to the pricing dash header.

[152:28]

Then we're going to have an H1.

[152:30]

We're going to say class name is equal to the pricing dash title.

[152:36]

Then we're going to say simple transparent

[152:42]

pricing.

[152:42]

And we'll remove this comma because that doesn't make sense.

[152:45]

Underneath the H1 we'll put a paragraph tag and we'll say start free

[152:51]

with up to two members.

[152:54]

Upgrade to Pro for on limited members okay.

[153:00]

Then we're going to go beneath this div and we're going to check if they are an admin.

[153:05]

So we're going to say is admin question mark.

[153:08]

If they are an admin we're going to put a paragraph tag and we're going to say

[153:12]

sorry if they're not an admin because we have the exclamation point.

[153:15]

So if they're not admin we're going to have the class name equal to.

[153:19]

And this is going to be text dash muted and then pricing dash admin dash note.

[153:25]

And this is going to say contact your organization's admin

[153:30]

to manage the subscription.

[153:34]

And then otherwise okay so let's go here.

[153:38]

Otherwise what we're going to do is we're going to display the pricing table.

[153:42]

And it's important that we put four organization

[153:45]

because sometimes you can have individual pricing as well as organization pricing.

[153:50]

So in our case we just go organization.

[153:52]

And then we spell subscription correctly.

[153:55]

And now if we go to the pricing page we should see the pricing table.

[153:59]

So let's go here refresh let's go pricing.

[154:03]

And you can see

[154:04]

because we are the admin of the organization, it shows us the free tier as well as the pro tier.

[154:08]

And what we can do is we can subscribe to the pro tier.

[154:12]

So let's do that.

[154:13]

If we subscribe to the pro tier we can just pay with a test card.

[154:17]

So let's do that.

[154:19]

And when we pay with the test card we'll just go continue it.

[154:22]

We'll make the payment.

[154:23]

And now if we go back to pricing you can see that this pricing is active.

[154:27]

And we are pro.

[154:28]

So if we go back to dashboard okay.

[154:30]

It's giving us an error saying view permission required.

[154:33]

Let's see why we're getting that okay.

[154:36]

So I just had a look at this.

[154:37]

And the reason why we're getting that error is because if we have a look in the plans

[154:42]

here in our pro tier, we needed to add this tasks

[154:45]

as a feature to the organization in order for any of the other permissions

[154:50]

that we apply per the user roles to actually work so effectively.

[154:55]

What we need to do is we just have to go here to add feature and then just check on tasks.

[154:59]

And when we do that now all of this is good.

[155:01]

And this feature will be available to people that are in the Pro tier

[155:06]

now by default is available to the free tier.

[155:09]

So if you have a look here you can see it's already in there.

[155:11]

If we didn't want users to be able to create tasks in the free tier, we could just remove that feature.

[155:16]

But I need to add it to the pro tier so it's available there as well.

[155:20]

That's why we were getting that issue saying, hey,

[155:21]

you don't have permission to view this, but now it's working because I re-added that part.

[155:26]

Okay, so now the pricing is working.

[155:28]

If we go to the organization and we manage it, for example,

[155:33]

we can actually view the billing right now and we could manage it.

[155:36]

We can cancel the subscription.

[155:37]

We can change the plans.

[155:39]

We can view the billing history, the statements, the payments, the payment

[155:42]

method, all of that kind of stuff is handled directly by Clarke.

[155:45]

For us. Cool.

[155:47]

So let's quickly fix this members count and then let's move on to the next feature,

[155:51]

which is actually allowing the organization to invite more members.

[155:54]

Because right now, if I go here and I try to invite someone else,

[155:58]

you'll see Kenny at Dev launched us.

[156:02]

It's not going to allow me to do that because I haven't yet updated the organization

[156:06]

membership limits.

[156:07]

Okay, so the issue is pretty straightforward, but effectively

[156:10]

I forgot to write memberships instead of membership.

[156:14]

So I just need to change the same memberships.

[156:15]

And this the same memberships.

[156:17]

And now if we go back here and we have a look,

[156:19]

you can see it shows two members and the account is now updated.

[156:23]

Okay.

[156:23]

So next like I said, what we're going to do is make it so that we can actually update

[156:27]

the subscription account or update the number of members.

[156:30]

Sorry for a particular organization when it upgrades to pro.

[156:34]

So if you go to settings for example, inside of organizations, right,

[156:38]

you'll see that it says limited membership, but it shows us that we can actually update

[156:43]

the membership in the dashboard or the number of members or via Clarke's back end API.

[156:48]

So if we click into this, you'll see Clarke has all kinds of different APIs that you can use,

[156:52]

one of which allows you to actually change

[156:55]

the max allowed memberships per an organization.

[156:59]

So what we're going to do is we're going to create a webhook where when someone purchases the Pro

[157:04]

subscription and it's verified by Clarke, we send a request to Clarke

[157:08]

telling it to update this organization so that it can have more members.

[157:13]

Now, the way the webhook works is that we're going to set an endpoint on our server

[157:18]

that Clarke will send a request to to tell us, hey, this user just purchased Pro.

[157:22]

When they purchase Pro for an organization, we're then going to tell Clarke,

[157:26]

hey, go ahead and update the membership count.

[157:29]

They can have unlimited members.

[157:31]

So in order to do that we're going to go into Clarke.

[157:33]

We're going to go to developers and then webhooks.

[157:38]

Now when we go here, what we're going to do is create a new webhook.

[157:41]

So we're going to go add endpoint, and we're going to scroll down

[157:45]

and we're going to find let's have a look at this here subscription.

[157:49]

And we're just going to check check the box right here for subscription.

[157:53]

When we do that anytime anything changes with the subscription,

[157:57]

Clarke will send a request to the URL that we're going to put right here.

[158:01]

Now, in order for the webhook to work in development, we're going to use something called Ngrok.

[158:06]

Ngrok is free, and it works as a proxy that allows you to take a public IP address

[158:12]

and associate that with something running on localhost or your own computer.

[158:16]

So what you're going to need to do is download Ngrok.

[158:18]

I'll leave a link to it in the description here, so you can just search for Ngrok.

[158:21]

Once you download it, you're going to need to sign into an account.

[158:25]

So make a new account on ngrok and then sign into it in your terminal.

[158:28]

The way that you're going to do that is you're simply going to go in your terminal.

[158:32]

Once Ngrok is installed, you're simply going to type Ngrok.

[158:35]

When you do that, it's going to tell you to sign in to Ngrok.

[158:39]

In my case, I'm already signed in, so I don't need to do anything.

[158:42]

And when you try to sign in, it's going to prompt you to sign in with the browser

[158:45]

you can sign in, it will connect your account and then you are good to go.

[158:49]

So download and grok.

[158:50]

Make sure you create an account on the website.

[158:52]

Sign into that account

[158:53]

from your terminal by either just typing the command I'm about to type or typing ngrok.

[158:57]

It should prompt you to sign in and then type the command Ngrok Http

[159:02]

8000, where 8000 matches the port that your back end server is running on.

[159:07]

So port 8000.

[159:08]

Okay, when you do that, it's going to create a forwarding URL.

[159:12]

So this one right here is what you want to look at.

[159:14]

And now any requests sent to this URL will be forwarded to your back end API.

[159:20]

So localhost port 8000.

[159:22]

This allows us to test in development

[159:25]

mode the webhook which you're going to see in one second.

[159:28]

Okay.

[159:29]

So what I'm going to do now is I'm going to go to my clerk here and I'm going to paste this URL.

[159:35]

Make sure you remove all of the spaces.

[159:37]

And I'm going to put slash API slash webhooks like that.

[159:42]

I'm then going to put one more slash and I'm going to put click.

[159:47]

So now what will happen is anytime a subscription changes in my Clercq project, it's going

[159:52]

to send a request to this endpoint, which is one that we're going to write in one second in fast API.

[159:58]

So we're going to go ahead and press on create.

[160:00]

And then from here we're going to grab the webhook signing secret.

[160:03]

So we're willing to reveal that and copy it.

[160:06]

We're now going to go back into our code.

[160:08]

We're going to go into our back end now.

[160:10]

So no longer our front end.

[160:11]

We're going to go to our env file.

[160:14]

We're going to paste the clerk webhook webhooks are a secret.

[160:18]

Now this secret is going to allow us to actually verify that the requests that were sent

[160:24]

came from clerk.

[160:26]

Because anybody can hit this webhook that we're creating.

[160:29]

So we have this special key.

[160:30]

The clerk is going to send along with the request.

[160:34]

We're going to look at it.

[160:34]

We're going to go, okay, if you're actually clerk then you would have sent this key.

[160:39]

If it sends the key we know it's clerk. It's good.

[160:41]

We can actually verify that webhook requests, if they didn't send the key

[160:45]

or the key is wrong, then it means someone is trying to fake being clerk

[160:48]

and kind of get like a free pro account, which we're of course not going to allow them to do.

[160:52]

So that's why you need this signing secret to verify the webhook.

[160:56]

Okay.

[160:57]

So now what we're going to do is we're going to go into app API webhooks.py.

[161:02]

And we're going to start writing this file.

[161:04]

So we're going to say import

[161:06]

Json.

[161:07]

We're going to say from fast API import the API router

[161:13]

request Http exception.

[161:18]

Let's spell that correctly.

[161:19]

And status.

[161:20]

We're then going to say from civics civics dot webhooks import.

[161:27]

And we're going to import webhook

[161:29]

and webhook verification error.

[161:32]

We're then going to say from App Score dot config import settings.

[161:37]

So we can get the webhook secret.

[161:39]

And we're going to say from app score clerk import, click

[161:45]

okay.

[161:45]

Now what we're going to do here is we're going to create a router

[161:48]

because this is going to be an API endpoint.

[161:50]

So we're going to say API router.

[161:52]

And this is going to say prefix

[161:55]

is equal to slash API slash webhooks.

[161:59]

Let's spell webhooks correctly.

[162:01]

Then we're going to say tags is equal to webhooks.

[162:06]

Then we're going to say pro underscore tier

[162:09]

underscore slug is equal to pro underscore tier

[162:13]

which is what we're going to look for from the webhook data that is sent to us.

[162:17]

We're going to say the free tier underscore limit is equal

[162:21]

to the settings dot free tier limit.

[162:24]

And we're going to say the unlimited limit

[162:27]

is equal to.

[162:28]

And we're just going to put a really large number.

[162:30]

Because when we set unlimited number unlimited sorry.

[162:33]

Seed is not truly unlimited.

[162:35]

We're just going to set a really, really large value so that you can have as many members as you want.

[162:40]

We're then going to make a function.

[162:42]

So we're going to say define set underscore org

[162:45]

underscore member underscore limit.

[162:49]

We're going to say org underscore id

[162:51]

string and then limit int.

[162:55]

And then what we're going to do is say Clarke

[162:57]

dot organizations dot update

[163:01]

directly from the Clarke SDK.

[163:02]

And we're going to say the organization ID

[163:06]

okay.

[163:06]

So organization ID is equal to the org ID.

[163:08]

And the max allowed memberships

[163:13]

is equal to whatever limit is passed to this function.

[163:16]

And that's as easy as it is using

[163:17]

the Clarke backend SDK to just update an organization's membership limit.

[163:22]

Now we're also going to have a function that says has active Pro plan.

[163:28]

And we're going to say items

[163:31]

list.

[163:32]

And then what we're going to do is return a boolean.

[163:36]

Now from here we're going to say return

[163:40]

any and we're going to say item dot.

[163:43]

Get plan.

[163:46]

And then this is going to be an empty dictionary in case plan doesn't exist.

[163:51]

Dot get slug

[163:54]

is equal to the pro tier slug

[163:58]

and item dot get status

[164:03]

is equal to active for item in items.

[164:07]

Now the reason for this is what's going to happen is whenever the webhook is sent, it's

[164:11]

going to give us a bunch of data in that that data there's going to be a list of items.

[164:15]

We're going to look through those items to see if any of the items refer to the plan.

[164:20]

Right. So we're looking for a plan.

[164:22]

And if it does refer to a plan

[164:23]

we're going to check the name of the plan, which is a slug, to see if it's the pro plan.

[164:27]

And we're going to see if it's active.

[164:28]

So if we found a pro plan that's active, that means we need to update

[164:31]

that organization's membership limit, which is what we're going to do.

[164:35]

So we're going to have an add router post event here.

[164:39]

We're just going to post this to slash clerk.

[164:42]

And we're going to say async define.

[164:43]

And this is going to be the clerk underscore webhook.

[164:46]

This is where we're going to actually handle the request coming from.

[164:49]

Clerk.

[164:50]

We're then going to say inside of here

[164:53]

the payload is equal to await.

[164:56]

And this is going to be the request dot body.

[164:59]

This is the data that clerk is sending us.

[165:01]

And we're going to say the headers is equal to the dictionary

[165:05]

of request dot headers.

[165:07]

Then we're going to attempt to verify the webhook.

[165:10]

So we're going to say if settings dot clerk

[165:13]

underscore webhook underscore secrets

[165:17]

then what we're going to do is say try

[165:19]

and we're going to say which is equal to webhook.

[165:23]

And we're going to say payload and then headers.

[165:26]

And we're going to say event is equal to h dot verify.

[165:32]

And we're going to verify with the payload and headers.

[165:36]

And sorry the webhook I just wrote this in the wrong way.

[165:38]

It needs to be settings dot clerk webhook webhook secret.

[165:42]

Sorry okay.

[165:44]

Then we're going to say accept error.

[165:46]

We're going to accept a webhook verification error.

[165:51]

And we're going to raise an Http exception.

[165:55]

We're going to say status dot Http.

[165:57]

And then this is going to be 400 bad request.

[166:00]

We're going to say invalid signature

[166:04]

okay.

[166:04]

So what we're doing right now is we're essentially just verifying to see okay

[166:08]

is the data that was sent to this webhook actually valid.

[166:11]

So we're saying do we have a webhook secret.

[166:13]

If we do we're going to create a webhook object.

[166:15]

And we're going to verify that the data that was just sent to us is valid and contains that signature.

[166:21]

Now otherwise we're just going to say event

[166:24]

is equal to Json dot loads.

[166:26]

And then payload.

[166:28]

So if we didn't define a secret then we'll just allow

[166:30]

anything to hit this endpoint okay.

[166:33]

Then we're going to say the event type is equal to event dot. Get.

[166:38]

And we're going to get the type because the webhook

[166:41]

is going to respond to different events related to the subscription.

[166:44]

So like created, canceled, pass due, etc.. Right.

[166:47]

It's going to send all those different types of events.

[166:49]

So we need to look at those events and then handle the correct ones.

[166:53]

We're then going to say data is equal to event

[166:57]

dot, get data and then an empty dictionary in case data doesn't exist.

[167:01]

And we're going to check the different events that could occur in this webhook, specifically

[167:06]

if a subscription was created or was updated,

[167:09]

or if a subscription was deleted or canceled.

[167:12]

So we're going to say if event

[167:15]

underscore type is in sorry.

[167:18]

And this is going to be a list, we're going to say subscription dog

[167:22]

created or subscription dot.

[167:26]

And this is going to be updated.

[167:27]

So these are the ones where we're essentially going to allow the user more limits or more seats.

[167:32]

We're going to say org ID is equal to data dot get

[167:36]

and then pair.

[167:37]

This is the person who paid okay dot get the organization ID.

[167:43]

So we're looking for the ID of the organization of the person who paid.

[167:48]

Then we're going to say if org id

[167:52]

what we're going to do is say limit is equal to.

[167:55]

And then this is going to be unlimited limit

[167:58]

if the has active

[168:01]

pro plan data dot get.

[168:05]

And then here items.

[168:07]

Otherwise an empty array.

[168:12]

And sorry that needs to be inside of here okay.

[168:16]

Otherwise we're going to say else free tier limit.

[168:19]

All right.

[168:20]

So we're going to make the limit unlimited if they have an active pro plan.

[168:24]

So we're going to check the plan to make sure that it is actually active.

[168:28]

If it is active then we'll give them the unlimited limit.

[168:30]

Otherwise we're going to set their limit to be free limit okay.

[168:33]

Then underneath this.

[168:34]

If so in the same block we're going to say set underscore org member limit.

[168:39]

And then we're just going to pass the org ID.

[168:42]

So org id and this new updated limit okay.

[168:47]

Now if the event type is in.

[168:51]

And now this is going to be the A subscription dot deleted or

[168:57]

the subscription dot canceled.

[169:00]

Then what we're going to do is say the org underscore ID is equal to data dot get.

[169:05]

And same thing as before.

[169:06]

We're going to get the payer okay.

[169:09]

And then we're going to get the organization underscore ID.

[169:14]

Then we're going to say if there is some org ID we're just going to set the org member

[169:18]

limit of that org ID to be the free tier limit because they canceled their subscription.

[169:25]

Then we're going to return

[169:27]

received is true

[169:31]

okay.

[169:31]

We're just going to spell received correctly.

[169:34]

So that's all we need for the webhook.

[169:36]

Now Clarke can send a request to this.

[169:39]

We can handle that request.

[169:40]

And we can update the limit for a particular organization.

[169:44]

So if we go now, we can just refresh the webhook okay.

[169:47]

Now from here we can go to testing and we can test this with a particular event if we want.

[169:52]

And we can see if that works.

[169:53]

However we'll just test it with a new organization.

[169:56]

So we're going to go here I'm just going to refresh what I'll do

[169:59]

is just swap over to my test 123 organization.

[170:03]

Now I want to verify with this organization that we cannot invite more members.

[170:07]

So let's try to invite a few.

[170:09]

Let's go in and type with Tim Dot

[170:11]

net or Tim and Deb launched us

[170:16]

okay.

[170:16]

Let's send the invitation.

[170:18]

Let's try to send another one.

[170:20]

Let's go Kenny, at

[170:23]

Deb launch.us

[170:26]

and send that and says we've reached a limit of two organization members.

[170:30]

Now what I'll do is I will upgrade to that pro plan.

[170:33]

So I'm going to go pricing and I'm going to go subscribe okay.

[170:38]

From here I'm just going to pay with the test card.

[170:41]

And when I pay with this now let's go continue.

[170:44]

And now I'm going to go to my dashboard.

[170:46]

I'm going to go here manage members.

[170:49]

And I'm going to try to invite Kenny.

[170:51]

So let's go Kenny at dev launch us and we got an error.

[170:56]

So let me check my logs.

[170:58]

And it looks like the webhook is saying it was not found.

[171:01]

So API webhooks clerk So the reason why is we did not actually

[171:05]

connect the webhook to our main app, which I need to do right here.

[171:09]

So let's connect the webhook the way that we can do that is we can just go here.

[171:13]

We could say from actually webhooks.

[171:16]

We can just import it like that.

[171:18]

And then we can simply say app, app dot include

[171:24]

underscore router and then webhooks dot router.

[171:29]

Okay. So now that we do that the webhook should work.

[171:31]

However it's giving us an issue saying there's no free tier limit in our settings.

[171:36]

So let's quickly go check our settings and see if that's the case config.

[171:41]

And we do have a free tier free tier membership limit okay.

[171:45]

So let's just change this to just say limit and then run this.

[171:49]

And hopefully now it should work okay.

[171:52]

And now let's test the webhook again.

[171:55]

So what we can do is we can go to for example billing.

[171:58]

We can go manage and we can cancel this.

[172:01]

And then let's just do it again.

[172:02]

So now go manage and okay let's go resubscribe and subscribe continue.

[172:09]

And now if I go look at my logs you can see that it posted to the webhook.

[172:13]

And now if I go to my organization and I try to add a new member, let's try it

[172:19]

okay.

[172:19]

Can see at dev launched us.

[172:23]

Let's see.

[172:24]

And you see that the invitation goes through because it successfully updated our lip.

[172:28]

Now if we were to cancel then it would again not allow us to add more members.

[172:31]

You get the idea.

[172:33]

Okay, so that is pretty cool.

[172:35]

And honestly, with that said, that's going to wrap up everything that I wanted to show you in this video.

[172:39]

We handled pricing.

[172:40]

We handled permissions, we handled different member roles.

[172:43]

We handled billing. We handled organization.

[172:46]

We handled signing and signing out webhooks front and back and a lot of things went into this project.

[172:51]

And all of the code for this project will be available from the link in the description.

[172:55]

In case you missed anything or you want to copy anything directly.

[172:59]

Overall, the major thing here is that using Clarke and some of the features

[173:02]

that I showed you and take in, there's a lot more that you can use.

[173:06]

You can build a full B2B SaaS that allows you to collect money and handle

[173:10]

all of the complex organization roles and permissions without having to write all of the code manually.

[173:16]

So if you guys enjoyed the video, make sure leave a like subscribe to the channel.

[173:19]

I I'll see you in the next one.

Download Subtitles

These subtitles were extracted using the Free YouTube Subtitle Downloader by LunaNotes.

Download more subtitles

Related Videos

Download Subtitles for Azure AI Foundry Basic Agent Setup

Download Subtitles for Azure AI Foundry Basic Agent Setup

Enhance your learning experience by downloading accurate subtitles for the Azure AI Foundry Basic Agent Setup video. Follow step-by-step instructions easily with clear captions, ensuring you don't miss any crucial details. Perfect for accessibility and improved comprehension.

Download Subtitles for How I Started My Small Business Video

Download Subtitles for How I Started My Small Business Video

Enhance your learning experience by downloading accurate subtitles for the 'How I Started My Small Business' video. Captions help you follow along easily, catch every tip for shipping and selling online, and ensure you don't miss any important insights. Perfect for visual learners and non-native speakers alike.

Download Subtitles for CLAUDE CODE Full Course 2026

Download Subtitles for CLAUDE CODE Full Course 2026

Enhance your learning experience with downloadable subtitles for the CLAUDE CODE FULL COURSE 4 HOURS: Build & Sell (2026). These captions help you follow along easily, improve comprehension, and revisit key concepts anytime. Perfect for learners who want clear, accessible content at their own pace.

Download Subtitles for WooCommerce SEO Tutorial with Yoast Plugin

Download Subtitles for WooCommerce SEO Tutorial with Yoast Plugin

Enhance your learning by downloading accurate subtitles for our WooCommerce SEO optimization tutorial using the Yoast free WordPress plugin. Easily follow along and grasp every step to improve your product listings and boost online visibility.

Download Accurate Subtitles and Captions for Your Videos

Download Accurate Subtitles and Captions for Your Videos

Easily download high-quality subtitles to enhance your video viewing experience. Subtitles improve comprehension, accessibility, and engagement for diverse audiences. Get captions quickly for better understanding and enjoyment of any video content.

Most Viewed

Descarga Subtítulos para NARCISISMO | 6 DE COPAS - Episodio 63

Descarga Subtítulos para NARCISISMO | 6 DE COPAS - Episodio 63

Accede fácilmente a los subtítulos del episodio 63 de '6 DE COPAS', centrado en el narcisismo. Descargar estos subtítulos te ayudará a entender mejor el contenido y mejorar la experiencia de visualización.

Subtítulos para TIPOS DE APEGO | 6 DE COPAS Episodio 56

Subtítulos para TIPOS DE APEGO | 6 DE COPAS Episodio 56

Descarga los subtítulos para el episodio 56 de la tercera temporada de 6 DE COPAS, centrado en los tipos de apego. Mejora tu comprensión y disfruta del contenido en detalle con nuestros subtítulos precisos y accesibles.

Untertitel für 'Nicos Weg' Deutsch lernen A1 Film herunterladen

Untertitel für 'Nicos Weg' Deutsch lernen A1 Film herunterladen

Laden Sie die Untertitel für den gesamten Film 'Nicos Weg' herunter, um Ihr Deutschlernen auf A1 Niveau zu unterstützen. Untertitel helfen Ihnen, Wortschatz und Aussprache besser zu verstehen und verbessern das Hörverständnis effektiv.

Download Subtitles for 2025 Arknights Ambience Synesthesia Video

Download Subtitles for 2025 Arknights Ambience Synesthesia Video

Enhance your viewing experience of the 2025 Arknights Ambience Synesthesia — Echoes of the Legends by downloading accurate subtitles. Perfect for understanding the intricate soundscapes and lore, these captions ensure you never miss a detail.

Download Subtitles for 'Asbestos is a Bigger Problem Than We Thought' Video

Download Subtitles for 'Asbestos is a Bigger Problem Than We Thought' Video

Access accurate and easy-to-read subtitles for the video 'Asbestos is a Bigger Problem Than We Thought' to enhance your understanding of this critical environmental and health issue. Download captions to follow along better, improve accessibility, and share information effectively.

Buy us a coffee

If you found these subtitles useful, consider buying us a coffee. It would help us a lot!

Let's Try!

Start Taking Better Notes Today with LunaNotes!