In this video, you will build the profile settings section of your application, including an image upload component, and configure your schema to provide referential integrity in your data.
👉 More videos from this series • Previous: Build A Fullstack App with Remix, Prisma & MongoDB: CRUD, Filtering, and Sorting | https://youtu.be/Mx9Xsq9JNXo
Welcome everybody, to the fourth video of this series where I'm walking you through how to build a full stack application using Remix Tail and CSS Prisma and MongoDB. In the last video, we set up our main functionality for our website, which included a user's list, the Kudos feed with searching and filtering options, and also the ability to send Kudos to a user. In this video, we're going to be setting up our profile settings section for our users and also building an image upload component so a user can upload their profile picture. So let's go ahead and take a look at where we left off and then get into our project. So popping up in our code base, we're going to see all of the components and reps that we set up in the last video. Some of the main ones to point out are the Kudos component and our modal and portals that we set up so that we can have a modal pop up on the screen. And then we also set up a lot of these utility files like the Kudos server TS. And we added quite a bit to the user server. Ts to gather user information and display it on the page. And in this video we'll be adding upon all of this and hopefully finishing out our application and getting it ready to be deployed. So let's go ahead and get started on the first task we have ahead of us. The very first thing we'll be doing is setting up a new modal that will hold our profile settings form. So this is going to be set up pretty much the same way our Kudos sending modal will set up. So let's go ahead and add a new subrout to our homepage. And this is going to be called Profile. Tfx. And this is going to export a new component called profile modal. And for now, this is going to return a modal. And for this modal we need is open true. We want it to always be in the open state and within this modal we're going to write profile settings for now just to give it something to show. So this is going to be a sub route of the home page just like the Kudosetting modal. And when that gets rendered, it will end up rendering right here where the outlet tag is. So let's go ahead and add a way to open this. Now, let's head over to our application and actually look at how we're going to do this. So if I pop open Local Host 3000 pulled over here. So what we're going to do is we're going to render one of these user circles up here in the top, right? So it'll be similar to how a lot of websites have their profile settings. You're going to click on this and then our motor will pop up. So let's go ahead and add that to the search bar. We go into our code head over to components and open the search bar file. We're definitely going to need the user circle component, so I'll import that one there and we're going to render that right at the end of the page. So let's go ahead and add that here. And this is going to take in a few different things and I'm going to copy over a couple of classes. We're going to append to it because this specific user circle will have a couple of different styles applied to it than the other locations in our website. But another thing this is going to take is the profile, which we're going to need to pass to our search bar from the parents. So that will be there in just a little bit. And when we click on this user circle, we want to trigger a navigate function. So we're going to need to import the oh, we've already got you navigate here, so we can just use this. We're going to use the navigate function to go to profile. And actually this will just be profile without a slash because it's going to a nested route there. So when we click on our user circle, it should open up that modal. But right now we don't have a profile to supply this user circle. So let's go ahead and get that. So we're going to go to our home and within the home here we're going to need to get our user details in the loader and return that. So it's available in the component. So right up here I'm going to do Const user equals await get user and this is a function we wrote in our Office server file and this takes in the request object and returns the logged in user. So that's exactly what we want. Let's get that and return it from our function here. And then we can pull it out from our use loader data hook right here. And then I've gone ahead and added profile to this and we're passing it our user profile because all we need is the profile data for that user circle. So if you go back over into our search bar now, we can set up some props for this component. So let's do a new interface and we will call it props and this will take in a profile and that should be of the profile type that Prism is generated for us. So that is great. Let's pull out our profile here and give the props our prop type. Awesome. So now we have some profile to work with. We'll pass that into the user circle, and now that we've got that data there, we should be rendering this user circle properly. But there's one thing I'm going to go change over here in profile TSX. This export should be a default export. That way Remix will know that this is the component we want rendered on the screen. So let's go to the browser now and see what we get. We're going to see our button here and when we Hover over it, we get this nice little animation. I have done that by providing a transition and some Hover transition. So when we Hover on this element, it's going to scale up to basically 10% bigger. It's going to get a border and the border is going to be yellow. And because I've given it an ease in out for this transition, it's not going to do it really snappy. It's going to just sort of ease into it. So it gives it that nice little growing effect. So that's how we're doing that. And if we click on this guy, we're going to see our profile settings mobile. So this is ready to be worked with. We can go ahead and start adding our form here now. So to start this guy off, let's head back into the code and we'll open up our profile file here and let's go ahead and create a loader. So exporter this is a loader function and we're going to pull the request object out of the screen again. And what we're going to want to do up here is basically just get our user and return that as JSON because we're going to pre populate the form with the existing users details. That way the user can see what they have already and then they can make the changes as they need. So let's grab our current user confuserate get user and that's again from the off dot server file. And we pass our request and then we'll return a JSON response using remix. And we'll pass the user and then down here as we've done before, we'll pull that out using the use loader data hook. User equals use loader data. And of course we need to import this. Cool. So we have some user data available. Now let's go ahead and start actually building the form. I'm going to give this modal an extra class name here and we're going to want to make sure this form is pretty big. So we're going to make sure it's a third of the window size. And then on the inside of that we're going to add a new Div, the last name of T three. I want to give some padding to this Div. And then inside of this I'm going to paste in a couple of things that I've already prepared just to style this form a little bit. So we're going to have a header here that just says your profile making the text pretty big, making it blue centering. It doing a couple of different things to it. Let's go ahead and add a flex box here. And this is what's going to hold our form. We're using Flex because we're going to have some of the form on the left side and some of it on the right. And then within each of those we're going to have some different layouts. So we're going to use flex to get it side by side. And then for the very first side we're going to leave that empty for now because we're going to come back to this once we do an image upload component. So this will just be empty. And I'll leave a comment here image uploader. But for the next one we'll do Div class name equals and we're going to make this one a flex one. And that's because we want it to take up all the space that I can after considering the previous element. And within that that's where we're going to build our form. So let's do a new form and within there we're going to add a couple of fields. So we're going to need the form field component. I'll go ahead and import that up here at the top. And we're also going to need the select box component because we are going to add a select box where the user can select which Department they work in. So we'll add select box and we'll update this here to select box. I think for now that's it. So let's go ahead and start building our form. We're going to first have the first name, that's going to be an editable field, the last name, and then the Department. So let's add those. We'll have a form field here and I'll just go ahead and knock this out a little bit. We're going to have this second form field and then we're going to have the select box. And in the first form field we have the HTML four. That's going to be first name and the label is going to be first name in a human readable format and we're going to give this value and for now I'm going to write form data first name and this auto format is fixed. That doesn't exist yet. But once we're done building these elements, we'll go ahead and add it. And then when we do a change event on here, we're going to get that event object and we're going to pass it into a function we'll create called Handle input change. This is going to be really similar to how we handled our sign in and sign up forms. So let's do first name and let's see. I think that's all we'll need for now. So I'm actually just going to copy this down now and replace this with last name and I will fix this last name and then we're going to have our select box. The select box is going to be a little more complicated, so I'm going to paste these values in. I did that because we have these class names here that we're adding to it just to give it some styling. The default select box is pretty much unstyled, so we want to make sure to make it look pretty in our form. We're giving an ID of Department and the label is a human readable version of that. And then the form elements name will be Department. So when we submit our form, the Department key is what we'll look for. We're going to pass it in some options in a variable called departments and that does not yet exist. So let's go ahead and create that. We're going to add that to our I think we have a constant file here and I have those and I will paste them in, I'll paste them down at the bottom and then I'll go ahead and import them in this file. So that just has key value pairs that show all the different departments that we can work with. And these are the values to be stored in the database because that's what's in our enums. That's looking good. Next we just need to add our form data element. So let's create a new piece of state to hold our form data. So we create cost and we'll do form data and a way to set that. And then we'll say use state and the state will be an object. But let's go ahead and import this. Now that we have that will add first name, last name and Department there. First name. This is going to default to our user profile first name and the last name will be similar. It's going to default to the user profile last nameanddeepartment, user profile Department. So now that we have those, the last little piece that is missing here is our handle input change function. So let's go ahead and write that Const handle input change and this will be a function and it's going to take in an event and this event will be react change event and it will be for an HTML input element. And then similar to how we've done in the past, we'll also pass in the field so that we'll know which field we're updating in the form data and that will be a string and then we can build the actual logic for this. So let's set form data and we'll get the form as it is right now and we will spread that. That way we maintain the data that's already in there and then we're just going to overwrite whichever field we're updating with the new value. So event target value. And there we go. Now when any of our form fields change, it's going to trigger this function which will pass in the event field and update our form data accordingly. So now at this point there's no errors that we're looking at. So we can go ahead and see what this looks like in the browser. And this is nice. So you can see we have that empty Div here that we had put that's where our image upload component is going to go. And then over on the next side because we're using Flex, we have our form and this has all of our defaults set for us. So save in Adams are there. And there we go. So that's looking good. The last piece we're going to need on this form before we start making things functional is a submit button. We're going to want to be able to save this form. That's pretty important. So I'm going to copy that over. I did that because this is going to be a little bit different than how we've done some of the other fields. This is going to be a width full and it's going to be all the way on the right that way it shows up down here because we don't want it to be this full with that would just kind of look funny. So I've wrapped it in a Div to give it some extra stylings. And then of course we're using this same format we've used before where we have an action. And that's because later on we're going to add the ability to delete a user and we'll reuse the action function in this file. So we're going to need to be able to differentiate between which action we're calling. So that's an explanation of why we're doing it that way. Let's go ahead and start making this thing functional. In order to make this thing actually save and get it, we're going to need an action function. So let's export one of those here and cool. So we've got that action function and we're going to need to pull out the user ID from our request because when we do update our data, we're going to need to be able to tell which user we're updating. So let's get that Const user ID and we're going to pass it request and we could see up here that automatically imported for us. That's nice. And then we're also going to need our form data that we're submitting. So we'll get that. And then finally we'll pick out all the little pieces that we're going to need and I'll pace that over for time sake. So we're doing form gets again to get our first name, last name Department and the action that we're sending. And then we'll go ahead and do a switch statement here so we can handle various actions. Right now we only have the one. So we'll do case. If it says save, then we're going to perform some chunk of code and then we'll also add a default. And so if something other than save is given to us, we're going to return a JSON response with an error. So let's do JSON and error Invalid format. And then we will also give this a status of 400 to let the client know that that was an error status in this block here. We can handle this right now. So let's do that. Let's go ahead and first check that we're getting all strings. So we're doing what we've done in the other forms that we've built so far is we're just making sure all the data that we're getting isn't null. That's essentially what we're doing here. And if something is null, then we're returning an error saying hey, something went wrong, we don't know why, but something broke. And then the next piece is we're going to validate all of these and all of these just need to have some string value. There's no real restriction on what it should look like so long as there is a string being provided. So we can use our validate name function that we wrote in the second part of the series. So I'll import that here and that's in our validators file and then we will build up a Const and this Const is going to be called errors and it's going to have the keys of our form fields so it'll have first name and then we'll validate that that has a value and we'll pass it in our first name there it'll have last name and we will validate the name again. And let's go ahead and add Google here. So we stopped getting these errors. Last name and then Department validate name. There we go. So now we're going to have an object letting us know whether or not we got errors on any of our fields. And I'll use the snippet that we've used before if there are any of these errors. So if it finds some value in here with a boolean, that will mean that there is an error and then we'll return an error response and then we're going to pass back the fields as well so that our form doesn't get cleared, it will repopulate and the user won't have to Retype all the changes that he or she made. Now we just need to save it. And to do so we're going to have to create a new function called Update. User and we'll do that in the user server. Ts file right in here. So in this file we're going to need to add this new function and we'll go ahead and call it just update. User. So we'll export new constituezer and that's going to be an asynchronous function and the data that we're going to get there is a user ID. So this will be the ID of whichever user we're updating and then we're going to get a profile object and this is going to be a profile type from what Prisma generated for us. But we're only going to be passing in a partial type. So that means we don't necessarily have to provide the entire profile object. We might just give a first name or last name. In our scenario, we are going to be giving it the entire object, but this would just be for a more flexible function. So I like to write it that way just in case. And then this function is going to be pretty simple. We'll just do a wait because Prisma does return a promise and we'll do Prisma user update. So this will update a single user and we want to update a single user where the ID matches the user ID that we passed in and then we can provide it the data that we're going to be updating it with. So we'll do data and we are updating the profile embedded document and because it is an embedded document, we can use this update key and pass our profile information. So this is going to say update the profile embedded document with whichever data we passed in here. That's all we need here. This will do what we want it to. It's going to update our user so we can now import that into this file over here the profile TSX to import update user and then we will call that here. So we'll do await update user user ID. We are already grabbing that and then we'll do first name, last name and Department. This is a none. So this is going to complain a little bit. So we have to say Department as Department we are going to import that enumeration there so that we can validate the type. This should be functional. Now when our form submits, it should hit this action function and it will run all this and we'll hit this save condition. If we hit the save button and then it should do all of this properly. But our form currently does not have a method, so let's give it a method and that should be post and then we are just about good. If we now open up our browser, we can test this form out and let's make a change here. Let's say my name is now saving Smith. I'm going to hit save. So that's good. That means our data is saving properly and we can close the modal just by clicking off of it. So that all looks great. The next piece that we're going to need to add is some of this error handling. Right now our form is submitting successfully and if anything goes wrong we are returning errors, but we're currently not doing anything with them. So let's go ahead and get that working. We're going to use a similar approach to do this. As we have in previous forms, we are going to use some state to hold form errors and then we're also going to have a first load variable that determines if we've hit our first load yet. If we have, then we'll go ahead and clear out any errors if the form changes. But if we haven't hit our first load yet, then we won't clear out any errors because we want our user to see them at least once. Get the action data which will hold our error objects use action data, it will import that from Remix and now we have access to that. The next thing we're going to have is a piece of state called form error and then we'll be able to set that with Set form error and we'll default that to the action data error that may not exist. So we need to have the Elvis operator there just in case. And then if it does not exist, if that fails, we are going to give it an empty stream. We're just going to show all of the errors on this form right up at the top. So now that we have the form error state, let's go ahead and add our first load ref. So Const first load equals use ref and that defaults to true. And of course we'll need to import that hook and now those are in place. We'll go ahead and build our effects. So let's add a new effect. This will have a function inside of it and this effect is going to depend on the form data. So basically any time our form data changes at all, if this is not the first load, let me get the current value of that. So if this is not the first load, we are going to clear form error. There we go. The form error will be an empty string if the form data changes and it's not the first time we're Loading the page and then we will set our next effect. This one's important. This one needs to be at the bottom so it will run after all of the previous effects and this will be dependent on nothing. This will only happen one time when the page first loads and it will set first load current to false because that will no longer be the first load after updates are made from then on. So we're capturing our form level errors and we are setting these effects in place to handle those, but we don't yet have a way to display them. So let's go ahead and add one. I've got one prepared here. I'm just going to add it right under the header. That way it's right up at the top of the form and the user will certainly see it and it will render out our form error. And this will just not appear if there is none. So that should be fine. Let's go ahead and intentionally trigger an error just to make sure this is working. I'm going to copy let's say this guy here and paste it right up at the top. That way it cancels out anything that would have happened after it and instead just returns an error if we submit our form. So I'll hit save here, we get our Invalid form data. That's perfect. So I'll get rid of this now and the next thing that we need to handle is our field level errors. So if any of the validation fails, we're sending all of our fields back with some errors. So let's go ahead and add those. And I'm using Elvis operators across all of that because there may not be an error. So that will not always exist. And I've only added error handling for the first name and last name because the user can actually type in values to those. The select box is preset values so we won't really need to handle anything there. And before we go ahead and give us a test, there's one more update. We're going to make our form data right now defaults to the user's information, but we're also passing back fields if there was an error so that we can repopulate the field with whatever the user had changed but was unsuccessful. So let's go ahead and default to that value actually. And if there were no errors, then we can go back and default to the user and I'll go ahead and paste what that would look like in here. So if our action data has a fields key, there must have been some sort of error with the first name. So we'll use that. But if it didn't have this, then we'll use the user's profile first name. So with that, we can head over to our form and give this a go and see if our validation is working. I'll just empty out the last name and we should get something telling us yes, please enter a value. So that looks great. And now we can move on to adding the image upload component. So to set up our image upload component, we're actually going to need to set up an AWS S Three bucket to store images in, which will mean you'll need an AWS account. I'm going to go to the dashboard right here. If you don't have an account already, feel free to create one and then come back to this video. But if you do, let's go ahead and get going on here. The first thing you'll want to do is click up here in the top right corner and hit security credentials. We're going to set up an Im user, which will be a user that we give access to interact with s Three. So then over here on the side, you're going to hit users and you can hit Add User. This will take you through a quick little wizard. I'll just add a name. I'll say this one's name is Leland and then you're going to want to click this access key option. This will create an access key ID for you and a secret key once we have our user set up. So we're going to need to use that one because we're going to use those keys to use the AWS SDK in our code. So we'll do next. And we're going to want to look at existing policies here and just type in S Three and you'll see Amazon S Three full access. And that's the one we're going to want because we need our user to have full access there. You can optionally add these Tags to better organize your users. I'll just leave those blank for this purpose. If all this looks good, which it does, go ahead and hit Create User. So this will give you your keys, and then you're going to need to save these in your EMV file. So I'll go ahead and copy my access key and that will go over to EMV. I'll name this Kudos AccessKey ID and I'll pay for that there. And then the next one will be Kudos secret access key and that is this guy here. Copy it, paste that here and I'll be sure to delete this user afterwards in case any of you are worried. And then the next piece we're going to need to do here is actually set up the S three bucket, head up over here to the search bar and type in S three and you should see this guy here. Go ahead and click that. I already have a couple of buckets, so your screen might be a little bit different, but there should be a button somewhere that says Create bucket and just go ahead and hit that guy and give your bucket a name. I'm going to do Kudos Images bucket and you will want to save this name along with the other environment variables because we're going to need to know which bucket name we want to connect to. So I'll name it Kudos bucket name and the next piece is going to be the region. So I'll just get that ready and the region that I'm going to select is the California one. I live in California, so that makes most sense for me. Yours might look a little bit different, but the important piece is this bit at the end, US West one, whatever that value is for you, move that over into here. So I'll do US West one. The SDK is going to need to know which region your bucket is in so we'll know how to connect to it. So we're going to need to access that through the environment. But now that that is good, you are going to need to not block all public access because we are going to need to allow outside parties to access this. Obviously our front end is going to need to display some of these images. So go ahead and open that up and acknowledge that that can be unsafe. We'll go ahead and create that bucket now and then we can jump into it. The first thing we're going to need to do now that we have this bucket is we're going to need to go into the permissions and we need to add a policy. So what this policy is going to do is it's going to determine who can access this bucket and the resources within. So there's a policy here that I'll paste in and you can go ahead and copy this. But the important piece is that we want to put our bucket name in here. So we'll do Kudos Images bucket and so this will allow people to read our images publicly. That should be good. We will hit save changes and that should be all that we need to do here. Now we can actually go into the code and start interacting with this bucket. The first thing we'll want to do to get our image upload component built is hop into the Prisma schema. We are storing our images in S three, but we need some way to store the location of each image in our database so we know which image URL goes with user's profile. So in the profile embedded document here, we're going to add a profile picture field, and that will be a string. And this will be optional because a user won't have to have a profile picture set. And that's the only change we're going to need to make there. So we will stop our Dev server, run NPX Prisma DB push, and we could have done a Prisma generate there. The DB push won't actually make any scheme of changes. It's just going to generate the client for us. So we could have just run the generate there. But now that we have that change made, we can start adding the different pieces. We're going to build an image upload component, so that will be a rat component, and then we'll also build a service in a new utility file to actually handle the uploads. Let's go ahead and start with the component, though. And so over in components, you're going to create a new file called image component is going to be pretty hefty. So I'll go ahead and paste the code for it in and just sort of walk through what it's doing. So we have our image uploader component. It's going to take in an unchanged property and an image URL. So the on change is going to be called essentially whenever a new picture is dropped onto the component, or if a user clicks onto the component and opens up their file Navigator and selects an image, this on change function is going to be called. So ideally, what we're going to do with this is basically emit the file data, and then we can use that in whichever parent loads this image uploader component to submit to our database. And yeah, so the next piece here is we have this prevent defaults function that's basically just the utility function to prevent defaults. When we do click, like maybe a drag or a drop event on here, we don't want the default action to occur in the browser, and we're using that here. When we handle a drop, we don't want to do the default action, which would be opening the file in our web browser. What we want to do is actually handle that and trigger the on change. So, yeah, that's what we're doing here. And handle change is just going to handle the change in the input field down here, that is the file input. And then we have the actual JSX. So this is just a Div, and we've got a bunch of different handlers on it for the different drag events and for the drop events and a click event. And the reason that we have all of these on there is because we're going to be applying different styles depending on whether or not the user is currently dragging a file over the component. That way we can give some interactivity to the design there. And then a couple of these are just setting different pieces of state and clicking buttons to make the users experience a little bit easier. And then right here with the image URL prop. If there is an image uploaded, the URL, which will end up being the URL to the S Three image after it's been uploaded to S Three is going to display within the image uploader component to show that there is already something there. So, yeah, that's what this component does. And it's going to make a little more sense how this works once we get it rendered onto the screen. But before we do that, we'll go ahead and build the image upload service. So go into your Utils folder and create a new file called S three Server TS. And this is where we're going to do any S Three related coding. But before we can do any S Three coding, we need to install the AWS SDK so that we can actually interact with the different services in AWS. So I'm going to go down here and do NPMI AWS SDK, and that should install the development kit for us. And I have this import here now for AWS SDK. And we're going to use the S Three client. That's the one that we're interested in right now, and we're also going to need to install another package. So let's do NPMI. And it's called cuid. And what this is going to do for us is this will generate guidance and we're going to use those to create random file names. That way we don't have to come up with our own way to name the files that are getting created in S Three. So I'll import that as well, and I'm going to call it cuid. And now we can get going setting up the actual S Three clients, we will create a new variable called S three. And that's going to equal a new S Three. And yeah, let's go ahead and capital this just for convention sake. And this takes in an object and this will contain our region. And so this is why we had to store the region in our environment. So we'll do process envosions, and then the next one is the AccessKey ID. So we'll do access key ID. And that will be process Env kudosaccesspid. And then finally our secret access key. We got that one, process EMV kudossecret access key. And that's all we'll need to do to set up our S Three clients so we can now interact with S Three and perform operations on it. The next thing that we are going to need to do is import a nice tool that Remix gives us. I'll import that here and then explain what it does we're going to be using this function called unstable parse multipart form data. So the way we're going to get our file data to the server is we're going to post it via a form. And that form is going to have to post a multipart type of data because it is going to post a whole file. It's not just like a string value or a number or something like that. It's going to be an entire file. And so we have to use a file type called form data multipart. And what this is going to do for us is it's going to give us a way to stream the file data out of the form data on the back end and then run it through a function. We're going to call upload handler, and that function can handle different operations with that file data stream for us. So what we're going to do is we're going to use that to upload the file, and then that's going to return the files URL for us. And then after all of that said and done, we're going to use this multi part form data function here in the function that uploaded this data to retrieve that URL that gets returned from the file data stream. That may sound a bit confusing, but we're going to jump into it here, and it should hopefully make a little bit more sense. So the very first thing that we're going to do is create a function called upload avatar, and I will go ahead and start typing that guy out. It's going to be an async function and it's going to be called upload avatar, and it's going to take in a request object. And that will be of the type request. And what this is going to do is it's going to take a request, and this will ideally have a form data object in it with a file data stored in there. So we're going to be passing that through the parse multipart form data function. And we're going to give it our request and we're going to give it a function called upload handler. And that function does not exist yet. So let's go ahead and write it right above here, upload handler. And it is an upload handler type. And this will be an asynchronous function. And what this is going to pull out is the name, the file name and the stream. And what the name is that's the name of the key within the actual request form data. Let's check that name and make sure it's something that we expect. So we're going to say if that name does not equal profile pick, then we are going to resume our stream and then exit out of here. So we're just going to say go ahead and continue streaming the file, but just exit out of the function. We don't want to do anything with that because we don't know what that is. But otherwise, if everything does look good. We are going to write this function to upload the file. And this function is going to return an object that contains various pieces of data about our newly uploaded file. The one we care about is the URL of it. So that's why I put that there. So we're going to do this. We're going to use the s three upload function and what this is going to take in is a bucket name. So that's why we had to store the bucket name in our environment variables. And the next piece here is the key. And what the key is is what we're going to be naming the file within s three. So this is where we'll use our cuid function to generate a random good. And then we'll do filename split and then slice one. So what this piece here is doing is getting the extension from the file name. So now we can handle like a. Jpg file or a. Png file and we won't have to worry about doing any sort of processing on the different types of files. Instead we're just pulling off that extension from the end of the file name. But the last piece that we're going to need here at the bottom is the body. And what this is is our file data stream. And this is still complaining here because I think this is potentially undefined and this bucket is required. So we're going to say that if that is not found it should just be an empty string. And then the reason why this here is complaining is because this is a function that we need to call promise on so that we can await the response of the upload. And now we get our location correctly there and we can return it. So with that done back down to here, we're using this parse multipart form data function. We're giving it our request data and that's how it's getting all of this. And then we pass that through the upload handler and the upload handler is going to upload it to s three, give us back the URL. And now this format should be basically just as if it was a normal form. We can grab that by going constile equals form data, get profile pick and yeah, so that should be our URL now. So if we look back at maybe one of the other forms that we've done, it looks basically just as if we did the wait request format. That's basically what we're working with now and we can use the different form data functions on it. Now that we've got that, we'll go ahead and return file because whichever component calls us upload avatar is going to want to get the new files URL and display it somewhere. So that's our upload service and we have our uploader component. Next we will actually implement it in our form. To add this to our form, we're going to have to go over to our profile. Tsx file and we'll import that new component up here. So we'll do import image uploader. And so we've got that there. And now where we have this comment over here, instead of just rendering nothing in this date, we will render our component and then this takes in the on change. And what this is going to do is when the file actually gets uploaded, we are going to run a function that we're going to write and we're going to call that function handle. File upload. So I will just say handle file upload here and let's go ahead and write that function. So we'll do Const handle file upload. That's going to be an async function and it's going to get a file and that will be of the type file because that's what gets emitted from the image uploader. The event that it emits contains the file. We're going to get a new form data object. So let's do, let's move. We're essentially building up a form that we're going to manually submit to a resource route that we're going to set up. So let's append the profile picture data to that form data object. So we'll do input form data append and we called it profile tick. That matches up with what we had right here and we will give it our file data. Once we have that, we can actually reach out to an endpoint and submit our data to it, but we need to build that first. So create a new resource route in the routes folder called avatar. Ts and pop that guy open. What this resource route is going to be is basically a route that we can post data to. It's going to get a request object with our form data and then it's going to handle it by first validating that the person requesting this route is a valid user and then it's going to upload the image if the user is valid, and then once it's uploaded that image, it's going to get the URL back and it's going to update the user associated with this request with that new profile picture. That way it is stored as their own. So to do this, we're going to have to first export an action function because this will be a post request. Then we're going to validate that our user is valid. So we will do Const user ID equals wait, require user ID and pass that the request. Next we will upload our image using the request data and that does return the URL of the newly created image. So we'll store that in constant as well and we'll call it image URL. That's going to equal await upload avatar and we'll import that and we'll give it the request object as well. And now that we have those two pieces of information, we can finally update our user with the uploaded images URL. So we'll import Prisma here and then we'll do await and this update we're going to give it some data and we're updating the profile key. That's the embedded document there and we're specifically giving it the profile picture only. And this is going to be an update not a set because we're not replacing the entire object, we're just replacing one key or updating one key. We'll do update here and profile picture and this is going to equal the image URL we got back. We only ever want to update that image URL if the user's ID matches the user ID that we got there. This is throwing an error because image URL could potentially be null when it's returned from s three and this does not want that to be null. So let's go over to the s three place here and we will say dot two string and if that is not found it will just be an empty value there. So now we can go back over here and that should stop complaining about us. So that looks good. Now we have our action function. After that we will just return a JSON response with the image URL. Cool. So now that we have that we can actually hit this endpoint from our profile form. So this will use just the fetch library. We're going to fetch that request and then we will save the response in a variable. So let's do Const equals await fetch and we are hitting the avatar route and let's configure that. We want to make sure we're sending it a post request and the body will be the form data that we just put together. And then once that finishes up we'll pull the image URL from that response because we are returning it from the action function. We have to use this response JSON with the fetch API to get our response data. And then finally we will set form data and we will maintain the existing form data. But we'll overwrite the profile picture key with the new image URL and that doesn't exist yet. So we will go add that right here. So there we go. That should be working and I think that's going to do all that we need it to. The last piece we need to do here is add our image URL. So if there is an image URL for our user it will show. So now this should be built. Let's go ahead and start our Dev server. Over in the browser we'll open up this form we should see our image upload component. If we Hover over it we get a little bit of an animation here and let's give it a file. So I have a desktop images, I have an image that I'm going to upload here and we see as we drag over it we get that nice little animation we set up and let's drop and there we go. Once we dropped it it fired off our on change event that emitted a file which got captured by our handle file upload function and that passed it onto our avatar resource URL, which then uploaded it to S three. So there's a little bit of a chain of events going on there. But once all of that finishes, it should return a new URL and display it. And we are seeing an image here. So something must have uploaded. Double check though. We'll go into our S three bucket and let's give this a refresh. Here we have our new PNG and it's got a random name and it was just created. So that's perfect. It looks like it's working really well. We aren't really doing anything with the image yet, so let's change that. We need to use that in our user circle. So let's go here. And the way we're going to accomplish that is we're actually going to use that image URL as the background image. So I have a snippet of styles that I'll paste in here. What this is saying is if our user does have a profile picture, set the background image to that profile picture and make it cover the entire area. And then there's one last little change I'm going to make. I'm going to replace this H two down here with a little boolean operator. That's saying we don't need to show that if we have a profile picture. So now if we go onto our site once again, we should see that our picture is being shown here. We were to sign out of here and go to another account. Let's go into his profile settings and let's give him another image and then we will upload it. There we go. Hit save. And we see now that anywhere that Leland's account was showing, it should show that dog. I think the last piece that we want to add is a way for a user to delete their account. And this is going to have to do a couple of things we need to keep into consideration referential integrity. And what I mean by that is we need to make sure that there's no orphan records being created when we hard delete data in our application. A scenario that could happen if we were to delete a user is that we would have a lot of leftover kudos in the database that are no longer linked to a user. So they'll never actually be accessed, they can never be read, but they do exist and take up space. So we want to make sure to delete those whenever a user is deleted. And we can actually through Prisma set up referential actions with MongoDB to handle that for us, which is not a native feature of Mongo, it's a native feature of some relational databases. But now that we are using Prisma with this, we can actually do it directly in Mongo. So all we're going to have to do is go into our Prism schema and on our author here we can make a little change. We'll say when the author is deleted, it should cascade. And so basically let's imagine a scenario where my user sent a Kudos to somebody else. And so now I have my user record and a cuddle record. If I were to delete my user record, I was the author of that Kudo. And so this is going to say my author's being deleted. All right, now cascade that event onto me and then the Kudos will be deleted as well. And that will happen with as many Kudos that author had. So if we stop our Dev server and we'll do NPX Prisma. Db push, that will save our changes and generate Prisma client for us. And that's the only change we needed to make. Now, when we delete a user account, it will also delete posts. But we don't actually have any way to delete a user yet. So let's add that right now we will do this via a button on the profile settings form. And I'm just going to go ahead and paste that button right down here below the select box. And because this will be a completely different action than the save, we're giving this the underscore action key with the value of delete and we'll have to key into our switch statement up here. Yes, there it is. And we're going to want to delete the user. And so this is going to be a pretty simple action, but we are going to need to create a new function in our user server file. And this function is going to be called delete. User. So I will export a Const called delete. User. And this will take in a user ID, and that will be a string. And this query is going to be rather simple. We're just going to await Prisma. User delete and we're going to do where ID equals the user ID we provided. And so this is actually really cool. It's a simple query, but it's doing a lot under the hood. So it is deleting our user account. But because we did set up that referential integrity, it's going to go ahead and delete all of the related posts that this user is the author of as well. So that's all we needed there. We will import that and use it in this function, we'll await delete. User and we will import that and we'll pass it the user ID. And then after this, once the user's account is deleted, there's no reason they should be on any sort of home page or profile settings page. So let's go ahead and just log them out, which will destroy the session cookie and send them to the login form. That's really all we needed to get the delete user working. We'll run the site one more time and we can see that the saving account has sent Leland a Kudos before. If we actually go in MongoDB, we can see that in Kudo there is one and in user we have two. So I'm going to log in as the savings account and then I'll go ahead and delete my account. Actually now that I'm thinking about this I have one more change I want to make that seems like a pretty dangerous operation to just delete an account without any sort of warning. So let's add an onsubmit function and I have an example of that right here so I'll just paste this one in that's basically going to say before you submit the form, are you sure? And if you hit yes, it will continue otherwise it's going to prevent the form from being submitted so that seems a lot safer to me. Now I'll hit the delete account button it's going to say are you sure I'll hit okay and so that deleted my data and then it logged me out and sent me to the login screen and we can see that in here so if we refresh, we'll see. Now we only have one user but we'll also go into Kudo and we have no kudos. That's because of that referential integrity. So now we don't have to worry about stale data getting stuck in there and just sticking around forever. That becomes really hard as your application grows to track down and delete to save some storage space. So if you set it up like that right from the beginning it's something that you do not have to worry about. So with that complete we've come to the end of what we're going to accomplish in this video and also we've finished up development on the application so the application is full and complete. At this point in the next video we will handle deploying it using Versailles and we'll see how that goes. So I look forward to wrapping this up with you in the next video and thank you for watching. Bye.