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

4554 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

[00:02]

tutorial on how to build a B-2B SAS

[00:05]

application that includes organizations,

[00:08]

multiple team members, billing,

[00:10]

subscriptions, permissions, roles, and

[00:12]

everything you would need to create a

[00:15]

SAS app. We're going to be doing this

[00:17]

using Python, specifically with fast API

[00:19]

for the back end, and then with React

[00:21]

and JavaScript for the front end. We're

[00:24]

also going to be using Clerk for

[00:25]

handling all of the complex billing and

[00:27]

organization related features as well as

[00:30]

the authentication like signing in,

[00:32]

signing out, etc. This will be a long

[00:34]

project with a lot of coding. So, for

[00:36]

that reason, I've included all of the

[00:37]

code that's written in this video linked

[00:39]

in the description. You can find a

[00:41]

GitHub repository in case you need to

[00:43]

copy anything or you prefer to follow

[00:44]

along that way. Also worth noting that

[00:47]

this video is not designed for

[00:48]

beginners. While I will explain to you

[00:50]

everything that I'm doing and the reason

[00:52]

for the decisions that I have, I'm not

[00:54]

going to be going over the fundamentals

[00:56]

of the frameworks or the language. So, I

[00:58]

won't be teaching you fast API or

[01:00]

teaching you React. This is more about

[01:01]

how you assemble all of the things, you

[01:03]

know, into a large complex project that

[01:06]

will act as a really solid base if you

[01:08]

wanted to build something like a B2B

[01:10]

SAS. Anyways, with that in mind, that's

[01:12]

all I had to go through in the intro.

[01:14]

What I'm going to do now is give you a

[01:16]

quick demo of the project and then we'll

[01:18]

get into coding it. So let's get

[01:19]

started. All right. So let's get started

[01:21]

here with a quick demo of the finished

[01:23]

application. Now we're going to be

[01:24]

building a teambased taskboard. So think

[01:27]

of it something like notion or a sauna

[01:29]

obviously with some simplified features.

[01:32]

So you can see I have a simple kind of

[01:34]

landing page here and I have the ability

[01:35]

to view the pricing which requires me to

[01:37]

sign in or to sign in or sign up. So

[01:40]

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

[01:42]

notice that what we're using to sign

[01:43]

into this is Clerk. Now, Clerk has

[01:45]

kindly sponsored this video, but they

[01:47]

are completely free. You do not need to

[01:49]

pay to use them. And they are the

[01:50]

easiest way to set up authentication as

[01:53]

well as things like organization,

[01:55]

billing, all of that stuff that you're

[01:56]

going to see in this video, which is

[01:58]

precisely why I partnered with them and

[01:59]

why we're going to use them. So, you can

[02:00]

see it gives me a nice email address

[02:02]

field here. Let me sign in and then I'll

[02:04]

be right back and show you what the app

[02:05]

looks like. Okay, so I was able to sign

[02:07]

in here and you'll notice right away

[02:09]

that the first thing I see is the

[02:10]

current organization that I'm inside of.

[02:13]

So I have this new org test. I also have

[02:15]

this hello world organization and I have

[02:17]

the ability to create another new

[02:19]

organization by simply pressing this

[02:21]

button right here. Now if I go to my

[02:23]

dashboard, you'll notice it's going to

[02:24]

start loading my different tasks. Right

[02:26]

now we don't have any. Shows me we have

[02:28]

two members in this organization. If I

[02:30]

want to make a new task, I can go like

[02:32]

test, you know, this or something, put

[02:34]

it in the to-do category, and you can

[02:35]

see that it's here. And then if I want

[02:37]

to change its category, I can simply

[02:38]

just swap it. And you see that it moves

[02:40]

right over. Now, the interesting thing

[02:42]

is that if I change over to my Hello

[02:44]

World organization, you'll notice that I

[02:46]

now see some different tasks popping up

[02:48]

in this board because the tasks that I

[02:50]

have are defined per organization. Now,

[02:53]

what I can also do is click on the

[02:54]

pricing. You'll see that we have some

[02:56]

pricing tiers here. So, we have a free

[02:58]

tier and then we have a pro tier. And

[03:00]

the way that I've set this up is that if

[03:02]

you want to have more than two members

[03:03]

in your organization, you can use the

[03:06]

pro tier, which I just put out $1 per

[03:08]

month, but you could set this to any

[03:09]

price that you want. And if you have

[03:11]

less than that, so two or less members,

[03:13]

then you can use the free tier. Now, the

[03:14]

way that you manage the members is you

[03:16]

go to manage with inside of your

[03:18]

organization. You go to members, and you

[03:20]

can see the various members that appear

[03:21]

here, and you can set the roles for

[03:23]

those members. So, for example, you can

[03:24]

make them an editor, a member, an admin.

[03:27]

I'll talk about all of that in this

[03:28]

video. But you can see that this really

[03:30]

is kind of like a B2B SAS setup where

[03:33]

you have billing, you have members, you

[03:34]

have organizations, you have

[03:36]

subscriptions, right? $1 per month. We

[03:38]

have kind of data that is shared between

[03:40]

different teams. We have different roles

[03:42]

for members. That's what I'm trying to

[03:43]

show you in this video. With that said,

[03:46]

let's hop over to the code editor, start

[03:48]

writing some code, and building this

[03:50]

project. So I'm inside my code editor

[03:52]

here and what I've done is I've just

[03:54]

opened a new folder called B2B SAS on my

[03:56]

desktop. So open any folder in any code

[03:58]

editor that you want and you'll notice

[04:00]

that for this video I'm going to be

[04:02]

using PyCharm. Now like I said you can

[04:03]

use whatever you like but typically I

[04:05]

recommend PyCharm for large Pythonbased

[04:08]

applications. In this case a lot of the

[04:10]

work is going to be in fast API. PyCharm

[04:12]

is specifically designed for working

[04:14]

with frameworks like that. Gives us

[04:15]

really good typins as well as

[04:17]

recommendations. Has a lot of great

[04:19]

features. So, I would recommend using

[04:20]

it. Now, if you do want to use PyCharm

[04:22]

and you want to access their Pro

[04:24]

subscription, try it for free and see if

[04:26]

you like it, you can do that from the

[04:28]

link in the description. I do actually

[04:29]

have a long-term partnership with them.

[04:31]

So, again, if you click that link below,

[04:32]

you'll be able to test it out and use it

[04:34]

completely for free and see if you like

[04:36]

it. Anyways, with that said, let's get

[04:37]

into the setup here. What I'm going to

[04:39]

start doing is setting up two folders.

[04:41]

One for my backend and one for my front

[04:43]

end. So, I'm going to open up my

[04:44]

terminal here. I'm going to go into my

[04:46]

B2B SAS application and I'm just going

[04:48]

to make one folder here. So let's go new

[04:50]

folder. Let's simply call that backend.

[04:53]

Then let's make another folder.

[04:55]

Actually, we'll do that just from the

[04:56]

command line. That will be our front

[04:58]

end. So let's create our front end. In

[05:00]

order to do that, you're going to need

[05:01]

NodeJS installed on your computer. So

[05:03]

make sure you have that installed

[05:04]

because we're going to be using React.

[05:05]

And we're going to type npm create vit

[05:10]

at latest like that. And then we're just

[05:13]

going to simply put front it. Now when

[05:15]

we do that, it should create a React

[05:17]

application for us if we select the

[05:18]

correct framework. So I'm going to

[05:20]

select React here. I'm just going to use

[05:22]

JavaScript. I'm not going to use

[05:23]

TypeScript for this particular project,

[05:25]

but you could if you want to. I'm going

[05:27]

to go no for roll down V. And I'm going

[05:29]

to say install with mpm and start now.

[05:31]

Yes. And it's going to start installing

[05:32]

and creating this front-end folder for

[05:34]

me. So this is the setup for the front

[05:36]

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

[05:38]

end. We're actually going to write the

[05:39]

entire backend first. We're going to

[05:42]

understand the architecture of our

[05:43]

application and then we're going to

[05:44]

start building the front end, the UI and

[05:46]

kind of connecting all of the pieces

[05:48]

together. Okay, so you can see our front

[05:49]

end is running. If I click on it, you

[05:51]

know, this is kind of the default dummy

[05:53]

front end that you get when you create

[05:54]

the React application. I'm just going to

[05:56]

shut this down by hitting C. I'm going

[05:59]

to CD into my backend directory and I'm

[06:01]

going to start setting up the code that

[06:02]

I need here. So for this project, I'm

[06:05]

going to use UV. So I'm going to type UV

[06:07]

init and then dot to create a new UV

[06:09]

project inside of this backend

[06:11]

directory. You can see when I do that it

[06:13]

creates five files here. We can delete

[06:15]

this main file because we don't need

[06:17]

this one for right now. Now if you're

[06:18]

unfamiliar with UV, I'll leave a video

[06:20]

on screen that explains how it works,

[06:22]

but essentially it's a better version of

[06:23]

pip. You can install it and then you can

[06:25]

just use the UV command like I am here.

[06:27]

All right. So for our particular backend

[06:29]

project here, we're going to need a few

[06:31]

different Python backend frameworks,

[06:33]

right? Or Python modules. So, what we're

[06:35]

going to do is type uv add and we're

[06:37]

going to add the fast API module because

[06:40]

of course we're using fast API for our

[06:42]

backend API. We're going to install

[06:44]

uicorn. This is the web server that will

[06:46]

run our backend API. We're going to

[06:49]

install SQL Alchemy. This is what's

[06:51]

going to be used for connecting to the

[06:52]

database. Then we're going to use

[06:54]

Python.env. This is for loading

[06:57]

environment variable kind of

[06:58]

configurations. Then we're going to use

[07:00]

pi JWT. This is for verifying JWT

[07:03]

tokens. We're going to bring in the

[07:04]

Clerk backend API. This is the Python

[07:08]

package for Clerk that allows us to use

[07:10]

Clerk on the backend for handling all of

[07:12]

our authentication. We'll talk more

[07:14]

about that later. And we're going to

[07:16]

bring in SVIX. This is what we're going

[07:18]

to use to actually validate a web hook

[07:20]

that we're going to get from Clerk later

[07:22]

on, but for now, let's install all of

[07:24]

those. Okay. So, you're going to see

[07:25]

that that then creates the virtual

[07:26]

environment for us, installs everything.

[07:28]

and we get this VNV folder and now we're

[07:31]

good to go and start setting up our

[07:33]

backend. So what I'm going to do to

[07:34]

begin so going to make a new file and

[07:36]

I'm going to call this env. This is

[07:39]

where we're going to put some

[07:40]

environment variables that we need for

[07:41]

the project. Now for the env file I'm

[07:45]

just going to put in the variables and

[07:46]

then we'll grab the values that we need

[07:48]

later. So first things first we're going

[07:50]

to type clerk secret_key.

[07:54]

We're then going to type

[07:54]

clerk_publishable_key.

[07:58]

Then we're going to type clerk under

[08:00]

undersc sorry jwks

[08:04]

URL. Then we're going to have the

[08:06]

database URL which we can actually fill

[08:09]

in right now. And for the database we're

[08:11]

going to use a local SQL light database,

[08:13]

but you could use anything that you

[08:14]

want. So, I'm going to type SQLite like

[08:18]

that and then colon three forward

[08:20]

slashes dot slash and then taskboard.

[08:24]

DB, which is going to create a database

[08:26]

called taskboard for us where we store

[08:28]

all of the information. We then are

[08:30]

going to have the frontend URL and we're

[08:33]

going to make this equal to http/lohost

[08:37]

and then port 5173 which is where our

[08:40]

react application is going to run. And

[08:42]

we're going to have the clerk_web hook

[08:46]

secret. And we're actually going to put

[08:48]

this up top because that is one of the

[08:51]

clerk values. So we might as well just

[08:52]

keep them all together. Okay. So we're

[08:55]

going to look at all these values later

[08:56]

on. But like I mentioned, for all of the

[08:58]

authentication, all of the login, all of

[09:00]

the organization, billing,

[09:01]

subscriptions, we're going to use a free

[09:03]

framework or a free tool, I guess,

[09:05]

called clerk. Okay, so that's why I'm

[09:07]

putting in the variables here. We're

[09:09]

going to create that clerk. We're going

[09:10]

to create that clerk account in 1 minute

[09:12]

after I start stubbing the API and then

[09:14]

you'll see why we're using this and why

[09:17]

we need this these particular values.

[09:18]

Sorry. Okay. So for our API, what I want

[09:21]

to start by doing is just creating the

[09:23]

different directories and files that

[09:24]

we're going to need so that we can start

[09:26]

to kind of understand the architecture

[09:27]

and how things are going to be set up.

[09:29]

Then after that, we'll start actually

[09:30]

writing the code and going into the

[09:32]

individual files. So for now, inside of

[09:35]

our backend folder, we're going to make

[09:36]

a new file and we're going to call this

[09:38]

start. py. This is going to be the entry

[09:41]

point for our application, the file that

[09:43]

we run to effectively run the app. We're

[09:46]

then going to make a folder here. So,

[09:47]

new directory called app. And this is

[09:49]

where all of the code for our

[09:50]

application is going to live. Now,

[09:52]

inside of app, we're going to make a few

[09:54]

new folders. So, we're going to have one

[09:55]

folder, and this is going to be called

[09:57]

API. This is for all of the API routes.

[10:01]

We're then going to have another folder.

[10:03]

This is going to be called core for the

[10:05]

core functionality essentially. Another

[10:08]

new directory. This is going to be

[10:09]

called models for our database models

[10:11]

and then another directory and this is

[10:14]

going to be schemas. Now the schemas are

[10:16]

going to be used to essentially validate

[10:17]

data. Then inside of the app directory

[10:20]

we're going to make a new file. This

[10:22]

file is going to be called main.py.

[10:25]

Okay. And then we'll just go ahead and

[10:26]

press cancel there. So what we have now

[10:29]

is app. We have four folders. API core

[10:31]

models schema. We have our main.py. And

[10:34]

now what we're going to do is start

[10:35]

writing the different files that are

[10:36]

going to go inside of these folders. Now

[10:38]

I'm not going to write all of the code,

[10:39]

but we're just going to write the file

[10:41]

names so that at least we know what

[10:43]

we're going to have and how the app is

[10:44]

kind of going to be structured. So from

[10:46]

API, we're going to make a new Python

[10:49]

file. And this is going to be tasks.

[10:53]

py for handling all of the API routes

[10:55]

related to our tasks. We're then going

[10:57]

to make another new file and this one is

[11:00]

going to be called web hooks. py. Later

[11:02]

we'll discuss what a web hook is, but

[11:04]

essentially this is going to be for all

[11:06]

of the endpoints to receive data from

[11:08]

clerk. So when some event happens like

[11:10]

we create a new organization or we

[11:12]

subscribe to a particular billing plan,

[11:14]

we can get that information and handle

[11:16]

it inside of our app. We're then going

[11:18]

to have core. So inside of core, we're

[11:20]

going to make a few new files. So here

[11:22]

we're going to have a file called o. py

[11:24]

for handling authentication. We're going

[11:26]

to have another new file. This is going

[11:28]

to be called clerk. py. Okay, this going

[11:31]

to be a lot of files. Just bear with me

[11:32]

and then we'll start actually writing

[11:33]

them out. We're going to have another

[11:35]

one called config. py for all of the

[11:37]

settings for our application and we're

[11:39]

going to have another one called

[11:40]

database.py

[11:42]

for you guessed it handling the

[11:44]

database. Now we're going to go inside

[11:46]

of models for our database models. These

[11:48]

are going to be pretty easy. We're just

[11:49]

going to have one called task. py and

[11:52]

then we'll write the model directly

[11:53]

inside of there. And then for our

[11:55]

schema, same thing. We're going to make

[11:57]

a new file and this is going to be

[11:58]

called task. py. Okay. So, quickly pause

[12:02]

the video, have a look at all of the

[12:03]

files that we've created. Make sure that

[12:05]

your app looks the same. And this is

[12:06]

just going to make your life easier.

[12:08]

Sorry, as we continue that all of the

[12:10]

files are already created. Okay, so now

[12:13]

that all of those files are created,

[12:14]

we're going to start grabbing some of

[12:15]

the credentials that we need to store

[12:16]

inside of this environment variable

[12:18]

file. Okay, so we're going to go to our

[12:20]

browser and I'm going to quickly explain

[12:22]

to you why we're using Clerk and how to

[12:24]

create an account here. So, like I said,

[12:26]

Clerk allows you to essentially manage

[12:28]

all of the users, authentication,

[12:30]

organizations, billing, subscription,

[12:32]

etc. for your application. So, it has

[12:35]

pre-built components like the ones you

[12:36]

saw right here for things like signup,

[12:38]

signin, user button, user profile, all

[12:40]

of the ones you actually already saw in

[12:42]

the app that I demoed to you. Things

[12:44]

like switching the organization, you

[12:46]

know, creating the organization, looking

[12:47]

at a profile of the organization. So, it

[12:49]

just makes our life as a developer

[12:51]

significantly easier. And again, the

[12:53]

best part is this is free and it's

[12:55]

actually very widely used. You've

[12:56]

probably seen these UIs in a lot of

[12:58]

applications and large SAS. Okay, so in

[13:02]

order to use Clerk, what we need to do

[13:04]

is just create a free account here. I'll

[13:06]

leave a link in the description where

[13:07]

you can make that account. Once you do

[13:09]

that, simply go to your dashboard and

[13:11]

we're going to create a new Clerk

[13:13]

project. So if you just go into your

[13:15]

workspace, you go here and you just go

[13:18]

create application, it will allow you to

[13:20]

give the application a name. So, in this

[13:22]

case, I'm just going to call this, you

[13:23]

know, B2B

[13:26]

task manager or something. Or let's just

[13:28]

go like SAS task manager maybe. Okay.

[13:32]

And you'll notice that you can enable or

[13:33]

disable a ton of different sign-in

[13:35]

methods. So, I'm just going to leave

[13:36]

email for now, but you could enable

[13:38]

Google Signin, Facebook signin, you

[13:40]

could enable, you know, all of these

[13:42]

signins at once, right? And this is the

[13:44]

benefit of using Clerk is that if you

[13:45]

ever want to change the authentication

[13:47]

methods, you just directly do it from

[13:48]

this nice UI. You don't have to do a ton

[13:50]

of setup. It's very easy to do it. So

[13:52]

even like you can sign in with notion.

[13:54]

Okay. So we're going to go with just

[13:55]

email for now. We're going to press on

[13:58]

create application. This is going to

[14:00]

make a new clerk app for us. Now there's

[14:02]

going to be a lot of instructions here

[14:04]

that explain to you how to add this to

[14:05]

your front end. We will do that later

[14:07]

when we start setting up the front end.

[14:09]

But for now, what we're going to do is

[14:11]

go to this configure button and start

[14:13]

grabbing some of the keys that we need

[14:15]

for our environment variable file. Now,

[14:17]

you'll also notice that there's a ton of

[14:18]

settings here that you can mess with and

[14:20]

play with. For now, we'll just leave it

[14:22]

as it is. Okay. So, we're going to go

[14:24]

down here to where it says API keys from

[14:26]

the settings, and we're going to grab a

[14:28]

few of the keys that are here. So, first

[14:29]

one is the publishable key, otherwise

[14:31]

known as the public key. We're just

[14:33]

going to copy that, and we're going to

[14:35]

put that into our environment variable

[14:37]

file where it says publishable key. So,

[14:39]

let's paste that. The next one is the

[14:41]

secret key. So, I'm just going to copy

[14:43]

that. And same thing, I'm going to paste

[14:45]

that into the clerk secret key variable.

[14:48]

We then need the JWKS URL. We're going

[14:51]

to find that from the right hand side

[14:52]

here where it says JWKS URL. Same thing.

[14:55]

We're going to copy that and just paste

[14:57]

that right there. And then lastly, we

[14:58]

have the clerk web hook secret. We're

[15:00]

not going to have that right now. So,

[15:01]

we'll just put an equal sign and we'll

[15:03]

fill that value in later when we get to

[15:05]

the web hook. Okay. So, now we have the

[15:08]

values that we need in our environment

[15:09]

variable file. Again, we'll go back to

[15:11]

clerk later on, but for now, we're going

[15:13]

to start filling in some of the Python

[15:15]

files that you see here. So, for now,

[15:17]

where we'll start is inside of this core

[15:19]

folder. We're going to go into config.

[15:22]

py, and we're going to start writing the

[15:23]

code to load in essentially all of the

[15:25]

settings that we need for this app. So,

[15:27]

we're going to start by saying import

[15:29]

os. We're then going to say from.env

[15:33]

import load_.env.

[15:35]

I'm just going to configure my

[15:36]

interpreter here so we don't get those

[15:38]

errors. Okay. So now we're going to type

[15:40]

load.env. What this is going to do is

[15:42]

load this environment variable file for

[15:44]

us so we can access some of the values

[15:46]

inside of it. We're then going to create

[15:47]

a class called config. This is standard

[15:50]

in fast API to store all of your

[15:52]

settings and variables in one class. And

[15:54]

we're simply going to write a mapping

[15:56]

from our environment variable files or

[15:58]

variables sorry two variables inside of

[16:01]

the class. So we're going to say clerk

[16:03]

secret key. We're going to say this is

[16:05]

of type string is equal to os.get get

[16:07]

env. And then this is going to be the

[16:09]

clerk secret key or an empty string if

[16:12]

for some reason that doesn't exist.

[16:14]

Next, we're going to get the clerk

[16:15]

publishable key. So, we're going to say

[16:17]

clerk, if we can type this in capitals,

[16:19]

underscore publishable,

[16:22]

okay, underscore key. And then this is

[16:25]

of type string in lowercase. And this is

[16:28]

going to be equal to os.get env. And

[16:30]

then the clerk publishable key. And

[16:31]

again, an empty string. We then are

[16:33]

going to have the clerk underscore web

[16:36]

hook

[16:38]

secret and this is going to be a string

[16:41]

which is equal to the os.get env and

[16:44]

then the clerk web hook secret and again

[16:46]

empty string. We then are going to have

[16:48]

the clerk_jwks

[16:51]

url same thing string is equal to os.get

[16:54]

enviable clerk jwks URL. We then are

[16:58]

going to have the database

[17:01]

URL which is a string. Same thing equal

[17:04]

to os.get env database URL. We then have

[17:08]

the frontend URL. So front end URL

[17:12]

string is equal to os.get env frontend

[17:15]

URL. And then we're going to have two

[17:16]

variables that we're going to define

[17:18]

oursel. The first is going to be the

[17:19]

free tier_membership

[17:23]

limit. This time it's going to be an int

[17:27]

which is equal to two. And then we're

[17:29]

going to have the procore tier

[17:32]

membership limit which is going to be

[17:34]

equal to zero which stands for

[17:37]

unlimited. Now what I'm doing is just

[17:39]

putting two values here that we'll need

[17:40]

to use later to essentially define okay

[17:42]

how many members can you have in a free

[17:44]

plan? How many can you have in a pro

[17:45]

plan that is stored in here and we're

[17:47]

not hard coding in multiple places in

[17:49]

our app. We're then going to go and say

[17:51]

settings is equal to config like that so

[17:56]

that we have access to all of

[17:57]

effectively the settings for our

[18:00]

application. Okay, so that is it for the

[18:02]

config.py file. Now we're going to go

[18:04]

and just write the database connection.

[18:06]

So from database we're going to say from

[18:09]

SQL alchemy import create engine. We're

[18:12]

going to say from SQL alchemy and then

[18:15]

this is going to be

[18:18]

import the session maker and the

[18:21]

declarative base. We're then going to

[18:23]

say from app.core.config

[18:26]

we're going to import the settings like

[18:30]

so. Okay. We're then going to continue

[18:32]

and we're going to say engine is equal

[18:34]

to create underscore engine and we're

[18:38]

going to say settings dot and this is

[18:41]

going to be the database

[18:44]

URL is going to be essentially how we're

[18:46]

creating the database or what the URL is

[18:48]

we're using for that particular

[18:49]

database. And then we're going to say

[18:51]

the connect arguments is equal to a

[18:53]

dictionary and we're going to say check

[18:55]

same thread and then is equal to false.

[18:59]

I'm not going to explain this too much

[19:00]

in depth, but it's just going to make

[19:01]

sure that we're able to run the database

