Now that we have the app type for our API, let's go ahead and follow the guide for RPC a bit further, right here, to connect it to the client side. So, we're gonna do the following. Let's go ahead and go inside of source, lib, and let's create a new file called hono.ts. Let's go ahead and let's import hc from hono slash client and let's import app type from at slash app api catch all and then the route itself. And then let's export const, let me just add a space here, export const client, hc, pass in the type to be app type and inside of here we have to pass in our full URL.
So let me show you an example right here. You can see how they pass the full URL. So the relative path will not work. If you just go ahead and add slash API, this will not work. So what we have to do to make this dynamic is go ahead and create the environment file.
So let's just save this as it is for now and then in the root of your project create a dot environment dot local file and add a next public app URL which will be HTTP localhost 3000 without the slash at the end. So just ensure that it is the one where your app is running. You can take a look at that in the terminal. So this URL right here and then you will change this later in production. And now you can go ahead and use that here.
So process.environment and I recommend that you copy and paste so you don't do any misspellings. And to fix the error simply put an exclamation point at the end here. There we go. So now we have our client util ready to be connected to React query. So that is the second step.
Let's go ahead and let's install react query. So we're going to be using the 10 stack query, of course, and let's go ahead and install it first. So just make sure you're always at the latest version. Let me just expand this a bit. So in here, just ensure you're on the latest version.
Let's go ahead and let's click install and let's follow the instructions. So I'm using BUN so I'm going to be using this option. If you're using any of the above, of course, use those. So let's go ahead and add that inside of our terminal. There we go.
Bun add 10 stack react query. And now what we have to do is we have to create a provider for our React query because we are using Next.js. So I just think I clicked on an example here. So I think you can search for advanced server rendering right here. And then inside of here, you can see server components and Next.js app router and inside of here you have the initial setup right here.
If you can't find it don't worry you can simply find the file which I'm going to create right now and then just copy it. So I'm going to go ahead and go inside of my global components. So I'm gonna close everything and go inside of source components and I'm gonna create the query provider.tsx like this. Let's go inside of the query provider and let's copy this entire thing. Again, if you can't find this documentation, but as I said, let me actually show you how it looks in the sidebar here.
Perhaps that's going to be more useful for you. There we go. So it's right here. Advanced server rendering. That's the one you have to find under guides and concepts right here.
And then inside of here you're going to have the initial setup. So go ahead and copy the initial setup and paste it here. And now we're just gonna go ahead and do some slight modifications here. But again, if you just, if you want to, you can simply find this file in the source code and copy it. So first of all, we mark this as used client.
That's completely fine. The only thing I'm gonna add here is create an interface, which I'm going to call query provider props. Children will be react.reactnode. And then go ahead and assign the props here. So we get rid of the TypeScript error and I will change this to not be a default export instead it's going to be a query provider like that perfect and I'm just gonna add some semicolons here there we go So now what we have to do is we have to add this to our application Let's go ahead and do the following We're gonna go and go inside of source components create a new file providers.vsx let's mark this as use client and let's import the query provider which we've just created from ./.queryprovider I'm gonna change this to use the components prefix.
Let's create an interface providers props, which is only going to accept the children. So using this method we can mark this entire component as use client but the children can be server components. And let's export const providers which will accept the providers props here. Let's get the children and then let's return oops, I used the wrong bracket so let's return query provider here and simply pass in the children there we go so now we have to go inside of our root layout so let's go inside of the app folder layout right here and let's go ahead and let's import the providers from components providers and then we are simply going to wrap our children in the providers right here. So this way we created a single component where we are going to nest all of the providers which we are going to need in this project.
And you don't have to worry about this being marked as use client. So this will not convert your entire app to client components. This is a method where you inject server components as children inside a client component and this is the recommended way of doing it from the Next.js documentation itself. Great! So now if in your app layout you have providers and if in your providers you have the query provider and your query provider is as defined in my component right here you are ready to use React Query.
So let's go ahead and let's create the first feature here which I'm going to call images and inside of here we're going to go ahead and we are going to actually create the API folder and inside we'll get use getimages.ts like this. But before we can do that we first have to actually create our API route. So let's go back inside of the API route and let's remove the user. We are not going to need the user itself. So let's go back inside of here.
Let's remove this import and we are not chaining this. Instead we're gonna have the images and we're gonna work with images. So let's go ahead and import images from our non-existent images and then inside of here instead create images.ts let's import Hono from Hono const app is new Hono and remember we chain like this so this will be asynchronous and inside of here let's return c.json images which will be an array. I'm gonna make a convention where I'm only going to return stuff inside of a data object. So my API will have the following structure.
I'm always going to return things inside of data like this. Now let's export default app right here. Then inside of route we can now import images. Okay let's try again. There we go.
Images. And now we can add our route images. There we go. Let's just hover over the app type to see if it's here. There we go.
Slash API slash images now has a get. Perfect. And now we can go back inside of our features, images, API, use get images. And inside of here, first things first, let's go ahead and let's import use query from Tansnac React query. And then let's go ahead and add the client from libhono.
So this is our client which has the whole app type right so this client will be able to do stuff like client.api.images.get for example right so this is how we're gonna write our api routes this is our rpc and then in the use get images you will export const use get images you will get the query so use query and let's add a query key here to just the images. And query function will be an asynchronous method right here which will do the following so inside of here we're going to get a response which will be await client dot api images get get comes with a prefix of a dollar sign right here and then remember this is not Axios so you can't wrap this inside of try and catch that will not handle the errors instead you have to check if you did not get an okay from the response and then simply throw a new error here, failed to fetch images. There we go. Otherwise, we can go ahead and destructure the data by using await response.json and simply return the data. There we go.
And remember to return the query in the end. Perfect. So we have our use get images hook ready here and I'm going to leave it as it is for a moment and now I want us to create the image sidebar. So if you don't remember our image sidebar is used in the editor. So let me go inside of the editor one two three here just something so we load the editor.
And in the sidebar here we have the options for text and shape, right? And now we're going to enable this one for the image. So first things first, I want you to go inside of source, inside of features, editor, components and in here you should just have your normal sidebar. So confirm that you have a sidebar item image which uses the active tool images and uses on change active tool images. And then let's go ahead and let's copy from features editor components.
How about we copy the font sidebar? Let me just find it. There we go. Font sidebar. I will copy and paste it and rename this to image sidebar.
Make sure you're inside of this component, close everything else and then rename the three instances of font sidebar to image sidebar. First things first, let's go ahead and let's change the active tool to check for images so that's when this will be available and then inside of the div scroll area you can actually remove the entire font iteration so just have an empty div here And then change this to images and let's change the description to be add images to your canvas. Then we can remove this value right here. We are not going to need it and you can remove the button and you can remove the fonts import like that. So just a completely empty image sidebar.
And then go inside of the editor component itself, you can copy and paste the font sidebar and change this to image sidebar and this will be the image sidebar. There we go. And then find where you last added your font sidebar and you can copy and paste it and change this to be image sidebar. And there we go. So now if you try this, let me zoom out a tiny bit.
If you click on image, it opens the images sidebar. Perfect. And now we're going to go ahead and add our newly created query. So let's go ahead and go inside of the use get image right here. So we're going to call this query in order to load a list of our images here.
So let's go ahead and add it. I'm going to add an import here. Use get images from features images API use get images and then inside of the image sidebar we're gonna be able to add it here use get images and we are gonna be able to extract the data is loading and is error there we go So right now if you try this it's actually you know you won't be able to iterate over anything because inside of our images we simply return an empty array right But what we can do already is we can do some... We can create the loading states, right? The loading and the error state.
So how about we try that? So outside of the scroll area here let's add if is loading in that case create a div here with the class name flex items center justify center and flex one and then inside of this div let's add a loader option from Lucid React. So just make sure you've added the import. I'm gonna move this all the way to the top because this is a global import. And then I'm gonna go ahead and give the loader a class name of size four, text muted foreground and animate spin.
There we go. And let's go ahead and try and see if we can render this now. So I'm going to refresh this. I'm going to click on image and it doesn't actually take any time to load so perhaps that's why we can't see it. So let's try and hard code this to true and there we go.
So this is how the loader will look like while the images from Unsplash are loading right. So it's going to be this little loader. Perfect. So we know the style is working so we can change this back to is loading. And now I want to create an equivalent error message.
So copy and paste this and change this one to be is error. Like this. So instead of the loader here we're gonna use the alert triangle from Lucid React so just make sure you've added this import as well and remove the animate spin. So just a self-closing tag like this without the animate spin and below this we can add a paragraph failed to fetch images with a class name text muted foreground and text extra small there we go Let's change this to true as well so we can see it. Alright, so it looks like we do have to make some changes and this will be changing this to flex column and gap y 4.
Like this. There we go. So this is how it will look like. Failed to fetch images in case that happens. I'm going to change this back to isError now.
Great! And the last thing we're going to have to do here is the actual iteration over our data if it exists. The problem is I don't want to build that at the moment because our images are returning an empty array meaning that we won't be able to see what we are developing. So what I want to do is I want to focus on this images group of routes and let's actually connect to Unsplash and render the images. So let's head on to Google and search for Unsplash API.
Go ahead and find the first result, log in and go ahead and go inside of your apps here. As you can see, I've used this for two of my other projects, my Trello tutorial. So let's go ahead and click on new application right here and let's go ahead and apply to all of these here. Accept the terms. I'm going to give my application a name of imageAI and the description will be imageAI image generation.
Let's click on create application right here. And inside of here they give you a form to apply for production. You do not have to do that here at all. Instead, you can go ahead and just get your keys. So you can go ahead and get apply for production once you are finished, right?
But you don't have to do that for development. Of course, don't share your access or your secret keys. I'm going to remove this project after this is uploaded. So this is just for showing you how to do it here. So let's go ahead and add these two values to our project.
Let's revisit our .environment.local right here and let's add Next public unsplash access key. So that's the one we need. I'm just going to separate these things. So let's copy the access key and let's paste it here. Of course, make sure it is in one line like this.
Now that we have the next public Unsplash access key let's go ahead and let's create a util similar to our Hono util. So I'm gonna go inside of source lib and I'm going to create the unsplash TS and we have to add a package called unsplash dash JS so bun add unsplash dash JS let's run our app again and let me just quickly see whether we have so we are importing create API from Unsplash.js and we do have the types perfect and then just export const Unsplash to be create API access key will be process.environment and then simply copy what you've named your Unsplash access key here. There we go. And you have to define how it will be fetched. So I want to use the native fetch to fetch this.
There we go. Now that we have the Unsplash util, we can go inside of our images route right here and we can now create the proper functionality. I'm first going to define two variables here. So the default count, so that is how many images we are going to load. I'm going to keep that at 50.
And then the default collection IDs. So you can change this if you want to. So IDs are basically albums which exist in Unsplash. And I found this one to be quite nice. So 317099, you can use this exact number and that will be the album for the Unsplash images.
You can of course, you know, Google Unsplash collection IDs and just pick any album that you like from there. And now what we're going to do is the following. So inside of here, we're going to get the images using a weight unsplash from lib unsplash. Let me just separate these two like that. Unsplash.photos.getRandom.
Collection IDs will be the default collection IDs and the count will be the default count. There we go. If images.errors occurs, in that case, I'm going to return c.json and I'm going to write my custom error here, something went wrong with a 400 here. Wrong with a 400 here. Like this.
And then since the response of the images can be either a single item or an array of items I want to standardize that so let's add let response will be images dot response and then we're going to check if it's not an array let's turn it into an array like that and then we can safely just pass in the data response there we go So now if you hover over your app here, you can see your data will be an array of all of these information for each image, which we are going to have here. So we can already check this in the inspect element. So you can see whether you've done this correctly or not. So let's go ahead and go... My apologies, not here.
Back inside of localhost here. And I'm gonna go ahead inside of my network. And I'm gonna focus on the images search. So I will open the image sidebar and let me just refresh this. Make sure to refresh your app if you've shut it down to install a package like I did.
And there we go. You can see that the images are a 200 successful request and you can see my response. Data full of images here and we're now going to be using the URLs here to display them inside of here and then insert them. So that is our next step. So just make sure that this is how you've named your unsplash access key otherwise it's not going to work if it doesn't have the next public prefix because the package needs it to have the next public prefix right here.
Great! So now let's go ahead and let's go back inside of the image sidebar and now we can go ahead and actually iterate over our data because that's what it will be. So let's go ahead and write if data and data.map like this. So if we have data, let's iterate over the data. We're gonna go ahead and get the image.
And then Let's go ahead and return a button. So a native button element. Let's give it a key of image.id. Let's go ahead and give it a class name, which will be empty for now, but just so we can remember, let's add a self-closing image. The image comes from the next image package.
So let me just add that to the top here. There we go. Make sure you've added the image. The image itself will have the fill property, the source will be image URL small, the alt will be image.altDescription, the class name will be object-cover. And for this you can go ahead and add a pipe pipe or simply write image in case there is no description here great so now let's go ahead and give this button yes we are now getting errors here, don't worry, we're gonna fix that in a second.
Let's go ahead and give this a class name of relative, full width, a height of 100 pixels, group, hover, opacity 75, transition, background muted, rounded small, overflow hidden, and border like this. And now let's get rid of this error right here. First of all, let's read the error. So as you can see right here it says that it is an invalid source prop. It's trying to read from the images.unsplash.com domain but it's not configured in our next.config.js.
So let's go ahead and configure it. I'm gonna go ahead and find the next.config.mjs in my case right here and let's go ahead and let's add images, remote patterns, protocol, HTTPS, hostname, images.unsplash.com. Https hostname images.unsplash.com so basically you need to write inside of here exactly what is errored here so images.unsplash.com Save this and I think you can just refresh. Let's see if it's going to work. If it doesn't work you can shut down the app and run it again but I think this should be sufficient.
So if I click on images now you can see how it's loading and there we go. We now have a list of images! Perfect! Let's go ahead and develop it further. So Unsplash API actually has a rule which you have to follow and that is that you have to display the artist's name whenever you show the image.
So let's go ahead and add that feature here. So I'm gonna go ahead and below the image, I'm gonna add a link from next slash link. Where is my link? There we go right here. Let me fix this.
So import link from next link and let me fix the capitalization. There we go. It's not going to be a self-closing tag, my apologies. Instead, this is what it's going to render. So the href for the link will be image.links.html and the link itself will display image username, so the name of the author.
The target for this will be underscore blank so it opens in the new tab and now let's go ahead and give it a class name so I don't want this to always be displayed only when we hover on the image let's do it like that because that's how other websites have done it so I assume that is the correct way to do it. By default it's going to be invisible but when we hover on the group so we defined our whole button here as a group so when we hover on the button on the parent element Then we're going to change the opacity to 100. That's the first thing. Then let's also give this an absolute rule with left zero, bottom zero, full width, text, 10 pixels, truncate, text, white, hover, so when we hover on this specifically we're going to add the underline to indicate it's a link, a padding of 1, background of black with a 50% opacity and the text assigned to the left side. Aligned to the left side.
And there we go. So now we can access the author of this image and when we click on it in a new tab it's going to open the original source. So this obliges by the unsplash rules. Great! And now let's go ahead and change this div right here to not be just you know columns one below another instead a grid.
So we're gonna do the following. We're gonna keep this padding 4 here and should we keep the padding 4? Yes, let's just keep the padding 4 here like this and then in here let's add a div with the class name grid columns2 and gap4 And encapsulate the entire thing like this. So let's see whether this improves it. And there we go.
So you can see how now this becomes a proper grid like this. Perfect! Now what we have to do is an onClick method. So when we click on the image itself I expect it to be added to our canvas. So let's go inside of our useEditor hook and inside of here we're gonna add a new method specifically inside of the build editor here.
So above our last method which we've added which is delete add add image which is a method which accepts the url which is a string we can mark this as value and then we're gonna go ahead and do fabric dot image from url In the first argument, pass the value, which is the URL. And then we're going to get the actual image in the callback like this. Let's get the workspace using getWorkspace method. What we're gonna do here is we're going to scale the image so image scale to width will be workspace dot width or zero looks this is a method my apologies. So workspace width or zero, copy and paste this, scale to height, and then add to canvas the image.
So the image is the object, right? And then we have to add the options here. These are important. So cross origin will be anonymous. There we go.
So this part is very important. Make sure you've added the cross origin anonymous. Otherwise you won't be, Fabric won't be able to load the actual image. Perfect. Now that we have this, so let's go inside of the types here to the interface of the editor, add image, accepts a value which is a string and returns a void.
Let's go back inside of the image sidebar. We have this button which does not have an on click. So let's go ahead and add it here. On click, and inside of here, we're gonna call editor, add image, and pass in image URLs regular. So we are using small to display it here but we are going to load it in a larger size.
Let's try it out now. I'm gonna go ahead and refresh this and I'm gonna find an image that I like and there we go. You can see how it's scaled to our height right here. Perfect. And we now have images inside of our project.
Amazing. You can go ahead and play around with these. Just keep in mind that in development mode, you only have 50 requests of the API per hour. So don't spam this too much, but don't worry. Even if you run out of your requests, the next thing we're gonna do is we're going to implement uploading an image.
Great, go ahead and play around with this and admire the nice work you've created. This is starting to turn into a real editor. So after we implement the next features which are going to be the upload functionality, we're going to change our toolbar so that it accepts some specific image only features like image filters or AI image removal. Great, great job!