So now I want to go ahead and I want to connect to the Unsplash API before we wrap up this entire create board component. So go ahead and Google Unsplash developers or just go to unsplash.com slash developers and go ahead and register as a developer. After you do that, go ahead and go back to unsplash.com slash developers because there is a chance that it redirects you and just go ahead and click on your apps. So you're going to see I have a different button now that I'm logged in. And as you can see I already have a Trello application here but I'm going to go ahead and guide you through the process of creating a new one.
So find the button which says new application and you have to agree to all of this, well, guidelines right here and we are of course going to oblige by these guidelines. For example, they require you to show the user which created the picture and a link to redirect to that user's original picture. So we're going to go ahead and oblige by that and just click accept terms. Let's give this application name hrelo-tutorial and I'm gonna go ahead and do the same thing for the description and click create an application. And as you can see now it redirected us to this page where it says that we can apply for production.
So you can do that of course if you want to. If this is just for your demo you don't have to do that. Production is free as I understand and they allow you up to 5, 000 requests per hour. If you need more than that you can get in touch with the theme of course. So let's go ahead and scroll down here and as you can see for the demo we have 50 requests in an hour.
Of course this is completely free. So let's go ahead and let's use this access key to create our library called Unsplash. So I'm going to go ahead and close everything here and I'm going to go inside of my lib folder and create a new file unsplash.ts and inside of here I'm going to go ahead inside of my terminal first and I'm going to run npm install unsplash-js like this So a package from the official unsplash documentation and go ahead and import create-api from unsplash-js and then export const unsplash is going to be create API and in here we're going to pass in the access key to be process.environment.next public unsplash access key And put an exclamation point at the end. And fetch is just going to be our regular fetch. And now let's copy this environment key and let's add it to our dot environment after the database URL and then go back to your application and copy the access key from here.
So just click copy and go ahead and put it here. So that's it. That's all you need to do. And I actually want you to keep this open for now just because I want to show you how this is measured right here. Great so I'm just going to switch back to this screen now and now that we have this I want to go ahead and I want to create a component called form picker.
So let's go ahead inside of our components form and create a new file formpicker.vsx and let's go ahead and mark this as used client and then let's create an interface form picker props that have an ID which is a string and errors which are going to be a record string and string array or undefined and export const form picker here and just return a div form picker and now I just want to assign the props inside so form picker props and extract the id and the errors great So now I want to go ahead and add this component inside of our form popover right here which we are working in. So above the form input where we have the label board title let's add form picker component from ./.formpicker and let's go ahead and give it an ID of image and errors of field errors great so now when you click here you should have a text which says form picker just above the board title so we are now slowly going to style it so it actually renders the images. So we'll go back inside of form picker here and let's go ahead and let's import our Unsplash library here so import Unsplash from add slash lib Unsplash here let's also go ahead and let's import use effect and use state from react Now let's go ahead and let's assign the initial images here.
So const images and set images are useState and by default it's an empty array. And let's go ahead and define it as array and record of string any like that and then let's go ahead and let's create a use effect which is going to fetch the images using the Unsplash API. So useEffect. Let's go ahead and pass in the empty array. And let's write const fetchImages() to be an asynchronous function.
And go ahead and open a try and catch block. Let's resolve the error very simply by adding console.log error so we know something went wrong and let's add set images and let's just put an empty array in here. And now let's go ahead and let's write const result to be await unsplash.photos.getRandom and now let's give it a property to only read from a specific collection IDs and that's gonna be 3 1 7 0 9 9. So where did I get this number from? Well this is just an ID of an album from Unsplash.
This specific one is the same that the original Trello uses. So I went and looked at their network requests and I saw that they're making a request for this collection ID. And the reason I think it's really good well first of all this collection is the official collection from the Unsplash editorial team. And the second thing that's really good about it is that most of the images inside are compatible to be wallpapers because as you know on Unsplash we might have some images which are not in the resolution or aspect ratio which fit the wallpaper so that's why this collection ID is perfect for what we need. You can of course create your own collection in Unsplash of free images or you can just find any other collection that you like.
Great. And besides that we're gonna pick a count of 9 so no more than that. And then let's go ahead and run if we have result and if the result has a response, in that case const images, let's simply define them as result.response as array record string and any and then let's write set images to be images and I just misspelled images like this great Actually it looks like this constant images and this constant images is the same so let's call this result and then actually not result let's call it okay new images So just something different and then set images new images because you can see it's confusing when I write images here there is no error here because we defined images in here but this code will not work so let's be explicit and let's rename this constant to new images and then assign the new images inside. Great! And then let's write an else function to be console.error fail to get images from unsplash.
Great! And now let's go ahead and let's simply add a loading state here. So we indicated the user that we are loading the images. So isLoading and setIsLoading is going to be used state and by default is going to be true because we immediately start loading. And then just in the finally block set is loading to be false.
Great and then outside of this constant fetch images we have to actually call it so at the end of useEffect just fetch images. Like that. Perfect. And now let's go ahead and write if isLoading. In that case, let's return a div with a Loader2 element from Lucid React.
So make sure you import Loader2. And let's give it a class name of H-6W-6-sky-700-spin and let's go ahead and give it a class name of padding6 flex-items-center and justify-center. Great, and now inside of here I want to go ahead and actually render the images. But just before we do that, let's go ahead and let's add one more state here. So const selectedImageId and setSelectedImageId is going to be useState null.
And let's also extract the pending state from useFormStatus because this component is also gonna be used exclusively inside of forms. So we can do this and then we can, we didn't have to pass in any outside loading state. Instead we can use this pending one, regardless if this is going to be an input or not. Right. Great.
So now let's go inside of the form picker itself and let's give this div a class name of relative. Let's go ahead and open up a div with a class name of grid, grid dash calls dash three and gap dash two and margin bottom of two. Let's go ahead and let's do images.map Let's get the individual image here and let's create a div with a class name and let's actually collapse it like this. Oops. So this is gonna be dynamics at CN from add slash libutils like this.
And let's go ahead and add the cursor-pointer, relative, aspect-video, group, hover opacity-75, transition and BG muted and then let's add the dynamic if it's pending in that case opacity is 50, hover opacity 50 as well and cursor is auto and let's give this a key of image.id and then let's go ahead and give it an on click option. If it's pending, break the function. Otherwise, set selected image ID to be image.id. So we show the user which image they selected. Perfect.
So now, let's go ahead and let's try this out. So inside of here we're going to render an image component from next slash image so just make sure you add next slash image import here. It is a self-closing tag. And let's go ahead and give it a property fill. Let's go ahead and give an alt of unsplash image.
Let's give a class name of object dash cover and a rounded small. And let's give it a source of image.urls.thumb like this and if you save you should get a little error so let's go ahead and try this out now so I'm going to refresh I'm going to click here and there we go we have an error because it's trying to load an unsplash image but we don't have the hostname images.unsplash configured. So the same thing that happened when we tried to render organization image from clerk. So go back to next.config.js and go ahead and create a new remote pattern. So the protocol is HTTPS and hostname is images.unsplash.com and what I recommend you do is actually restart your entire application.
So let me just do that. I'm going to close this and let's do npm run dev again. Let's refresh the entire application here. Let's just wait a second for this to initialize. All right, and when I click here, there we go.
Look at all of these beautiful images and we have a nice little indicator that we are hovering over them and that we can click on them. But let's take a look at our requests here. So I'm gonna refresh and there we go. You can see that I already used six requests from this hour right so while you're developing I think like 50 is probably something you're going to surpass so this is what I'm going to show you to do next So I want to create a fallback right in case something goes wrong with the Unsplash API I want to have like a constant of nine images which can always be used even if we run out of our requests in an hour. So two options for that.
You can go inside of my github, you can go inside of constants here and select images and you can just copy this entire images here. You can see there's nine of them and it's a bunch of objects and all the kinds of information that we need to render them or you can create your own version of that so for that you're gonna go ahead well let's let's just prepare that first so let's go ahead and create I think we have the constants folder do we we have the config folder we don't have the constants okay so let's go ahead and create the constants folder and inside let's create images dot VS like that and let's write export default images sorry export const default images like that and let's just make it an empty array for now and now what I want to do is I want to open my inspect element and I want to open my network tab and in here when I click we should be seeing wait let me just try and click all here. There we go. Okay. So you shouldn't be seeing, well, an API request which starts with random.
So you can go ahead and search for random inside of here. And there we go. You should see the images here. So go ahead and click on copy and I'm not sure which one it is. I think it's copy response.
Let's try that. So I'm gonna go ahead and try and copy the response from that and paste it here. I don't think it's that one. It doesn't seem formatted. Perhaps we can just copy it like directly from here.
Just copy everything. Let's try that. So this is how I did it. I just want to show you how you can do it yourself. There we go.
That's it. Now it works. So again, you can either, if you don't want to do this, like there's no need, this is, you know, just cosmetics. You can just go inside of my GitHub and go into constantimages.ts and just copy them. So this is also going to save you if for any reason you didn't manage to set up the Unsplash API.
And this is what we're gonna do next. So we're gonna make sure that we use this default images. So let's go back inside of our form picker component. And now Let's go ahead and let's import those default images right here at the bottom. So let's write import default images from constant images like that.
And what I want to do is inside of this error, if something went wrong, let's just set the images to be those default images or you can also add them to be inside of the well the initial like array of images right so let's try if that's working so when I click here well it seems to still be working Let's try and kind of break this function. I don't know. I'm going to go ahead and I'm just going to throw an error already. So something like that, Let's say something went wrong in our API. When I click here, there we go.
Something went wrong, but you can see that every time I open, I have the same set of images and that's enough for us to work with. Perfect. So what we have to do Next is obliged by API unsplash guidelines. And they say that we need to hotlink to the original creator. So we're gonna do it the same way Trello does and that's by creating a little black bar at the bottom of each image so we can see exactly who created it.
So let's go ahead and let's find where our image component is, it's right here. And let's go to the bottom and let's add a link component from next slash link. So just make sure you add this import. And let's go ahead and let's give it an href to be image.links.html like that and I'm just actually going to collapse all the props we're going to have inside so besides the href we're also going to have a target, which is just going to be underscore blank. And then we're going to have a class name of Opacity 0, group-hover, Opacity 100, absolute, bottom-0, w-full.
Then we're going to have some very small text of 10 pixels truncate so if name is too long it doesn't overflow text dash white hover underline and padding 1 and bg black slash 10 like that. Perfect. So let's just go ahead and confirm that for this group hover we actually added a group element with it. Great. So now whenever we hover on this top div this thing is going to become visible.
Perfect and inside of this link let's go ahead and run their image.user.name. So let's try that out now and we can actually remove this error so make sure you remove this error from fetch images. Great and let's try it out now when I click here. There we go. You can see how we have a nice little hotlink to the actual user.
I just feel like this can be a bit darker this background right because it's barely visible here for example. So let's just see if we did that correctly. So I'm gonna go all the way here so BG Black 10. How about we do BG Black 50 for example. Is that better?
There we go yeah that looks better. So it has kind of a darker background. And if you click on here, it's going to hotlink you to the original creator of that image. So this is what we need to do to oblige by Unsplash API guidelines. If you ever want to, you know, click on this apply for production thing you of course have to read the entire guidelines right so you can see everything you need to do here perfect so now that we have that let's actually create the functionality that when we click on an image it shows that it is selected.
So I think we already have that. Yeah we have the onClick and we set the selectedImageId but we don't have any indicator that that is true. So above this image let's go ahead and write if selected image ID is equal to the current image ID Which is in the array in that case Let's go ahead and let's render a div with a class name of absolute inset Y 0 H fool W fool BG black 30 blacks items center and justify center and inside I just want to render a check icon from Lucid React so make sure you import the check alongside loader2 here and let's give it a class name of h-4 w-4 and text-white and let's try that out now so I'm going to refresh and when I select an image it should be selected but it is not. Let's see and debug why that is not happening here. So let's see set selected image ID image ID.
Let's go ahead and debug why this is happening. So set selected image ID. Let's first see if this is working. This on clicks, the console log image ID selected image. Let's try that first.
So I'm going to go ahead inside of my inspect element and I click here. Okay, it obviously sends the correct ID and it definitely sets it in the store here. And then in here we check if that ID is matching but for some reason it's not showing. Oh, I think it's because I have to put this below the image component. I think it's because of that.
Let's try if this is then going to work. Yes, that's it. As you can see now when I click on something it shows a little check icon in the middle. Perfect! So that is what we needed and now I can remove...
Did I remove the console log? I did. Great! What I want to do now is I want to add the errors component because remember we are also passing in the errors in this so let's go all the way to the bottom just by the end of this div and render the form errors component from ./.formerrors so just make sure you import that it from ./.formerrors here and let's go ahead and let's pass in the ID to be image and errors, errors like that. Great and now I have to add a little hidden input here which is actually going to trigger when we select a specific image.
So for that I'm going to use the input type radio. So just write a native HTML input element and give it a type of radio like that and now let's go ahead and give it an id of id a name of id and let's give it a class name of hidden let's also go ahead and write checked to be selected image ID to be image.id whoops let's go ahead and give it on change to well just be an empty actually I don't think we even need to pass on change. And let's give it a disabled prop of pending. And we also need to give it a value of the image which we select, right? So let's take a look at our network request.
For that, I can just go here. Let's take a look at everything we have. So we're not going to be working with any of this stuff. What we're going to save in the database actually is just the actual... Let me try and find...
Actual URLs. So We have raw, full, regular, small. So these are our sizes, right? So we're gonna save a couple of them and we're gonna do it in this way. So in this input type radio give it a value of and open up backticks.
So first it's gonna have the image ID and then we're gonna add a pipe and we're gonna open this template literal again and write image that URLs the thumb open a pipe again and then we're gonna have image that URLs dot fool Then open a pipe again and then we're going to write image.links.html and then image.user.name. That's going to be the last one. So Let me zoom out so you can see this in one line. So value is image.id, pipe, imageURLs.thumb, pipe, imageURLs.full, pipe, imageURLs, sorry, imageLinks.html, pipe, and imageUsername. So these pipes are important because in the server action we're gonna extract them, we're going to split this string by pipes and then each item in the array is going to represent the ID, the thumbnail, the full image, the HTML link to link to that image and the user that created that image.
So make sure you do it exactly like this. If you have a feeling you did something wrong, you can always visit my GitHub, find the form picker component and just copy the value which I assigned to this hidden radio right here. Great, so let me refresh now. Let's just confirm that nothing weird is happening. Great, so why did we have to add that input?
So now when we submit our form we're gonna have access to the ID of that image inside of our form data so let's go ahead and let's go back inside of the form popover so let's go into form popover here in here we have the onSubmit and we extract the title so now let's write const image to be formData.getImage as string and let's console.log the image and I'm gonna comment out the execute function for now so it doesn't mess with anything else so I'm gonna open my terminal now and when I select an image and click create there we go you can see my object that image is a string which has a lot of URLs and each of them is separated by a pipe so that's exactly what we wanted to achieve. Perfect. So why is this working? How do we know that we can access the selected image inside of formdata.getImage? So if you take a look at our form picker here we pass the id to be image and inside of our form picker we assign that ID and that name to this hidden input component which is checked when the selected image ID matches and we do that by clicking on the outer div.
So that's why this is working. Perfect! So now what we have to do is we have to modify our schema Prisma to actually accept all of these values. So let's go back inside of our Prisma schema. So where is it?
Prisma schema. And let's now modify this board a bit so besides the ID it's also going to have organization ID and that is going to be a string it's going to have the title and then it's going to have image ID, which is a string. And let's just write lowercase d image thumb URL, which is a string and db.text. So it can accept a very long amount of characters, then image full URL, also a string and db.text. Then we're going to have image username string and db.text as well and image link HTML to be string db.text as well.
And while we are here, let's also add the created at, the date time and default now. And let's also add the updated at, the date time at updated at. Perfect. And this is what I like to do is I like to keep all of those aligned the same. Right.
Just a small little cosmetic here. Okay. Great. And now we have to update our schema and let's go ahead inside of our terminal here and first I want to show you how to reset your entire database so just make sure you're doing this in development you know make sure you're not doing this in production database, even though you should have necessary systems in place to prevent this from even attempting to happen. So let's write npx prisma migrate reset.
So this will reset the entire database. And as you can see, it's asking us if we are sure so just go ahead and press y and now it's going to reset the entire database. So the reason I'm resetting the database is because we have a bunch of those boards created from before which don't have all the necessary data which we need and now we can write npx prisma db push and then that is going to assign this new schema prisma to your mysql database and let's also just in case run npx prisma generate so we locally have all of those new types. Perfect! So now we are ready to actually save that in the database.
So now we have to modify our schema a bit but not Prisma schema but the action schema for create board. So go inside of schema.cs here and alongside title we also need to validate the image to be a string and let's give it a required error of image is required and let's also give it an invalid type error of image is required as well. Great and now let's get back inside of index.vs and you can see we already have some errors here because our requirements to create a board have changed. So first things first besides user ID I also want to extract the current organization ID and we will not proceed further if that is not available as well as the user ID. Great and now besides extracting title from the data we're also going to extract the image from the data and then let's go ahead and let's write const open up an array like this and write image.split and we're going to split by that pipe element because inside of our form picker as you can see in the value here we separate all of these items by a pipe element.
So let's go ahead and now extract all of them. So they're going to appear in the array by the order they are in. So first we have the id, then the thumbnail, then the full, then the html link and then the username. So let's go ahead and extract the image ID, the image thumb URL, then let's extract image full URL, then image username, then image link HTML. Actually, I think it goes link and then user so let's just reverse this too so first link and then user perfect and now let's go ahead and let's write if there is no image id or if there is no image thumb url or if there is no image full url or if there is no image thumb URL or if there is no image full URL or if there is no image username or if there is no image link HTML.
In case any of those is missing return an error missing fields, failed to create board. Alright and now let's go ahead and let's modify our DB board create here. So we're gonna pass in the title, the organization ID, the image ID, the image thumb URL, the image full URL, image username and image link HTML. And let's just see if we have this image link HTML it's right here and it seems like our has a little error here could be because it's differently stored in the schema Let me just revisit my Prisma schema here. So image link HTML, this should be available here.
So image link HTML, can I just pass it like this? Oh, so I think we extracted it incorrectly. Oh yes, we extracted it with lowercase TML. So let's just remap this to be HTML in capitals and then also modify the if clause to use that and then we can safely use it here. Perfect.
And I think that should be it. Yes, that looks like it's good enough and what I want to do next is well I want to help you out a bit just in case you know this is a bit complicated what we're doing with the images so go ahead and just add a console log here and I just want you to copy this array and paste it here and I just want you to confirm that you have all of this here and actually change it from array to be an object. It's gonna be easier for you to notice if something is missing. So let's go ahead and try this out now. I'm gonna go ahead and open my terminal here and make sure you do npm run dev because we updated our Prisma schema.
So let's go ahead and try this now so once I select an image and write test and click create is there an error happening? Let's see. Why is it not working? Whoa. Let's try test form popover.
Okay. Let's debug. Let's go back inside of our form popover component where we called, oh, it's because I logged out the execute. Okay. So yeah, and passing the image here as well.
So I forgot to log out the execute. Okay. Let's try it out again. So I'm gonna prepare my terminal here just because I want to see my image selection. So when I click create, there we go.
Confirm that you have image username, confirm that you have image link HTML, full URL, thumb URL on image ID. None of this should be undefined. If some of these are undefined, I highly advise you that you double check that your form picker is exactly as mine is with these pipes right here and inside of the index right here. Confirm that you don't have any misspells in the name here and that you're using them properly throughout this if clause and as well as here but I mean you're obviously going to get some errors if anything is missing. Perfect!
So we can remove this console log and mine was successfully created. I don't know if you saw the message. So I'm gonna go ahead and just run npx prisma studio. So we can actually see this in the database and there we go. Look at my board right here.
I have an id, I have an organization id, the title, image ID, image thumbnail, full URL, username of the author, the hotlink and we have created that and updated that. Perfect. So this is what we needed to do to finish up our form popover. So just two more things that I want to do here. First, I want to go back inside of my components, form, form submit and I'm going to change this variant in the props to be a default primary Like this, so I want it to be this bluish color.
The second thing I want is that when we successfully create a board I want this popover to close. So let's go ahead and do that now. We have to go back inside of our Form popover here and we have to create a ref Called close ref. So let's go above this use action and add const close ref to be use ref from React make sure you add the useRef here I'm just gonna move it here to the top all right and let's go ahead and give it an elementRef which you can also import from React. Open pointy brackets and inside just write a type of button and by default it is going to be null.
And now we have to assign this close ref to the popover close so find the popover close right here it's wrapping our button and give it a ref of close ref and then here on success what we're going to do after we well we no longer need this console logs right. We now have the toaster and what I want to do here now is do close ref.current?click Like that. Perfect. So now if you try it out and click here. After it's success, there we go.
You can see the board now closes. Perfect. And I want to add one more thing. I want to add the router here. So const router, use router from next slash navigation.
Let me show you where I imported that I'm just gonna move it here with the big imports and then I wanna do router.push slash board data.id like this, So make sure it's board and not boards. And I think this is incorrect because my data, oh, my data is unknown. Oh, that's not good. Let's try that out. Let's go inside of actions, create board and let's look at...
Oh yeah we have some errors it seems. Okay let's take a look at types here. Board... Why is there an error here? All right, all I had to do is press command shift P or control shift P and then type reload window here.
It looks like that our types were a bit outdated. Because this board which we use you can see now has all of these new items but looks like something happened with Visual Studio Code IntelliSense and since we updated our Prisma schema it didn't really sync up. Right now there are no errors here and in my form popover you can see that it says that data ID is a string. Perfect. So let's just try this out as well.
Now it should redirect me to a 404 page. There we go. So it goes to slash board and then that board id which is a 404 page and last thing we have to do is just enable that to be opened when we click on the create button here at the top. So let's go inside of the navbar component. So let me just close everything here.
And let's go inside of app folder, platform, dashboard, components, navbar. And in here, let's go ahead and let's import Form Popover from Components -> Form -> Form Popover and go ahead and wrap the button inside of Form Popover like this and Let's go ahead and let's give it an align off start. Let's give it a side off bottom and let's give it a side offset of 18. So make sure you are on desktop mode. So have this big create button and there we go.
You can see how now we can do it from here as well as from here. And we also have to wrap this mobile version into a form popover. And for that one, we don't need to do any additional stuff. So now make sure you are on mobile mode and there we go it's right here, perfect great, so you just wrapped up creating the form popover component which is used to create boards and redirect users to that board. What we're gonna do next is render all of our active boards here inside of that grid.
Great, great job.