[19:03]

successfully and we don't have any

[19:05]

problem with it running inside of the

[19:07]

same thread as something else. We're

[19:08]

then going to say session local is equal

[19:10]

to the session maker and we're going to

[19:13]

say auto commit is equal to false. We're

[19:17]

going to say auto flush is equal to

[19:21]

false as well. And we're going to bind

[19:23]

this to our engine. Now, I know a lot of

[19:26]

this will look a little bit confusing,

[19:27]

but this is very standard when working

[19:28]

with fast API to be able to connect to a

[19:31]

SQLite database. We're then going to say

[19:34]

base is equal to declarative base. And

[19:36]

what we're going to do is say define get

[19:39]

database and we're going to say database

[19:41]

is equal to session local. When we call

[19:44]

this, it's essentially going to bind to

[19:46]

the database engine. It's going to

[19:48]

create the database if it doesn't

[19:49]

already exist. If it does exist, it's

[19:51]

simply just going to yield it to us.

[19:53]

We're then going to say try and we're

[19:55]

going to say like this yield and we got

[19:59]

to spell yield correctly the database

[20:02]

and we're going to say finally. So why

[20:05]

is this not working like this finally we

[20:08]

are going to say db.close.

[20:12]

Okay. So this is all we need for the

[20:14]

database. What this is doing is

[20:16]

essentially just creating a database

[20:17]

session and then we have this function

[20:18]

where when we want to access the

[20:20]

database we just call it. it yields the

[20:22]

database to us and if there's any issues

[20:24]

then it just closes the database and

[20:25]

make sure that it saves successfully.

[20:27]

Okay, so let's continue here and let's

[20:29]

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

[20:33]

write this to connect to clerk. This is

[20:35]

super straightforward. What we're going

[20:36]

to do is say from clerk_backend

[20:39]

api import clerk and we're then going to

[20:42]

say from app.core

[20:46]

config.

[20:48]

Okay, if we spell that correctly, import

[20:50]

settings. And then we're going to say

[20:52]

clerk is equal to clerk. And we're going

[20:54]

to say the bearer off is equal to

[20:58]

settings. clerk.

[21:01]

Okay, let me just spell this correctly.

[21:02]

clerk secret_key.

[21:06]

Now, the reason why we need to set up

[21:08]

some stuff related to clerk here in the

[21:09]

back end is that clerk is going to

[21:11]

handle all of our authentication

[21:12]

automatically for us in the front end.

[21:14]

Essentially, we can just bring in a

[21:16]

clerk component and it will allow the

[21:18]

user to sign in, sign out, handle the

[21:20]

organizations, and it will just do all

[21:22]

of the O. However, we only want

[21:25]

authenticated users to be able to send

[21:27]

requests to our Python backend because

[21:30]

our backend is going to handle things

[21:31]

like creating the tasks, setting where

[21:33]

the tasks are, maybe deleting a task.

[21:36]

So, what we want to do is ensure that

[21:38]

only users that have the right

[21:39]

permissions to handle those tasks are

[21:41]

able to do so. So what we're going to do

[21:44]

is from our front end, we're going to

[21:46]

include the clerk user data to a request

[21:49]

to our backend and our backend is going

[21:51]

to verify that this user is who they say

[21:53]

they are by using this clerk package. So

[21:56]

essentially we're going to go to clerk

[21:57]

and we're going to say hey clerk is this

[21:59]

actually one of the users who signed

[22:00]

into our app. Clerk is going to say yes

[22:02]

or no. And then based on that

[22:04]

information we can kind of gate what

[22:05]

that user is able to do in our backend

[22:07]

application. It'll make more sense later

[22:10]

on, but that's kind of the basics of why

[22:11]

we're setting up Clerk like we are. So,

[22:14]

with that in mind, let's actually go

[22:15]

back to Clerk and I'm going to start

[22:16]

setting up some of the things that we

[22:18]

need in order to make this

[22:19]

authentication work. So, from Clerk,

[22:21]

we're going to go to organizations here

[22:24]

and we're going to go to settings. Now,

[22:25]

what we're going to do is enable

[22:27]

organizations for our app. We're going

[22:29]

to turn off allow personal accounts and

[22:31]

just enable this. Now, effectively what

[22:33]

we're doing here when we enable

[22:35]

organizations is we're allowing users to

[22:38]

create organizations and invite other

[22:40]

members to them. This is very common in

[22:42]

something like a B2B software as a

[22:45]

service where one person creates the

[22:46]

org, they invite different members and

[22:48]

maybe you pay per member, right? Or you

[22:50]

have like a certain number of seats and

[22:52]

you upgrade to another plan to unlock

[22:53]

more seats. Clerk allows you to do that

[22:55]

and that's kind of what we're enabling

[22:57]

right now. So, what we're going to do is

[22:58]

we're going to set the limited

[23:00]

membership to two right now and make

[23:02]

that saved. The reason for that is that

[23:05]

I want to limit any new organizations or

[23:08]

free organizations to just have two

[23:10]

members and then force them to pay to

[23:12]

upgrade their organization in order to

[23:14]

have unlimited seats. Okay. Now, we can

[23:16]

scroll down here. There's a few other

[23:18]

things that we can set. For example, the

[23:20]

default role for new members, the

[23:21]

creators initial role in the

[23:23]

organization, allow members to delete

[23:25]

organizations. Right? There's a bunch of

[23:27]

stuff that we can do here. And for now,

[23:28]

we'll kind of leave it as is. We also

[23:31]

could turn off allow user create

[23:33]

organization so that you had to manually

[23:35]

create an organization yourself as the

[23:37]

app creator or admin in order for

[23:39]

someone to join that or all right. So,

[23:41]

next thing though, we're going to go to

[23:43]

roles and permissions. Within

[23:45]

organizations, you can have various

[23:46]

different roles, right? Viewer, editor,

[23:49]

admin, you know, content manager, sales

[23:51]

manager. You've probably seen that

[23:52]

before if you've used any kind of SAS.

[23:54]

So notice that we have admin currently,

[23:57]

right? And we can just kind of describe

[23:58]

what the admin can do. We have member,

[24:02]

right? And then we can create our own

[24:03]

rule. So for this particular app,

[24:05]

because we're going to have kind of this

[24:06]

task board, I want to create a new uh

[24:09]

role here called editor. So I'm going to

[24:12]

give this role editor. See the key is

[24:13]

editor. I'm going to say a user who can

[24:17]

edit tasks. Cool. And I'm going to go

[24:19]

ahead and I'm going to save that. Now

[24:22]

with this role, we can just check if a

[24:24]

user is admin, editor or member. But we

[24:26]

can even get more specific and we can

[24:28]

add particular permissions per each

[24:31]

role. So what I can do here is go to

[24:33]

features. From features, I can press add

[24:36]

feature. This particular feature I'm

[24:38]

going to call is task. Okay? Or let's

[24:41]

call it tasks actually. And I'm going to

[24:43]

say task in the task board.

[24:47]

And what I'm going to do is start

[24:48]

setting some permissions that I want to

[24:50]

be able to set for particular users. So

[24:52]

I'm going to go create permission. For

[24:53]

the first permission, let's call this

[24:57]

create. We're going to say can create

[25:00]

tasks. And we're going to go ahead and

[25:01]

create that. And you can see this one

[25:03]

permission has now been created. Let's

[25:04]

make another one. We're going to call

[25:06]

delete. Can delete tasks.

[25:09]

Let's make another one. Let's call this

[25:11]

view. Can view tasks.

[25:14]

Okay. And let's make another one and

[25:16]

call it edit and say can edit tasks.

[25:20]

Okay. So now we have four permissions.

[25:22]

Create, delete, view, and edit. Those

[25:24]

are within the feature of tasks. We're

[25:27]

going to go ahead and save that. Go out

[25:29]

of here. And you'll see now that we have

[25:31]

this tasks feature created. So now if we

[25:35]

go back to our organizations, we go to

[25:38]

roles and permissions. We can go to the

[25:40]

admin for example, and you'll see the

[25:42]

permissions are now appearing. and we

[25:44]

can enable all of them for the admin.

[25:46]

Okay, let's go ahead and save that. Now,

[25:48]

let's go back to the editor. Same thing

[25:51]

for the editor. Let's allow them to do

[25:53]

all of this. Okay, so save. And for the

[25:56]

member, let's just allow them to view.

[25:59]

So, they cannot edit, delete, or create,

[26:00]

but they can view the tasks in the

[26:02]

taskboard. Cool. So, there you go. We

[26:05]

just added a few different roles. We set

[26:07]

up the permissions here and now we can

[26:09]

start using these directly from our code

[26:11]

whenever someone signs in with their

[26:13]

clerk account so we know what

[26:15]

permissions they actually have access

[26:16]

to. However, before we do that, I want

[26:18]

to start setting up the billing and the

[26:20]

subscription as well while we're on this

[26:21]

page. So, I'm going to go to billing and

[26:23]

I'm going to go to settings. Now, from

[26:25]

here, what I'm going to do is enable

[26:27]

organization billing and press on save.

[26:29]

Now, when I do that, it's going to give

[26:30]

me a few other settings like allow me to

[26:32]

connect my Stripe account or to directly

[26:34]

use the clerk payment gateway. Now, if

[26:37]

you were in production, this would

[26:38]

become more important for now because

[26:39]

we're just testing in development. It

[26:41]

doesn't really matter which one you use

[26:42]

here. And we'll be able to just test

[26:44]

with kind of like a fake credit card,

[26:46]

which you'll see in a second when we

[26:48]

talk about these kind of different plans

[26:50]

and upgrading to pro and all of that

[26:51]

kind of stuff. Now, what you also see

[26:53]

here is that it shows me plans. So, it

[26:55]

says create plan. I can also just go to

[26:57]

subscription plans here and I can start

[27:00]

creating various different plans for

[27:02]

organizations or for individual users.

[27:05]

So, what I'm going to do for now is I'm

[27:06]

going to create a new organization plan.

[27:08]

I'm going to call this pro tier like

[27:11]

that. And I'm just going to write a

[27:13]

description and say unlimited

[27:18]

seats in the organization.

[27:22]

Okay, like that. And then for the fee,

[27:25]

I'll just make it $1. You can make it

[27:26]

literally anything that you want. You

[27:28]

could enable a free trial, make it

[27:29]

publicly available, add various features

[27:32]

to this particular plan. For example,

[27:34]

you could add, you know, the tasks

[27:35]

feature. So maybe you can actually

[27:37]

unlock different features with different

[27:39]

plans. In my case, I'm not doing that,

[27:42]

but maybe we have some, you know, AI

[27:44]

feature, something that you only get

[27:45]

access to if you're in the pro plan.

[27:47]

Then you could make that a feature,

[27:49]

enable it directly in clerk, and then

[27:51]

have it actually one of the things

[27:52]

associated with this plan, and your code

[27:54]

can then check that and see if the user

[27:56]

or the organization has that particular

[27:58]

feature. This is super powerful. That's

[28:00]

why I'm using it. But you can see for

[28:02]

now I just created this new plan. Now we

[28:04]

have the free plan, sorry, and the

[28:06]

protier plan. And later I'll show you

[28:08]

how we can display that. And notice it's

[28:11]

actually kind of showing it to us

[28:12]

already by using this pricing table

[28:14]

component in React and using this method

[28:16]

and kind of protect to protect certain

[28:18]

features based on what plan the

[28:20]

organization or the user has. Okay, so

[28:23]

that's pretty much all I wanted to do

[28:25]

for now. I will quickly mention to you

[28:27]

that everything you see inside of clerk

[28:28]

you can customize, you can change.

[28:30]

There's all kinds of other stuff you can

[28:31]

look at here. For now though, we'll

[28:33]

leave this as is and we're going to go

[28:35]

back to our code and we're going to

[28:36]

start writing this off module which now

[28:39]

hopefully should make a little bit more

[28:40]

sense because we just enabled those kind

[28:43]

of permissions inside of clerk. All

[28:45]

right, so what we're going to do for our

[28:47]

off here is we're going to start by

[28:48]

importing httpx.

[28:50]

We're then going to say from fast API

[28:53]

import depends HTTP

[28:57]

exception if we can spell exception

[29:00]

correctly request.

[29:03]

Okay. And status. We're then going to

[29:06]

say from the clerk backend API.curity

[29:10]

import the authentication request

[29:12]

options. We're then going to say from

[29:14]

app.core.config

[29:18]

import settings. and we're going to say

[29:20]

from app.core.clerk

[29:23]

import clerk. Okay. All right. So now we

[29:27]

have the imports that we need and in

[29:28]

this particular file we're going to

[29:29]

handle all of the authentication and

[29:32]

essentially understanding if a

[29:33]

particular user has the ability to edit

[29:36]

a task, delete a task, etc. Now

[29:38]

effectively what's going to happen is

[29:40]

when someone signs into our front end,

[29:42]

like I mentioned, they're going to be

[29:44]

using clerk. From our front end, we're

[29:46]

going to include the user's clerk

[29:48]

account details, which includes a JWT

[29:51]

token, which is essentially their

[29:53]

identity as the user in our app. And

[29:55]

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

[29:58]

So front end is going to send request to

[30:00]

the back end. Okay? And this is going to

[30:02]

include a JWT token from clerk. Now,

[30:06]

what we're going to do on the back end

[30:08]

is the back end is going to authenticate

[30:10]

this token. When we do that, we're going

[30:13]

to get the user details from clerk and

[30:17]

check the user's permissions. Okay, so

[30:20]

this is effectively what this file is

[30:22]

going to do. We're receiving a request

[30:24]

from the front end that includes some

[30:25]

details about the signedin user. Our

[30:28]

backend is going to authenticate these

[30:29]

details are valid. We're going to get

[30:31]

the details from clerk about this

[30:33]

particular user and we're going to check

[30:34]

their permissions to see, okay, are they

[30:36]

a member? Are they an editor? Are they

[30:38]

an admin? What do they actually have the

[30:40]

ability to do? So I'm going to make a

[30:41]

class here called o user.

[30:44]

Inside of here I'm going to say define

[30:47]

innit and I'm going to take in self a

[30:51]

user ID which is a string and org id

[30:55]

which is also a string and org

[30:59]

permissions like this which is a list.

[31:03]

Then what I'm going to do is say self do

[31:05]

user ID is user ID self.org ID is or ID

[31:09]

and self.org org permissions. I'll spell

[31:12]

this correctly. Our org permissions.

[31:15]

Then I'm going to say define has

[31:17]

underscore permission. We're going to

[31:20]

take in self and then a permission

[31:24]

which is a string and we're going to

[31:26]

return a boolean. We're then going to

[31:28]

say return

[31:30]

permission in self.org permissions. Now,

[31:34]

this is simply just going to check if

[31:36]

this user has a particular permission

[31:38]

inside of this permissions list. That's

[31:41]

because whenever we check the user in

[31:43]

clerk, it's going to give us a list of

[31:45]

permissions that user has. Those

[31:47]

permissions are going to include the

[31:49]

ones that we just created like can they

[31:51]

edit a task, can they delete a task,

[31:53]

etc. So, we're going to check those

[31:55]

essentially and if we have them, then

[31:57]

we're going to tell the backend or

[31:59]

whatever function, okay, yes, they can

[32:00]

actually do that particular task. So now

[32:03]

I'm going to create a few properties. So

[32:05]

I'm going to say app property define can

[32:09]

view. Okay, this is going to take in

[32:11]

self. It's going to return a boolean and

[32:14]

we're going to return self.permission

[32:17]

and then we're going to say org colon

[32:20]

tasks colon view. So the way that the

[32:24]

permissions are set up inside of clerk

[32:26]

is that the particular permission that

[32:27]

we created like view, create, delete,

[32:30]

edit is associated with some feature. In

[32:33]

this case, the feature is tasks and that

[32:36]

feature is associated with an

[32:37]

organization. So we're saying or tasks

[32:40]

view. We're checking in this particular

[32:42]

organization in the tasks feature, do we

[32:45]

have the view permission? So I'm going

[32:47]

to copy this now uh this method and

[32:49]

we're going to write it for all of the

[32:51]

different properties that we have. So,

[32:52]

as well as can view, we're going to have

[32:54]

can create. And we just change this to

[32:57]

create.

[32:59]

Same thing for can delete. Change this

[33:03]

to delete. And then what is the last one

[33:06]

that we have? So, we have view, create,

[33:07]

delete, and then we're going to have

[33:08]

edit. And then simply change this to

[33:11]

edit. And that's it. We now have these

[33:13]

four different properties in this class.

[33:15]

And I can use this O class to determine

[33:17]

if a user has a particular permission.

[33:19]

Okay. Okay, so now we're going to write

[33:21]

a few functions that essentially takes

[33:23]

the data that our front end sends to the

[33:25]

back end and sends it to clerk to

[33:27]

validate this data and make sure the

[33:29]

user is who they say they are. So we're

[33:31]

going to write a function called

[33:32]

convert_2_http

[33:36]

request. Okay. Now inside of here, what

[33:39]

we're going to do is have a fast API

[33:42]

request which is of type request. And

[33:46]

this is going to return an httpx dot

[33:49]

request. Now inside of this function,

[33:52]

I'm going to go return httpx

[33:55]

dot request and I'm going to have the

[33:58]

method which is equal to fast API

[34:01]

request do method. I want to have the

[34:03]

URL which is equal to fast API request

[34:06]

URL. And we're just going to convert

[34:08]

this into a string. And we're going to

[34:12]

have headers which is equal to a

[34:14]

dictionary. And this is going to be the

[34:16]

fast API request dot headers. Now,

[34:19]

effectively all we're doing here is

[34:21]

we're just taking the fast API request

[34:23]

object and we're converting this into an

[34:25]

HTTPX request object. Just a different

[34:27]

request object that's going to be used

[34:29]

directly with the clerk module. Now,

[34:31]

we're going to have another function.

[34:32]

This is going to be async define and

[34:34]

this is going to be get_currencore

[34:37]

user. This is going to take in a request

[34:40]

which is a fast API request and it's

[34:43]

going to return an authenticated user.

[34:46]

Okay. And let's spell request correctly

[34:49]

here. Awesome. So from this function,

[34:52]

what we're going to do is we're going to

[34:53]

say httpx

[34:55]

request is equal to convert to httpx

[34:59]

request and then we're going to pass

[35:00]

this fast API request. So we just take

[35:03]

the request convert it into this httpx

[35:05]

one. We're then going to say the

[35:06]

request_state

[35:08]

is equal to clerk.auuthenticate

[35:11]

request and we're going to pass the http

[35:13]

request. Okay. As well as the

[35:16]

authenticate request options and we're

[35:18]

going to say the authorized parties is

[35:20]

equal to and this is going to be

[35:23]

settings dotfrontend

[35:26]

URL. All right. So effectively what this

[35:29]

is saying is okay get the data from this

[35:32]

HTTP request authenticate it with clerk

[35:35]

and also just make sure that we're only

[35:37]

going to authenticate ones that come

[35:38]

from our front-end URL. We're then going

[35:41]

to say if not request_state

[35:46]

is

[35:48]

signed in. Okay, because what's going to

[35:51]

happen now is this request state is

[35:52]

going to give us a bunch of information

[35:53]

about the user. What's their username?

[35:55]

Are they signed in? are they signed out?

[35:58]

Have they created an account? Etc., etc.

[36:00]

So, if they're not signed in, then we're

[36:02]

going to raise an HTTP exception and

[36:04]

we're going to say the status_code

[36:07]

is equal to status.http_41

[36:10]

unauthorized and we're going to say

[36:12]

detail is equal to not authenticated.

[36:17]

Okay, so that's going to check if the

[36:19]

particular user is signed in. Then we're

[36:21]

going to say claims

[36:23]

are equal to request_state

[36:27]

dotp payload. We're going to say the

[36:29]

user ID

[36:31]

is equal to claims.get

[36:34]

and then sub. Now what's going to happen

[36:36]

effectively is that when clerk

[36:38]

authenticates this request, right? And

[36:41]

it kind of pulls out the information

[36:42]

from this JWT token, it's going to

[36:45]

include something called a payload. Now,

[36:47]

in the payload, we're going to have some

[36:49]

information like the sub, which is

[36:50]

actually the user's ID in clerk, as well

[36:53]

as the organization ID that they're

[36:55]

inside of and things like their

[36:56]

permissions. So, we're going to get that

[36:58]

information so we can then use that from

[37:00]

our backend. So, we're going to say the

[37:01]

org ID is equal to claims.get and then

[37:04]

or

[37:05]

ID. We're going to say, let's get out of

[37:08]

this parenthesy here. Okay, go back.

[37:12]

that the org

[37:15]

permissions are equal to claims.get and

[37:19]

we're going to get permissions

[37:22]

or we're going to get claims.get

[37:26]

and then org

[37:29]

permissions

[37:31]

or this because it could be stored under

[37:33]

permissions or permissions or if we

[37:34]

don't have either of those then we're

[37:35]

just going to have an empty list because

[37:37]

we have no permissions. We're then going

[37:39]

to say if not user id then same thing

[37:43]

we're just going to raise this error

[37:46]

because if we don't have a user ID that

[37:47]

means there's some error and the user is

[37:49]

probably not signed in. We're also going

[37:50]

to say if not and this is going to be

[37:54]

org ID. So if they're not a part of some

[37:56]

kind of organization then what we're

[37:58]

going to do is we're going to return a

[38:00]

400. So we're going to say 400 bad

[38:03]

request and we're going to say no

[38:04]

organization selected.

[38:07]

Okay, because they need to select some

[38:09]

particular or organization. Sorry. And

[38:11]

then if all of that is good, we're going

[38:13]

to return an O user and we're going to

[38:16]

pass to the O user the user ID which is

[38:18]

equal to the user ID, the org ID which

[38:22]

is equal to the org ID and the org

[38:27]

permissions which is equal to the org

[38:29]

permissions. So effectively we're saying

[38:30]

this function will get us the current

[38:32]

user from clerk. We're going to convert

[38:34]

the request. We're going to authenticate

[38:35]

with clerk. We're going to check if

[38:37]

they're signed in. If they are, we're

[38:39]

going to get information like their user

[38:40]

ID, the org ID, and the permissions they

[38:42]

have. If for some reason they don't have

[38:43]

that data, there's some kind of error.

[38:45]

So, we'll return that. Otherwise, we

[38:47]

will return the authorized user. Now,

[38:49]

just from this file, we're going to

[38:50]

create a few more functions as well,

[38:52]

which are going to act as helper

[38:53]

functions to essentially gate access to

[38:56]

particular functionality. Bear with me.

[38:58]

These are going to be pretty simple once

[38:59]

we write the first one. The first one is

[39:01]

going to be called require view. This is

[39:03]

a function we're going to use to require

[39:05]

the view permission from a particular

[39:07]

user. So we're going to say user and

[39:09]

then this is going to be off user equals

[39:12]

depends and then this is going to depend

[39:14]

on the function get current user. Okay,

[39:17]

we're not going to call the function.

[39:18]

We're just going to write its name.

[39:19]

We're then going to say this will return

[39:21]

an o user. And what we're going to do

[39:23]

from here is we're going to say if not

[39:25]

and then user.can_view

[39:28]

then we're going to raise an HTTP

[39:30]

exception. And inside of here, we're

[39:32]

going to have a status code which is

[39:34]

equal to status.http_43,

[39:40]

okay, underscore forbidden, saying you

[39:42]

don't have this permission. We're then

[39:44]

going to say detail and we're going to

[39:46]

say the view

[39:48]

permission required. Okay, pretty

[39:51]

straightforward. Then down here, we're

[39:53]

going to return the user. Now, we're

[39:56]

going to copy this function and we're

[39:57]

going to have this for every permission.

[39:59]

So rather than now require view, we're

[40:01]

going to have require create. We're

[40:03]

going to change this to can create.

[40:07]

We're then going to have create

[40:09]

permission required.

[40:11]

Okay. Then we're going to do the same

[40:13]

thing except rather than require create,

[40:15]

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 delete

[40:23]

permission required. Okay. Now let's go

[40:26]

down here. rather than require view this

[40:29]

is going to be require edit we're going

[40:31]

to change this to say can edit and then

[40:33]

same thing edit permission required okay

[40:37]

that's all that we need the reason why

[40:39]

I'm writing these four functions is

[40:40]

we're going to use these as what's

