
No Comments Yet
Be the first to share your thoughts and start the conversation.
Be the first to share your thoughts and start the conversation.
By logging in, you'll unlock full access to this and other free tutorials on JSM Pro.
Why? Logging in lets us personalize your learning experience, track your progress, and keep you in the loop with new workshops, coding tips, and platform updates.
You'll also be the first to know about upcoming launches, events, and exclusive discounts.
No spam—just helpful content to level up your skills.
If that sounds fair, go ahead and log in to continue →
Enter your name and email to get instant access
00:00:00Â To get started working on the ShareModel, let's head over to our components folder and create a new component called ShareModel.tsx.
00:00:12Â Run RAFCE right within it, and let's use it within our collaborative room.
00:00:18Â So if you go above the editor.
00:00:21Â Still within the header, because that's where we want to activate it, we can go below the active collaborators and we can call the share modal component.
00:00:32Â It's a self-closing component to which we can pass the room ID equal to room ID to know the access to which room are we sharing.
00:00:41Â We're going to have the list of active collaborators on there, which is going to be equal to user.
00:00:47Â We need to have the creator ID so we know who is sharing this access.
00:00:52Â And we can get that from roommetadata.creatorID.
00:00:55Â And we need to know whether the user even has the permission to share it.
00:00:59Â And we can get access to that by checking the current user type.
00:01:03Â Great.
00:01:04Â Now we can head over to the share model and we can accept all of these props by getting the room ID, the collaborator creator ID.
00:01:15Â and the current user type.
00:01:17Â And those will be of a type shareDocumentDialogProps.
00:01:24Â Great.
00:01:24Â So now we have defined that and we can start declaring some states which we'll need to make this model happen.
00:01:30Â The most important one, of course, is the open.
00:01:33Â So use state snippet and we'll call it open as well as set open at the start set to false.
00:01:41Â We'll use this to track whether the model is open or closed.
00:01:45Â Next, we have to import this from react.
00:01:48Â And let's immediately turn this into a use client component because we'll have some interaction with states and keyboard and mouse.
00:01:56Â While we're here, we can also add the loading state.
00:01:59Â So let's open up the state snippet and say loading and set loading at the start set to false.
00:02:05Â And as I said already, we'll have to have the information about which user is trying to make that change, is trying to change the permissions.
00:02:13Â So we can get access to that by saying const user is equal to use self coming from LiveBlocks React suspense.
00:02:22Â And here we also have to declare a state for the user type.
00:02:26Â And then we have to get access to the email of the user we're trying to add.
00:02:30Â So that's going to be an email state.
00:02:32Â at the start set to an empty string.
00:02:34Â And once we have the new email state, we have to have the state to decide which type do we want to give to this user?
00:02:42Â Is it a viewer type or an editor type?
00:02:44Â So we can call that user type.
00:02:48Â And at the start, it'll be set to viewer and it'll be of a type, user type, like this.
00:02:57Â Great.
00:02:58Â On the way, we'll also have to have a handler function to share the document.
00:03:02Â So we can create it right away by saying const share document handler is equal to an async arrow function, which for now we can leave blank.
00:03:13Â And now we can focus on the JSX part of things where we can show this nice looking dialogue.
00:03:19Â For that, we'll use a chat CN dialogue, a window overlaid on the primary window.
00:03:26Â Here's an example.
00:03:27Â We click it and it opens up.
00:03:30Â So we first need to install it by saying mpx.shadcnuilatest.addDialog.
00:03:36Â So back in our code, you can paste that right here, press enter, and it'll install it.
00:03:42Â Once you install it, we can copy its usage and paste it right here at the top.
00:03:46Â And we can then copy the actual dialog component, which we can use to replace this div right here.
00:03:53Â And we can indent it properly.
00:03:55Â Now to this dialog, we can pass an open state, which we already have in our states, as well as the onOpenChange, which will be equal to our setter function setOpen.
00:04:08Â In the dialog trigger, we won't simply say open, rather we will render a complete button.
00:04:14Â And within the button, we will render an image.
00:04:18Â So let's import the button from UI button and let's import the image from next image.
00:04:24Â To that image, we can pass a source equal to forward slash assets, forward slash icons, forward slash share.svg.
00:04:34Â We can give it an alt tag of share, a width of about 20, a height of about 20, and a class name equal to min dash w dash four,
00:04:47Â and on medium devices, size of five.
00:04:50Â Right below that image, we can render a p tag that will have a class name equal to margin right of one, hidden, and then on small devices and larger,
00:05:00Â block.
00:05:01Â So it's hidden only on extra small.
00:05:04Â And here we can have the text that'll say share.
00:05:07Â Now, if you open up a document, you should be able to see this share button on top.
00:05:12Â But let's style it further.
00:05:14Â I'll style it by giving this button a class name of gradient-blue flex.
00:05:21Â h of 9 and a gap of 1 alongside a padding x of 4. We can also give it a disabled property.
00:05:29Â So we'll say disabled if the current user type is not equal to the editor.
00:05:37Â Then we cannot press that button.
00:05:39Â In this case, we are the editor, so we can click it.
00:05:42Â But as you can see, this doesn't look so good right now.
00:05:45Â So let's modify the content.
00:05:46Â We can do that by finding the dialogue content and giving it a class name of shad-dialogue.
00:05:54Â Next, we can style the header by changing the text in the title of something like manage who can view this project.
00:06:03Â And in the description, we can say something like select which users can view and edit this document and we can save it.
00:06:16Â Now, if you go back, this looks so much better.
00:06:20Â But now we want to go below it and we actually want to form an input where we can add an email address of the person which we want to invite.
00:06:29Â And we can do that within the content right below the header by forming a label.
00:06:35Â And this is a component which we have to install.
00:06:37Â So let's go right here and alongside dialogue, let's also add a label.
00:06:44Â Press enter and install it, and then you'll be able to import it from UI label.
00:06:50Â Alongside the label, we'll also need an input.
00:06:53Â So go ahead and install the input as well, and we'll use it soon.
00:06:58Â For now, let's give an HTML4 property so we know what this label is for of email and a class name equal to margin top of six and text-blue of 100. And
00:07:14Â we can say email address right here.
00:07:17Â Right below it, we can create a div which will serve as the container for our input.
00:07:23Â So let's give it a class name of flex, items-center, and a gap of three.
00:07:31Â Right within it, we can have another container div with a class name equal to flex, flex-1, and rounded-md with a bgdark of 400. And right within it,
00:07:46Â we can have our self-closing Shazian input component, which we can import from .slash UI forward slash input.
00:07:55Â We can give it an ID equal to email, a placeholder equal to.
00:08:00Â enter email address and a value equal to email because we have already created that state above.
00:08:08Â We can also specify the onChange, which is going to be a callback function where we set the email to be equal to e.target.value.
00:08:18Â And finally, we can give it a class name of share-input.
00:08:23Â If you did that properly, you can go back and now you should be able to see this beautiful email field.
00:08:29Â But now next to this email field, we have to choose whether we want to give the user view or edit permissions.
00:08:36Â So that means that we have to add another custom selector component right below the input.
00:08:41Â Let's go to components and create it right here.
00:08:45Â by calling it userTypeSelector.tsx.
00:08:51Â Run RAFCE, and then import it right here below the input by saying userTypeSelector.
00:09:00Â And we can automatically pass it a couple of things, such as the userType, which is equal to userType, as well as the setUserType,
00:09:12Â equal to set user type.
00:09:15Â So that way we can control what we're doing from inside of this component.
00:09:20Â Let's quickly accept those props right here at the top, such as the user type, set user type, And in some other cases, we'll also pass it the onClickHandler.
00:09:34Â And that'll be of a type UserTypeSelectorPerRaps.
00:09:39Â Inside of here, we want to create a typical select element.
00:09:42Â And for that, we'll, of course, use ChatsYen.
00:09:45Â So let's search for select.
00:09:48Â And you can see that this is a simple select.
00:09:50Â You just click it and it's selected.
00:09:53Â So let's install it.
00:09:55Â by copying this command, pasting it in the terminal, and then we can copy its usage.
00:10:01Â Paste it right at the top and also copy the component and override the div.
00:10:07Â If we do that and save it, it's going to look something like this, which is not perfect as you can see, but with a bit of styling will make it look so
00:10:15Â much better.
00:10:16Â But first of all, let's give this select a value equal to user type.
00:10:21Â And we also have to give it an onValueChange.
00:10:24Â So what's going to happen once the value changes?
00:10:27Â And that's a callback function where we get the type, which is of a type userType.
00:10:33Â And then we want to call a special function called accessChangeHandler, to which we can pass the type.
00:10:42Â This is a function which we can declare right above const access change handler, which accepts the type.
00:10:50Â And then what it does is simply calls the set user type as well as on click handler to which we pass the type.
00:10:59Â This is the special proper passing, and I'm going to explain later on exactly what it does.
00:11:04Â For now, just know that we have to set the user type as well as pass it to the click handler function.
00:11:10Â Moving down, we can style the trigger by giving it a class name of shad-select.
00:11:18Â We don't need a placeholder value here for the value.
00:11:22Â We can style it by giving it a class name of border-none.
00:11:27Â And bgDark of 200. And now we can further style the select item.
00:11:33Â I will only leave one.
00:11:36Â Give it a value of viewer and a class name of shad-select-item.
00:11:44Â And the text will say, can view.
00:11:47Â We can now duplicate this, change the value to editor and say, can edit.
00:11:54Â Perfect.
00:11:55Â Going back, take a look at how nice this looks like.
00:11:58Â We can enter our email address right here.
00:12:01Â And then we can select whether we're viewing or editing, which permissions are we giving to this user.
00:12:07Â So now we can go back to the share model.
00:12:09Â And now that we have the input that allows us to share it, let's also add a button right below this div.
00:12:16Â I'll create a button of a type is equal to submit.
00:12:21Â with an onclick property of share document handler and a class name similar to a share button.
00:12:30Â So that's going to be a gradient of blue flex.
00:12:35Â H dash full gap of one and the padding X of five, which if it's loading, can say something like sending dot, dot, dot.
00:12:47Â Else it can say invite.
00:12:50Â And we can also give it a disabled state.
00:12:52Â If it's loading, it can be disabled just so we cannot click it multiple times.
00:12:57Â So going back, this is looking absolutely perfect.
00:13:00Â Now below this div right here, we can create another div inside of which we'll show all of the collaborators that we have already invited.
00:13:10Â So taking a look at the design, this is what we have right now.
00:13:14Â And this right here is what we need to do next to show everybody and their permissions.
00:13:19Â So let's give this div a class name equal to margin Y of 2 and space of Y2 to create some space and an unordered list with a class name equal to flex,
00:13:34Â flex-col.
00:13:36Â And right within it, we want to map over all the collaborators.
00:13:40Â So we can say collaborators.map where we get each individual collaborator.
00:13:46Â And for each one, we automatically return a new component, which we'll create called collaborator like this.
00:13:54Â So let's immediately create it right here in the components folder.
00:13:57Â That's going to be collaborator.tsx.
00:14:03Â where you can run RAFCE and we can now import it from dot slash collaborator.
00:14:09Â And back in our app, you can see only one collaborator.
00:14:12Â That's us, the owner, but that means that it's working.
00:14:15Â To this collaborator, we'll pass a lot of things.
00:14:19Â We'll pass a key equal to collaborator dot ID.
00:14:24Â A room ID equal to room ID, a creator ID equal to creator ID, an email equal to collaborator dot email and the entire collaborator itself equal to collaborator
00:14:39Â alongside the user equal to user dot info.
00:14:42Â So we have everything we need inside of here and we can use it within the props.
00:14:47Â So let's accept these props right here.
00:14:51Â We have the room ID, the creator ID, the collaborator, the email and the user.
00:14:58Â And those are of a type collaborator props.
00:15:04Â In this case, we'll also have to know of which type is which user.
00:15:08Â And here we'll also be able to modify the type that is viewer or editor for the users that we already have in here.
00:15:16Â So we'll also need another use state, which will be called user type, set user type at the start set to collaborator dot user type.
00:15:29Â Or if that doesn't exist, we can simply say viewer.
00:15:32Â Let's import the use state from react.
00:15:35Â And we'll also need some kind of a loading state right here.
00:15:38Â So let's create a new loading set loading at the start set to false.
00:15:44Â And of course we'll have to have some kind of a function here because this is where the magic happens where we share the document.
00:15:50Â So I can create const share document handler, which is equal to an async function that accepts the type that we want to provide the access to of a type string.
00:16:03Â And we'll implement the logic for that very soon.
00:16:05Â We also want to have another function.
00:16:07Â So I'll duplicate this one that'll be called remove collaborator handler.
00:16:13Â So I'll say remove collaborator handler.
00:16:17Â And this one will take the email of the person which we want to remove the access for.
00:16:24Â Great.
00:16:25Â So now we can focus on JSX to actually show those people on the screen.
00:16:29Â Keep in mind, we are inside of an unordered list.
00:16:32Â So each one of these elements will actually be a list item.
00:16:37Â This list item will have a class name equal to flex items-center justify-between gap of two and padding y of three.
00:16:51Â Within it, we can have a div with a class name equal to flex gap of two.
00:16:59Â And within it, we can render an image that will show their avatar.
00:17:03Â So let's import this image right here and give it a source equal to collaborator.avatar with an alt tag equal to collaborator.name,
00:17:14Â a width of about 36, a height of about 36 as well, and a class name of size-none and a rounded-full property.
00:17:27Â If we save it and go back, you can see a nice looking JSM logo right here, as currently we're the only collaborator.
00:17:35Â That's just ourselves, me, myself, and I, and we're the owner at the same time.
00:17:40Â But good that we can show something here.
00:17:42Â So let's go below this image and create another div.
00:17:46Â And within that div, create a p tag that'll render the collaborator.name.
00:17:52Â And we can also style it further by giving it a class name of line-clamp-1, text-small or sm, font-semi-bold, leading-4,
00:18:06Â and text-white.
00:18:09Â So here we're showing the name.
00:18:11Â And just next to that name, we want to have a span element where we can check if we are loading.
00:18:17Â And if we are, we can render a string that says updating, dot, dot, dot.
00:18:22Â Remember that thing when we're saving the document title?
00:18:25Â Well, this is a similar thing.
00:18:27Â So here we can give it a class name, make it small, like text-10-regular, padding left of 2, and text-blue-100.
00:18:40Â So if we want to remove somebody's permissions or just change them, we'll be able to see updating.
00:18:45Â Below this p tag, we can create another p tag that'll have a class name of text-sm, font-light, and text-blue-100.
00:18:57Â So let's fix this typo right here.
00:19:01Â And it'll render the collaborator.email.
00:19:06Â Great.
00:19:06Â We can go two devs down and here we can show the owner.
00:19:10Â Okay.
00:19:11Â So we can say creator ID is triple equal to collaborator.
00:19:17Â And that's going to mean that we are the owner.
00:19:20Â So here we can say a PTAG with a class name equal to text-sm and text-blue 100. And it can simply say owner, else we can have a div where we can then remove
00:19:35Â or change permissions.
00:19:37Â So let's wrap that in a div with a class name of flex and items-center.
00:19:45Â Here we can render the same user type selector that we have already had to which, as you might know, we need to pass a couple of props such as the user
00:19:56Â type equal to user type as user type set.
00:20:03Â user type, which will be the set user type function or just a string of viewer and the onClick handler.
00:20:12Â So what will happen once we click it, we want to share the document handler.
00:20:17Â Okay.
00:20:18Â So once we go ahead and click here, we want to actually run the function that'll update those permissions for real this time.
00:20:28Â So this is why this onClick handler is connected right here.
00:20:32Â And right below that, we can have a button.
00:20:35Â Of course, we have to import it first from UI button with a type is equal to button.
00:20:43Â And then on click where we simply call a callback function where we can say remove collaborator handler.
00:20:50Â to which we pass the collaborator.email to know which one to remove.
00:20:56Â And then we have to just say remove.
00:20:59Â So you can see how this looks like when finished.
00:21:02Â We have the owner right here, that's us.
00:21:05Â So we cannot change our own permissions.
00:21:07Â But if we had some other users, you would be able to remove them or change their permissions, as you can see on the design.
00:21:14Â So why am I just saying that?
00:21:15Â Let's actually make it happen.
00:21:17Â Well, to be able to see it, we have to implement the logic for sharing the document and removing the collaborator.
00:21:24Â So you know the drill.
00:21:25Â Whenever there's some kind of a logic happening, we have to go to actions.
00:21:30Â In this case, we'll have to go to room.actions.ts and we'll need to create a new export const update document access, which is going to be equal to an
00:21:43Â async function that accepts a couple of props.
00:21:47Â Room ID, email, user type, and updated by.
00:21:53Â We need all of that info to properly update the document access.
00:21:57Â And that's of a type, share document params.
00:22:01Â Let's properly close it and open up a try and catch block.
00:22:06Â You know the drill.
00:22:07Â In the catch, we will simply console.log.
00:22:10Â Error happened while updating a room access.
00:22:14Â And in the try, we'll try to make the logic happen.
00:22:17Â So first, we have to form user accesses by saying const users accesses like this, make sure that it is plural with the S.
00:22:29Â of a type, room accesses, is equal to an object where the key will be dynamic, and it will be of a type email.
00:22:38Â And we have to call the get access type coming from .slash utils to which we pass the user type.
00:22:46Â And just to satisfy TypeScript, we can say as access type, like this.
00:22:52Â So this contains a list of all of the user accesses.
00:22:56Â And then once we have that, we just want to update a room by saying const room is equal to await LiveBlocks.updateRoom to which we pass the room ID.
00:23:09Â And as the second parameter, we provide an object with the updated user's accesses.
00:23:16Â So that's going to look something like.
00:23:19Â Next, if we have a room, so if room, that means that we can trigger a notification.
00:23:25Â So this is something we'll do very soon.
00:23:27Â To do, send a notification to the invited user.
00:23:31Â Finally, we want to revalidate the path, specifically the document's room ID.
00:23:37Â So we can change the share model, and then we can return parse stringify the new updated room information.
00:23:46Â And while we're here, what do you say that we also implement the functionality for removing a user?
00:23:52Â We can do that by creating a new export const removeCollaborator is equal to an async function where we have a room ID and an email.
00:24:03Â That's going to be of a type.
00:24:04Â A room ID is of a type string and the email is of a type string as well.
00:24:10Â And we open up a try and catch block.
00:24:13Â In the cache, we console.log, error happened while removing a collaborator.
00:24:18Â But in the try, we can try to fetch the information about the room by saying const room is equal to await liveblocks.getRoom to which we pass the room
00:24:32Â ID like this.
00:24:33Â Once we get the room, we can make a check whether the email that we have there matches.
00:24:38Â So if.
00:24:40Â Room.metadata.email is triple equal to the email that we're trying to remove.
00:24:45Â We're going to throw an error saying that you cannot remove yourself from the document.
00:24:50Â As you're the owner, you're the only person who can modify permissions, you cannot remove yourself.
00:24:56Â Else, we're going to say const updated room is equal to await liveblocks.updateroom.
00:25:03Â We pass in the room ID and we pass the user accesses by removing the access for that specific email.
00:25:12Â Finally, we revalidate the path and return par stringify the updated room.
00:25:18Â I hope this makes sense.
00:25:19Â As you can see, it's getting more and more, not repetitive, but it follows a specific structure.
00:25:26Â which is always good.
00:25:27Â So now if we go back, we can implement those server actions within corresponding function handlers.
00:25:34Â So in the share document handler, we can set the loading to be true at the start, and then we can set it to false at the end.
00:25:43Â As a matter of fact, we can do the same thing for removing the collaborator.
00:25:46Â And within it, we can say await, update document access, which you need to import from utils, And now we need to pass the room ID,
00:25:57Â the email, the user type of a type user as user type.
00:26:04Â This is just for TypeScript and the updated by where we can pass the user right here.
00:26:12Â Let's actually spread this into multiple lines.
00:26:14Â It might be a bit easier to understand what's happening.
00:26:18Â So we have a room ID, email, user type, and finally the updated by property.
00:26:24Â And we can do a very similar thing for removing the collaborator.
00:26:27Â So that's going to be await, remove collaborator, which we need to import from actions, and then pass in the room ID and the email.
00:26:37Â Great.
00:26:38Â So what do you say that we give it a shot?
00:26:41Â Oh, but don't forget, if we're going to actually test this for real, we have to see where we added a to do for ourselves to play with permissions.
00:26:50Â So we have a delete button right here.
00:26:52Â We have one four.
00:26:54Â There we go.
00:26:56Â Good thing that I left it to do for myself.
00:26:58Â It says, bring this back when we have the permission system in place.
00:27:02Â This is exactly what I wanted to see.
00:27:04Â So now we'll actually look into the access of the user that we're inviting.
00:27:10Â Great.
00:27:10Â And if you didn't leave it to do for yourself, this is just under room actions, line about 40, where you can delete that as well.
00:27:19Â or uncommented.
00:27:20Â So now if we go back, I am currently logged in with JavaScriptmastery00 at gmail.com and I'll try to invite my alter ego of contact at jsmastery.pro.
00:27:33Â And let's say that I want to give them the edit permissions as well.
00:27:37Â And I'll click invite.
00:27:38Â In this case, it looks like not much happened.
00:27:41Â Even if I reload the page and go to share, we're still alone here.
00:27:46Â So why is nothing still happening when I click it?
00:27:49Â If I go back, I mean, I really did add a handler to this select.
00:27:54Â Oh, but we are not playing with select here.
00:27:56Â We're working with the invite button.
00:27:58Â So if we go to search and search for invite, you'll see that we're using it here on this button of a type submit that has a shared document handler.
00:28:08Â And this one is completely empty.
00:28:10Â This one is in the share model functionality right here.
00:28:14Â So here we have to do exactly the same thing that we have done in the other share handler.
00:28:20Â Let's see where it is.
00:28:22Â It is this one.
00:28:23Â So let's just copy what we have here and paste it inside of here as well.
00:28:29Â Import the update document access.
00:28:31Â And in this case, the type is a user type, which is stored in the state.
00:28:36Â The updated by will be user.info.
00:28:41Â Great.
00:28:42Â So now if we go back, we can try to truly invite, contact adjsmastery.pro and there we go.
00:28:48Â For a second, you are able to see loading and now we have a remove button and we can also change the permissions.
00:28:55Â If I click can view, check this little updating thing.
00:28:59Â Super cute.
00:29:00Â And actually it worked.
00:29:02Â This is great.
00:29:04Â Let's see if the changes are reflected on live blocks aside.
00:29:08Â If you go to rooms and if you find the room that we've been working with and go under permissions, you can see that contact at JS Mastery Pro and JavaScript
00:29:18Â Mastery both have the room right access.
00:29:21Â If we quickly change it to view and go back, you can see that now room read Oh, this is interesting.
00:29:27Â So currently it's write, but typically it's read.
00:29:31Â In any case, this is working great.
00:29:33Â And I also think we have to fix the default accesses.
00:29:36Â Yes, this is very important because now we have a complete permission system put in place and we don't want every user to have write access by default.
00:29:44Â So let me think about where that was.
00:29:46Â I think I commented it out.
00:29:48Â I can search for room write, I think, and we should be able to find it somewhere.
00:29:54Â I cannot seem to see it like this, but maybe if I search default accesses, there we go.
00:29:59Â It's in the room actions.
00:30:01Â And actually this was supposed to be just an empty array right here, because we no longer want to give write access by default.
00:30:09Â Great.
00:30:10Â So now to truly test this out, let's create one new document from the JavaScript Mastery email called something like My First Collab.
00:30:20Â And let's actually invite contact at jsmastery.pro and I'll do it as a viewer.
00:30:27Â And I'll do something like, hi, you here?
00:30:31Â And now we can see if that shows up on their account.
00:30:34Â Would you look at that?
00:30:36Â Notification is not there yet as we haven't yet implemented that.
00:30:39Â We will soon, but my first collab created just about now is here.
00:30:46Â We are invited.
00:30:47Â And as you can see, we have a view only access and we can see the same text.
00:30:53Â We cannot collaborate, but if I quickly go here and modify the axis to edit, in real time, it actually changed near the title,
00:31:02Â but we still might to reload just for the editor changes to take effect as well.
00:31:07Â And now, as you can see, I am typing and on the left side, you can see the changes.
00:31:12Â Wonderful.
00:31:13Â That means the sharing functionality alongside the full permission system has now been put in place.
00:31:19Â Great work.