[40:41]

called a dependency injection in a

[40:43]

minute in fast API so that if a user

[40:46]

wants to for example create a new task

[40:48]

we just call this function it will then

[40:51]

get the current user and see if the user

[40:53]

is actually able to create the task or

[40:55]

not that's why we have that inside of

[40:57]

this O file. So, I know that was kind of

[41:00]

complicated. That was a lot of code, but

[41:01]

now we've handled the core work for our

[41:04]

API and we're going to start working on

[41:05]

the database. Then we'll write the

[41:07]

backend API routes and then we're done

[41:09]

with the API. We can test it and then we

[41:11]

can move on to the front end. So, let's

[41:13]

close all of this for now. Again, we

[41:15]

have kind of the off stuff handled,

[41:16]

which is the most complicated. And what

[41:18]

we're going to do is start writing our

[41:19]

database models. So, the only database

[41:22]

model that we're going to have here is

[41:23]

simply to represent a task, right? And a

[41:26]

task can be in different stages. So like

[41:27]

pending, started, completed. That's kind

[41:30]

of how we'll set it up, but you can make

[41:31]

this more complicated if you want. So

[41:34]

from this task file inside of models,

[41:36]

I'm going to define what a task looks

[41:38]

like. So I'm going to import UU ID,

[41:40]

which will allow us to create a unique

[41:42]

ID. I'm going to say from datetime,

[41:46]

import date time. I'm then going to say

[41:50]

from SQL Alchemy. Okay. and then import

[41:56]

column

[41:58]

string text datetime and enum. And by

[42:03]

the way, if you're getting lost here,

[42:04]

all of this code will be available from

[42:06]

the link in the description. I should

[42:07]

have mentioned that earlier. Now, let's

[42:09]

continue. I'm going to say import enum.

[42:12]

And I'm going to say from app.core

[42:16]

database

[42:17]

import base. Okay, we're then going to

[42:20]

say class task

[42:23]

status and this is going to be string,

[42:27]

enum enum. And we're just going to make

[42:30]

a simple enum in Python. So we're going

[42:32]

to have pending which is equal to

[42:34]

pending in lowercase.

[42:36]

We're then going to have started which

[42:39]

is equal to started in lowercase and

[42:42]

we're going to have completed.

[42:46]

Okay, which is equal to completed in

[42:49]

lowercase as well. So this is just the

[42:51]

status for a particular task. You can

[42:53]

have more, but we're just going to have

[42:54]

pending started completed. Then we're

[42:56]

going to have class task. This is going

[42:59]

to inherit from base. And what we're

[43:01]

going to do is just define the table

[43:02]

name. So underscore table named_.

[43:05]

Okay. And this is going to be equal to

[43:07]

tasks.

[43:08]

Then we're going to define all of

[43:10]

different data that's going to be stored

[43:11]

on this task. So the first is going to

[43:14]

be the ID.

[43:16]

So we're going to have id column this is

[43:18]

going to be string primary key true and

[43:20]

then the default is going to be lambda

[43:22]

and then string uyu IDU ID4. What this

[43:25]

is going to do is just by default create

[43:26]

a new unique ID for all of the tasks

[43:29]

automatically for us. Next we're going

[43:31]

to have a title of the task. This is

[43:33]

also going to be column. It's going to

[43:34]

be of type string. Now we're going to

[43:36]

put 255 to limit the length to 255

[43:39]

characters. And we're going to say

[43:40]

nullable is equal to false. Meaning this

[43:42]

cannot be empty. We're then going to say

[43:44]

description. This is going to be column.

[43:47]

We'll just make it text because it can

[43:48]

be long. But this time we're going to

[43:50]

say knowable is equal to true because it

[43:53]

can be empty. We're then going to say

[43:55]

status is equal to column. This is going

[43:56]

to be of type enum with the task status.

[43:59]

The default is going to be task

[44:01]

status.pending and nable is going to be

[44:03]

false. This cannot be empty. Then we're

[44:06]

going to have the org ID because every

[44:07]

task needs to belong to some org. So

[44:10]

we're going to say column

[44:12]

string and then we're going to say

[44:14]

nullable is equal to false and we're

[44:17]

going to say index is equal to true so

[44:19]

that we can look up values based on this

[44:21]

column very quickly to find all of the

[44:23]

tasks for a particular organization.

[44:26]

We're then going to have created by and

[44:28]

this is going to be the user who created

[44:29]

this. So this is going to be column and

[44:32]

then string and then nullable

[44:36]

is equal to false. We're then going to

[44:38]

have created at and this is going to be

[44:40]

column datetime and we're going to say

[44:43]

nullable is equal to false or actually

[44:45]

we don't need that. We're just going to

[44:46]

say default okay default is equal to

[44:50]

datetime doc now and then we're going to

[44:54]

have updated at and this is going to be

[44:59]

column and same thing datetime and then

[45:03]

default is going to be datetime.

[45:06]

now. Okay, perfect. So, what this is

[45:09]

going to do is it is going to

[45:10]

essentially just define the table schema

[45:13]

for our task. So, we have an ID, title,

[45:16]

description, status, the or this task

[45:18]

belongs to, who it was created by, when

[45:20]

it was created at, and when it was

[45:21]

updated at. That's all of the

[45:23]

information that we need to store about

[45:24]

any given task. Now that we have that,

[45:27]

we're going to go into schemas and we're

[45:29]

going to write essentially the data

[45:30]

validation for creating a task, updating

[45:33]

a task, and getting a task so that we

[45:35]

have the correct types in our fast API

[45:37]

application. So what we're going to do

[45:39]

is we're going to say from datetime

[45:42]

import datetime. We're going to say from

[45:45]

paidantic import the base model. We're

[45:48]

going to say from typing

[45:51]

import optional. Okay. Okay. And we're

[45:55]

going to say from app domodels.task.

[45:59]

Okay. The one we just created imports

[46:01]

the task status. Now what we're about to

[46:04]

create and let's go back to here is

[46:05]

something called schemas. Schemas are

[46:08]

essentially Python classes that use

[46:10]

Pantic to help us validate information

[46:12]

that gets sent to or returned from our

[46:14]

API. So if someone wants to create a

[46:17]

task, for example, they need to pass a

[46:19]

title, a description, a status. That's

[46:21]

what we're defining here and that we'll

[46:23]

use in just one minute. So we're going

[46:24]

to say class task create. This is going

[46:27]

to be the schema that's going to be used

[46:29]

when someone wants to create a task.

[46:30]

It's going to inherit from the base

[46:31]

model. We're going to say title string.

[46:35]

We're going to say description and this

[46:38]

is going to be optional.

[46:40]

Okay. And then string. We're going to

[46:42]

say status and this is going to be task

[46:46]

status. And then is equal to task status

[46:50]

dot

[46:51]

pending. Okay, so that's kind of the

[46:53]

default when they create a task is that

[46:55]

it will go inside of pending. So now if

[46:57]

a user wants to create a task, they need

[46:58]

to pass this information effectively,

[47:00]

which we'll use in a minute. Next, we're

[47:02]

going to have the task update. Now this

[47:05]

is going to be pretty much the same

[47:07]

thing as the other one except all of the

[47:08]

fields are going to be optional. So

[47:10]

we're going to say title and then

[47:12]

optional string, okay, is equal to none.

[47:16]

Then we're going to say description and

[47:19]

same thing. This is an optional string

[47:21]

is equal to none and the status okay

[47:25]

optional task status equal to none. So

[47:27]

if you want to update the task you can

[47:29]

pass any or none of these different

[47:31]

combinations of values it will update

[47:33]

based on the ones that you pass. Right?

[47:35]

Then we're going to have class task

[47:38]

status update. Okay. So if this is just

[47:41]

for updating the status and we're going

[47:43]

to take in the status and the task

[47:45]

status like so. And then lastly, we're

[47:48]

going to have the class task response.

[47:51]

Now, the reason why we write a task

[47:52]

response model or schema is because

[47:54]

sometimes we don't want to include all

[47:56]

of the information that's in our

[47:58]

database when we return information

[48:00]

about data in the database. So, in this

[48:03]

case, we're kind of writing a slim

[48:04]

version of a task that we'll return to

[48:07]

the front end that might hide any of the

[48:09]

sensitive data that we don't want to

[48:10]

return. So, we're going to say ID int.

[48:14]

We're gonna say title string. We're

[48:17]

going to say description.

[48:19]

Okay. And this is going to be optional

[48:21]

string. We don't need to make this equal

[48:22]

to none, just optional. Then we're going

[48:24]

to say status is going to be the task

[48:26]

status. We're going to say the org

[48:29]

ID is a string. We're going to say

[48:31]

created_by

[48:33]

is a string. We're going to say created

[48:36]

at is a date time object. Let's go

[48:41]

created and spell that correctly by the

[48:42]

way. And let's go updated at updated at

[48:47]

date time. And then we're going to say

[48:49]

class config. And we're going to say

[48:52]

from underscore attributes equals true.

[48:56]

Which means this will automatically

[48:58]

create this response class based on the

[49:00]

attributes of another class which you

[49:02]

will see in a minute. Okay. So that's

[49:03]

the schema. Again, we're going to use

[49:04]

this in a second where it will make a

[49:06]

little bit more sense. Now what we're

[49:08]

going to do is we're going to go to our

[49:10]

tasks.py PI file inside of the API and

[49:13]

we're going to start using all of the

[49:15]

code that we just wrote to define the

[49:16]

endpoints or kind of the routes of our

[49:19]

API for the different operations related

[49:21]

to our tasks. So we're going to say from

[49:25]

fast API import the API router

[49:30]

the depends the HTTP exception and the

[49:35]

status. We're then going to say from SQL

[49:37]

alchemy

[49:39]

import session we're going to say from

[49:42]

typing import the list we're going to

[49:46]

say from app.core

[49:48]

do database

[49:50]

okay we're going to import the get

[49:52]

database we're going to say from

[49:54]

app.core core.

[49:57]

import the get_curren

[49:59]

underscore user. We're going to import

[50:02]

require view

[50:04]

require create. So all these functions

[50:06]

that we wrote require delete and require

[50:11]

edit. Okay, I know you can't see that,

[50:13]

but I wrote require edit. All right,

[50:15]

then we're going to say from app

[50:18]

domodels.task

[50:20]

import the task model. And we're going

[50:23]

to say from app.s schemas.task

[50:27]

imports the task create the task update

[50:33]

the task status update and the task

[50:37]

response. Okay. So that's all of our

[50:40]

imports. Now what we're going to do is

[50:41]

we're going to create our router. So

[50:42]

we're going to say router is equal to

[50:43]

API router. The prefix is going to be

[50:46]

/appi/tasks.

[50:49]

And then we're going to say tags is

[50:51]

equal to tasks. What we're effectively

[50:53]

doing here is saying, okay, all of the

[50:55]

endpoints I'm about to write here are

[50:57]

going to be prefixed with API/tasks.

[51:00]

So if I have an API here like

[51:02]

slashcreate, I would go to /

[51:04]

API/task/create.

[51:07]

And also, let me just quickly fix this

[51:08]

because this needs to be inside of a

[51:10]

list for the tax. All right, so now

[51:12]

we're going to say at routouter.get,

[51:15]

get we're going to put just an empty

[51:18]

string for now which means if you go to

[51:20]

this exact route what we're going to do

[51:22]

is we're going to specify the response

[51:24]

model which is going to be equal to a

[51:26]

list of task response and what we're

[51:29]

going to do is return all of the tasks

[51:31]

that are in the current users

[51:33]

organization. So we're going to say

[51:35]

define and this is going to be list

[51:37]

tasks. We're going to take in the user.

[51:40]

So we're going to say user off user

[51:43]

equals depends and then require view. So

[51:47]

what's going to happen now is we're

[51:48]

saying okay and we need to make this a

[51:50]

capital story. We're going to depend on

[51:53]

this require view function. So we're

[51:55]

going to call the require view function

[51:57]

and if that doesn't give us back an

[51:58]

authenticated user it means we don't

[52:00]

have the ability to actually view these

[52:02]

tasks. So then we'll just raise an

[52:04]

exception. Right? If we do get the

[52:06]

authenticated user, then we're good to

[52:08]

go and we can view them. We're then

[52:10]

going to say DB session is equal to

[52:13]

depends and then get database. So we

[52:15]

need the database as well as the

[52:17]

permission to view in order to run this

[52:19]

function. We're then going to say tasks

[52:22]

is equal to DB doquery.

[52:25]

We're going to query the task and we're

[52:27]

going to say dot filter. Then we're

[52:29]

going to say task.org

[52:32]

ID is equal to user.org org id. Okay.

[52:37]

And then dotall.

[52:39]

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

[52:40]

going to filter and it's going to make

[52:42]

sure that the user's organization ID

[52:44]

matches the tasks organization ID. If it

[52:47]

does, then we will return those

[52:48]

particular tasks because those are the

[52:50]

tasks that this user is currently in the

[52:52]

organization of. Uh I think that makes

[52:54]

sense why I said that, but hopefully you

[52:56]

get the idea. Okay. Next, we're going to

[52:58]

write something very similar except this

[53:00]

time it's going to be to create a task.

[53:03]

So, I'm going to copy this and I'm just

[53:05]

going to start changing some things. So,

[53:06]

rather than router.get, we're going to

[53:08]

change this to router.post, which means

[53:10]

you need to send a post request to this

[53:12]

particular endpoint. Now, for this

[53:14]

endpoint, we don't need to change it. We

[53:16]

can actually just leave it as this

[53:17]

because if you send a get, we'll list

[53:18]

the tasks. If you send a post, we will

[53:20]

create the task. Now, rather than the

[53:23]

response model being a list this time,

[53:25]

it's just going to be one individual

[53:27]

task. And similarly to before, we're

[53:29]

going to take in the user, except this

[53:31]

time it's going to be require create.

[53:33]

We're going to take in the session

[53:35]

because we need the database. But before

[53:37]

that, we're also going to take in the

[53:38]

task data, which is going to be from

[53:41]

task create schema that we wrote

[53:43]

earlier. Now, let's actually just clean

[53:45]

up this function a little bit so it's

[53:46]

easier to read. So, let's put our

[53:48]

dependencies on separate lines and then

[53:51]

go like this and start writing the rest

[53:53]

of the function. Okay. So similarly to

[53:56]

before again we're getting the user

[53:57]

we're getting the database but now this

[53:58]

time we're taking in the data from this

[54:00]

schema. Let's have a look at it. Right?

[54:02]

So we need the title description and

[54:04]

status in order to create the task. What

[54:07]

we're going to do now is just create the

[54:08]

task. So we're going to say task is

[54:10]

equal to task. We're going to say title

[54:13]

is equal to the task data.title.

[54:16]

We're going to say the description is

[54:18]

equal to the task data.escription.

[54:21]

We're going to say the status is the

[54:22]

task data status. We're going to say the

[54:24]

org ID is the user.org ID and created by

[54:29]

is going to be equal to and this is the

[54:32]

user do user id. Okay, so that now

[54:36]

creates a task. And by the way, the

[54:38]

reason why I don't need to check if any

[54:39]

of these values exist is because fast

[54:41]

API automatically does that for us by

[54:44]

using that schema we created. So when I

[54:46]

put this in here as a requirement for

[54:49]

this endpoint, fast API checks, okay,

[54:52]

this schema, did you pass all of this

[54:54]

correct data? And if you didn't, it

[54:55]

automatically returns an error message

[54:57]

to the user. Okay, so let's continue

[54:59]

here. We're now going to say db.add

[55:02]

the task. So after you create the task,

[55:04]

you need to add it to kind of the

[55:05]

staging area for the database. We're

[55:07]

then going to say db.comit.

[55:10]

When we say db.comit, this is actually

[55:12]

going to save it to the database. And

[55:13]

then we can type db.refresh. refresh

[55:16]

this task and return the task. And what

[55:20]

refreshing does is it checks the

[55:22]

database and kind of repopulates this

[55:24]

object with any new values that were

[55:27]

created. So for example, when I create

[55:28]

this task here, I didn't set the ID for

[55:30]

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

[55:32]

the update at time. But when I refresh

[55:34]

here, because I added that to the

[55:36]

database and the database will

[55:38]

automatically make that for us, it adds

[55:40]

those values back to this object. and

[55:42]

then we can return it with all of the

[55:44]

populated kind of hydrated values.

[55:47]

Hopefully that makes sense. Now let's go

[55:49]

and start writing a few more endpoints

[55:51]

that we need. So the next one we need is

[55:52]

going to be to get a particular task. So

[55:55]

we're going to say router.get

[55:57]

and we're going to type slash and then

[55:59]

task ID because this is going to be a

[56:01]

dynamic path. We're going to say the

[56:04]

response

[56:05]

model is equal to the task response.

[56:09]

We're then going to say define get task.

[56:12]

We're going to take in the task

[56:15]

id which is a string which is going to

[56:18]

match with what's in this path parameter

[56:19]

right here. We're then going to take in

[56:21]

the user right which is the off user

[56:23]

equals depends and then this time it's

[56:25]

going to be require view because we're

[56:27]

viewing a particular task. And then same

[56:29]

thing db session okay autocomplete come

[56:32]

on is equal to depends get database. And

[56:35]

let's clean this up again like we did

[56:36]

before so that we can read these values

[56:38]

a little bit easier. Okay. And now let's

[56:41]

start writing this function. So what

[56:43]

we're going to do to get a particular

[56:45]

task is we're going to say db.query

[56:47]

task. Okay. And we're going to filter

[56:51]

the task. ID is equal to task ID. And

[56:57]

the task.org ID is equal to the user.org

[56:59]

org ID to make sure that both these

[57:01]

things match because sure you can know

[57:03]

the task ID but it needs to be the task

[57:05]

that's in this particular organization.

[57:07]

Now we do dot first which just gives us

[57:09]

the first response if it exists. Now if

[57:11]

it doesn't exist we're going to say if

[57:13]

not task then we can just return a 404.

[57:16]

So we can say raise HTTP exception.

[57:19]

Okay. Now let's go back here. This is

[57:23]

going to be status code is equal to

[57:26]

status.http_45. http_44

[57:29]

not found and then we can say detail is

[57:33]

equal to task not found and then if

[57:36]

that's not the case so if we did find

[57:37]

the task we can simply return it okay

[57:39]

and let's add a space there so we get

[57:41]

rid of that highlight okay so that is

[57:44]

getting one particular task now let's go

[57:46]

to updating a task okay so to update a

[57:49]

task we're going to say at routouterput

[57:53]

we're going to go slash and then same

[57:54]

thing we're going to take the task ID

[57:56]

and there's response model will be the

[57:58]

task response. I'm doing putut because

[58:00]

that means we're updating if you send a

[58:02]

put request and we're going to call this

[58:04]

define update task. Now for the

[58:07]

parameters, we're going to take in the

[58:08]

task ID which matches this. We're also

[58:11]

going to take in the task data which is

[58:13]

going to be the task update schema.

[58:16]

We're then going to have the user,

[58:17]

right? And we're also going to have the

[58:19]

database like so. Okay. So now that we

[58:22]

have all of those, we're going to try to

[58:23]

find the task, right? And we're also

[58:25]

going to make sure, sorry, that we have

[58:27]

the requireedit permission. So let's

[58:29]

make sure that it says require edit in

[58:31]

the depends right there. Okay, so first

[58:33]

things first, let's try to find the

[58:34]

task. So let's just copy what we found

[58:36]

here. The task needs to exist in order

[58:38]

for us to edit it. So that's the first

[58:39]

thing that we're doing. Now, similarly

[58:41]

to before, we can actually just copy

[58:43]

this right here and paste it because if

[58:46]

the task doesn't exist, well, we cannot

[58:48]

edit it. Now, what we're going to do is

[58:50]

say if the task data.title title is not

[58:56]

none. Then what we're going to do is say

[58:57]

task.title is equal to task data.title.

[59:01]

Now we're going to do the exact same

[59:02]

thing for the description. So we're

[59:04]

going to say if the task data.escription

[59:06]

is not none, then task.escription is

[59:09]

equal to the task data.escription. And

[59:11]

we're going to say if the task

[59:13]

status, okay, or sorry, task data.status

[59:18]

is not none, then same thing, the task

[59:20]

status will be equal to the task data.

[59:22]

status. So we're essentially checking

[59:24]

the update data saying okay well if the

[59:26]

update data is not none then we will

[59:28]

actually update this current task with

[59:30]

that information and then we can just

[59:32]

type db.comit which will save that

[59:34]

information. We can then refresh this

[59:36]

right from the database. We can refresh

[59:38]

the task and we can return the task and

[59:41]

that will be the update function. Now

[59:44]

the last function we need is simply to

[59:45]

delete a task. So it's going to be very

[59:47]

similar to what we did before. In fact

[59:48]

let's copy everything we have here.

[59:51]

Let's come down. Let's paste that. Let's

[59:54]

change this to router.delete.

[59:56]

Let's make sure they have the required

[59:57]

delete permission. We'll remove the task

[60:00]

data. We don't need that. We'll try to

[60:02]

find the task. We'll make sure the task

[60:05]

exists. And if it does, we can just

[60:07]

delete it. So, db.delete task. And then

[60:11]

we can say db.comit to save that in the

[60:15]

database. And then we can just return

[60:17]

none. And for the response model, we're

[60:19]

just going to change this to actually

[60:22]

remove it. And we're going to say

[60:24]

status_code

[60:25]

is equal to status.http_204

[60:29]

no content because when you delete

[60:30]

something, well, there's nothing really

[60:31]

for us to return. So, we're just

[60:33]

returning none, right? Find the task if

[60:35]

it exists, delete it if we have the

[60:37]

right permission to do that. And then

[60:39]

we're good to go. Perfect. So, that is

[60:41]

it for the tasks API. Okay, this allows

[60:44]

us to get the task. I also realized that

[60:46]

sorry this needs to be called create

[60:49]

task. So it doesn't have the same

[60:50]

function name as list task but okay get

[60:52]

all the tasks create a task get one

[60:55]

individual task and then same thing here

[60:57]

delete a task. I forgot to change the

[61:00]

name and now we have pretty much all of

[61:01]

the functionality that we need for

[61:03]

handling the various different tasks.

[61:05]

The last thing we need to do is

[61:06]

essentially hook this up to actually

[61:07]

start running the API. Add kind of all

[61:10]

of the configuration and then we can

[61:12]

test it. So let's quickly write that. So

[61:13]

we're going to go to this main.py PI

[61:15]

file now that we've written all of these

[61:16]

individual files and we're going to say

[61:18]

from fast API import with some

[61:22]

capitalization fast API. We're also

[61:24]

going to say from fast API dom

[61:27]

middleware docores

[61:30]

import the course middleware. We're then

[61:32]

going to say from app.core.config

[61:37]

import and this is going to be settings.

[61:41]

We're then going to say from

[61:42]

app.core.database

[61:44]

database import the engine and the base

[61:48]

and we're going to say from appi

[61:51]

import tasks like that. Now what we're

[61:54]

going to do is we're going to say base

[61:56]

dot metadata okay metadata

[62:00]

create_all

[62:02]

and we're going to say bind is equal to

[62:04]

engine. What this is going to do is it's

[62:06]

going to look for all of the different

[62:08]

models that we've defined in our

[62:09]

database and create them if they don't

[62:12]

already exist in the database. We're

[62:14]

then going to say app is equal to fast

[62:17]

API. We're going to say the title of the

[62:20]

app is equal to and let's go taskboard

[62:23]

API. We can give it a description.

[62:27]

So we can just say you know B2B task

[62:30]

board app.

[62:32]

Okay. And then we can go version and we

[62:36]

don't need to put this here, but I'm

[62:37]

just going to put version 1.0.0.

[62:41]

Okay. Then I'm going to say app dot and

[62:44]

this is going to be add middleware. And

[62:47]

I'm going to add the corores middleware.

[62:50]

Now corores stands for cross origin

[62:52]

resource sharing. This is going to allow

[62:54]

us to have the API be called from a

[62:57]

different domain or a different resource

[62:59]

essentially or origin sorry. So I'm

[63:01]

going to allow this to be called from

[63:03]

our front end. So I'm going to say allow

[63:06]

origins equal to settings dot frontend

[63:13]

URL to make sure our front end is able

[63:14]

to actually call this. I'm then going to

[63:16]

say allow credentials true so we can

[63:20]

pass our authorization tokens. I'm going

[63:22]

to say allow method star. So allow

[63:24]

everything and allow header star to

[63:27]

allow all of the various headers. Okay.

[63:29]

I'm then going to say at app.include

[63:31]

router

[63:33]

and I'm going to include the tasks.outer

[63:37]

which is the API router that we just

[63:39]

wrote here. So we just connecting this

[63:41]

to our main fast API application. And

[63:44]

then I'm going to say uh actually I

[63:46]

think that's it. That's all that we need

[63:47]

from this particular file. Okay. Now I'm

[63:50]

going to quickly go to my start. py file

[63:53]

because this is the entry point of my

[63:54]

app. I'm going to import uicorn and I'm

[63:58]

going to say if underscore named

[64:01]

equals and this is going to be

[64:02]

underscore main

[64:05]

then uicorn.r

[64:08]

run and I'm going to run like this app.

[64:14]

App say host is equal to 0.0.0.0 zero

[64:19]

and port is equal to 8,000 and reload is

[64:24]

equal to true because we're doing this

[64:25]

in debug mode. Now, what this is going

[64:27]

to do is it's going to look for this app

[64:28]

folder. It's going to look for this main

[64:30]

file. It's going to look inside the main

[64:32]

file for an app called app and it's

[64:34]

going to run it using Unicorn. It's

[64:36]

going to run it on localhost, which is

[64:37]

this right here on port 8000. So, now we

[64:40]

should be good to actually run the API

[64:42]

and see if it works. So, what we can do

[64:44]

is make sure we're in the backend

[64:45]

folder. We can type uv run start.p py

[64:50]

and we should see that the API starts

[64:52]

running. If the API is running that's

[64:54]

good. Uh however it's giving me some

[64:56]

error saying no module named backend. Uh

[64:59]

so let me quickly check why I'm getting

[65:01]

that. Okay so I just had to make a fix

[65:03]

here inside this file I had from

[65:05]

backend.app.core.

[65:07]

I just need to change this to say from

[65:09]

app.core.

[65:10]

But actually I think that I should have

[65:12]

just put this import up here. So O user

[65:15]

like that. that. So, I'm just going to

[65:16]

remove this from here. Have the O user

[65:19]

imported from that. And now, if I save

[65:21]

this and run it, it looks like we're all

[65:22]

good. The application is started up and

[65:24]

we're no longer getting that error. So,

[65:26]

the API should be functioning. Now,

[65:28]

we're not able to really test this until

[65:30]

we actually have a clerk account and

[65:32]

we've signed into our front end.

[65:34]

Fortunately, that's pretty easy to do.

[65:35]

So now what we'll do is we'll leave the

[65:37]

back end running because pretty much all

[65:38]

of the functionality is done and we can

[65:41]

start working on the front end where we

[65:43]

actually sign in users, write the UI and

[65:45]

then send the requests to our backend

[65:47]

where they're authenticated by clerk. So

[65:49]

we're now moving on to the front end.

[65:52]

Now for the front end we're going to

[65:53]

change directories in our terminal into

[65:55]

the front end directory. You'll see it

[65:56]

looks something like this. What we're

[65:58]

going to do from here is just install a

[66:00]

few npm packages that we need

[66:02]

specifically for clerk again for

[66:04]

handling all of the authentication and

[66:06]

then for react router DOM for routing

[66:08]

between different pages. So we're going

[66:10]

to type npm i and then this is going to

[66:13]

be at clerk slash clerk and then dash

[66:18]

react and then beside this we're going

[66:20]

to install react dash router-d.

[66:25]

So, let's go ahead and install that and

[66:27]

wait for that to finish. Okay, so all of

[66:29]

that has been installed. I'm now going

[66:30]

to close the terminal and I'm going to

[66:31]

go into my front-end directory. Now, for

[66:34]

this project, I am not going to write

[66:36]

all of the CSS completely from scratch

[66:38]

because there is a ton of it and it's

[66:40]

just a huge waste of time for me to do

[66:42]

that on the video. Uh, you know,

[66:44]

truthfully speaking, most of the CSS was

[66:46]

just generated with AI anyways. So, what

[66:48]

I'm going to allow you guys to do is

[66:49]

just download all of the CSS and just

[66:52]

bring it into your project and then

[66:53]

write all of the components manually if

[66:55]

you want to follow along with me so that

[66:57]

you don't have to go through all of this

[66:59]

tedious, you know, hundreds, actually

[67:00]

probably close to a thousand lines of

[67:02]

CSS. So, what I'm doing is I'm linking

[67:04]

all of the code in the description via

[67:06]

GitHub. If you go to that repo, you go

[67:09]

to front end, you go to source, you go

[67:11]

to styles, you'll see all of the styles

[67:13]

are organized by pages. So, badges,

[67:16]

cards, canband, layout, all of this kind

[67:18]

of stuff. So, you can click into them

[67:19]

and you can view all of the different

[67:20]

CSS styles and just drag them right into

[67:23]

your project, which is where I'm

[67:25]

starting. All of the CSS is already

[67:27]

here. Again, I'm not going to write any

[67:29]

of it. It's just already inside of the

[67:31]

project. Now, same thing with this

[67:33]

index.css file. What I've done is I've

[67:35]

just imported all of the individual

[67:37]

styles from this styles folder inside of

[67:40]

this index.css file. So, just make sure

[67:43]

you were aware of that. Now, what we're

[67:45]

going to do now that we have all of the

[67:47]

styles is we're going to actually start

[67:49]

setting up the clerk authentication.

[67:51]

Then, we're going to start writing all

[67:53]

of the pages that we need. So, for now,

[67:55]

I'm just going to delete this assets

[67:56]

folder because I don't need it. So,

[67:57]

let's get rid of this. Okay. And we're

[67:59]

going to go back to Clerk and I'm going

[68:01]

to show you how we can set this up on

[68:03]

the front end. All right. So, from

[68:05]

Clerk, we're going to go to overview.

[68:07]

We're going to select React and it's

[68:09]

going to show us here exactly how we can

[68:12]

set up Clerk. So what we need to do is

[68:14]

install the React package which we

[68:16]

already did and we need to create av

[68:18]

file where we put in this clerk vit

[68:21]

publishable key or vit clerk publishable

[68:23]

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

[68:24]

code. We're going to go inside of front

[68:26]

end. We're going to make a new file

[68:28]

called env. And we're just going to

[68:30]

paste this in here. Vit clerk

[68:32]

publishable key. Okay, perfect. So we've

[68:35]

got that in. Now let's go to the next

[68:36]

step. And it's telling us that what we

[68:38]

need to do is we need to import this

[68:40]

inside of our main.tsx tsx file where we

[68:43]

have the clerk publishable key. So, let

[68:44]

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.jsx.

[68:52]

Now, from here, I'm just going to copy

[68:54]

in these two variables. So, the

[68:56]

publishable key like that. And I'm just

[68:58]

going to change a few things here

[68:59]

because I need to essentially wrap my

[69:01]

app in the clerk provider component, but

[69:04]

I also need to wrap it in my browser

[69:06]

router so I can do the uh what do you

[69:09]

call it? uh kind of routing or page

[69:10]

routing. So from here I'm going to say

[69:14]

import and then this is going to be the

[69:16]

clerk provider. Okay, from and this is

[69:20]

going to be at clerk slash clerk-react.

[69:24]

I'm then going to import the browser

[69:28]

router

[69:30]

from and this is going to be react-rower

[69:34]

DOM. Okay. Now for my app, I'm going to

[69:36]

wrap this in the clerk provider. For the

[69:40]

clerk provider, we're going to provide

[69:41]

our publishable key, which is stored in

[69:43]

the variable publishable key. Okay? And

[69:46]

then we're going to put our app directly

[69:48]

inside of here. Now, around that, we're

[69:51]

also going to wrap, let's just close

[69:53]

this, the browser router. So, we're

[69:55]

going to say browser router like that.

[69:57]

Okay? And again, inside of the browser

[69:59]

router, we're going to put our app.

[70:02]

Okay. So, now we should be good from the

[70:04]

main.js. JSX. So what we're doing is

[70:06]

we're just wrapping the app in the clerk

[70:08]

provider and the browser router so we

[70:10]

get access to the clerk settings or the

[70:12]

clerk configuration as well as the React

[70:15]

router DOM features. Okay. Now what

[70:18]

we're going to do is go inside of

[70:19]

app.jsx.

[70:21]

From here we're just going to clear

[70:23]

everything that is inside of this app

[70:26]

component. We're also going to remove

[70:28]

the import of app.css because I've

[70:30]

deleted that. We're going to remove the

[70:32]

imports of the React logo and the Vit

[70:34]

logo. And even same thing with use

[70:36]

state. We actually don't need that. And

[70:38]

we're going to start setting this up to

[70:39]

actually handle the routing for our

[70:41]

application. Now, before I can do all of

[70:43]

the routing, I do need to have the

[70:45]

different pages that I will be routing

[70:46]

to. So, what I'm going to do is just

[70:48]

stub those pages in my front end. So,

[70:50]

from source, I'm going to make a new

[70:51]

folder. So, let's go new directory like

[70:55]

this, and we're going to call it pages.

[70:57]

Now, inside of pages, we're just going

[70:59]

to create some empty pages that we can

[71:01]

route to. So, the first page we're going

[71:03]

to have, so let's make a new file is

[71:04]

going to be the dashboard.

[71:07]

Okay? And then page.jsx.

[71:11]

Now, for this, all we're going to do is

[71:12]

we are just going to write a really

[71:14]

simple component. So, we're just going

[71:15]

to say function

[71:18]

dashboard like so. And actually, this

[71:20]

can be dashboard page. And then we're

[71:22]

just going to return an empty fragment.

[71:25]

Okay? And then we're going to say export

[71:27]

default

[71:28]

the dashboard page like so. Okay. Now we

[71:31]

can just copy that and let's go to the

[71:33]

next page. Now for the next page, this

[71:35]

is going to be the homepage. So let's

[71:37]

just go homepage.jsx.

[71:39]

Okay. And then we can just change this

[71:41]

from dashboard page to say homepage

[71:44]

like so. Now let's have another page. So

[71:47]

let's go new file. This is going to be

[71:49]

the pricing page. So pricing page.jsx.

[71:54]

Same thing. Just change this to say

[71:57]

pricing

[71:58]

and

[72:00]

pricing. Perfect. Now we have two more

[72:03]

pages. I believe we're gonna have the

[72:04]

sign in and the sign up page. So we're

[72:06]

going to say sign up.jsx.

[72:09]

Same thing. Just change this to be the

[72:11]

sign up page and the sign up page. And

[72:16]

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

[72:19]

And this is going to be the signin

[72:20]

page.jsx.

[72:23]

And from here, sign in. Like that. And

[72:28]

sign in. Okay. So, those are our five

[72:31]

pages. 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

[72:37]

router to route between these different

[72:39]

pages. So, once we write them, they're

[72:40]

already showing up. So at the top of our

[72:42]

file, we're going to say import and then

[72:45]

route and router or sorry not router

[72:49]

route from and this is going to be react

[72:52]

dash router-dom.

[72:55]

Okay, that's the first import. Then

[72:57]

we're going to say import signed in

[73:01]

signed out and redirect to signin from

[73:05]

at clerk/clerk-react.

[73:07]

This allows us to essentially gate pages

[73:09]

so that if you're signed in, we show you

[73:10]

something. If we're signed out, we show

[73:12]

you something else. We're then going to

[73:14]

import the homepage. Okay, we are then

[73:18]

going to import the signin page. Okay,

[73:21]

so let's do that. Then we're going to

[73:23]

import the sign up page. Then we're

[73:28]

going to import the dashboard page. And

[73:30]

then I think the last one we need is the

[73:32]

pricing page. So let's import the

[73:34]

pricing page like that. Now we're going

[73:36]

to create a very simple protected route

[73:38]

function. So we're going to say

[73:39]

function. And this is going to be

[73:41]

protected route like so. And we're just

[73:44]

going to take in some children. And what

[73:47]

we're going to do is we're going to

[73:48]

return a react fragment. And inside

[73:52]

here, we are going to say if you are

[73:54]

signed in, then we will simply show the

[73:59]

children of the protected route.

[74:01]

Otherwise, if you are signed out, then

[74:05]

what we're going to show is the redirect

[74:07]

to sign in. Okay. So, all this is going

[74:10]

to do is redirect to the sign-in page if

[74:12]

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

[74:14]

easy. Now, from our app, what we're

[74:16]

going to do is we're just going to

[74:17]

define the different routes. So, if

[74:18]

you've never used React Route or DOM,

[74:20]

all we're doing here is we're saying,

[74:21]

okay, these are all of the different

[74:22]

pages that you could access. So, like

[74:24]

/home/signin slign out. That's all we're

[74:28]

putting here. And then when you go to

[74:29]

one of those routes, it will just render

[74:31]

the component that we wrote, which is

[74:32]

one of these pages. So, we're going to

[74:34]

say routes. Now, we're going to make a

[74:36]

route here. For the first route, we're

[74:38]

just going to say path is equal to

[74:41]

slash. Okay, so that's it. So that's it

[74:44]

for that. Now, inside of this route,

[74:46]

we're going to have a bunch of other

[74:47]

nested routes. So we're going to say

[74:48]

route index. Index is the default one.

[74:51]

And we're going to say element is equal

[74:53]

to and then this is going to be the

[74:55]

homepage.

[74:57]

Okay, so let's close the component like

[74:59]

that. And then for the route, we

[75:00]

actually don't need to close the route.

[75:01]

We can just make it self-encclosed. Then

[75:04]

we're going to have another route. Now

[75:06]

for this route we're going to say path

[75:08]

is equal to sign-in slash and then

[75:11]

asterisk and then we're going to say the

[75:13]

element is equal to and this is going to

[75:17]

be the signin page like so. Then we're

[75:22]

going to have another route. Okay. So

[75:24]

let's just do actually the exact same

[75:26]

thing that we did here. And let's change

[75:28]

this to be sign up. And then you guessed

[75:31]

it. This is just going to change to sign

[75:33]

up. Then let's copy that again for the

[75:36]

next route. Let's change this to be

[75:38]

slpricing.

[75:39]

Okay, so that's going to be the pricing

[75:40]

page. For the element, this is going to

[75:42]

be the pricing page. And then we can

[75:46]

have the dashboard. So for the

[75:47]

dashboard, we're going to say route.

[75:49]

We're going to say the path is equal to

[75:52]

dashboard. Then beneath here, we're

[75:55]

going to say the element is equal to and

[75:58]

for the element this time it's going to

[75:59]

be the protected route. Okay, so

[76:03]

protected route and inside of the

[76:05]

protected route we are going to have the

[76:07]

dashboard page. So essentially what

[76:09]

we're saying is okay, if you are signed

[76:11]

in, we'll allow you to access the

[76:12]

dashboard. If you're not signed in,

[76:14]

we're not going to let you access the

[76:15]

dashboard. So that's why we're using

[76:16]

this protected route component. For all

[76:19]

of the other pages, you can access them

[76:20]

even if you're not signed in. That's

[76:22]

fine. Okay, now that's it for kind of

[76:25]

handling the routing. Now, if we run

[76:26]

this and we go to these different pages,

[76:28]

you'll see that it will actually just

[76:29]

render those pages. although they won't

[76:31]

look any different because we don't have

[76:32]

anything different in those components.

[76:34]

But we can actually test it for now

[76:35]

because we can test for example the

[76:37]

protected rept. So let's open this up.

[76:40]

Let's go npm rundev. Okay, it should run

[76:43]

that for us. Let's open up our front

[76:45]

end. Okay, you notice this is kind of

[76:46]

the homepage. And if I go to slash

[76:49]

dashboard for example, you'll see that

[76:51]

it should redirect me. And you see it

[76:53]

redirects me to the sign-in page here

[76:55]

with clerk where it asks me to sign into

[76:57]

my account because I am not already

[76:59]

signed in. Okay, let's go back now.

[77:01]

Let's try to go to slashs signup or

[77:03]

something. And you can see we can just

[77:05]

go to that route. But if we try to go to

[77:06]

the protected one, then it brings me to

[77:08]

the signin page. Awesome. So that is

[77:12]

running. Now what we're going to do is

[77:14]

we're going to start building kind of

[77:15]

like the navbar which allows us to sign

[77:17]

in and sign out, see if we're signed in,

[77:19]

navigate between the different pages,

[77:21]

and then we'll start building the

[77:22]

various other components. So we're now

[77:24]

going to make another folder inside of

[77:26]

src. So, new folder, call this

[77:29]

components. And for the components,

[77:31]

we're going to start by creating a

[77:32]

layout component, which is going to

[77:34]

always be on the screen, which is going

[77:36]

to act kind of like a navbar. So, I'm

[77:38]

going to say layout.jsx.

[77:40]

Now, inside of here, I'm going to import

[77:43]

outlet as well as link from okay, and

[77:47]

this is going to be react router DOM.

[77:50]

Then I'm going to say import and this is

[77:53]

going to be signed in

[77:56]

signed out user button. The user button

[77:59]

is kind of like the profile button. Then

[78:01]

we're going to have the organization

[78:04]

if we can spell spell this correctly. Uh

[78:06]

switcher as well as use organization

[78:11]

from at clerk/clerk-

[78:13]

react. And for some reason it's giving

[78:15]

me an error here. It says it's defined

[78:17]

but never used. Okay, that's fine. So we

[78:19]

will use it later. Then we are going to

[78:22]

define the function. So we're going to

[78:23]

say function layout like so. We're going

[78:27]

to say const

[78:30]

organization.

[78:32]

Okay. And this is going to be equal to

[78:35]

use organization. And we're just going

[78:37]

to put this inside of braces. So we can

[78:41]

grab the organization. We're then going

[78:43]

to say return. And we're going to start

[78:44]

building this layout component. Again,

[78:47]

the way that the layout works is that

[78:49]

we're effectively just going to have

[78:50]

this always be on the screen where we're

[78:53]

kind of showing this like navbar at the

[78:54]

top of the page. So for this div, we're

[78:57]

going to have class name equal to

[78:59]

layout. We're then going to have another

[79:02]

div. So let's add this here. We're going

[79:04]

to say class name is equal to and this

[79:09]

is going to be nav. We're then going to

[79:10]

have another div. If you can't tell,

[79:12]

right now we're building the navbar.

[79:13]

We're going to say class name is equal

[79:16]

to nav dash container. We're then going

[79:19]

to have a link. So we're going to say

[79:21]

link and then this is going to be two

[79:23]

equals slash so to the homepage. And

[79:25]

we're going to say class name is equal

[79:28]

to nav- logo. And for this we're going

[79:31]

to put the name. So we're going to say

[79:32]

taskboard. If you press this it will

[79:34]

just bring you to the homepage. Now

[79:36]

beneath that we're going to have another

[79:38]

div. This is going to be class name

[79:40]

equal to nav-links where we'll have the

[79:42]

different links that you can navigate

[79:43]

between. So the first link, let's put

[79:46]

this here is going to go to and we're

[79:49]

going to go to the pricing page. Okay,

[79:53]

so is it going to be slashp pricing?

[79:54]

Yeah, it needs to be slashpricing. The

[79:57]

class name will be equal to nav-link.

[80:01]

And then here we can simply say pricing.

[80:04]

Okay, now we're going to have some

[80:06]

dynamic things. So, we're going to say

[80:08]

signed out. So, if you're signed out,

[80:10]

then we'll show you the ability to sign

[80:12]

in or to sign up. So, we're going to

[80:14]

have a link. Okay? And we're going to

[80:17]

say two is equal to and then same thing.

[80:19]

This is going to be slash sign-in.

[80:22]

Then, we're going to have class name

[80:25]

is equal to nav-link.

[80:28]

And you guess what this one is going to

[80:29]

say? It is sign in. Then, let's copy it.

[80:32]

And directly below that, we're going to

[80:34]

have the sign up. and just change this

[80:37]

to sign up. Okay, that's it. So, if

[80:39]

you're signed out, we'll show you this.

[80:41]

However, if you're signed in, then we're

[80:44]

going to show you your organization as

[80:46]

well as your profile. So, to show the

[80:48]

organization, we're going to say

[80:50]

organization switcher. This is a

[80:51]

component that comes directly from

[80:53]

clerk. And there's a bunch of settings

[80:55]

that we can pass here to change the

[80:57]

styling, which is what I'm going to do.

[80:59]

So, first I'm going to say hide

[81:00]

personal. Then I'm going to say after

[81:03]

okay and this is going to be create

[81:06]

organization

[81:08]

URL is equal to dashboard

[81:12]

then I am going to say after select

[81:17]

okay let's spell this correctly

[81:18]

organization

[81:20]

URL is equal to the dashboard as well

[81:25]

then I'm going to say create

[81:28]

organization

[81:30]

mode is equal to modal. So this means

[81:33]

it's going to pop up as like a popup on

[81:35]

screen rather than be a separate page

[81:37]

that we navigate to. And then we can

[81:39]

override the appearance which I'm just

[81:40]

going to quickly change because we need

[81:42]

to change it from kind of light mode to

[81:43]

dark mode. So we're going to say

[81:45]

elements and then here we're going to

[81:47]

say use or not use user preview main

[81:53]

identifier

[81:55]

text

[81:57]

personal workspace.

[82:00]

I know this is a lot of code but this is

[82:02]

how I change the color is going to be

[82:04]

color and then white like that. Again it

[82:08]

looks like we spelled identifier

[82:09]

correctly. So incorrectly sorry. So let

[82:11]

me just fix that. Okay. Now after that

[82:13]

one we're going to have organization

[82:16]

preview main identifier_ganization

[82:24]

switcher

[82:25]

trigger is and let's spell trigger

[82:29]

correctly and then same thing I always

[82:30]

keep spelling identifier incorrectly is

[82:34]

going to be color-white.

[82:36]

Okay. Again, I just need to change this

[82:38]

from light mode effectively where the

[82:39]

text is going to be black to dark mode

[82:41]

where the text will be white. Okay. So,

[82:43]

that's it for the organization switcher.

[82:45]

Kind of annoying to do that override,

[82:46]

but that is just one thing we need to

[82:48]

do. And then I'm going to say, okay, if

[82:49]

you're currently in an organization, so

[82:51]

let's spell organization correctly. So,

[82:54]

we're going to say organization and and

[82:56]

then we're going to have a link. And the

[82:58]

link is going to say to equals and then

[83:01]

slash dashboard. Okay. and class name

[83:06]

is equal to nav-link

[83:10]

and then here we're just going to say

[83:11]

dashboard like that. Okay. So the idea

[83:14]

here is that a user might be signed in

[83:16]

but they might not yet be a part of an

[83:18]

organization. So if they're not a part

[83:19]

of an organization well I can't show

[83:20]

them the dashboard because the dashboard

[83:22]

only exists per organization. So that's

[83:24]

kind of what we're talking about here in

[83:26]

this signed component. Now if they're

[83:28]

not signed in of course they're not in a

[83:30]

uh what do you call organization cuz

[83:31]

well they're not signed in. So we'll

[83:33]

just show them this to sign in or to

[83:35]

sign up. Okay. Then we need to export

[83:37]

this. So export default layout. And I

[83:41]

think we are all good. Now the only

[83:43]

thing is that we need to render this

[83:46]

outlet and we also need to display the

[83:47]

user button which I forgot. So let's

[83:49]

quickly do that. So out so inside but at

[83:52]

the end of this signin container we're

[83:54]

going to put the user button which is

[83:56]

just going to show the user's profile.

[83:59]

And then down here in the end of this

[84:01]

div, we're going to say main and we're

[84:03]

going to put outlet.

[84:05]

Okay. Now, what this is going to do is

[84:07]

it's going to take whatever should be

[84:09]

showing in the page that we're

[84:10]

navigating to and just put it inside of

[84:13]

here. So, effectively the way the layout

[84:15]

works is that we're just rendering the

[84:16]

page inside of this outlet. Everything

[84:19]

else is just static and always stays on

[84:21]

screen. So, this layout is always

[84:23]

visible based on what's happening here.

[84:25]

and then we show whatever else kind of

[84:27]

the content that the current screen is

[84:29]

showing inside of this out. Hopefully

[84:32]

that makes sense. So now what we need to

[84:34]

do is just import layout from here. So

[84:35]

we're just going to say import layout

[84:39]

from and this is going to be components.

[84:42]

So I think it's dot /components/

[84:44]

layout like that. And then we're going

[84:46]

to go to our route like this and we're

[84:49]

going to say the element is equal to

[84:56]

layout. Cool. So now if we do that and

[84:59]

we go back here, we should see the

[85:01]

navbar popping up. And you can see that

[85:04]

we have pricing, sign in, sign up. And

[85:06]

for some reason the styling is not

[85:07]

working 100%. So I'll have a look at

[85:09]

that in one second. Um, but that is a

[85:11]

good start. Okay. Okay, so I was just

[85:13]

having a look at the styling and I

[85:14]

noticed some issues right in terms of

[85:16]

how the styling was here. So I just

[85:18]

realized I made a few errors when I was

[85:20]

building this layout where first for

[85:23]

this link I actually needed to remove

[85:25]

the nav link that was here before and

[85:27]

change it to this btn btn- primary and I

[85:31]

need to take all of this and just put it

[85:34]

one div up. So essentially after the

[85:37]

pricing it goes inside of here. Okay. So

[85:41]

that it's in this nav links div which it

[85:43]

wasn't in before. Okay. And let's just

[85:46]

fix this a bit so that it is all inside

[85:50]

of here correctly. Let's fix the

[85:51]

indentation. And you can see now that

[85:53]

it's inside of this nav links div. So

[85:55]

now if I come back you can see that it's

[85:56]

properly on the right hand side of the

[85:58]

screen which is what I was looking for

[86:00]

before. Okay. So now the layout

[86:02]

component is finished. And what I want

[86:04]

to do is start writing the sign in and

[86:07]

sign up page. So we actually have the

[86:09]

ability to create an account and sign

[86:11]

into our application. So let's start

[86:13]

with the sign up page. Now for the

[86:15]

signup page, this is very easy. All we

[86:17]

need to do is just say import sign up.

[86:21]

And then this is going to be from the

[86:24]

at@ clerk/clerreact

[86:27]

package. And then what we can do is just

[86:28]

return a simple div. For the div we can

[86:31]

say class name is equal to off dash

[86:34]

container. And then inside the div we

[86:37]

can put a signup component. We can say

[86:40]

routing is equal to and this is going to

[86:43]

be path. And then we can say path is

[86:45]

equal to slash sign up. So it knows that

[86:48]

it goes back to this page. And then

[86:50]

we're going to say the signin URL is

[86:52]

equal to slash sign-in.

[86:56]

Okay. Then we can actually just

[86:57]

selfenclose this component. And that is

[87:00]

the sign up component from clerk. We

[87:03]

just use it and it will work like this.

[87:05]

Now, let's do the same thing for the

[87:07]

signin page. So, for the sign-in page,

[87:09]

it's going to be literally the exact

[87:11]

same thing except sign in. So, let's

[87:13]

just copy what we have here. Okay. So,

[87:15]

we're going to say from or we're going

[87:17]

to say import, sorry. And this is going

[87:19]

to be sign in. Okay. From at clerk/clerk

[87:22]

react for the return, make it the same

[87:24]

thing except this is going to be sign

[87:28]

in. This is going to be sign in. And

[87:30]

this is going to be sign up. And this is

[87:33]

going to be the sign up URL. So just

[87:36]

swapping those around. And now let's

[87:38]

remove this sign up component. If we go

[87:41]

back here, we refresh and we press sign

[87:44]

up for example, you see that it brings

[87:46]

us to the sign up page where we see the

[87:48]

sign up modal. If you go to sign in,

[87:50]

brings us to the signin page. So what we

[87:53]

can do now is try to make an account.

[87:55]

Let me create one. Press continue and

[87:57]

we'll go from there. Okay. So I'm just

[87:58]

going to make an account here with my

[88:00]

email. Let's close that. Uh, okay. I'm

[88:03]

going to need a new password. So, let's

[88:05]

set a new one. Go continue. And then

[88:08]

what it's going to do probably is ask me

[88:10]

to verify my email. So, yes, you can see

[88:11]

I have to verify the email. Let me do

[88:13]

that and I'll be right back. Okay. So,

[88:15]

this is the code I got in my email. And

[88:17]

now I press enter and I get brought back

[88:19]

to the page. Looks like there must have

[88:21]

been some error or something. I don't

[88:22]

think it signed me in maybe. So, I

[88:24]

signed up, but it didn't sign me in. So,

[88:27]

let me try to sign into that account

[88:29]

now. Uh, oh, now it's saying set up your

[88:31]

organization to continue. Okay, that's

[88:33]

interesting. So, let's just create an

[88:35]

organization, I guess, like Tim A or

[88:38]

something. Okay, and let's see if it

[88:40]

works. And there we go. Okay, now we get

[88:42]

brought into the dashboard where it

[88:44]

looks like we are all good. So, it's a

[88:46]

little bit buggy when we're creating the

[88:47]

new account where we need to actually

[88:49]

create the organization first, otherwise

[88:51]

it doesn't let us access this page. I

[88:53]

also still need to change the color. So,

[88:55]

I think I might have just had a spelling

[88:56]

mistake when I was adjusting the

[88:57]

parameters here. But you can see I'm

[88:59]

inside an organization. I can now manage

[89:01]

this information from this kind of clerk

[89:03]

dashboard here and I have the ability if

[89:06]

I want to invite someone to my

[89:08]

organization, I can view the dashboard.

[89:10]

I can go to the pricing page even though

[89:12]

there's nothing there. And then if I

[89:14]

press on my little user account guy, you

[89:15]

can see that I have the account again

[89:17]

because of clerk. And I can for example

[89:19]

just go here and go sign out. And then

[89:22]

the UI will update and show me this. And

[89:24]

if I go back to clerk now and I refresh,

[89:27]

it should start showing me my users. And

[89:29]

you can see that I have a user here,

[89:30]

timotiv.net. I can view all the

[89:32]

information about the user, when they

[89:34]

signed in, their password. Well,

[89:35]

actually, I don't think I can view their

[89:37]

password, but I can change or reset

[89:38]

their password, etc., etc., etc., and I

[89:41]

can even see their organizations, which

[89:42]

one they belong to, invite them to an

[89:44]

organization, mess with all of their

[89:46]

settings. You get the idea. Okay, super

[89:48]

cool. So, the O is working. We're able

[89:50]

to sign in. We're able to sign up,

[89:51]

create a new account, and create

[89:53]

organizations. Now, what we want to do

[89:54]

is start handling kind of the taskboard

[89:56]

and then get into some more of the

[89:58]

advanced stuff where we're inviting

[89:59]

different users, having the billing, the

[90:01]

subscriptions, the pricing, all of that.

[90:03]

All right. So, now that we finished the

[90:04]

login functionality, what we're going to

[90:06]

start working on is the actual task

[90:08]

management. So, having that kind of

[90:10]

canban style board like you saw,

[90:12]

creating tasks, deleting tasks, and then

[90:15]

of course the pricing, and upgrading the

[90:16]

organization. So let's actually start by

[90:18]

going to the homepage and let's code

[90:20]

this out for now so that we have kind of

[90:22]

some styling and we have like a nice

[90:23]

landing page people can go to. Then we

[90:26]

can build the dashboard where you're

[90:27]

going to be able to actually see the

[90:29]

different tasks and move those around.

[90:31]

Okay. So from the homepage we're going

[90:33]

to start by importing a link from React

[90:37]

Router DOM. Then we're going to import

[90:42]

signed in and then signed out use

[90:46]

organization which comes from clerk as

[90:48]

well as create organization. Okay. All

[90:51]

right. So now we're going to go and

[90:54]

inside of the homepage component we're

[90:56]

going to check the organization the user

[90:57]

is in. So we're going to say const

[90:59]

organization is equal to use

[91:02]

organization. Okay. So let's pull in

[91:04]

that react hook. And from here, let's

[91:07]

start creating the UI. So, we're going

[91:09]

to return a div. For the div, we're

[91:12]

going to have class name is equal to,

[91:14]

and this is going to be the home dash

[91:18]

container.

[91:20]

Okay. And let's spell container

[91:22]

correctly like that. Now, inside of this

[91:26]

div, we're going to have an h1. The h1

[91:28]

is just going to be the title. So, we're

[91:29]

going to have class name equal to home

[91:33]

dash title. Here we're going to say team

[91:36]

task management like that. And then

[91:39]

we're going to put a span. We're going

[91:41]

to say class name is equal to home and

[91:44]

then dashtitle and then dash accent. And

[91:47]

we're just going to have made simple

[91:50]

just to add some kind of nice styling

[91:51]

here to the header. Then we're going to

[91:53]

have a paragraph tag. We're going to say

[91:56]

class name is equal to and for the class

[91:59]

name this is going to be the home dash

[92:02]

subtitle.

[92:04]

Inside the subtitle we're going to say

[92:06]

organize your team's work with powerful

[92:11]

and let's spell this correctly with

[92:13]

powerful task boards. Then we're going

[92:16]

to say create, assign, and track tasks

[92:22]

across

[92:24]

your organization.

[92:27]

Okay. Then we're going to have a signed

[92:29]

out container for the users that are

[92:31]

signed out. We're essentially just going

[92:32]

to tell them, hey, you know, go and like

[92:34]

create a new account. So we're going to

[92:35]

have a div. We're going to have class

[92:38]

name is equal to the home-buttons.

[92:42]

Then inside the div, we're going to have

[92:44]

two links. So for the first link, we're

[92:45]

going to say two is equal to signup and

[92:49]

this is going to be /s signup. And then

[92:51]

for the class name, we're going to say

[92:53]

this is equal to btn btn- primary btn-lg

[92:59]

for large. And inside the link, we're

[93:01]

going to say get started for free. Okay.

[93:05]

Then we're going to have another link.

[93:06]

For this one, we're going to say two.

[93:08]

And this is going to be slash signin.

[93:11]

And then we're going to say the class

[93:14]

name is equal to this will be btn btn-

[93:19]

outline btn-large

[93:21]

again. And then this is just going to

[93:23]

say sign in. Okay, so that's it. If you

[93:26]

are signed out, now if you are signed

[93:28]

in, the UI is going to look a little bit

[93:30]

different. So first we're going to check

[93:32]

if you're currently in an organization.

[93:33]

So we're going to say organization

[93:35]

question mark. Okay. Now if you are in

[93:38]

an organization, then we're just going

[93:39]

to have a link. And the link is going to

[93:41]

go to the dashboard and just tell you,

[93:43]

okay, you know, go manage your task from

[93:45]

the dashboard. So here we're going to

[93:47]

say the class name for this link is

[93:49]

going to be equal to btn btn- primary

[93:52]

and then btn-lg.

[93:55]

Okay. And we're going to say go to

[93:58]

dashboard. Then otherwise if you do not

[94:01]

have an organization, we're going to

[94:03]

have a div. So let's create the div

[94:05]

here. The div is going to have a class

[94:07]

name which is equal to home-create-org

[94:13]

and then we're going to put create

[94:16]

organization like this. So this is

[94:18]

essentially a form that is rendered by

[94:20]

clerk. So we want to show them hey you

[94:22]

need to create an organization. So we're

[94:23]

just going to show this directly on the

[94:25]

homepage so that they can see it and

[94:26]

they can create it. And then we're going

[94:28]

to have after create

[94:31]

organization

[94:33]

URL is equal to slashdashboard.

[94:38]

Okay. So after they create an

[94:39]

organization, we'll redirect them to the

[94:41]

dashboard. Okay. So that's going to be

[94:43]

it for the homepage. Effectively, what

[94:46]

we're saying is all right, if the user

[94:47]

is signed out, tell them to sign in or

[94:49]

sign up. If they're signed in, check if

[94:51]

they have an organization. If they do,

[94:53]

allow them to go to the dashboard. If

[94:54]

they don't have an organization, then

[94:56]

simply tell them to create one. Okay, so

[94:58]

that's the homepage. Let's test it out.

[95:00]

Let's go here. And there we go. Nice.

[95:02]

Team task management made simple. I

[95:04]

think we're going to have to add like a

[95:06]

line break there because looks like it's

[95:08]

on the same thing. So, let's just add a

[95:10]

BR like that. There we go. Looks good.

[95:13]

And we can see we have this nice UI.

[95:16]

Okay. So, let's continue from here. And

[95:18]

the next thing we're going to do is

[95:19]

start coding out some of the components

[95:21]

related to the tasks. So, actually, I'm

[95:24]

just going to close all of these windows

[95:25]

for right now. I'm going to make a new

[95:27]

folder here, and I'm just going to call

[95:29]

this one services.

[95:31]

And inside of services, what I'm going

[95:33]

to do is I'm going to write a file

[95:35]

called API.js, where we're going to put

[95:37]

all the calls to our backend API. So,

[95:39]

we're going to say API.js. Here, we're

[95:41]

going to make a variable. We're going to

[95:42]

say const API URL is equal to

[95:46]

import.env.vit_appi

[95:50]

vit_api

[95:52]

URL or then this is going to be http

[95:56]

slash okay localhostport8000

[96:01]

now if we want to define this as a

[96:03]

variable in the environment variable

[96:05]

file we can otherwise it'll just load

[96:07]

this by default if we don't have

[96:09]

anything there then we're going to have

[96:11]

a function so we're going to say export

[96:13]

async function and this is going to be

[96:15]

fetch with o what this is going to do is

[96:18]

this is going to ascend a request to our

[96:21]

backend with the clerk authentication.

[96:24]

So what I'm doing in this file is I'm

[96:26]

writing all of the requests that will be

[96:27]

sent to our backend. So we can simply

[96:29]

just call the functions from our

[96:30]

front-end code. Now here we need to make

[96:33]

sure that we include the correct headers

[96:35]

which includes the clerk JWT token which

[96:38]

is essentially how clerk identifies our

[96:40]

signedin users. We're going to send that

[96:42]

to our backend along with every single

[96:44]

request. So that's what this kind of

[96:45]

helper function is going to do. So we're

[96:47]

saying export async function uh and this

[96:49]

is fetch with o. We're going to take in

[96:51]

some endpoint. We're going to take in

[96:53]

get token and then options which is

[96:57]

equal to an empty uh object. We're going

[97:00]

to say const and this is going to be

[97:02]

token is equal to a weight get token.

[97:05]

This is a function that we're going to

[97:06]

call from clerk which is going to give

[97:08]

us the token that we need to use. Then

[97:11]

what we're going to do is we're going to

[97:13]

say const response is equal to await

[97:18]

fetch. What we're going to do is we're

[97:20]

going to fetch the following URL. So

[97:22]

this is going to be inside of a dollar

[97:24]

sign because we're putting a variable

[97:26]

the API URL. And then we're going to

[97:29]

have another dollar sign and then we're

[97:30]

going to put the endpoint that was

[97:33]

passed to this function. So that's the

[97:35]

URL that we're calling. Now the options

[97:37]

that we're going to pass is dot dot dot

[97:40]

options. So this right here one of our

[97:42]

parameters as well as we're going to

[97:45]

pass the content dash type and this is

[97:48]

going to be application /json and then

[97:53]

we're going to have o

[97:56]

and then we're going to have

[97:57]

authorization

[97:59]

is

[98:01]

bearer and then we're going to put our

[98:03]

token in here. This identifies the user.

[98:06]

And then we're going to say dot dot dot

[98:10]

options dot headers. And this reminds me

[98:13]

that I need to actually fix something

[98:14]

because I need to put headers like this.

[98:16]

So I need to say headers and then all of

[98:18]

this is going to be a header that we're

[98:20]

passing with our options. Okay. So let's

[98:22]

just quickly fix that as we did.

[98:24]

Perfect. So now after the response,

[98:26]

we're going to say if not response.

[98:32]

So response. Okay. Then what we're going

[98:34]

to do is say con error is equal to await

[98:39]

response.json.

[98:41]

Let's spell response correctly.

[98:44]

catch. And for the catch, we're just

[98:47]

going to put something empty here

[98:48]

because we're not going to handle that

[98:49]

right now. Then we're going to say throw

[98:52]

new

[98:53]

error. And we're just going to throw the

[98:55]

error dot detail or we're going to throw

[98:59]

request failed like that. Okay. Then

[99:03]

we're going to say if the response

[99:07]

status is equal to 204, so it has no

[99:11]

content, we're just going to return

[99:13]

null. Otherwise, we're going to return

[99:17]

response.json, which is essentially the

[99:19]

response, right? And whatever we got

[99:21]

back from the API. Okay, so this is the

[99:24]

function that we're going to call when

[99:25]

we want to send a request to the backend

[99:27]

with our clerk authentication. So now

[99:29]

what we're going to do is we're going to

[99:30]

write the individual functions for the

[99:32]

operations that we want to perform in

[99:33]

the back end. So the first one is going

[99:35]

to be export async function and this is

[99:38]

going to be get tasks and this is going

[99:39]

to get all the tasks from our back end.

[99:41]

So same thing we're going to take in

[99:42]

this get token function and we're going

[99:44]

to return fetch with off.

[99:49]

Okay. And when we fetch with Oth, we're

[99:51]

going to fetch the slash API slash tasks

[99:55]

sorry route. We're going to pass the get

[99:58]

token function. Okay. Now let's copy the

[100:00]

same thing. And for the next one, rather

[100:02]

than get tasks, we're going to say

[100:05]

create task. This time we're going to

[100:06]

take in get token and we're going to

[100:08]

take in a task. And then we're going to

[100:10]

call API/tasks, but this time it's going

[100:13]

to be a post request. So what we're

[100:15]

going to do is we're going to add in

[100:17]

some options here. For the option, we're

[100:19]

going to say method is equal to in all

[100:21]

capitals post. And we're going to say

[100:24]

the body is equal to JSON dot stringify.

[100:28]

Stringify like so. And we're going to

[100:30]

pass in the task. Okay. So that is now

[100:34]

how you create the task. Now we'll paste

[100:36]

this again. And now we're going to do

[100:37]

update task. So we're going to say

[100:40]

update task. Okay. We're going to take

[100:42]

in get token. We're going to take in

[100:44]

task ID as well as the task. We're going

[100:47]

to fetch with Oth. Same thing,

[100:48]

API/tasks.

[100:50]

And now this time we're going to say the

[100:52]

method is equal to put because that's

[100:55]

the method we use when we want to

[100:56]

update. Then we're going to say the body

[100:59]

is JSON dot stringify. And we're going

[101:02]

to stringify the task. And actually one

[101:04]

small change, we need to include the

[101:06]

task ID in the URL. So we're just going

[101:09]

to do that like this by changing these

[101:10]

to back. Okay, it already is back.

[101:12]

Perfect. That's great. So slash API

[101:15]

slashtasks slash the task ID of the task

[101:19]

that we want to update. And then in the

[101:21]

body, we just include the new task data

[101:23]

that we want to use when we're updating.

[101:25]

Okay. And then the last thing we need to

[101:26]

do is delete a task. So we're going to

[101:28]

change this to say delete task. Same

[101:32]

thing, we're going to take get token and

[101:34]

this time the task ID. Then we're going

[101:36]

to change this to be a back tick. Okay?

[101:40]

and we are going to include the task ID

[101:43]

we want to delete. So let's include

[101:45]

that. And we're going to change the

[101:46]

method to be delete. So we're going to

[101:48]

say method

[101:51]

delete in all capitals like that. And

[101:54]

now we have the four functions. So get

[101:56]

task, create task, update task, and

[101:58]

delete task. And now we can use those to

[102:00]

call the backend API. Okay. So with that

[102:03]

in mind, let's start writing the

[102:04]

components that we need for representing

[102:05]

the tasks. So we're going to go in the

[102:07]

components directory. And the first

[102:08]

component that we're going to work on is

[102:10]

just going to be our task card. So we're

[102:12]

going to say task card.jsx.

[102:15]

And inside here, we're just going to

[102:16]

write a card that represents one

[102:17]

individual task. So we're going to say

[102:19]

function task card. And we're going to

[102:23]

take in as values here the task onedit.

[102:27]

So a function to call if we're ending

[102:28]

the task and a function to call if we

[102:30]

are deleting the task. Then we're going

[102:32]

to say const and we're going to say

[102:34]

canedit is equal to exclamation mark

[102:37]

exclamation mark onedit. So essentially

[102:40]

did we pass that function? Then we're

[102:41]

going to say const can delete. Okay. And

[102:44]

this is going to be equal to exclamation

[102:45]

point exclamation point and then on

[102:47]

delete. Okay. Then we're going to return

[102:50]

the UI. So we're going to return a div.

[102:53]

We're going to set the class name is

[102:55]

equal to and this is going to actually

[102:56]

be in backtix. And we're going to say

[102:58]

task dashcard. And then we're going to

[103:01]

have a dynamic name based on if we can

[103:03]

edit the task or not. So we're going to

[103:05]

say can edit question mark. And then if

[103:07]

we can edit, we're going to have the

[103:09]

following task-cardclickable.

[103:12]

So you can click the task to edit it.

[103:15]

Otherwise, we're just going to have an

[103:16]

empty string. So if you can't edit it,

[103:18]

we're not going to let you click on the

[103:19]

task or at least we're not going to show

[103:20]

that with this class name. Next, what

[103:23]

we're going to do is have the onclick

[103:24]

handler. So that's going to be in the

[103:26]

div as well. So let's fix this here. So,

[103:28]

we're just going to fix the styling.

[103:30]

Okay. And we're going to say like this

[103:34]

on click is equal to same thing. We're

[103:37]

going to say canedit

[103:39]

question mark. If we can edit, then

[103:41]

we're going to call the onedit function.

[103:44]

So, onedit with the task. Otherwise,

[103:48]

we're going to say undefined. Okay. Then

[103:51]

we're going to have another div inside

[103:52]

of here. For the div we're going to have

[103:54]

class name equal to and we're going to

[103:56]

start with the header. So we're going to

[103:57]

say task card header. Inside this div,

[104:01]

we're going to have an H4.

[104:04]

So let's write that. For the H4, we're

[104:05]

just going to put the task name. So

[104:07]

we're going to say class name is equal

[104:09]

to task card-title.

[104:13]

And then inside of here, we're going to

[104:14]

have task.title.

[104:18]

Okay. Now, underneath the H4, we're

[104:19]

going to have a can delete. So if we can

[104:22]

delete, we're going to show a delete

[104:23]

button. We're going to say can delete

[104:25]

and and and then we're going to put a

[104:27]

set of parenthesis. We're going to put a

[104:29]

button. For the button, we're going to

[104:31]

say the class name is equal to and this

[104:35]

is going to be task-card-bascard-b.

[104:44]

Okay. Now, let's just fix the classes a

[104:47]

little bit here. Beneath this, we're

[104:49]

going to have the on click. The on click

[104:51]

is going to be the following. So we're

[104:53]

going to have E. Okay. Then we're going

[104:55]

to go into a function. Now inside of the

[104:57]

function we're going to say E.top.

[105:00]

And this is propagation. Okay. Then

[105:02]

after that we're going to call the ondee

[105:04]

function that's passed into the parent

[105:06]

with the task ID to delete it. Okay.

[105:11]

Then we're going to continue and we're

[105:13]

going to have title and the title is

[105:15]

going to be delete

[105:17]

task like that for this particular

[105:20]

button. And then we're just going to

[105:21]

simply have an X inside of the button.

[105:23]

That's what we're going to render on

[105:25]

screen. Okay. So, if we can delete,

[105:26]

we're going to show that. And then

[105:28]

beneath this, we're going to say task

[105:29]

dot description just to make sure it

[105:32]

does have a description. If it does,

[105:34]

then we're going to show the

[105:35]

description. And for that, we'll just

[105:36]

have a paragraph tag. We'll say class

[105:39]

name is equal to and this can be

[105:42]

task-card

[105:44]

description

[105:46]

like that. And then for the description,

[105:48]

we can just show the task dot

[105:50]

description. Okay, so that should be it

[105:53]

for the task card. Then what we can do

[105:55]

is export this. So export default.

[105:59]

Okay, and then task card like so. And

[106:02]

then we can start using the task card.

[106:05]

Okay. So as well as the task card, now

[106:06]

we're going to create the task column

[106:08]

because we're going to have three

[106:09]

columns full of task cards. So we're

[106:11]

going to say task column.jsx.

[106:14]

For the task column, this is just going

[106:16]

to display the various task cards. So

[106:18]

we're going to say import task card from

[106:21]

there. Okay. We're then going to say

[106:23]

const status

[106:26]

labels is equal to and then we're going

[106:29]

to have the label. So we're going to

[106:30]

have pending is equal to todo.

[106:35]

We're going to have started which is

[106:38]

equal to in progress. And then we're

[106:41]

going to have completed which is equal

[106:44]

to done. Okay. Okay. Then we're going to

[106:46]

have function task column. This is the

[106:49]

component. We're going to take in

[106:51]

status. We're going to take in the

[106:52]

tasks. We're going to take an onedit and

[106:55]

ondee as those functions. And then we're

[106:58]

going to return the UI. So we're going

[107:00]

to return a div. The class name

[107:04]

is going to be for this first div the

[107:06]

canban dash column. Okay. Now inside of

[107:10]

this div, we're going to have another

[107:11]

div. So for the first one, we're going

[107:13]

to have class name. And this is going to

[107:16]

be inside of back tick because it's

[107:17]

going to be dynamic. It's going to be

[107:19]

canban dash column

[107:23]

dash header and then canban dash column.

[107:28]

Let's spell column correctly. dash

[107:30]

header dash and then we're going to put

[107:33]

the status. So whatever the actual

[107:35]

status is, we're going to use that in

[107:36]

the class name so that we can show a

[107:37]

different color um what do you call it?

[107:39]

board kind of based on what the status

[107:41]

is. Then we're going to have an H3 tag

[107:44]

for the name of the column. So we're

[107:45]

going to have a class name for the H3

[107:48]

and the class name is going to be the

[107:50]

canban dash column dashtitle. And what

[107:55]

we're going to do is we're going to use

[107:56]

the status labels and we're going to

[107:58]

pass in the status so that we get kind

[108:00]

of the string representation that we

[108:02]

actually want to show in the header. And

[108:04]

then we're going to have a span that's

[108:06]

just going to show the number of tasks

[108:07]

that are here. So, we're going to have

[108:09]

class name equal to cananban dashc

[108:13]

column dash count. And inside of here,

[108:17]

we're going to have the tasks.length.

[108:20]

Okay. Now, beneath that, we're going to

[108:23]

have a div. We're going to say class

[108:25]

name is equal to cananban dash column

[108:30]

dashbody. This is where we're actually

[108:31]

going to display the tasks. And we're

[108:33]

effectively just going to take all the

[108:34]

tasks and we're going to map them to

[108:36]

their components. So, we're going to say

[108:37]

task. Okay. And then this is going to

[108:39]

map to the task card. So, we're going to

[108:41]

say task card

[108:44]

like that. Now, for the task card, we

[108:46]

need to write all of the values. So,

[108:48]

let's actually just put this in

[108:50]

parenthesis. Okay. Put another set of

[108:53]

parenthesis and put this down here so

[108:56]

that we can actually write it properly.

[108:57]

So, we're going to say key is equal to

[109:01]

task. ID. Then, we're going to say the

[109:03]

task is equal to the task. We're going

[109:06]

to say onedit is equal to onedit. And

[109:10]

we're going to say ondee is equal to

[109:12]

ondelete.

[109:14]

Okay. And that's going to render the

[109:16]

tasks. And then that's it. We can just

[109:18]

export default the task column from this

[109:22]

component or from this file. Perfect. So

[109:24]

now we have the task column. The task

[109:26]

column renders the task cards. Now we

[109:28]

need essentially like a canban kind of

[109:30]

layout. So we can render the canban

[109:33]

layout which renders the columns which

[109:34]

renders all of the tasks. So let's write

[109:37]

another component. This time it's going

[109:39]

to be called the canban

[109:42]

board.jsx.

[109:45]

And inside of here we're going to start

[109:46]

writing all of the kind of complex logic

[109:49]

with the functions to actually pull the

[109:50]

tasks display them in the correct

[109:52]

columns etc. So we're going to say

[109:54]

import use state from and then we're

[109:59]

going to go to react. Then we're going

[110:01]

to say import

[110:03]

use organization

[110:06]

from at clerk slash clerk react. Then

[110:11]

we're going to import the task column

[110:15]

from dot slashtask column. Okay. And I

[110:19]

don't need the jsx there. Then I'm going

[110:21]

to import the create task

[110:26]

update task. Okay. as well as delete

[110:30]

task. Let's write this from the

[110:34]

services. Okay, so from dot dot slash

[110:37]

services/ API because that's where we

[110:39]

wrote these different functions.

[110:41]

Awesome. Now, we're also going to need

[110:42]

to have a form for creating the tasks,

[110:45]

but we will write that in 1 second. And

[110:47]

then we're going to have const and in

[110:49]

capital we're going to have statuses is

[110:51]

going to be equal to pending

[110:54]

started and completed as it matches up

[110:57]

with our back end. Now we're going to

[110:59]

have the components. We're going to say

[111:00]

function canban board. Here we're going

[111:04]

to have the tasks. We're going to have

[111:07]

set tasks and we're going to have get

[111:10]

token as the functions that we need. Now

[111:13]

inside of here we're going to say const

[111:16]

membership

[111:17]

is equal to use organization and it's

[111:20]

going to tell us what our role is within

[111:22]

the organization. So if you want to get

[111:24]

the role of the particular user, you can

[111:26]

use this use organization hook from

[111:28]

clerk and then you can pull membership.

[111:30]

So previously we were looking at the

[111:31]

organization itself here. However, we

[111:34]

can also get the membership and that

[111:35]

will tell us if they're a member, an

[111:37]

admin, an editor, whatever their role in

[111:39]

the org is as you've defined in clerk

[111:42]

when you set it up. We're also going to

[111:43]

have const

[111:45]

show form set show form. This is going

[111:47]

to be for showing the creation form

[111:49]

which we will write in a second. So,

[111:51]

we're going to say use state and that's

[111:53]

going to be false. And we're then going

[111:55]

to have const editing task.

[111:58]

Okay, set editing

[112:02]

task and this is going to be equal to

[112:04]

use state and false. And then let's get

[112:08]

rid of that. We're going to get the

[112:10]

role. So we're going to say const ro is

[112:12]

equal to membership question markroll.

[112:14]

So this is going to give us their role

[112:15]

in the organization. We're going to say

[112:17]

const can manage. So essentially can

[112:19]

they edit is equal to ro equal equal to

[112:23]

and then we're going to say org colon

[112:25]

admin. So this is the role if they're an

[112:27]

admin in the organization or role is

[112:30]

equal to or colon editor which is the

[112:34]

other role that we created. If you had

[112:36]

your own custom roles all you would do

[112:37]

is just change what you see here after

[112:39]

the colon colon sorry to that custom

[112:41]

roles name right as you saw in clerk.

[112:44]

Okay. Then we're going to have function

[112:46]

get tasks by status.

[112:50]

We're going to take in a status and

[112:52]

we're going to return tasks like this

[112:56]

dot filter and then task and we're going

[112:59]

to go task dot status is equal to the

[113:04]

status. So this is just going to filter

[113:05]

all the tasks in our task list and give

[113:08]

us the ones that are a particular status

[113:09]

so we can put them in the correct

[113:11]

column. Then we're going to have a

[113:12]

function and this is going to be

[113:14]

handleedit.

[113:17]

We're gonna have a task and inside of

[113:19]

here we're going to say set editing task

[113:22]

and we're going to put the correct task

[113:24]

that we're currently editing and we're

[113:26]

going to say set show form to true.

[113:29]

Okay, so actually I need to change this

[113:30]

state to say null. So essentially the

[113:33]

task that we're currently editing we're

[113:35]

going to store in this variable right

[113:37]

here. The reason we need that is because

[113:38]

in a second we're going to pop up a form

[113:41]

that's going to show the information for

[113:43]

the task that we either want to create

[113:45]

or edit. So we have two variables,

[113:46]

right? One, okay, should we show the

[113:48]

form on screen? Yes or no? And if we are

[113:50]

showing the form, do we have a task

[113:52]

we're currently editing? If we do, we'll

[113:54]

put that inside of the form so we can

[113:55]

edit that particular task. Okay. Now, a

[113:58]

few other functions that we're going to

[113:59]

need. So, for example, like deleting the

[114:01]

task, etc. So, what I'm going to do here

[114:03]

is say function, and this is going to be

[114:06]

actually an async function because we're

[114:08]

going to call our back end. So, we're

[114:09]

going to say async function handle

[114:12]

delete. This is going to take in a task

[114:15]

ID to delete.

[114:18]

And what we're going to do here is we're

[114:19]

going to say if not, confirm. And we're

[114:23]

going to say, are you sure you want to

[114:27]

delete this task?

[114:31]

Okay. And we're just going to return. So

[114:34]

if they didn't confirm this, because

[114:35]

this is just going to pop up on the

[114:36]

window essentially ask them, hey, like

[114:38]

do you want to delete this or not? So if

[114:39]

they don't confirm it then we will not

[114:41]

delete the task and we will return.

[114:43]

Otherwise what we'll do is we'll say con

[114:45]

task to delete is equal to tasks.find

[114:52]

and we're going to find the task with

[114:54]

the particular ID. So T ID is equal to

[114:57]

the task ID like so. We're going to say

[115:00]

set tasks and then we're going to say

[115:03]

previous and we're just going to remove

[115:04]

this task from the screen so that it

[115:06]

goes away right away. We're going to say

[115:07]

previous.filter filter and then t do ID

[115:11]

does not equal the task ID. So

[115:14]

effectively what we're doing is we're

[115:15]

just finding the task that we want to

[115:16]

delete and then we are just removing

[115:18]

that task from the task list immediately

[115:21]

so that it no longer shows on screen. So

[115:24]

we're then going to say try await

[115:28]

and then delete task like so. When we

[115:31]

delete the task, we're going to pass in

[115:32]

the get token function. And the get

[115:35]

token function is right here because

[115:36]

we're passing it to this component.

[115:38]

We're also going to pass in the task ID

[115:41]

that we want to delete. We then are

[115:43]

going to catch any errors here. So,

[115:46]

we're going to say catch error and we're

[115:48]

going to say set tasks. So if there was

[115:50]

an error and we're going to add this

[115:52]

task back on the screen. We're going to

[115:53]

say dot dot dot previous and then the

[115:56]

task that we originally were going to

[115:57]

delete. And we're going to say console

[116:00]

error and we're just going to show the

[116:03]

error like that. Okay. So this is the

[116:06]

handle delete function which I think is

[116:08]

all good. Now what we're going to do is

[116:10]

handle the submit function which is

[116:12]

either going to update a task or create

[116:14]

a task for us. Okay. Okay, so we're

[116:16]

going to say async function handle

[116:19]

submit. Again, this is going to be for

[116:21]

editing or deleting a t or not deleting,

[116:23]

creating a task. Sorry, we're going to

[116:25]

take in some task data. Okay, what we're

[116:28]

going to do here is we're going to say

[116:29]

if we are currently editing a task. So

[116:33]

if we have one that we're editing, then

[116:35]

what we're going to do is we're going to

[116:36]

say const updated task is equal to and

[116:40]

we're going to say dot dot dot editing

[116:42]

task and then dot dot dot to the new

[116:45]

task data that we have. So we're going

[116:47]

to merge those two together to

[116:49]

effectively update the task that's

[116:50]

currently on our UI. We're then going to

[116:52]

say set tasks. We're going to take the

[116:54]

previous list of tasks and we're going

[116:56]

to say previous map and then we're going

[116:59]

to do is say t okay standing for task

[117:02]

and we're going to say t dot id and this

[117:06]

is going to be triple equal to editing

[117:08]

task do ID question mark if it is we're

[117:12]

going to put the updated task otherwise

[117:14]

we're just going to put the original

[117:15]

task and that's going to update the

[117:17]

tasks on screen for us we're then going

[117:19]

to say set show form false and set edit

[117:23]

editing task to null. Okay. Now, inside

[117:27]

of this if statement, we're then going

[117:28]

to send the request to our backend to

[117:30]

actually delete it. So, effectively,

[117:31]

what we're doing is we're updating the

[117:33]

UI. So, it looks like it happens right

[117:35]

away and then we're actually deleting

[117:37]

the task by sending the request to the

[117:39]

back end. So, we're going to say try

[117:42]

await and this is going to be update

[117:45]

task. Okay, so this is calling that

[117:47]

backend function, right, that we

[117:48]

imported here. So, update task. Now when

[117:52]

we call sorry update not updated when we

[117:54]

call update task we're going to pass in

[117:56]

the get token the editing task id as

[118:01]

well as the task data that we want to

[118:03]

update it with. Then we're going to

[118:05]

catch any errors. Let's fix this here.

[118:08]

So we're going to say catch error and

[118:10]

for this we're going to say set tasks

[118:13]

and then same thing we're going to

[118:14]

restore the previous task if there was

[118:16]

an error. So we're going to say previous

[118:18]

previous dot map t do id is equal to

[118:24]

editing task do ID question mark editing

[118:27]

task colon and then t okay so

[118:30]

essentially we check is the current task

[118:32]

that we're looking at the one that we

[118:33]

were editing if it was okay just put the

[118:35]

editing task back otherwise just put the

[118:38]

original task okay we're then going to

[118:40]

say console error and we're just going

[118:42]

to log the error so we can see the

[118:44]

message okay now if that's not the case.

[118:46]

So if we're not updating the task, then

[118:48]

that must mean that we're creating a new

[118:50]

one. So what we're going to do is say

[118:51]

try const new task is equal to await

[118:56]

create task. To create the task, we're

[118:58]

going to pass again that get token

[119:00]

function as well as the task data. We're

[119:03]

then going to say set tasks and we're

[119:05]

going to add this new task. So we're

[119:06]

going to say previous and then this is

[119:08]

going to be a list. We're say dot dot

[119:09]

dot previous and then the new task like

[119:12]

that. And then we're going to say set

[119:14]

show form

[119:16]

false like that to no longer show the

[119:18]

form. And then we can catch any errors.

[119:22]

And if we have any errors, we can just

[119:24]

console dot error the error, right?

[119:28]

Okay. So let's spell error correctly.

[119:30]

Perfect. So that should be good for

[119:32]

essentially handling the submit. So this

[119:34]

is when they submit the form. But we

[119:36]

still need to have a few other

[119:37]

functions. So we're going to have the

[119:38]

function sorry to handle cancel. So this

[119:42]

is if they're cancelling an edit or a

[119:44]

creation. So to handle cancel, we're

[119:46]

going to say set show form is false and

[119:50]

set editing task to null. Okay. And then

[119:54]

we're going to have a function and this

[119:55]

is going to be handle add task. Let's

[119:58]

spell handle correctly. What we're going

[120:00]

to do here is we're going to say set

[120:02]

editing task to null. So if they're

[120:04]

adding one, then we're no longer editing

[120:05]

one. And then we're going to say set

[120:07]

show form to true to show the form

[120:10]

that's going to allow us to start

[120:12]

editing. Now I know we don't have that

[120:13]

form yet. We're going to put that on the

[120:15]

screen in just one second. For now

[120:17]

though, let's return kind of that empty

[120:19]

canban board so we can at least have a

[120:20]

look at it and then we can create that

[120:22]

creation form. So we're going to have

[120:24]

div class name here. This is going to be

[120:27]

the cananban- wrapper. Then we're going

[120:30]

to have another div. Inside this div,

[120:32]

we're going to have a class name which

[120:34]

is equal to the cananban dash header.

[120:37]

Then inside of here, we're going to have

[120:39]

an h2. For this, it's going to be called

[120:42]

tasks. And then we're going to have the

[120:45]

class name equal to the cananban- title

[120:49]

for this particular H2. We're then going

[120:51]

to have a can manage variables. Okay. If

[120:53]

we are able to actually manage the

[120:55]

tasks, that means we can create one. So

[120:58]

what we're going to show is the ability

[120:59]

to create a task by adding a button with

[121:02]

the class name which is equal to btnb

[121:05]

btn- primary. Then we're going to have

[121:09]

on click which is equal to and this is

[121:12]

going to be let's write this again

[121:14]

handle add task like so. And then for

[121:18]

this we're going to say plus add task.

[121:20]

So essentially if you have the ability

[121:21]

to edit so you can create a new task

[121:23]

then we will show you this button where

[121:25]

when you press the button effectively

[121:27]

we're going to call this where it will

[121:28]

show the form and let you make a new

[121:30]

task. Okay. So let's move out of that

[121:32]

div because that's the header and let's

[121:33]

make another div. This time we're going

[121:35]

to say class name is equal to and this

[121:37]

is going to be the canban board. Okay.

[121:40]

For the cananban board what we're going

[121:42]

to do is show three columns. So we're

[121:44]

going to say statuses right dot map. I

[121:48]

think we called it statuses up here.

[121:50]

Let's check. Yeah, statuses dom map.

[121:52]

We're going to take the status and we're

[121:54]

going to map this to a task column

[121:56]

component. So here, let's make sure this

[121:59]

is correct. And we're going to put task

[122:02]

column, which I believe we imported

[122:04]

already. For the task column, let's go

[122:07]

down here. 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

[122:15]

course equal to the status. Then the

[122:18]

tasks for the tasks this is going to be

[122:20]

the function get tasks by status and

[122:23]

then we'll pass the status there to get

[122:26]

all the tasks. Then we're going to have

[122:28]

on edit which is going to be equal to

[122:29]

can manage question mark handleedit

[122:33]

otherwise null. And then we're going to

[122:35]

have on delete which same thing is going

[122:38]

to be equal to can manage question mark

[122:40]

handle and this is going to be delete

[122:44]

and then otherwise null. Okay. So only

[122:47]

if we're able to edit are we going to

[122:48]

have the ability to delete or of course

[122:50]

well edit the tasks. And then down here

[122:52]

we're going to have show form and and

[122:55]

for now I'm going to write null but in a

[122:57]

second this is where we're actually

[122:58]

going to show the form that allows us to

[122:59]

create new tasks. Then we're going to

[123:02]

say export

[123:03]

default the canban board. Okay. So what

[123:07]

we're missing here is the form to be

[123:09]

able to create the tasks. However,

[123:12]

before we put that there, I want to

[123:14]

first show this Canananban board on

[123:15]

screen. In order to do that, I need to

[123:17]

go to my pages and I need to go to my

[123:20]

dashboard page and I need to quickly

[123:22]

write the dashboard page to display the

[123:24]

cananban board and also load all of our

[123:26]

tasks. So, let's do that. So, we're

[123:29]

going to go to the top of our canban our

[123:31]

dashboard, sorry. And we're going to say

[123:32]

import use state import use effect

[123:37]

import use callback from React. We're

[123:43]

then going to say import

[123:46]

use off use organization as well as

[123:51]

create organization from clerk/clerk

[123:53]

react. Then we're going to say import

[123:57]

get tasks. Okay. And this is going to be

[124:00]

from dot dot /services.

[124:03]

So the file we write before slash API.

[124:06]

And we're going to import the cananban

[124:08]

board from the components file. From the

[124:11]

dashboard page, there's a few things

[124:12]

that we need. So first we need that get

[124:14]

token function so we can get the token

[124:16]

from clerk. So we're going to say const

[124:18]

get token

[124:21]

equals use off. Now this is how it works

[124:23]

in clerk. You can use this hook called

[124:25]

use o which will give you access to a

[124:27]

get token function. We can call the get

[124:29]

token function which will then give us

[124:31]

the clerk authorization token or JWT

[124:34]

token which we can then pass to the

[124:36]

backend to authenticate the request. So

[124:38]

that's where I'm getting it right from

[124:39]

this particular hook. Then same as

[124:42]

before I can say const organization

[124:46]

membership okay is going to be equal to

[124:49]

use organization. And for this what I'm

[124:52]

going to do is I'm going to say

[124:54]

membership. Okay. And then

[124:58]

infinite

[125:00]

is equal to true. Now what this is going

[125:02]

to allow me to do is get the membership

[125:04]

status as well as the organizations.

[125:06]

Okay. And when I say memberships

[125:08]

infinite true, what that's going to do

[125:09]

is just allow us to display all of the

[125:12]

memberships that a particular user has,

[125:14]

which is what we're looking for. Okay.

[125:16]

So now what we're going to do is say

[125:18]

const and then we're going to say tasks

[125:21]

set tasks is equal to use state and this

[125:25]

is going to be an empty list for now

[125:26]

because we haven't loaded the tasks.

[125:28]

Then we're going to say const loading

[125:31]

set loading

[125:33]

is equal to use state and to start the

[125:36]

loading state is going to be true. Then

[125:39]

we're going to have const

[125:42]

error. And then we're going to say set

[125:45]

error is equal to use state. And then

[125:48]

for now this is going to be null. Then

[125:51]

we're going to say const

[125:53]

member and this is going to be count is

[125:56]

equal to membership question mark.ount

[126:00]

or zero. So we can figure out how many

[126:02]

members are in the particular

[126:04]

organization because that's what we want

[126:06]

to display on this page. Then what we're

[126:08]

going to do is we're going to say const

[126:11]

or id is equal to organization question

[126:14]

mark ID. And now we're going to start

[126:17]

writing some functions to load the task.

[126:20]

So we're going to say const load tasks.

[126:22]

Okay, this is a function. So we're going

[126:25]

to say use callback

[126:28]

and this is going to be async and then

[126:30]

inside of here for this particular

[126:32]

function we're going to say try.

[126:34]

We're going to say set loading

[126:37]

is equal to true. We're going to say set

[126:40]

error. Okay, is error spelled correctly?

[126:42]

Yes, it is to null. Then we're going to

[126:45]

say const data is equal to await. Get

[126:48]

tasks which is the function we're

[126:50]

calling from that file we wrote before.

[126:52]

Now we're passing that get token uh

[126:54]

function. So we can actually get the

[126:56]

clerk token. We're going to catch on

[126:58]

error and we're going to say set error

[127:01]

and this is going to be error dot

[127:04]

message. And then we are going to go

[127:07]

here to a finally block. And for the

[127:10]

finally we're going to say set loading

[127:11]

equal to false because no matter what we

[127:13]

want to stop that loading state. Then

[127:15]

what we're going to do is just remember

[127:17]

to set the tasks with the data because

[127:19]

we need to do that otherwise this isn't

[127:21]

going to update anything for us. And

[127:22]

we're going to put in the dependency

[127:24]

array of this callback, the get token

[127:26]

function. So that if that updates that

[127:28]

we will do this again to get the new

[127:31]

tasks for this new token, which will be

[127:33]

based on the organization that the user

[127:35]

is currently active inside of as well as

[127:37]

which user is signed in. Then we need a

[127:40]

really quick use effect hook so that we

[127:42]

can run this automatically when the

[127:44]

component mounts. So in the dependency

[127:46]

array, we're going to put the org ID as

[127:48]

well as the load tasks function. And

[127:52]

then what we're going to do here is

[127:55]

we're going to say if org ID so if we do

[127:58]

have an organization load tasks

[128:02]

okay otherwise we're going to say set

[128:06]

loading to false. Okay. And load tasks

[128:10]

is this function right here. Perfect. So

[128:13]

we're essentially doing a use effect.

[128:14]

We're saying all right when it loads if

[128:15]

we have an organization ID we'll try to

[128:17]

load the tasks. If we don't, we don't

[128:19]

need to load anything because there's no

[128:21]

task to load unless we're inside of an

[128:22]

organization. Now, quickly, we're going

[128:25]

to say if you are not in any

[128:29]

organization, then what we need to do is

[128:32]

return just a quick little UI to show

[128:34]

you something. So, we're going to say

[128:35]

class name is equal to dashboard

[128:39]

dash container. Then, we're going to

[128:42]

have another div with class name equal

[128:44]

to no-orgontainer.

[128:48]

Then we're going to have an H1. This is

[128:50]

going to have a class name which is

[128:52]

going to be equal to no-orgt.

[128:56]

And then we're going to say welcome to

[128:59]

taskboard which is going to be the name

[129:01]

of the app. Right? Then we'll put a

[129:03]

paragraph tag and we're going to say

[129:05]

class name is equal to no-org

[129:10]

text. And then inside of here, we're

[129:12]

going to say create or join an

[129:15]

organization

[129:17]

to start managing tasks with your team.

[129:22]

Okay. And let's spell managing

[129:25]

correctly. Now, after that paragraph

[129:27]

tag, we're just going to show the create

[129:30]

organization form

[129:33]

from clerk, which I think was right

[129:36]

there. Perfect. So create organization

[129:38]

and after create organization

[129:42]

URL is going to be equal to slash

[129:44]

dashboard which I know is the page we're

[129:46]

currently on but it will refresh the

[129:48]

page. Okay, perfect. So that's if you

[129:50]

don't have an organization. Now if you

[129:52]

do have an organization we're just going

[129:53]

to simply show the dashboard with the

[129:55]

cananban page. So we're going to say div

[129:58]

class name is equal to the dashboard

[130:01]

container.

[130:04]

Then inside of this div, we're going to

[130:05]

have another div for the header. So

[130:07]

we're going to say div class name is

[130:11]

equal to and this is going to be the

[130:12]

dashboard-

[130:14]

header. Then we're going to have a div

[130:16]

with no class name. And here we're going

[130:18]

to have an h1. For the h1, we're going

[130:21]

to put the name of the organization. So

[130:22]

we're going to say organization.name.

[130:25]

And this is going to have a class name.

[130:28]

The class name is going to be equal to

[130:30]

dashboard-title.

[130:33]

Then we're going to have a paragraph

[130:34]

tag. This is going to be class name

[130:37]

equal to the org dash members. Then

[130:41]

we're going to show the member count. So

[130:42]

we're going to say member

[130:44]

count. And then we're going to say

[130:46]

member and then we're going to put the

[130:49]

member count does not. Okay, so does not

[130:54]

equal one. Question mark. We're going to

[130:56]

have an s. Otherwise, we're going to

[130:57]

have an empty string. So we're going to

[130:59]

say like one member or two members or

[131:01]

three members. So plural or not plural

[131:03]

depending on if we have one or more.

[131:05]

Okay. So this is our little header

[131:07]

component right here. Now beneath that

[131:10]

we're going to say loading question

[131:11]

mark. So if we are loading we're going

[131:13]

to quickly show let's do this. We're

[131:15]

going to quickly show a loading

[131:17]

paragraph tag. So we're going to say

[131:19]

paragraph

[131:20]

class name is equal to text dash muted

[131:25]

and we're just going to say loading

[131:28]

tasks dot dot dot. Otherwise, we're

[131:31]

going to say, is there an error? If

[131:33]

there is an error, then we're quickly

[131:35]

going to show what that error is. So,

[131:36]

we're going to put a div. We're going to

[131:38]

say class name is equal to, and this is

[131:41]

going to be card- error. And then we're

[131:44]

going to have a not div, but actually a

[131:47]

paragraph tag with class name equal to

[131:51]

text- error. And then this is inside of

[131:54]

here, text- error-title.

[131:59]

And we're going to say error like this

[132:02]

loading tasks. And then we're going to

[132:04]

show what the error actually is. So

[132:06]

we're going to do another paragraph tag.

[132:08]

And same thing, we're going to have

[132:09]

class name is equal to text dash error.

[132:14]

And then this is going to be text-

[132:16]

error- message and we're going to put

[132:20]

the particular error. Okay. Now if

[132:23]

that's not the case, so if it's not

[132:25]

loading and we don't have an error,

[132:26]

we're finally going to show the cananban

[132:28]

board. So we're going to show canban

[132:30]

board. For the cananban board, we need

[132:32]

to pass in the information. So we're

[132:35]

going to pass in the tasks, which is

[132:37]

equal to tasks. We're going to pass in

[132:40]

set tasks, which is equal to the set

[132:43]

tasks state. And we're going to pass in

[132:45]

the get token, which is equal to that

[132:47]

get token function. Okay, so that should

[132:51]

now load all of the tasks for us and

[132:53]

display them in the dashboard. Let's try

[132:56]

it out by signing into our account and

[132:57]

seeing if it works. Okay, so I just

[132:59]

signed into my account. I also just

[133:01]

created a new organization called test.

[133:03]

And you can see that we now have the

[133:04]

cananban state. We have to-do in

[133:06]

progress and done. It's all showing up

[133:08]

properly for us. And if we decide to

[133:11]

maybe make a new organization, test one,

[133:13]

two, three or something. Okay, create

[133:14]

organization. And notice that this

[133:16]

changes now right in the background to

[133:18]

test one two three and we can change

[133:21]

between the different organizations and

[133:22]

the dashboard page will update

[133:24]

accordingly. Now we need to make the

[133:25]

form to create a new task. Let's go

[133:27]

ahead and do that. So let's go to

[133:29]

components. Let's make a new file and

[133:32]

this is going to be called the task

[133:33]

form.jsx.

[133:35]

Now for the task form we're going to

[133:37]

start by importing what we need. So

[133:39]

we're going to say import use state and

[133:43]

use effect from React. We're then going

[133:47]

to say function

[133:49]

task form and we're going to have the if

[133:53]

we can write this correctly task

[133:56]

onsubmit

[133:58]

and on cancel.

[134:00]

Now what we're going to do here is say

[134:02]

const and we're going to say title

[134:05]

set title is equal to use state. So,

[134:08]

we're going to start putting in all of

[134:09]

the fields that we need to create new

[134:11]

tasks. We need a title, a description,

[134:12]

and a status. So, we have the title.

[134:15]

Now, we're going to do the description.

[134:16]

So, const

[134:18]

description

[134:20]

set description is equal to use state.

[134:23]

And then, same thing, empty string.

[134:25]

Then, we're going to have const

[134:27]

status set status is equal to use state.

[134:32]

And by default, we'll just start it in

[134:33]

the pending status. Then we're going to

[134:35]

have const is editing is equal to not

[134:41]

not task. Okay, so do we already have a

[134:44]

task? If we do, then we're editing. If

[134:45]

we don't have a task, we are not

[134:47]

editing. We're then going to have a use

[134:49]

effect and we're essentially going to

[134:51]

check, okay, are we currently editing

[134:53]

the task? If we are currently editing

[134:55]

the task, so we need that in the

[134:56]

dependency array, then we need to update

[134:58]

the state here with what is passed from

[135:01]

this task. So, we're going to say if

[135:05]

task. Okay. Then what we're going to do

[135:07]

is say set title. And this is going to

[135:10]

be task.title.

[135:12]

We're going to say set description. And

[135:14]

this is going to be task.escription.

[135:18]

Okay. Or and then this is an empty

[135:21]

string. Then we're going to say set

[135:24]

status. And this is going to be task

[135:27]

like oops. Let's go fix this. task

[135:31]

status like so. Okay, we're then going

[135:33]

to have an else. In the else, we're

[135:35]

going to say set title to an empty

[135:38]

string. So, this is if we're not

[135:39]

editing, then we just want to make sure

[135:40]

that it's empty. We're going to say set

[135:42]

description to an empty string. And

[135:45]

we're going to say set status equal to

[135:48]

pending. Okay, so this is the beginning

[135:50]

of our form. again just handling if

[135:52]

we're editing or not because we could be

[135:54]

passed some data in this task uh

[135:56]

variable here that is what we want to

[135:58]

populate the form with to start. Then

[136:00]

we're going to have a function handle

[136:03]

submit on handle submit we're going to

[136:06]

have E. What we're going to do is say

[136:08]

E.prevent

[136:10]

default so we don't refresh the page.

[136:12]

We're going to say if not title trim

[136:16]

then return. We need to have a title to

[136:19]

be able to create a task. And we're

[136:21]

going to say on submit, which is the

[136:24]

function that's passed here, and we're

[136:27]

going to pass to this the data. So we're

[136:28]

going to say title is title.trim.

[136:32]

We're going to say description is

[136:34]

description. So let's go here. Is

[136:36]

description.trim

[136:39]

or null. And then we're going to pass

[136:42]

the status. Okay. So that's our

[136:45]

onsubmit. Now we need to render the

[136:47]

actual form. So we're going to say

[136:48]

return div. For the first div, this is

[136:52]

going to be a modal. So what I'm going

[136:53]

to do is say class name is equal to

[136:57]

modal dash overlay. Okay, let's spell

[137:00]

that correctly. We're going to have on

[137:03]

click is equal to on cancel. So if you

[137:07]

click outside of the modal, that's what

[137:08]

this is going to be. Then it will close

[137:10]

it. We're then going to have a div.

[137:13]

We're going to have the class name for

[137:14]

this div to be the actual modal itself.

[137:16]

So essentially we have like the modal

[137:18]

background and then the modal. If you

[137:19]

click outside of the modal which is this

[137:21]

then we'll close the modal. Now we're

[137:23]

going to have an on click in the modal.

[137:25]

So we're going to say on click is equal

[137:27]

to and this is going to be sorry e and

[137:32]

then this is going to be e.stop

[137:34]

propagation.

[137:36]

Okay. Now we're going to go inside of

[137:37]

this div. We're going to have another

[137:39]

div. We're going to have class name is

[137:42]

equal to and then this is going to be

[137:44]

the modal header. Then we're going to

[137:47]

have the H2. We're going to have class

[137:50]

name is equal to and this is going to be

[137:53]

the modal title. And what we're going to

[137:55]

do is say okay well if we are editing.

[137:58]

So if is editing then we're going to put

[138:00]

edit task. Otherwise we're going to put

[138:04]

new task as the title for this modal.

[138:07]

Now beneath that we're going to have a

[138:09]

button.

[138:10]

This button is going to have a class

[138:12]

name which is equal to the modal close

[138:16]

to close the modal. We're going to have

[138:18]

an on click which is going to be equal

[138:21]

to on cancel. And then we're going to

[138:24]

have an X. Okay, so that's the X button

[138:27]

in the header. Now beneath that we're

[138:29]

going to have a form. The form is going

[138:31]

to have an onsubmit. The odds submit is

[138:34]

going to be the handle submit function

[138:35]

that we already wrote. Inside of the

[138:37]

form, we're going to have some form

[138:39]

groups. So, we're going to have a first

[138:40]

one. So, we're going to have div class

[138:42]

name is equal to form-group.

[138:44]

Then, for the first form group, we are

[138:47]

going to put the title. Okay. So, let's

[138:50]

put that here. We're going to say label

[138:53]

and this is going to be class name is

[138:55]

equal to form- label. We're going to say

[138:58]

HTML 4. So, HTML 4 is equal to label or

[139:04]

is equal to title. Sorry. The label is

[139:07]

going to say title. And then beneath

[139:09]

that, we're going to have an input box.

[139:11]

So, we're going to say input. And for

[139:13]

the input, we're going to say ID is

[139:17]

equal to title. We're going to say type

[139:19]

is equal to text. We're going to say

[139:22]

class name is equal to the form dash

[139:25]

input. We're going to say the value

[139:29]

is equal to the title. So let's do that

[139:34]

there. We're going to say on change is

[139:37]

equal to and this is going to be e and

[139:40]

then this is set title e.target

[139:45]

dot value. Okay. Then we're going to

[139:48]

have the placeholder which is equal to

[139:52]

enter task

[139:54]

title. And then we're gonna have

[139:56]

autofocus

[139:58]

true. Okay, so this is the first input

[140:01]

that we need. Now we're going to copy

[140:02]

this entire one and we're going to go to

[140:04]

the second input which is the

[140:05]

description. So same thing, form group

[140:07]

form label. This time we're going to

[140:09]

write

[140:10]

description.

[140:12]

Then we're going to put description

[140:14]

here. So description

[140:16]

and just spell description

[140:19]

correctly. Okay. Okay. Now rather than

[140:21]

the ID being title, this is going to be

[140:25]

description. Now this time rather than

[140:27]

input is going to be a text area. The ID

[140:30]

is description. We can remove the type.

[140:32]

The class name is going to be form dash

[140:35]

text area. The value is going to be the

[140:40]

description. Again, I keep spelling

[140:42]

description incorrectly. Rather than

[140:44]

enter the task title, we're going to say

[140:46]

enter

[140:47]

description.

[140:50]

Okay. Okay. And then this is going to be

[140:52]

optional. Let's spell description

[140:55]

correctly. And rather than set title,

[140:56]

this is going to be set description.

[140:59]

Okay. So that's the description. And

[141:01]

then lastly, we need to handle the

[141:03]

status. So same thing, let's copy this

[141:05]

just to give us a head start. Let's

[141:07]

paste it. We'll have the label again.

[141:09]

And this time we just change it to say

[141:11]

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

[141:22]

this because we're going to use a select

[141:23]

box. So, we're going to say select and

[141:26]

then we're going to say inside of the

[141:28]

select here, the ID is equal to status.

[141:32]

We're going to say the class name is

[141:34]

equal to a form dash

[141:37]

select. We're then going to say the

[141:40]

value is equal to the status. And we're

[141:44]

going to say onchange is equal to same

[141:46]

as before e. And then we're going to say

[141:49]

set status e.target.

[141:53]

Okay, target dot value. And then inside

[141:58]

of the select, we're going to put the

[141:59]

select options. So we're going to say

[142:01]

option value is equal to pending

[142:07]

as the first value. And then for the

[142:09]

text, we'll just put to-do. We can copy

[142:12]

this two times and then put the other

[142:15]

options. So rather than just pending,

[142:17]

we're going to have started and we're

[142:19]

going to have completed. And then for

[142:22]

this, we'll have in progress and we'll

[142:26]

have done. And then lastly, we just need

[142:28]

one more div here to actually um what do

[142:31]

you call it? Complete the action. So

[142:33]

we're going to say div class name is

[142:35]

equal to form actions because this is

[142:38]

where we're going to put the submit

[142:39]

button. We're then going to put a

[142:41]

button. For the button, we're going to

[142:43]

say type is equal to button. We're then

[142:48]

going to say class name is equal to btn

[142:50]

btn dash outline. We're going to say on

[142:55]

click is equal to and then this is going

[142:57]

to be on cancel. This button is going to

[143:00]

say cancel. And then we're going to have

[143:01]

one more button. Let's copy this one

[143:03]

here. This one is going to say the

[143:06]

following. So if we're editing so we're

[143:09]

going to say is editing question mark

[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

[143:20]

going to be btn btn primary and we don't

[143:23]

need an on click because this will be

[143:24]

handled in the form submission because

[143:26]

we're going to change the type to say

[143:28]

submit and that should complete this

[143:31]

form. All right so now that the task

[143:33]

form is created what we're going to do

[143:35]

is just export this. So export default

[143:37]

task form and we're going to bring it in

[143:40]

to our I think it's the canban page

[143:42]

where we want to display it. Yes. So

[143:45]

we're going to first import it from the

[143:46]

cananban page. So let's go import task

[143:50]

form from task form and we're going to

[143:53]

go down here where we say show form and

[143:56]

we're going to show the task form and

[143:58]

pass the props that we need. So we're

[144:00]

going to say task is equal to and this

[144:02]

is going to be editing task. Then we're

[144:05]

going to have the onsubmit.

[144:07]

So onsubmit is equal to handle sububmit.

[144:11]

And then on cancel is going to be equal

[144:13]

to handle cancel. And let's fix the

[144:17]

formatting a little bit so that we can

[144:19]

actually read this. We're not going to

[144:21]

call these functions. We're just going

[144:22]

to pass the function names. And there we

[144:25]

go. We now have the form. So let's test

[144:27]

this out and see if the form works.

[144:29]

Okay. It looks a little bit messed up.

[144:31]

So I think I probably messed up some of

[144:32]

the CSS. Uh, let me see what I did wrong

[144:35]

there and I'll be right back. Okay, so

[144:36]

it looks like I have this whole form not

[144:39]

actually inside the modal. So I need to

[144:42]

essentially copy this whole form and

[144:44]

we're just going to delete it from there

[144:46]

and place it inside of the modal div so

[144:49]

it gets moved inside there. Okay, so

[144:52]

that's good. Now if we go back and we go

[144:54]

add task, you can see that it shows up

[144:56]

as a proper modal. Okay, so let's try to

[144:58]

add a task. Let's go hello world, new

[145:01]

task. and create it. And we can see that

[145:05]

nothing is happening. Okay. So, let's

[145:07]

check our console and see if we're

[145:09]

getting any errors here. And it says

[145:10]

failed to fetch. Um, you know, there's a

[145:12]

corores error that's been blocked. Okay.

[145:16]

Let me have a look at this and I'll be

[145:17]

right back. Okay. So, I just found the

[145:19]

error here. Essentially in our backend

[145:21]

in our task schema, we have an ID which

[145:24]

is an int which actually needs to be a

[145:27]

string. So, if we change this to a

[145:30]

string, it should fix it. We were

[145:31]

getting an internal server error that

[145:33]

was saying unable to parse int as

[145:36]

string. So we just need to fix that

[145:38]

which I just did again. Go to back end

[145:40]

go to app schemas task and then simply

[145:43]

switch the ID in the task response from

[145:45]

an int to a string. Now if we go back

[145:49]

here let's just refresh and try it

[145:51]

again. And you can see these tasks get

[145:53]

created and actually were created. They

[145:55]

simply uh were not being shown on the

[145:57]

screen because we were getting an error.

[145:58]

Now, we also have an error with this X

[146:00]

button that I want to fix. So, I'm going

[146:02]

to look at the styling for that. Let's

[146:04]

try to delete it. Looks like it deletes

[146:06]

successfully. Let's try to edit it.

[146:08]

Looks like we can edit it as well.

[146:10]

Perfect. So, it's working. And let's

[146:12]

just try to add another task. So, let's

[146:13]

go test. Okay. Hello. Create uh this

[146:18]

task. And we have hello. Um, okay. Let's

[146:22]

just refresh here. And yeah, it looks

[146:25]

like we are good. Yes. Test. Hello. Like

[146:27]

that. Perfect. Okay. So, let's now fix

[146:29]

the styling because this is a little bit

[146:30]

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, so

[146:35]

the issue is just that we have the

[146:36]

description inside of the wrong div in

[146:38]

our task card. So, we're just going to

[146:40]

move it out here like that. Uh, now in

[146:45]

order to fix this, just go to the task

[146:47]

card, scroll down to where you see the

[146:49]

description, and just move it out one

[146:51]

div. So, we had it inside of this div,

[146:53]

the header. We don't want it in the

[146:54]

header, we just want it outside of that.

[146:56]

And then we should be good to go. And if

[146:57]

we come back here, you can see now the

[146:59]

description is below and the X button is

[147:00]

in the top right, which is exactly what

[147:03]

we're looking for. So at this point, the

[147:05]

tasks are working. Now this member count

[147:07]

doesn't seem to be working, which we can

[147:09]

have a look at. But the next thing we

[147:10]

need to do is have a look at actually

[147:13]

increasing the organization member

[147:15]

limits and the pricing. So we can have

[147:17]

subscriptions and billings, etc. So

[147:19]

let's change over to this org. And I

[147:21]

just want to show you that if I go into

[147:23]

an organization here and I go to manage

[147:25]

for example and members, I can invite

[147:27]

members. So let me invite some more

[147:29]

members and show you what happens when

[147:30]

we try to invite more than two. Okay, so

[147:32]

I just invited Tim at dev launch.us, one

[147:35]

of my other emails. And when I went to

[147:36]

this email, you can see this is the

[147:38]

email I received, right, where it says,

[147:39]

"Hey, development invitation to join

[147:41]

test." I'm going to accept that

[147:43]

invitation. When I accept the

[147:45]

invitation, it should actually redirect

[147:46]

me back to my front end. And you can see

[147:48]

that it does. And I am. Let's have a

[147:51]

look at it here. Signed in with the

[147:52]

other account. So, let me just sign out

[147:54]

and sign in with this new account that I

[147:57]

accepted the invite from. Okay, so I

[147:59]

actually just accepted the invite again

[148:01]

because since I was already signed in,

[148:02]

it gave me an error. But when I accepted

[148:04]

the invite, when I wasn't signed in cuz

[148:05]

I just signed out. You can see it just

[148:07]

asked me to fill in a password. So,

[148:08]

let's just use that password. That's

[148:10]

fine. Press continue. And now you can

[148:13]

see that I'm signed in as Tim at

[148:15]

devaunch us. And the only organization

[148:17]

I'm in is test in which I am a member.

[148:20]

So, let me now sign out of this account.

[148:22]

Actually, let's go to the dashboard. So,

[148:24]

we can see the same taskboard as we saw

[148:26]

before. But notice that I can't do

[148:28]

anything. I can't edit or delete or add

[148:30]

a task because I'm just a member. I'm

[148:32]

not an editor. So, let me sign out of

[148:35]

this. Let me sign in with my other

[148:37]

account. Now, you'll see that it's Tim

[148:39]

at techwithim.net. If I go to the

[148:41]

dashboard now, I can actually change

[148:43]

stuff because I am an editor or in this

[148:45]

case an admin. And what I'm going to do

[148:47]

is try to invite another member. As you

[148:49]

can see, Tim at devaunch is invited. And

[148:51]

when I press invite and I go, for

[148:53]

example, you know, canydevaunch

[148:56]

us and I want to send that invitation,

[148:58]

it says I cannot because I reached a

[149:00]

limit of two organization memberships.

[149:02]

So that's exactly what we want. But what

[149:05]

we want now is that we allow the user to

[149:07]

actually create a subscription where

[149:09]

they buy our pro tier, which then allows

[149:12]

them to invite more members. So let's

[149:14]

start setting that up inside of Clerk

[149:16]

and then we'll fix this member count

[149:17]

issue as well. Okay. So, we're going to

[149:20]

go back to clerk. We're going to go to

[149:22]

configure and we're going to go to the

[149:25]

billing. Now, from the billing, we have

[149:27]

the subscription plans, right? So, we

[149:28]

created the pro tier. Just make sure you

[149:31]

have that. Now, all we need to do is

[149:33]

actually use this pricing table

[149:35]

component directly inside of uh our

[149:38]

website to be able to display the

[149:40]

different tiers that we have here and

[149:42]

then a user will be able to purchase it.

[149:44]

So what I can do is I can go to pages,

[149:48]

go to my pricing page, and I can write a

[149:50]

really simple component that just

[149:52]

displays the different pricing options.

[149:54]

So in order to do that, I'm going to say

[149:56]

import, and then I'm going to say use

[150:00]

organization from clerk/clerreact.

[150:03]

I'm also going to import the pricing

[150:06]

table as well as the create organization

[150:09]

view from clerk clerk react. Okay. Okay.

[150:12]

And actually all of this can just be

[150:13]

together, but I think we'll just leave

[150:14]

it in two because it's a little bit

[150:16]

easier to read. We now have the pricing

[150:18]

page. From the pricing page, I'm going

[150:19]

to say const and this is going to be

[150:21]

organization

[150:23]

and then membership like this is equal

[150:27]

to use organization.

[150:30]

Okay. And what we're going to do is

[150:32]

we're going to make sure that this

[150:33]

member is an admin before we allow them

[150:36]

to view this page because only an admin

[150:38]

should be able to manage the

[150:39]

subscription and billing of the

[150:41]

organization. So we're going to say

[150:42]

const is admin is equal to membership

[150:46]

question mark ro is equal to or colon

[150:50]

admin. Okay. Now we're going to say if

[150:54]

there is no organization because it's

[150:56]

possible the user is not a part of any

[150:58]

then what we're going to do is we're

[150:59]

going to say return div we're going to

[151:02]

say class name is equal to pricing dash

[151:06]

container we're then going to say div

[151:10]

class name is equal to no-org

[151:15]

container we're going to have an h1 it's

[151:18]

going to say view pricing

[151:21]

the class name is going to be equal to

[151:24]

no or d-title.

[151:27]

Underneath the H1, we're going to have a

[151:29]

P and we're going to say class name is

[151:32]

equal to no-org

[151:34]

text. And the text here is going to say

[151:37]

create or join an organization.

[151:41]

Okay. To view pricing plants.

[151:45]

Cool. And let's spell join correctly.

[151:47]

Then underneath this we're going to put

[151:50]

the create organization modal and we're

[151:53]

going to say after create organization

[151:58]

URL is equal to slashpricing.

[152:03]

Cool. So we have that now. So if there's

[152:05]

no organization it will show them this.

[152:07]

Otherwise we need to show them the

[152:08]

pricing table. So we're going to go here

[152:11]

and we're going to say div. We're going

[152:13]

to say class name is equal to the

[152:16]

pricing dash container.

[152:20]

Then we're going to do a div. We're

[152:22]

going to say class name is equal to the

[152:25]

pricing dash header. Then we're going to

[152:29]

have an h1. We're going to say class

[152:32]

name is equal to the pricing title. Then

[152:37]

we're going to say simple

[152:39]

transparent

[152:42]

pricing and we'll remove this comma

[152:44]

because that doesn't make sense.

[152:45]

Underneath the H1 we'll put a paragraph

[152:48]

tag and we'll say start free with up to

[152:52]

two members. Upgrade to pro for

[152:56]

unlimited

[152:58]

members. Okay. Then we're going to go

[153:01]

beneath this div and we're going to

[153:03]

check if they are an admin. So, we're

[153:05]

going to say is admin question mark. If

[153:08]

they are an admin, we're going to put a

[153:10]

paragraph tag and we're going to say

[153:13]

sorry if they're not an admin because we

[153:14]

have the exclamation point. So, if

[153:15]

they're not admin, we're going to have

[153:17]

the class name equal to and this is

[153:20]

going to be text-muted

[153:22]

and then pricing-minote.

[153:26]

And this is going to say contact your

[153:28]

organization's

[153:30]

admin to manage the subscription. And

[153:34]

then otherwise,

[153:36]

okay, so let's go here. Otherwise, what

[153:39]

we're going to do is we're going to

[153:40]

display the pricing table. And it's

[153:43]

important that we put for organization

[153:46]

because sometimes you can have

[153:47]

individual uh pricing as well as

[153:49]

organization pricing. So in our case, we

[153:51]

just go organization. And then we spell

[153:53]

subscription correctly. And now if we go

[153:56]

to the pricing page, we should see the

[153:58]

pricing table. So let's go here,

[154:00]

refresh. Let's go pricing. Uh, and you

[154:03]

can see because we are the admin of the

[154:05]

organization, it shows us the free tier

[154:07]

as well as the pro tier. And what we can

[154:09]

do is we can subscribe to the pro tier.

[154:12]

So, let's do that. If we subscribe to

[154:14]

the protier, we can just pay with the

[154:16]

test card. So, let's do that. And when

[154:19]

we pay with the test card, we'll just go

[154:21]

continue. It will make the payment. And

[154:23]

now, if we go back to pricing, you can

[154:25]

see that this pricing is active and we

[154:27]

are pro. So if we go back to dashboard,

[154:30]

uh, okay, it's giving us an error saying

[154:32]

view permission required. Let's see why

[154:34]

we're getting that. Okay, so I just had

[154:36]

a look at this and the reason why we're

[154:39]

getting that error is because if we have

[154:40]

a look in the plans here in our pro

[154:43]

tier, we needed to add this tasks as a

[154:46]

feature to the organization in order for

[154:49]

any of the other permissions that we

[154:51]

apply per the user roles to actually

[154:54]

work. So effectively what we need to do

[154:56]

is we just have to go here to add

[154:57]

feature and then just check on tasks and

[154:59]

when we do that now all of this is good

[155:01]

and this feature will be available to

[155:03]

people that are in the pro tier. Now by

[155:06]

default it is available to the free

[155:08]

tier. So if you have a look here you can

[155:10]

see it's already in there. If we didn't

[155:12]

want users to be able to create tasks in

[155:13]

the free tier we could just remove that

[155:15]

feature but I need to add it to the pro

[155:18]

tier so it's available there as well.

[155:20]

That's why we're getting that issue

[155:21]

saying hey you don't have permission to

[155:22]

view this. But now it's working because

[155:24]

I readded that perm. Okay. So now the

[155:27]

pricing is working. If we go to the

[155:31]

organization and we manage it for

[155:32]

example, we can actually view the

[155:34]

billing right now and we could manage

[155:36]

it. We can cancel the subscription. We

[155:38]

can change the plans. We can view the

[155:40]

billing history, the statements, the

[155:41]

payments, the payment method. All of

[155:43]

that kind of stuff is handled directly

[155:45]

by clerk for us. Cool. So, let's quickly

[155:48]

fix this members count and then let's

[155:50]

move on to the next feature which is

[155:51]

actually allowing the organization to

[155:53]

invite more members because right now if

[155:56]

I go here and I try to invite someone

[155:58]

else you'll see Kenny at devalanche us.

[156:02]

It's not going to allow me to do that

[156:04]

because I haven't yet updated the

[156:05]

organization membership limits. Okay, so

[156:08]

the issue is pretty straightforward but

[156:10]

effectively I forgot to write

[156:11]

memberships instead of membership. So I

[156:14]

just need to change to say memberships

[156:15]

and this to say memberships. And now if

[156:18]

we go back here and we have a look, you

[156:19]

can see it shows to members and the

[156:21]

count is now updated. Okay. So next,

[156:24]

like I said, what we're going to do is

[156:26]

make it so that we can actually update

[156:27]

the subscription account or update the

[156:29]

number of members, sorry, for a

[156:31]

particular organization when it upgrades

[156:33]

to pro. So if you go to settings for

[156:35]

example inside of organizations right

[156:38]

you'll see that it says limited

[156:40]

membership but it shows us that we can

[156:42]

actually update the membership in the

[156:44]

dashboard or the number of members or

[156:46]

via clerk's backend API. So if we click

[156:49]

into this you'll see clerk has all kinds

[156:50]

of different APIs that you can use. One

[156:53]

of which allows you to actually change

[156:55]

the max allowed memberships per an

[156:58]

organization. So what we're going to do

[157:00]

is we're going to create a web hook

[157:02]

where when someone purchases the pro

[157:04]

subscription and it's verified by clerk,

[157:07]

we send a request to clerk telling it to

[157:09]

update this organization so that it can

[157:11]

have more members. Now the way the web

[157:14]

hook works is that we're going to set an

[157:16]

endpoint on our server that clerk will

[157:18]

send a request to to tell us, hey, this

[157:21]

user just purchased Pro. when they

[157:23]

purchase Pro for an organization, we're

[157:25]

then going to tell Clerk, "Hey, go ahead

[157:27]

and update the membership count so they

[157:29]

can have unlimited members." So, in

[157:31]

order to do that, we're going to go into

[157:33]

Clerk. We're going to go to developers

[157:36]

and then web hooks. Now, when we go

[157:38]

here, what we're going to do is create a

[157:41]

new web hook. So, we're going to go add

[157:42]

endpoint and we're going to scroll down

[157:45]

and we're going to find, let's have a

[157:47]

look at this here, subscription. And

[157:50]

we're just going to check check the box

[157:52]

right here for subscription. When we do

[157:54]

that, anytime anything changes with a

[157:56]

subscription, clerk will send a request

[157:58]

to the URL that we're going to put right

[158:00]

here. Now, in order for the web hook to

[158:02]

work in development, we're going to use

[158:04]

something called Enrock. Enro is free

[158:08]

and it works as a proxy that allows you

[158:10]

to take a public IP address and

[158:12]

associate that with something running on

[158:14]

local host or your own computer. So,

[158:16]

what you're going to need to do is

[158:17]

download Enro. I will leave a link to it

[158:19]

in the description. You also can just

[158:20]

search for Enro. Once you download it,

[158:23]

you're going to need to sign into an

[158:24]

account. So, make a new account on Enro

[158:26]

and then sign into it in your terminal.

[158:28]

The way that you're going to do that is

[158:30]

you're simply going to go in your

[158:31]

terminal. Once Enro is installed, you're

[158:33]

simply going to type Enro. When you do

[158:36]

that, it's going to tell you to sign

[158:38]

into Enro. In my case, I'm already

[158:40]

signed in, so I don't need to do

[158:42]

anything. And when you try to sign in,

[158:44]

it's going to prompt you to sign in with

[158:45]

the browser. You can sign in. It will

[158:46]

connect your account. and then you are

[158:48]

good to go. So download Enro. Make sure

[158:50]

you create an account on the website.

[158:52]

Sign into that account from your

[158:53]

terminal by either just typing the

[158:55]

command I'm about to type or typing

[158:57]

enrock. It should prompt you to sign in.

[158:59]

And then type the command enro http8000

[159:03]

where 8000 matches the port that your

[159:06]

backend server is running on. So port

[159:08]

8000. Okay. When you do that, it's going

[159:10]

to create a forwarding URL. So this one

[159:13]

right here is what we want to look at.

[159:15]

And now any requests sent to this URL

[159:17]

will be forwarded to your backend API.

[159:20]

So localhost port 8000. This allows us

[159:23]

to test in development mode the web hook

[159:26]

which you're going to see in one second.

[159:28]

Okay. So what I'm going to do now is I'm

[159:30]

going to go to my clerk here and I'm

[159:34]

going to paste this URL. Make sure you

[159:36]

remove all of the spaces and I'm going

[159:37]

to put slash API slash web hooks like

[159:42]

that. I'm then going to put one more

[159:44]

slash and I'm going to put clerk. So now

[159:48]

what will happen is anytime a

[159:49]

subscription changes in my clerk

[159:51]

project, it's going to send a request to

[159:53]

this endpoint, which is one that we're

[159:55]

going to write in 1 second in fast API.

[159:58]

So we're going to go ahead and press on

[160:00]

create. And then from here, we're going

[160:01]

to grab the web hook signing secret. So

[160:04]

we're going to reveal that and copy it.

[160:06]

We're now going to go back into our

[160:08]

code. We're going to go into our back

[160:09]

end now. So no longer our front end.

[160:11]

We're going to go to our env file. We're

[160:15]

going to paste the clerk web hook web

[160:17]

hook, sorry, secret. Now, this secret is

[160:20]

going to allow us to actually verify

[160:22]

that the request that was sent came from

[160:25]

clerk because anybody can hit this web

[160:28]

hook that we're creating. So, we have

[160:29]

this special key that clerk is going to

[160:31]

send along with the request. We're going

[160:34]

to look at it. We're going to go, okay,

[160:35]

if you're actually clerk, then you would

[160:37]

have sent this key. If it sends the key,

[160:40]

we know it's clerk. It's good. we could

[160:41]

actually verify that web hook request.

[160:43]

If they didn't send the key or the key

[160:45]

is wrong, then it means someone is

[160:47]

trying to fake being clerk and kind of

[160:48]

get like a free pro account, which we're

[160:50]

of course not going to allow them to do.

[160:52]

So that's why you need this signing

[160:54]

secret to verify the web hook. Okay. So

[160:57]

now what we're going to do is we're

[160:58]

going to go into app API web hooks.py

[161:02]

and we're going to start writing this

[161:03]

file. So we're going to say import

[161:06]

json. We're going to say from fast API

[161:10]

import the API router

[161:13]

request http exception

[161:18]

let's spell that correctly and status.

[161:20]

We're then going to say from sphix svix

[161:24]

web hooks import and we're going to

[161:27]

import web hook and web hook

[161:31]

verification error. We're then going to

[161:33]

say from app.core.config

[161:35]

config import settings so we can get the

[161:38]

web hook secret and we're going to say

[161:40]

from app.core.clerk

[161:43]

import clerk.

[161:45]

Okay. Now what we're going to do here is

[161:47]

we're going to create a router because

[161:48]

this is going to be an API endpoint. So

[161:50]

we're going to say API router and this

[161:52]

is going to say prefix

[161:56]

is equal to slappi/web

[161:58]

hooks. Let's spell web hooks correctly.

[162:01]

Then we're going to say tags is equal to

[162:04]

web hooks.

[162:06]

Then we're going to say procore tier

[162:10]

slug is equal to procore tier which is

[162:13]

what we're going to look for from the

[162:15]

web hook data that is sent to us. We're

[162:17]

going to say the free tier limit is

[162:21]

equal to the settings.free tier limit.

[162:24]

And we're going to say the unlimited

[162:26]

limit is equal to and we're just going

[162:29]

to put a really large number because

[162:30]

when we set unlimited number unlimited

[162:33]

uh sorry seats it's not truly unlimited.

[162:36]

We're just going to set a really really

[162:37]

large value so you can have as many

[162:38]

members as you want. We're then going to

[162:41]

make a function. So we're going to say

[162:43]

define set_org

[162:46]

member_limit.

[162:49]

We're going to say org id string and

[162:53]

then limit int. And then what we're

[162:56]

going to do is say

[162:56]

clerk.organizations.update

[163:01]

directly from the clerk SDK. And we're

[163:03]

going to say the organization ID. Okay,

[163:06]

so organization ID is equal to the org

[163:08]

ID and the max allowed

[163:12]

memberships is equal to whatever limit

[163:14]

is passed to this function. And that's

[163:16]

as easy as it is using the clerk backend

[163:18]

SDK to just update an organization's

[163:20]

membership limit. Now, we're also going

[163:22]

to have a function that says has active

[163:26]

pro plan and we're going to say items

[163:31]

list and then what we're going to do is

[163:33]

return a boolean.

[163:36]

Now, from here, we're going to say

[163:38]

return

[163:40]

any and we're going to say item.get

[163:45]

plan

[163:46]

And then this is going to be an empty

[163:49]

dictionary in case plan doesn't exist.

[163:52]

Get slug

[163:54]

is equal to the pro tier slug and

[163:59]

item.get

[164:01]

status

[164:03]

is equal to active for item in items.

[164:07]

Now the reason for this is what's going

[164:09]

to happen is whenever the web hook is

[164:11]

sent, it's going to give us a bunch of

[164:12]

data. In that data, there's going to be

[164:14]

a list of items. We're going to look

[164:16]

through those items to see if any of the

[164:18]

items refer to the plan, right? So,

[164:20]

we're looking for plan. And if it does

[164:22]

refer to a plan, we're going to check

[164:23]

the name of the plan, which is a slug,

[164:25]

to see if it's the pro plan, and we're

[164:27]

going to see if it's active. So, if we

[164:29]

found a pro plan that's active, then

[164:30]

that means we need to update that

[164:32]

organization's membership limit, which

[164:34]

is what we're going to do. So, we're

[164:36]

going to have an at@ routouter.poste

[164:38]

event here. We're just going to post

[164:40]

this to /clerk

[164:42]

and we're going to say async define and

[164:44]

this is going to be the clerk web hook.

[164:46]

This is where we're going to actually

[164:47]

handle the request coming from clerk.

[164:50]

We're then going to say inside of here

[164:53]

the payload

[164:55]

is equal to await and this is going to

[164:57]

be the request.body.

[164:59]

This is the data that clerk is sending

[165:01]

us. And we're going to say the headers

[165:03]

is equal to the dictionary of request do

[165:06]

headers. Then we're going to attempt to

[165:09]

verify the web hook. So we're going to

[165:11]

say if settings.clerk

[165:14]

webhook

[165:16]

secrets.

[165:17]

Then what we're going to do is say try

[165:19]

and we're going to say wh is equal to

[165:22]

web hook and we're going to say payload

[165:25]

and then headers and we're going to say

[165:27]

event is equal to whverify

[165:32]

and we're going to verify with the

[165:34]

payload and headers. And sorry, the web

[165:37]

hook, I just wrote this in the wrong

[165:38]

way. It needs to be settings.clerk web

[165:41]

hook web hook secret, sorry. Okay, then

[165:45]

we're going to say accept. And we're

[165:47]

going to accept a web hook verification

[165:49]

error. And we're going to raise an HTTP

[165:53]

exception.

[165:55]

We're going to say status.http.

[165:57]

And then this is going to be 400 bad

[165:59]

request. We're going to say invalid

[166:01]

signature.

[166:04]

Okay. So what we're doing right now is

[166:06]

we're essentially just verifying to see

[166:08]

okay is the data that was sent to this

[166:10]

web hook actually valid. So we say do we

[166:12]

have a web hook secret? If we do we're

[166:14]

going to create a web hook object and

[166:16]

we're going to verify that the data that

[166:18]

was just sent to us is valid and

[166:19]

contains that signature. Now otherwise

[166:22]

we're just going to say event is equal

[166:24]

to JSON.loads

[166:26]

and then payload. So if we didn't define

[166:29]

a secret then we'll just allow anything

[166:31]

to hit this endpoint. Okay. Then we're

[166:34]

going to say the event type is equal to

[166:37]

event.get and we're going to get the

[166:39]

type because the web hook is going to

[166:42]

respond to different events related to

[166:44]

the subscription. So like created,

[166:45]

cancelled, passed to etc. Right? It's

[166:47]

going to send all those different types

[166:49]

of events. So we need to look at those

[166:50]

events and then handle the correct ones.

[166:53]

We're then going to say data is equal to

[166:55]

event.get

[166:59]

data and then an empty dictionary in

[167:00]

case data doesn't exist. and we're going

[167:02]

to check the different events that could

[167:04]

occur in this web hook. Specifically, if

[167:07]

a subscription was created or was

[167:09]

updated or if a subscription was deleted

[167:11]

or cancelled. So, we're going to say if

[167:13]

event

[167:16]

type is in sorry and this is going to be

[167:19]

a list. We're going to say subscription

[167:22]

docreated or subscription dot and this

[167:26]

is going to be updated. So these are the

[167:28]

ones where we're essentially going to

[167:29]

allow the user more limits or more

[167:31]

seats. We're going to say org ID is

[167:34]

equal to data.get

[167:36]

and then payer. This is the person who

[167:38]

paid. Okay. Get the organization ID. So

[167:43]

we're looking for the ID of the

[167:46]

organization of the person who paid.

[167:48]

Then we're going to say if org ID what

[167:52]

we're going to do is say limit is equal

[167:54]

to and then this is going to be

[167:56]

unlimited limit if the has active pro

[168:02]

plan data.get

[168:05]

and then here items otherwise an empty

[168:10]

array

[168:12]

and sorry that needs to be inside of

[168:15]

here. Okay. Otherwise, we're going to

[168:17]

say else free tier limit. All right. So,

[168:20]

we're going to make the limit unlimited

[168:22]

if they have an active pro plan. So,

[168:24]

we're going to check the plan to make

[168:25]

sure that it is actually active. If it

[168:28]

is active, then we'll give them the

[168:29]

unlimited limit. Otherwise, we're going

[168:30]

to set their limit to the free limit.

[168:33]

Okay. Then underneath this if so in the

[168:36]

same block, we're going to say set_org

[168:38]

member limit. And then we're just going

[168:39]

to pass the org ID.

[168:42]

So or id and this new updated limit.

[168:46]

Okay. Now l if the event type is in and

[168:51]

now this is going to be the

[168:53]

subscription.deed

[168:55]

or the subscription.

[169:00]

Then what we're going to do is say the

[169:01]

org ID is equal to data.get. And same

[169:05]

thing as before we're going to get the

[169:07]

payer. Okay. And then we're going to get

[169:10]

the organization

[169:13]

ID. Then we're going to say if there is

[169:16]

some org ID, we're just going to set the

[169:18]

org member limit of that org ID to be

[169:21]

the free tier limit because they canled

[169:24]

their subscription. Then we're going to

[169:25]

return

[169:27]

received

[169:29]

is true. Okay, we just got to spell

[169:32]

received correctly. So that's all we

[169:35]

need for the web hook. Now clerk can

[169:37]

send a request to this. We can handle

[169:40]

that request and we can update the limit

[169:42]

for a particular organization. So if we

[169:45]

go now we can just refresh the web hook.

[169:47]

Okay. Now from here we can go to testing

[169:49]

and we can test this with a particular

[169:51]

event if we want and we can see if that

[169:53]

works. However, we'll just test it with

[169:55]

a new organization. So we're going to go

[169:57]

here. I'm just going to refresh. What

[169:59]

I'll do is just swap over to my test 123

[170:02]

organization. Now, I want to verify with

[170:04]

this organization that we cannot invite

[170:06]

more members. So, let's try to invite a

[170:08]

few. Let's go. Timwithtim.net

[170:12]

or timdevaunch

[170:14]

us.

[170:16]

Okay, let's send the invitation. Let's

[170:19]

try to send another one. Let's go kenny

[170:21]

at

[170:23]

devaunch

[170:26]

us and send that. And it says we've

[170:28]

reached a limit of two organization

[170:29]

members. Now, what I'll do is I will

[170:32]

upgrade to that pro plan. So, I'm going

[170:34]

to go pricing and I'm going to go

[170:36]

subscribe.

[170:38]

Okay. From here, I'm just going to pay

[170:39]

with the test card. And when I pay with

[170:42]

this now, let's go continue. And now,

[170:45]

I'm going to go to my dashboard. I'm

[170:47]

going to go here, manage members, and

[170:49]

I'm going to try to invite Kenny. So,

[170:51]

let's go Kenny at devaunch. us. And we

[170:56]

got an error. So, let me check my logs.

[170:58]

And it looks like the web hook is saying

[171:00]

it was not found. So API web hooks

[171:02]

clerk. Aha. So the reason why is we did

[171:04]

not actually connect the web hook to our

[171:06]

main app which I need to do right here.

[171:09]

So let's connect the web hook. The way

[171:11]

that we can do that is we can just go

[171:13]

here. We could say from actually web

[171:16]

hooks. We can just import it like that.

[171:18]

And then we can simply say at

[171:20]

app.include include

[171:25]

routouter and then web hooks.outer.

[171:29]

Okay, so now that we do that, the web

[171:31]

hook should work. However, it's giving

[171:32]

us an issue saying there's no free tier

[171:35]

limit in our settings. So, let's quickly

[171:37]

go check our settings and see if that's

[171:39]

the case. Config. And we do have a free

[171:43]

tier free tier membership limit. Okay,

[171:45]

so let's just change this to just say

[171:47]

limit and then run this. And hopefully

[171:50]

now it should work. Okay. And now let's

[171:54]

test the web hook again. So what we can

[171:56]

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. So now

[172:03]

go manage and okay let's go resubscribe

[172:06]

and subscribe. Continue. And now if I go

[172:10]

look in my logs, you can see that it

[172:11]

posted to the web hook. And now if I go

[172:14]

to my organization and I try to add a

[172:17]

new member. Let's try it. Okay, Kenny

[172:20]

ate devaunch US.

[172:23]

Let's see. And you see that the

[172:24]

invitation goes through because it

[172:26]

successfully updated our libby. Now, if

[172:28]

we were to cancel, then it would again

[172:30]

not allow us to add more members. You

[172:32]

get the idea. Okay, so that is pretty

[172:34]

cool. And honestly, with that said,

[172:36]

that's going to wrap up everything that

[172:37]

I wanted to show you in this video. We

[172:39]

handled pricing, we handled permissions,

[172:41]

we handled different member roles, we

[172:44]

handled billing, we handled

[172:46]

organization, we handled signing in,

[172:47]

signing out, web hooks, front end,

[172:49]

backend. A lot of things went into this

[172:51]

project. And all of the code for this

[172:53]

project will be available from the link

[172:54]

in the description in case you missed

[172:56]

anything or you want to copy anything

[172:58]

directly. Overall, the major thing here

[173:01]

is that using Clerk and some of the

[173:02]

features that I showed you and take in

[173:04]

there's a lot more that you can use, you

[173:06]

can build a full B2B SAS that allows you

[173:09]

to collect money and handle all of the

[173:11]

complex organization roles and

[173:13]

permissions without having to write all

[173:14]

of the code manually. So, if you guys

[173:16]

enjoyed the video, make sure leave a

[173:17]

like, subscribe to the channel, I will

[173:19]

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 Introduction to DaVinci Resolve Full Course

Download Subtitles for Introduction to DaVinci Resolve Full Course

Enhance your learning experience by downloading accurate subtitles for the Introduction to DaVinci Resolve full course. Captions help you follow along effortlessly, improve comprehension, and make the tutorial accessible anytime, anywhere.

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.

Most Viewed

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.

ดาวน์โหลดซับไตเติ้ล DMD LAND 3 The Final Land Day 1

ดาวน์โหลดซับไตเติ้ล DMD LAND 3 The Final Land Day 1

ดาวน์โหลดซับไตเติ้ลสำหรับวิดีโอ DMD LAND 3 The Final Land Day 1 เพื่อช่วยให้เข้าใจเนื้อหาได้ง่ายขึ้น และเพิ่มความสะดวกในการติดตามทุกช่วงเวลา เหมาะสำหรับผู้ชมที่ต้องการความชัดเจนและเข้าถึงข้อมูลอย่างครบถ้วน

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.

Download Subtitles for Your Favorite Videos Easily

Download Subtitles for Your Favorite Videos Easily

Enhance your video watching experience by downloading accurate subtitles and captions. Enjoy better understanding, accessibility, and language support for all your favorite videos.

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!