Photo Album Next.js Tutorial (with Server Actions, Cloudinary, Shadcn)

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
all right everyone I have a really cool video tutorial for you all where we're going to be trying to build out this photo album using next.js and Cloud Nary so I'm going to walk you through everything that we're going to build in this tutorial I do think there's a lot of good information here that you can learn if you watch this video first thing is we have this Gallery right so you have all your images here and if I wanted to add a new image you can go over here and click this upload button and since we are using cloudinary for the image upload they have a nice widget that allows you to upload from your desktop you can actually upload from various URLs and all these other Integrations I'm going to go ahead and just upload something from unsplash and let's just go ahead and find like a horse or something okay that one looks pretty good let's just go ahead and upload that and after you do that you'll notice that the horse does pop up in your gallery something cool about the uploading though is that cloudery has built-in add-ons where when you upload an image it's going to run that through artificial intelligence in tag various objects that it sees in the upload so for example we just uploaded an image of a horse if I were to type in horse here notice that we can filter by that tag and this was automatically figured out when I uploaded the image right so I also have some cats that were Auto tagged I think of any dog that shows up that's a pretty cool feature that's built into cloudinary as an add-on the more features that we'll have in this application is you can actually heart various images right so if I were to heart you know the top three here the dog and the cats and the horse if I go to my favorites those will now be showing up in my favorites list and all of this I will say is done without any database I'm not using any database I'm purely using cloudnery's API this more specifically is using cloudinary's tagging feature so I'm tagging these images with a favorite string which allows me to go and like filter through them with the cloudinary API pretty cool so we also have the ability to add things to albums so if I were to go over here and click on this horse I can actually click add to album we get a nice little dialog that pops up I'm going to go ahead and say Outdoors like this and click add to album And now when I go over to the outdoors album you'll see that the horse pops up over there so this is done using cloudinary's folders where basically you can add a prefix to your image ID and that'll kind of behind the scenes put in a different folder and we're just using that for albums and over here we can kind of like go through our different albums like this scuba doesn't have anything all right another cool thing I want to show you is if I click on edit that'll take us to an edit page where we have access to filter this image using a various cloudinary Transformations right so for example if I wanted to blur the image all I have to do is basically use a pre-made next component that cloudinery provides us where you can do various image Transformations with very very little effort so we got blurring we got converting to grayscale we got pixelate we have removing the background which probably wouldn't work very well on this image it'd probably work better on a different one so I'll show you that in a second but we also have a cool generative fill right cloudinary has the ability to use AI to add objects and remove objects from images and also we have the ability to add in more stuff to the left and the right if we wanted to so for example if we wanted to add in a shark to this image let's just go ahead and try doing that notice I took the image it's expanded the width of the image and it started to add in various darks I will say that the generative fill is beta so it's not going to work perfectly but it is still pretty cool that you can easily do this just by basically using a built-in component and adding a very specific keyword to have it fill out various ways I do want to demo the remove background though so let's go back to our gallery and we're going to go ahead and dry AI to find a cat image how about this and we're going to click remove background here there you have it the image with the background removed and if I were to open this in a new tab you'll see that it doesn't have any background here and that is what we're going to be building in this tutorial so before I dive into this tutorial I do want to mention that this video is sponsored by cloudinary binary has a bunch of apis you can use to basically handle your media so images and videos as you saw with my overview they have various add-ons you can set up to auto tag things that you upload them remove background to do generative fill you can do color changing and they also have a really nice documentation that kind of explains everything that you need to know with using their API they have various ways that you can interact with their service in our case we're going to be using a next plug-in which has a bunch of pre-built components so like we have an image component here we have an upload widget they also have like a video player so if you haven't heard of cloud Neri before watching this tutorial I think you'll be very impressed with all the features that they provide you with their service and and as far as Technologies outside of cloudinary we're using next.js13 using the app router with react server components and server actions and then for the components we are using Shad CN UI which provides all of these components that you're seeing while we're going to build out this application so if this is something that you think will be really interesting to watch me build and maybe you can learn something from watching it be sure to stick around other than that let's just go ahead and get started and start with a brand new project alright so we are going to be using next JS in this tutorial so let's just go ahead and get started with that down here I'm going to click on this copy link and then I'm gonna go to a vs code terminal and we're going to call this cloud and Airy photos app go ahead and get that set up I'll say yes and then we are going to be using typescript we will use eslint go in CSS yep Source directory yes app router yes and would you like to customize the Alias Imports I'll say no second thing I'm going to do is I like to always have all my code in New repositories so I'm going to go to my repositories on GitHub I'm going to create a new one and we're going to name it the same thing that we did with a project so I'll say cloud and Airy photos app and we're going to go ahead and make sure that this is MIT and I'll keep it public let's create that repository awesome and I'm going to go ahead and just click on this repo link because we're going to set it up to the code that we just set up all right so let's open up that culinary project that we just created with the next CLI tool and let's just go ahead and make sure that if we do a git status this is a git repo but if you look at the git remotes you'll notice that it's not linked to anything so I'm going to say git remote add origin and I'm going to type in that link to that repo that we just created and then I'm going to go ahead and just commit everything I'll go ahead and do a force too so that'll be over there so let's just make sure if I refresh this page we have our code that has been pushed to our repo and as we're developing on the app you guys can kind of just like go through the commits or just kind of look at the stuff when it's finished if you need to the link will be in the description of the video all right so let's make sure we can run this I'll say inpim run Dev and that should host our next JS application at Port 3000 I'm going to command click that and now we have our next app running locally awesome all right so the next thing I'm going to do is since we have a next JS project set up button area provides a bunch of helper utility widgets and components that you can use in your next JS application so they have kind of like first class support for next JS which is really good let's just go ahead and copy this npm install command and go ahead and just run it in another terminal down here and while that's installing let's move on to step two it says add the following variable to your EMB local or EnV so I'm going to go ahead and make a DOT AMD because I don't have one and we're going to copy in this environment variable so for your Cloud name you actually log in to your cloudinary dashboard and go to get started you'll see that you have this area over here that tells you different ways to basically integrate with various languages they have to react angularjs python PHP but if you look here there is a cloud name here that's hard coded to the string I'm going to copy this string I'm going to put it right here secondly we need to make sure this git ignore is set is updated so that we don't have that EnV there so I'm going to say dianv just so that we don't accidentally commit that because we might have some secret variables that we never want to get committed to our git repo all right so scrolling down there is a couple of components that they give you out of the box we are going to be focusing on just using the upload button for right now and basically we want a way to upload images to Cloud Neri wellinary like I mentioned for is a place to store all of your media files and they give you tons of different utility functions behind the scenes to do filtering on those to do image processing on those object detection machine learning AI various things so what we're going to do is if we're trying to build a nexjs application for managing our photos it makes sense to start with uploading a photo okay so let's just go ahead and import this widget and notice down here it says if you're using next 13 app directory which we are it says You must add use client directive at the top of your file so let's just go ahead and do that we're going to try to find the main index page and we're going to simplify this a little bit all the stuff we're seeing now is basically the stuff that was given to you when you spin up an xjs project but we're going to kind of reduce it to the bare minimum needed I'm going to import this and then I'm also going to say use client for right now as we progress later you might want to actually have this be a react server component and you could have smaller sub components be the client but it's kind of easier just to put use client at the top right now for this entire page so let's verify if we were to use this button somewhere let's just go ahead and copy that entire snippet let's go and paste that right here all right so if you notice here in the upload component there is an upload preset that we actually need to set so right here this is just defaulted to some replacement value let's go over to cloudnary's console and let's try to find those upload presets so down here there is a settings gear Cog go ahead and click that and if you go to upload over here you scroll down a little bit you'll see that you have a bunch of different upload presets one is called ml underscore default that's kind of the default when you set up the account but different upload presets basically allow you to specify what gets attached to your images when you upload them so for example you can like define tags and other things but for this example right now let's just go ahead and allow anyone to upload images to our cloudinary media store I will click add a preset name um and if you see here it says set to unsign to enable unsigned uploading to Cloud Neri with this upload preset I'm going to say unsigned again you probably want to do signed if you want to prevent various people from just uploading things to your your media store but we're just going to kind of do the bare minimum right now to get this working there's also a bunch of other configurations you can kind of Click through but for right now let's just use the default another cool thing you can do is as people are uploading images you can run those through machine learning algorithms to basically auto tag stuff so for example someone takes a picture of their dog you can have cloudinary attach the dog tag to your image so that later on we can kind of view that tag they have a lot of cool stuff here's another cool one so when people upload images you can actually have it invoke a web hook API endpoint if you wanted to if you wanted to like notify people when people upload images or videos that might be interesting various other things I'm not going to cover everything that they support because they do have a lot of supported features let's just go ahead and click save and we're going to go ahead and grab that reset and let's use it here in our upload widget here so I'll go ahead and save that and then going back to our app let's refresh the page to a hard refresh and I should probably have my Dev tools opened up so that nothing is getting cached okay looks good let's click upload and notice here we get a nice cloudinary widget that's built out of the box for us that we can use you can upload files from your computer you can actually specify like a URL and that'll take the image from the URL and pass it into Cloud Neri you can do camera Google Drive Drop Box Etc I don't really have too many images on my computer so what I'm going to do is do unsplash go ahead and connect to it and let's just grab this cool image of a windy Mountain Road I'll click next and I'll click small and hopefully when I click upload that will be sent to our cloudinary media store go ahead and click done well so how do we know if that actually worked right our application doesn't really have any feedback let's go back to Cloud Nary and I'm going to go to my media store over here there's a media library if you just go ahead and click this zoom out a little bit notice that that image that we just selected shows up as one of our assets right you click on it you can actually add tags to it you can view various things give it a title but the point is we've successfully allow people to upload images from their computer or from a remote data source and put that into our cloudinary media library all right so that completes like the first part is how do we allow users to upload images into Cloud Nary now let's look at some of the other components that they provide you when you're using next.js they have a cloudinary image component which you can use to point to your public IDs the display images so let's just try that out I'm going to go ahead and grab this I'm going to put this up here we're also going to grab this widget and we will put it underneath here so the public ID that is going to be given to you when you upload your image so if we go back to our asset here and click it you'll notice that this is your public ID right here you can copy it if I were to just hard code this right here that should make the image display for us in our application and there you have it but that process obviously you can't have users just go in and like get the public ID from clicking through your UI what you're going to want to do is listen for when the upload completes so that you can get that ID so let's go ahead and look at the C if there's some type of property that we can use looks like there's an on upload let's just go ahead and do this and see what information we get back going over to their on upload callback it looks like it's a function that gives you a result and a widget okay so let's look at what result is I'll go ahead and rename this as a result I'm just going to go ahead and console log this so we're going to go ahead and just play around with this and verify that if we were to upload a different image go ahead and grab this hat one and click next go ahead and click small here click upload and here we go we got an event back let's look at what we get back we have info and inside of info we have an ID notice here we have the asset ID there's also a public ID now they may have type script types provided for this um this callback but for right now I'm going to show you something you can potentially do I'm going to go ahead and make my own type right sometimes you're dealing with apis that do not provide you types and this is something that actually happens in when you're doing real life development so let's just let's just go and make a type called like upload result and we're going to go ahead and Define an event here which has a what do we have down here it has an info and an event info and I think event at least for right now we know one is success okay and then for info we're going to just go ahead and put a public ID because that's all we really care about right now you could potentially take that whole data structure and you could put that into a converter and give you the type but we're going to start as simple as possible okay and we're going to type this result here and what this allows us to do is now we can actually say dot result info dot public ID and just go ahead and print that out and we're going to verify that this works so if I were to close this and if I were to go click done let's actually try to um have some state store that public ID so we can actually show that image using the widget later on so let's just go ahead and make some State over here I'll say const image ID and I'll say set image ID let's use state right now we're going to keep it as an empty string but when the upload is complete we're actually going to call that state I'm sorry set image ID and just set that public ID and what we could do is just only display this if the ID is set like so and then we can use that inside a source here and that should be good we also changed the width and height of this stuff so if I wanted to do like 400 by 300 we can do that let's just try this we'll do upload we're going to do a different image from unsplash this diaper looks pretty cool but it minimize this a little bit I'll click next click small go ahead and click done and there you have it so we've uploaded it to Cloud Nary we got a callback which gives us some information about the uploaded asset we get the public ID which is kind of used for knowing how to get that from cloud cloudinary CDN and then we set that in state and now the image is being displayed awesome so baby steps were one step closer to having a working photo application I mean uploading is one of the main pieces of functionality in showing images is another main piece of functionality all right so at this point you might be asking why would we use the cloudinary CLD image component instead of using next built-in optimized image component and one of the main reasons is cloudnery provides a bunch of optimizations for you so as you request the image it'll automatically scale the image down and optimize it for your client but another great benefit is that if you scroll down you'll see that you have the ability to add a bunch of different filters and transformations to the image before it's sent over from cloudnary so for example if you wanted to remove the background from the image add a new background and tint something all you have to do is just add a couple of properties and you're good to go so I did want to test that out I uploaded this picture of um this dog here and I'm going to copy this public link here and I'm going to default the state just temporarily to that public ID go back to our app and you see the dog show up here and so like mentioned the documentation you can actually add a tint to this image let's go ahead and put that in here and you will get a blue green purple tint added to the image before it gets sent over from culinary right and that's kind of a cool powerful thing you can do with the images that you can actually just on the Fly do various things with it right so if you go down the configuration to tell you all the different things you can pass to the component to basically change where it focuses for example like you could pass gravity to have it focus on faces down here we could blur it a little bit if you wanted to you could blur faces so let's try out blurring faces let's upload a new image just one more and we're going to go ahead and find one of a person all right maybe this person will work I'll go ahead and upload this woman and if I just go ahead and put blur faces here notice that her face got blurred out now let's go to the tint here there you go so with little effort on my side I can start doing various image manipulations on the images that are hosted in cloudinary and it's really cool I really like it feel free to like play around with this you can say cartoonify and then you've already applied a filter to the image but I think that's a good enough demonstration of like the power behind all the different configurations if you want to go through these examples they have a bunch of cool stuff that you can do here so right now our app looks kind of basic like what we could potentially do is bring in some type of component library to help you know create headers create buttons drop downs and stuff like that one component Library I like to use is um is shad CN UI has a very minimal dark mode to it but I believe you can customize it the way you want if you need to alright so let's go to installation I'm gonna click next JS and I'm gonna go ahead and just run this command because this one I already have set up this basically sets up your project for tailwind and typescript let's copy the Shad C and UI command go ahead and run that which style would you like to use I'll say default what color would you like to use I guess we could try gray I don't think I've tried gray or zinc zinc sounds like it might be pretty cool where's your Global CSS file we have app global CSS would you like to add would you like to use CSS variables for colors yes where's your tone yep go ahead and do that we're going to configure components yeah that's fine configure the Alias I'm just gonna go ahead and do the defaults for everything I think I accidentally put the wrong folder here I'm going to copy all this and I'm going to replace Global CSS real quick go ahead and delete that all right so you might ask why do I like using Shad C and UI basically it allows you to copy and paste components into your project and you have basically full ownership over those components so you can style them and customize them to what you want I like that a little bit better over using a pre-built component library because it's usually very hard to customize component libraries such as like chakra or material UI you're kind of stuck with what they give you versus this just gives you a way to basically add in a bunch of components so for example another thing I like about this is that you can actually view all the code that they did for building out this thing so if I click view code you'll see it takes me over here I can go into the page and you can start seeing how they set up various things so for example the code for the top nav bar here I think is right here so I could potentially just grab this code I can go back to our app and inside a layout let's just go ahead and add that in to the very top of the page because we want that to exist on every page so I'm going to paste all that in and a lot of this stuff we can kind of comment down for right now maybe I'll bring it in in a second but if we do this we go back to our app you can see we do have the header now I did mess up dark mode there is something that I think I messed up on go ahead and add Source there and then I'm secondly I'm going to add class name dark here so that it actually sets it to dark mode all right let's just go ahead and test this out and say hello and then over in this Diablo State world I want to see where these get spaced out we've got hello over here we got world over here all right and now we're getting into tail and CSS stuff so like if I wanted to basically make this thing stay within a container notice how it like as you zoom out it goes all over to the far of the page that's not the greatest for larger monitors so we're actually going to wrap this thing inside of a container and I'll say MX Auto that should hopefully contain this stuff so even if with bigger monitors you'll see that it stays within like a normal Realm of the page so secondly you'll notice that the Border probably needs to be added to something else so I'm actually going to put the container down one here yeah that looks a little bit better so for hello I'm actually gonna put a logo so I'll say like photos app you can come back and add like a cool logo or something then behind my head we have the world right so I might actually switch this over to the right so you can see this so instead of putting World here if you do have authentication you may want to actually have an avatar placed up there so one thing you can do is search all the components that are available here and you see there's one called Avatar and there's code here that tells you how to do that now by default we don't have a bunch of components with shad C and UI so you actually have to go and add them so down here if you click on this you can add these in using the CLI tool go ahead and add that in real quick we're going to add in the Avatar component and once you do that you'll see that you have an avatar component pop up down here so now we have access to using the Avatar component and if we look at the code potentially just copy this now we'll place that in part of world here and just Auto Import some of the stuff make sure you import it from the right components directory there you have it you got a little Avatar so so we may not do much authentication in this video we're going to stick to just doing as much as we can just kind of playing out the cloudinary API and getting some images up there but I did want to have some type of component Library going so that our app doesn't look terrible so another thing that'd be interesting to do is on the left we could have some type of side navigation where you can manage your photos maybe you can view different albums or something like that so let's see if there is a side navigation okay so something kind of like this maybe that could be for more advanced side navigations there's also some type of side navigations like this I think this one looks pretty nice so we might actually look at how they did this let's view the code and let's go to figure that out so if we go to the components I'm guessing they have a sidebar okay and in the sidebar they have all the stuff necessary to kind of get this going and we're not going to copy all this we're just going to copy I think the bare minimum to get this going they had to discover and then they have this section with some divs let me do that let's just go ahead and grab that and we're going to go and make a component up here for right now I'll say function side menu and I will return some divs now some things are missing like CN is actually something we can import as a utility and that's used for basically dynamically applying classes to various things um technically I don't think we need it right now so let's just delete it and then we also have buttons so button is another component that we'd have to install so let's go over to components over here let's just search for button here we go and we're going to go ahead and just add it to our project by running this CLI command very similar to how we did the avatar click yes and now we have access to a button so I'm going to go ahead and Auto Import this button and then going back to our app I don't think we're rendering anywhere right so let's just go ahead and make sure we render out this side menu somewhere I'm going to go ahead and do that probably inside I'm gonna go ahead and just do that right here side menu and then over here we can actually just have like different pages show up we got a side menu so if you're not really familiar with how like nested layouts work in next we have this layout here and the page and all the components that are defined inside that page will be rendered inside of this children right so if we wanted the side menu to live to the left of this stuff what we kind of need to do is we need to wrap it in some type of flex box I'm going to say div and I'll do this in the inside of here I'll give this a class name and we're going to say Flex and that should hopefully move the side navigation to the left now it's so right now this side menu is it's a little bit big for my liking so I'm going to try to find a way to reduce it just a little bit that seems like a lot of empty space here so what we could potentially do is inside a side menu we can actually say width of 1 4 or something like that and that'll reduce the size if you want to do less you could do one-fifth so now that the nap bar is a little bit skinnier I'm gonna go ahead and try to rename some of this stuff because we're not really building a music app we're building a photos app right so I'm gonna go ahead and say manage instead of discover and instead of listen now I'm going to go ahead and say view images or actually we can call it gallery about that down here we can say we'll say albums maybe and then down here we'll do archive archive like so if you like delete a file or something archive it for 30 days or something maybe we can call it like deleted then down here we're going to say favorited or favorites how's that but we got some icons that don't really match the names of what we're going for so the first thing that we're going for is Gallery right so let's see if we can find like images Maybe there's photo let's just grab that so we shall go over here and we're going to put that right there okay now the second thing we're looking for is albums I'll say like folder Maybe paste in that and then finally we're going to do favorites right so I mean a heart is probably like the de facto for that so let's just grab a fart and let's paste that in here now there's obviously not enough space between the image and the text so let's just go ahead and add some space between that what we could potentially do is if you wanted to add a flex to this and a gap before I think that's one way you can add space or it might be a little bit too much so let's just do like a two make sure that looks good I think it looks pretty good and then we'll do the same thing for the other buttons here so I'll go ahead and add it there and I'll add it here all right so we're going to focus on the gallery for right now and just a place to basically show all the images you have and allow users to upload images okay so it makes sense to have maybe a title here that said like your gallery and maybe an image a button over here that says upload images so let's go and try to make a gallery page now we haven't made a nested page here inside of the app router yet but we're going to do that we'll say gallery like this and inside a gallery I'm going to go ahead and just put a page dot TSX and I'll say export default function gallery age I don't know yeah it's with an E for some reason I'm calling it Gallery with an A so let me rename that gallery and inside of here let's just go ahead and return a fragment real quick awesome so to verify if this works what we could potentially do is just go to slash gallery and verify that it shows our Gallery page and it does so on the gallery page what we're going to try to do is we're going to add that upload button and also a title so let's just go ahead and give this maybe a section like that and we're going to go ahead and add a H1 called gallery and using Tailwind we can kind of make this text be for Excel maybe we can make it bold okay and then additionally this could be some type of div it has a flex with space between so that we have the H1 on the left and then we also have an upload button on the right so let's just go ahead and find that upload button and we're going to actually copy that and put it over here right and the the logic will change a little bit for the on upload so let's just comp that off right now but we could potentially grab the upload result from this page I'm just going to export it right now but at some point we could probably like move that to a shared types file or something yeah let's look at our app okay let's see if it's gonna work I think it's breaking for right now let's figure out why it's breaking oh yeah it's it's breaking because you cannot use this upload button unless you're in a client component so again use client make sure you add that in there we go and I actually don't think it's called space between I think it's called justify justify between that'll space out all right so justify between was I think supposed to space these out I think the issue is that this thing is not full width so somewhere either in the layout or somewhere else we need to make the nested children full width so here I'll say class name W fool and that should hopefully make this full width it does and you can tell it's kind of a tight squeeze there's no padding so I might add some padding to the X in y so padding X could be four padding y would be two okay um still not that much padding I might actually say padding top to be 12 or something just to bring it down anyway that looks okay um also this button doesn't even look like a button so we should probably go and make that look like a button in some way now unfortunately I think we have to keep this as a cloudinary upload button I don't know if there's a way to make a button I think actually if we add as child to this let's actually look at this real quick yeah that's how you do it all right so basically I'm going to wrap this with a button from our buttons component I'm going to add as child and that's going to kind of swap this around so this is actually acting as the button and this is more of just a styling type of thing I believe I need to go and read the docs exactly how that works but I do believe if you don't add this um I don't know if that's going to do the same thing yeah yeah you get you get a bunch of issues with that so I'm gonna go ahead and say as child and refresh the page and notice that works fine I kind of want to put a picture of an image or an upload so let's go back to Hero icons go here I'll say upload and let's just grab this one looks pretty good inside this upload button I think what we could potentially do is just put that jsx let's see what happens if we do this okay that's gonna break that so I'm gonna put a div here and I'll actually say class name Flex gap of two that we have the icon and then we also have the button over here and I think that should fix it cool at this point this Project's still just a work in progress It's like a prototype and we're slowly starting to evolve it and let it organically look good um I need to come back and actually make these links I need to show images here I think that could potentially be the next spot that we work on is showing the actual images that we have in cloudinary here so what I'm going to do is I'm going to commit everything we have because we have made a decent amount of progress and I want to kind of get all this stuff added at some point so let's just go ahead and add it now and I'll say working on gallery page and go ahead and publish that up and I'm also going to close out of all this and I'll open back up the gallery page so we can kind of focus on that all right so now that we have the gallery page I want to actually try to fetch all the images that are uploaded to Cloud Neri when this page loads or doing some type of an effect to fetch that to achieve that they have a search API they also have an admin API that you can run some node code so for our code to actually be able to invoke this we'd probably have to run this in a react server component and pass that data down since this is more of like a dynamic thing we could probably just make an API endpoint locally and invoke that so let's try to um let's try to use this and we're going to try to converter code a little bit to use react server components so that when the page loads we can actually invoke some backend node code to fetch this data and then display that in the UI so the first thing I'm going to do is we're going to have to actually not use a used client here okay now the only reason we have a used client was because we needed this button to basically work okay so what we could do is we could make another component here like so and I can just say upload button.tsx okay now this could be our used client and we're going to go ahead and pull out right now let's pull out all the code and I'm going to reduce this all to just return a button here okay like so and I'll say upload button okay does that make sense and we're going to go ahead and use that in place of where we had it over here go ahead and delete that the reason again I'm doing this is because we need to be able to use the cloudinary next um component but it has to be inside of a client component right so if we move that to another place that allows us to use this as a react server component and we can actually fetch some data from the back end and have that passed down and render out stuff so let's make sure our app still works it seems like it's still loaded that's good let's go to the search API and we're going to try to copy this and use it and what we're going to do is we're going to convert this to an async function so that next can actually invoke some asynchronous code I'm going to go ahead and run this code right here and this is a promise so we should be able to say const results is equal to await this and we're going to go ahead and just try to fetch every image right we don't need to do tags so we don't need to do uploaded days or anything like that but we're going to do that I haven't yet installed the cloudinery package so let's go down here and I'll say npm install cloudinary and that'll allow us to use this culinary library to make requests to the cloudinary API go ahead and import that like so and we should be almost good now what we need to do is we need to set up some environment variables so if I go over to the setup I'll go to node quick start so we installed this we just did that and this is saying that we need to set this as an environment variable so I'm going to go ahead and just copy this and put it in our DOT EnV like so but we're going to have to put an API key in an API key secret and a cloud name we have the cloud name right here so we can just go ahead and paste that in but the API key in secret we do not have yet so let's go to the cloudinary dashboard and we're going to have to create an API key so again click on the gear Cog down here and let's go down to access keys I'm gonna go ahead and rename this one to ordinary photos so that I remember to delete it later on and then over here I'm going to copy the API key and we're going to put that right here and we're going to also look at the secret and copy that put it right here all right so with that added tour.enb and again I'll go and delete this after this video is made and so you guys don't try to use my account it's a free account anyway so nothing much you could do with it but if we go to the page that's trying to do this fetch request that should hopefully do the request so if I were to go ahead and log out this information our next API should batch that information you can see here we have a bunch of data that when the page first loads it is going to fetch all of that so looking at the data returned we again we just need the public ID for right now so I think we could just make a type here I'll say type I'll say and then we're going to just go and say public ID which is a string is there anything else that would potentially be useful here I don't know if we have anything here that'd be useful again we can always just like print it out and get it later but I'm going to go ahead and just type this and say as an array of search results that makes sense and what this allows us to do is when this page loads we fetch the data we're going to get 30 right now just do like five so we don't like get too many images back and then we need to Loop over them okay so let's just go ahead and say results.map and I'll get a result and we're going to return a div right now but what we should probably do is actually return that cloudinary image that we had on this page so here we can actually use this I think this might have to be a client component we'll find out in a second I'm just go ahead and render that out and we're going to go ahead and say result dot public ID and hopefully that works out pretty good and I'm guessing this is red because we probably need to provide a key here so I'll say result public ID as well um actually this is red because I put curly braces here so let's delete the curly braces that should make it happy I'm going to try to import this but no promises this may not work then I'm going to wrap this in a div so that we can actually put this in like a CSS grid I'll say grid grid calls or and I'll do a gap of like four as well looks like we want to add an ALT here so I'll just say an image of something right now we don't really have an ALT all right saying results.map is not a function so I'm wondering if maybe I need to look at what was returned it looks like it's actually this we get back resources so I actually need to do this resources is going to be that this also has a next cursor so if you need pagination or if you have like infinite scroll we could add that in but for right now let's just save that and [Music] um we're going to go ahead and say results resources.map okay so this is the error that I was afraid we're going to get the reason this isn't working is because this is actually a client only component so we're gonna have to set up another component over here I'll just call it like cloudinary image and we're just going to do this so we can say use client at the top then we're going to say export function ordinary image and we're going to cut all this out we're going to return it import that cloudinary image and this thing really only needs a public ID which will be a string I might actually just delete this technically it doesn't even need a key because we're not doing it inside of a map there we go and then obviously there should probably be some other properties that are passed in as well so I'm gonna say dot dot dot props and we're going to go ahead and say dot props just so that you can actually pass in all these other things so for right now like we just need to pass in the source essentially everything else could be passed in you know to make this easier on myself I'm actually just going to say props any and I'm going to go ahead and just do this there now here I could just say cloudinary image and I'm going to go ahead and just pass in some of those same things that we had so for example we need a key result Dot public ID we need a source result.public ID and then this will complain without an ALT so I'll say an image of something technically we should have a width and a height Maybe yeah it's saying it's missing a width and a height so let's just go add a width here I'll say 400 height could be also 300 keep it kind of standard and hopefully we get some images awesome so hopefully that wasn't too too hard to follow we basically installed the quaternary package set up a EnV configuration so we can connect using our public and private Keys we do a fetch to get the images and then we render those we had to abstract this away into a client component so that next doesn't yell at us and then we display those let's do a little bit of um of code refactoring here so this thing should be at the top but now it's getting squeezed down to the side so what we want to do is we're going to go ahead and wrap these things in its own section or div and I'm going to go ahead and cut this out and I'm going to put that here on this div like this here we go and then I'm going to go ahead and say and then on this one I'm going to say Flex Flex column gap of eight just to space out the title and this upload button so let's see if we can come full circle so we have all these things displayed I'm going to go ahead and just try to upload another image I'm actually going to reduce this a little bit to like 10 images um there we go I'll click upload and let's see if we can just like I'm gonna try to upload one for my computer so I'm gonna go ahead and just take a screenshot I'm gonna click and drag that in go ahead and click done and that should have uploaded it but the issue is is that we're not telling next.js to like refresh the page so if I if I manually refresh it we should see that new image pop up but that's not the best user experience right so what we want to do is after this upload button has finished uploading I'm going to pass in an on upload and we're going to just go ahead and force react to re-render or I'm sorry I'm going to force next to refresh the page so now I think about it we cannot actually do this here we're going to have to do that um inside the upload button itself because unfortunately with react server components you can't bring in the router and tell it to refresh unless you're in a client component right so what we're going to do is we're going to say reconst router is equal to use router and import that and then down here inside of our on upload we're going to say router Dot reload and I actually think this is actually supposed to be called navigation not router when you're using the app router you got to use navigation I don't know why my Auto Import is bringing the wrong stuff but I think you could say refresh here and uh that should do it okay so sorry for that so let's try it again if you go back to our app here and let's just go ahead and try to upload an image from cloudinary let's just try to upload an image from unsplash let's just find a new one of at or something let's just grab uh that one looks okay click next I'll click a format I'll do a small I'll click upload okay and what that should have done is it should have reloaded my page which it did not I'm also just going to put a refresh here just so we can see if it prints out it should but just just to make sure and um yeah let's load up the console clear this out and let's try to find an image from unsplash type in like cat go ahead and select this one click next that click upload and click done all right and the issue that this these cats that we're adding are not showing up I think is because we're sorting by public ID so it that's kind of random right depending on what cloudinary decides to give it's not going to give you what you want so what we actually want to do is sort by something else so I think there might be a created at and we could say descending to give us the most recent thing that was created and there we have it so now the cats are showing up I also noticed that you have to click this button in a very specific place I think that's something I messed up on let's go to the upload button and let's see if we can kind of fix that so I'm going to put this SVG probably inside of this button let me get rid of the div too right and then I'm going to say upload okay that's a little bit better and then additionally I'm going to go ahead and just get this Flex gap of two space that out if you all are new to this channel you guys know that I like to do stuff live and I make mistakes and I like to fix them later on so hopefully you guys are staying up with the speed with what I'm doing so I fixed the upload button I don't like how this thing is like I already see how the upload text is higher than the actual button so another thing we could do is we could say items enter that should hopefully Center both of them and we're going to go ahead and click upload here and we're going to try this one more time I'm going to click unsplash do a pizza this time feeling kind of hungry you know let's select a slice of pizza I'll click next we're going to do small and I'll click upload and hopefully this will refresh the page and we should see pizza show up all right so unfortunately I think there might be some type of race condition going on where this is getting called before it's fully propagated to being queryable from the cloudinery API so this might be a little bit hacky but I'm actually going to say set timeout and we're going to actually defer this by about a second okay so we're going to only refresh the page after a second has passed and hopefully that's enough time for everything to be indexed inside the cloudinaries API and then the page will refresh and you'll see your image you could also probably figure out a way to do optimistic updates so that it you know gets added directly after you add it but uh sometimes just refreshing the page is the easiest thing let's just do an upload again and when this is done it's showing up okay so not the best not the best fix but these are things that you actually encounter when you're building out software and you try to do quick little fixes to get it working I'll have to come back in a little bit and try to figure out if there is a better way to do this all right we made some progress on the gallery page I'm gonna go ahead and say show images on gallery page well I think this is a good start to a gallery I would like to maybe add like a load more button which we could potentially add later on or some type of like lazy scroll loading maybe the ability to delete images would be interesting as well but I think what I want to work on now is the ability to favorite something okay so in order to achieve that I think on these images themselves we're gonna have to add some type of like heart and when we click on that heart we're going to go ahead and Mark it as favorited let's go to our cloudinary image component and we're going to try to add some type of heart overlay on this I'm going to wrap this in a div and hopefully that doesn't mess up the images at all all right and the reason I'm adding a div here is so that I can add a heart so I'm going to add a heart icon which I do believe we already have somewhere we added that on the layout we have an SVG for the heart this one and so that we don't like you know copy and paste this SVG in two different places let's just go ahead and make a components and I'm going to go ahead and say icons and let's just add one called heart.tsx export function art return that SVG okay so that's going to allow us to do this and Auto Import that there and that shows up and what that allows us to also do is we can go to our component here and I'll put a heart in this component as well and now we see a heart down here so what we're trying to do is we're going to take that heart and we're going to move it up to the top right of this parent container right so how you do that well you can say class name is equal to relative and then on the heart itself we could provide a class name I might have to pass that through so I'm gonna go ahead and say props any and I'm going to go ahead and just say dot dot dot props now there's a better way to type this rather than putting any I could say component props and then type in the element type so here's an example component props div let's go back to our code and hopefully do this the right way I'm going to go ahead and say SVG right because that's kind of like what we're allowing someone to decorate with and now when we go back here we should be able to pass in class name and we can give it a position of absolute and I could say the top will be 2 and the right will be 2. hopefully this will position it where we want it on the page um doesn't look like it's getting positioned let's double check to make sure this stuff is in fact getting added to the svg's class it doesn't look like it and I think the reason is because we overwrite it down here okay so what we need to do is in fact I'm going to wrap this SVG in a its own div and I'm going to move props up to that div and this will be changed to a div here so now there we go that's one way you can do it there's various ways you could have done that so now we have the ability to basically have a heart icon and when you click it you want to add that image to have like a tag of favorite or something um so how would we do that well on the heart itself let's just go ahead and add an on click like this what we're going to do is when someone clicks on this heart we're going to go ahead and try to add a tag to the existing image um so that we can like Mark it as favorited so instead of props any it might be useful to also bring in I think it's called like a public ID I think it was a public ID I forget where do we use that uh yeah I might pass in a public ID here just so that we have access to it in a more easier way and inside of the component we're going to change this to public ID and then I think what we do is when someone clicks on the heart we're going to say cloudinary dot dot B2 dot uploader.ad tag and the first argument is the tag that we want to add I'll just go ahead and say art or favorite and then we're going to go ahead and just pass an array of the ID like this make sure you do props public ID so I don't know if this will truly work because I do believe this has to run on node so if we go back to our app you'll see that it cannot resolve FS so here is where we have some issues right we can either bring in and create a public API so that this thing can invoke a public API to do this logic or we could try to bring in like a server action and pass that down into here so that the server action can do this logic without us having to create an API endpoint and do all this other stuff so I may bring in the experimental here experimental and I'll say server actions is true yeah and what this allows us to do is we can actually call a server action from our front end here so I'm going to go ahead and make a new file called actions and we're going to say use server and we're going to say export function [Music] Mark as favorite action I'll say set as favorite okay and we're going to move some of this logic out of the cloudinary image I'm going to move this whole line out I'm going to put it here means we need to pull this out put it up here and we're going to take in a public ID which is a string we'll do that and add tag does um return a promise so we should probably wait on this and we'll say async function like that now what this allows us to do again server actions are experimental but you'll see how much easier it allows you to kind of like write code instead of having to build a whole freaking API to do this you can actually just call the action Direction directly in your uh your client component right and I'm going to go ahead and pass props public ID here like that and technically we should be using a short transition so if I go over here I'm going to say const transition and start transition is equal to use transition like so and we're going to wrap this thing in a transition now the reason we do this is because you can have blocking logic and next recommends that you always wrap this stuff in a transaction or in a transition okay and if you wanted to show like a spinner or loader while this thing is doing its work you would probably do that okay so we're going to call set as favorite action but we're also going to call revalidate Path and what path are we on we are on the gallery so let's just go ahead and say gallery like that and that should hopefully invoke next and tell it to like refetch the images and we can use that to toggle on or off the hearts okay let's see if this works I'm not entirely convinced I have this coded up properly but what I'm also going to do is uh print out what results is here also.log results um I think it's complaining about server actions I'm gonna go ahead and restart my Dev server I think when you change certain things in the the next config you have to restart your server so let's try this out I'm going to refresh the page hopefully it's good now and this should probably be a button so if we were to hover over this I should probably hover like a button okay to make this easy for us I'm going to go ahead and just limit this to one because like we have so many console logs now that it's very hard to tell what data is coming back so let's just limit it to one so the idea is the heart button which I believe we put into culinary image which was here this should kind of have like a hover state so I'm gonna say hover and I'll say color red like actually I think I say text Red I have like a hard color right let's see if that works okay there we go and then I'm also going to say cursor pointer okay so now the idea hopefully this works is when I click this that should invoke a server action we should run that cloudinary code to add a tag to this one image so I'm going to click it and let's see what happens on the back end I'm not even sure if that worked let's go to the network tab over here I'm going to expand this a little bit and let's click it again and see what happens okay so it is invoking the server action that's good and it should be refreshing the page so what we're going to do is we're going to go to our admin API over here we're going to go to the console I mean and we're going to try to find the image let me just delete that one no offense to that woman so the thing we're trying to tag and Mark as a favorite is this over here and right now it doesn't have any tags on it let us go ahead and try it one more time because I think I uh I have two of them that's confusing okay so one of them actually has a tag a favorite right the other one the duplicate let's just delete it so we're not confused in fact I'm going to delete any duplicates right now we did the server action it adds a favorite tag to this image but the UI isn't really showing any type of difference when it gets hardened right so what we need to probably do is first of all I don't think we have access to the tags that come back I'm looking through this data that gets printed out and I do not see tags um so how do we change the code a little bit to allow us to get tags back I do believe there is a thing you can pass here to get tags so let's go back to the search API and let's see if there's a way to get the tags okay down here it says with field and you can actually specify I believe tags okay so with field tags that's what we want that we can actually toggle that image on or off based on if that tag is set so let's try it again this should have already fetched the tags let's go here we have a tags array okay so we're making some progress this thing is a array of search results this has a tags which is a string array so let's just go ahead and update that type so we can easily use it and we're going to go ahead and pass in I'm going to pass in the entire thing I'm going to go ahead and pass in like the image data we'll pass in result so that we don't have to keep on passing all this other stuff in like Source public ID I'm gonna delete that and we're gonna go and export the search result here so that we can use it down in Cloud Neri image okay so now instead of this I'm going to say it's an any and a search result import that in so that it's a lot easier for us to basically just do um props Dot public ID and then we also have the tags right so we can put a computed here and says is favorited equals props.tags includes favorite so if that tags already includes favorite we know we're favorited and what we could potentially do I'm getting a bunch of warnings because I think I deleted Source here so I'm gonna go ahead and just put source and this will be props.public ID just to fix that issue I was doing like an infinite Loop so I had to kill my server real quick okay but anyway we're trying to see if the thing was favorited or not and based on if it was favorited we're going to go ahead and change what heart is being displayed so I'm gonna go ahead and say if we are favorited we are going to display uh probably a full heart otherwise we will display the other hard time okay so I'm gonna go ahead and just put another heart here for now and we're going to actually change this to full heart okay and when you click it we'll have to do some logic in a little bit but we're going to go ahead and go to this hard component or this heart icon I'm going to clone it I'm going to say full heart and we're going to change this to full heart and now let's find the hero icons so hero icons let's find if there is a full heart so oh there is a solid one let's copy that jsx and we're going to paste that in here all right and maybe I'll rename this a solid heart you know it doesn't matter okay all right so that's good let's go back to here and I'm going to import full heart and technically we could probably just like modify our action a little bit to instead pass in like a false and a true so we can kind of reuse the same action and we're going to say Mark as favorite Boolean and if this thing is actually I'm gonna change this is favorite and then say if his favorite then we're going to do some logic otherwise we'll do some other launch okay and I'm guessing there's a remove tag yeah there's a remove tag right there okay so there you have it so now we have an action that can be used for doing two different things you just toggle it on or off um and potentially we could you know make this a ternary clean it up if that's your type of thing I think it's fine we're going to go ahead and save this and we're going to verify that when something has the favorite tag it actually displays the full heart all right it's saying props.tags is not defined I think what's happening is I'm I'm passing an image data actually and I typed this completely wrong so this needs to be image data of search result which means that this needs to be um I'm just going to go ahead and Destructor this restructure this from props so we can just like do this image data public ID public ID um yeah hopefully that's good and that one needs to be image data.tags all right so now it's favorited and I might make it actually red its favorite in so let's go to full heart here um I actually do this text White I'll say text Red of 500. all right so as you hover over it it'll like turn off all right let's test this out so I'm going to click on this thing and it did a request it sent over false so that should have turned it off it should have called remove tag on the public ID here and then it should revalidated the path so let's go over here and make sure like is that getting removed it doesn't look like it's getting removed so just to check this out I'm gonna go ahead and say like removing favorite from and we're going to print out public ID here so now when we click it it should be removing that you know what I'm I'm dumb I have this backwards if this is true we need to add it have these backwards I'm so sorry let's do that I think that'll fix the issue let's click this and let's refresh the page all right there we go click it and then you have to manually refresh the page again I think there is a little bit of delay I think we might just have to wait for a second like maybe this isn't the best approach but I'm going to say a weight new promise resolve that timeout resolve one second I'm not happy with that approach um but let's see if this actually fixes it so I think what's happening is that sometimes when you're dealing with third-party apis their data is eventually consistent so although you did tell it to add a tag behind the scenes it may take up to a second or less to like really propagate through their systems so I think that's like kind of what we're seeing I think the best approach to handling this is you want to do some type of optimistic updates so that the moment you click this we have to kind of go through State and manually change this to become like toggled okay but I'm gonna put that on a to-do list because I just want to get like the main functionality kind of implemented and at this point what we want to do is can we show all favorited images inside a favorites Gallery okay so what I'm going to do is at this point we have a decent amount of functionality created for this gallery and we're going to do the exact same thing minus a couple of changes on the favorites page so let's just go ahead and make a new folder here called favorites okay and then I'll make a page.tsx and we're going to go ahead and paste this in okay we don't need search results we're going to say favorites page we probably still need the cloudinary image now at this point we have like a shared component between two different pages I think it's okay just to keep it here some people would probably pull it out and put it into components until I know for certain that these aren't going to change because they're on two completely different pages I'm not going to bother with that I'm going to try to reuse it from the gallery right now um in the upload button we don't need to worry about because we're not going to be able to upload anything from this page so let's just go ahead and import the search result type we don't need an upload button but we do need a favorite images and what we're going to do is we're going to try to do another search but we want to get back all of the images that have a tag a favorite so let's go back to their API because I don't remember how to do this I believe you can search via tag somewhere right here you can say in tags of kitten so let's go back and we'll say find me all images that have a tag of favorite and I'm gonna go ahead and limit this to 30 and we'll get back to tags as well this should be okay so I think what should happen here is in fact I'm gonna go to the gallery too and I'm going to change this to 30. yeah so I'm going to add some of these to our favorites I'll add the scuba guy We'll add this one okay those are added in again it's a little bit delayed but it is what it is let's go ahead and add this shoe looks pretty cool hopefully they added so let's go to the favorites now unfortunately you can't even click on the side nav here so we could probably fix that real quick let's go to the layout and let's go to the side nav I guess I called it side menu and we're going to make these as links so let's do a let's do a link in here so this will be a next link and we're going to give it an href of gallery okay I'm going to wrap all this in a link and for the button I'm going to say as child okay and that should hopefully allow us to navigate to this and then over here we have albums we're not going to worry about albums yet in fact I'm going to comment this out so we don't have distractions I'm going to put a link here and that's going to be called favorites that again as child now I don't like how this is a secondary variant I might get a ghost for right now because it's going to require a little bit more engineering to figure out if this is the current page that we're on so I'm gonna go ahead and just do this so now we have the favorites here and we have the gallery right so I should be able to click on one of these to remove it from favorites and unfortunately this is where you run into an issue because there's hard-coded logic in the server action to refresh a particular page so if you look at the actions this is hard-coded to refresh Gallery we could potentially have this be passed in since a lot of this functionality is kind of consistent I'm gonna go ahead and say path is a string and we're going to pass that in okay so wherever we call this which is over here we're going to say this is going to be a particular path passed in okay and again this path would probably need to be path passed in right here this is honestly why I don't like um sharing components is because you get these weird situations where you start adding in code to get it to work but really these are two separate like ads use cases I guess I could say all right I think that's good um we're going to find out where we're calling cloudinary image one of this one of these is on the gallery page which is not this one I'll say favorites the other one is on the gallery and just for now I'm going to bump this up a little bit because I do believe one second wasn't enough in some instances let's try this again we're on the favorites if I click on the shoe and wait a little bit it should disappear go to Gallery I'm gonna click on some of these things all right so here is another issue with next js's app router um next JS does a bunch of client-side caching they do that for reasons that are probably good in terms of performance but if you aren't careful you're going to have a lot of issues when it comes to user experience notice how I just favorited a bunch of stuff and I went to the favorites page and they're not here by default you have to wait 30 seconds because the next router is going to cache all of the things you did if you already visited that page right so if I do a hard refresh these things will show up but if I were to go in here and like add this one as a favorite and then go to my favorites page you'll notice that it does not load up so the only solution I have found and this is a this is an xjs problem I have been vocal about this the only solution I have found to like work around this is you have to go to the page that you know has Dynamic data and you have to basically inject a client component into it until this react server component to refresh the moment the page loads so I'm going to add this little hack in here you guys can cringe in the comments but I'm going to go ahead and say Force refresh TSX this is going to export a function called Force refresh and it's going to have a use effect that on Mount is going to call router.refresh you don't have router so let's just say const router is equal to use router and we'll say router.refresh here all right and make sure we say use client at the top here okay so again hopefully you understand that caching issue I'm going to basically just add this anywhere in my component so that when this gets loaded on the front end let's see I actually need to return a fragment here so now you'll see the page will load but there'll be a slight delay and then I'll refresh and get all the data all right so if I do the heart image here of the cat and then I go over to favorites you'll see the cat finally pop up that's the only fix I know of to get this working although at this point I'm not too happy with like some of the set timeouts I have in this app I think we're making a little bit of good functionality um I'm going to commit what I have and just go ahead and just say adding in the favorites page and go ahead and commit that at this point I think I want to actually do some work to make this more responsive and like not have to wait a whole second for stuff to refresh so I'm going to bring in optimistic updating which isn't going to be too hard to do so to fix this the first thing I'm going to do is in the action itself when we click on a heart we want this to just be like instantaneous and not try to revalidate the path so let's go to the actions I'm gonna go ahead and get rid of the revalidate path and I'm also going to get rid of the set timeout here so we're going to kind of Backtrack on all this path stuff that we did okay go ahead and find where we're passing in the path I don't think we even need that anymore and then also where we're calling this let's go ahead and get rid of that path that would passed in all right so the next step is the the state of when we show the full heart or the empty heart what we could do is we could put this in state instead of it being underived so that we can modify it later on so I'm going to say const is favorited and set is favorited here I'll say use State and for the initial State we're going to go ahead and pass in this Boolean right and Auto Import that and the main difference that we're going to do is when we click on the full heart we're going to go ahead and say set favorite to false then we click on the empty heart we will set it to True okay so behind the scenes we're going to kick off an action that's going to update the database behind the scenes but we're going to optimistically just set it to true or false or vice versa when users click on the icons so let's just go ahead and try that out if I click on this notice that it instantly becomes a heart over here instantly becomes a heart you can also unhart all these things now as I go to my favorites Gallery you'll see that those kind of pop up now there's also an issue with the favorites Gallery as I unhart things you kind of expect these to be removed from the page right like they're not they're no longer harded so and the only way to get these to go away is you have to navigate away and then back and then they'll finally get off the page so let's do some more optimistic updates so just remove them when you unheart them okay so let's go to the favorites page this one might be a little bit more complicated um in order to remove things from an array we're gonna have to pull in another client component I would think so let's just go here and call this a favorites list.tsx and we're going to go ahead and copy this whole page paste it in and we're going to just get rid of everything else other than the grid and we don't need these results anymore okay we're going to actually have the resources passed in so I'll say resources resources equals search result I believe is what we called it okay that could be an array and now we're going to Loop over the property that's passed in like this and that should work fine now the main difference again is like we want to when someone unhearts an image we're going to go ahead and just remove it from this resources so we do need to add this to state I'm going to say const resources and I'm gonna say set resources equals use state and we're going to pass in we're actually rename this to initial resources like this we'll pass that in okay and the idea is that now we have this ability to modify state I need to get rid of this async here so now we can modify state right so after something has been unharted we'll say un unheart we can actually update this array and remove the thing that was unharted so this will actually be the unhearted resource and we're going to say set resources of current resources return resources.filter resource return resource dot public ID is not equal to unhearted Resource Dot public ID okay kind of verbose we can actually clean this up a little bit by returning removing the returns and then just kind of implicitly returning everything that makes sense now this new method that we added doesn't exist on the cloudinary image so we're going to go over here and we're going to add it in this will be a function that does nothing okay and we will also pass in a unhearted resource is a search result okay and the whole reason we're doing this is so that we can go down here and I can go ahead and bring that in unhearted resource we're going to go to the full heart and when you click it we want to also call unheart resource and I will just go ahead and pass in the image data that we have so basically our child component is invoking a callback so that our parent component knows when to remove it from the page let's go back up here this one I think I'm missing some typing let's see I might actually get rid of this props any I do think it's causing some issues with typescript so I'm going to go ahead and put props and this is going to be this and I'm going to say key is a string any actually I'm going to Define these and I'm also going to combine this with that other props I think it's called component props and which props do we want to do this will be a I might just do this let me rename this to unheart I think I uh was doing this wrong so unheard so we're defining a function that can be passing those props called on unheart and we're going to call it down here when you click on the full heart so let's see now this is kind of an optional thing so I might actually make this optional and we're going to only invoke this when it's defined okay so let's go back up here I'm going to say un unheart let's go back to the favorites list and we have on un unheart here that should all work fine um it's saying path is missing I think we actually remove paths let's delete that there is still a typescript error this is expecting source so I might say omit and we'll say source right here that should hopefully make typescript happy because I decide to put the source in the actual component so I know this is again kind of confusing um but really we're just trying to like I said know when someone clicks the unheart functionality so we can run some custom logic and filter out that array so now on the actual favorites page what we want to do is we're going to render out the favorites list which I don't think I named yet so let's go over here I'm going to call this favorites list like this go back over here I'm going to Auto Import that I'm going to delete what we have over here now this thing takes in I think resources yeah initial resources and we're going to say results dot resources so hopefully if we did this correctly we should be able to uh oh I forgot to add use client at the top use clients there we go so now if I go to my favorites and I were to go ahead and click some of these it should filter them off the page the moment I click them but there is a bug now that when we unfavor some stuff the router is still caching everything so if you go back to Gallery notice how there's four here if I click gallery then I go back it shows seven so there's like some caching issue going on and the reason this is happening is because the react server component is sending over some new props to this cloudinary um this favorites favorites list but this component doesn't know how to update it alright so you do have to add in a use effect like this and we can just go ahead and say watch when initial resources changes then we're going to go ahead and just reset this with initial resources and by the way if you know of a better way to do this leave a comment I feel like we shouldn't have to do this just to update props that get changed but this is the only thing I can think of like fix that issue so now if I go ahead and just remove a couple of things click away I come back notice that it does on the first click clear those out okay so now the app is like a lot more responsive I can click on this that this and that go to favorites to be added in and I'm actually a lot more happy with this although the code did become a little bit more complex I think this is a much better user experience and we still have the ability to use the server actions to still achieve what we want we just have to kind of do a little bit more optimistic updates to achieve what we want I want to try to clean up the gallery a little bit to make it look a little bit nicer right now you'll notice that there's like weird space between the images and I think if we kind of restructure the page a little bit to actually have four columns instead of using CSS Grid or Tailwind grid we can actually have four legit columns that will put the images stacked on top of each other I think it'll make it look a little bit nicer so I'm on the gallery right now and what we're going to do is we're going to write a function that's going to give us the entries that should live in the First Column second column third column Etc so I'm going to appear I'll say function git columns and this is going to be a function that takes in a number so I'll say like call index or something like that and that could be a number between zero one two three four and we're simply going to take the results here and we're going to map over them um actually we're going to filter and I will say let's say we get a free source and then also an index right and what we're planning to do here is we're going to Simply check if the index mod or is equal to the column index then that's when we will return that data now I don't like having a magic number hard coded here so I'm going to say const Max columns is equal to four we're going to replace that and we can also kind of clean up this code get rid of the return statements to also clean that up some all right so you might ask okay what's this used for why do we need a git columns method well instead of just looping over all these and mapping over them instead we could potentially do something like this I'll go ahead and make an array inside some curly braces and I'll say get column of one or sorry zero like this and then we'll just go ahead and do this four times and I'll simplify this in just a second but basically if we map over these columns and we return another div here like so we can give this a class name of flex and then Flex call and then like a gap of four and then all these divs are going to have all of these cloudinary images so if I go ahead and just put this in here and I will say column dot map that should hopefully make that a little bit better now I think this is complaining because we don't have a key on the top level map so let me go ahead and just do an index like this and that looks like it's pretty happy so let's make sure if we go back to the page and refresh I did this correctly we should see a nicer masonry grid which just makes the app a little bit nicer right but now the issue is if I were to go to the favorites page notice that this is using the same old approach so now we've kind of reached a point where maybe we could like have a shared component that's used for the gallery and the favorites and going down here I'll just go ahead and make another one called like image grid dot TSX just go ahead and work on this I'll say export function image grid and this thing could take in some images would be array of images like that and let's abstract the way all of this let's just go ahead and cut that out I'm going to paste it in here as a return so that we can use the same logic on various different pages now also the get column thing we can also pull that stuff out too like that and then we have to just start Auto and importing some stuff make sure we have all of our stuff defined let's actually put this function inside of that because I think it needs access to images I'll do this here we go and I think that should be good now let's go back over here I'm gonna go ahead and just say image grid like this and make sure we pass in the images so I'm gonna say images and we'll say results.resources cool so going back to the gallery let's make sure this didn't didn't break still looks good that's awesome but also the benefit of this shared component is we can actually copy this and we can go to the favorites and I'm going to go ahead and just look at this code it looks like the favorites list probably needs to be replaced a little bit I'm going to go ahead and just put a component here called image grid and go ahead and Auto Import that unfortunately this actually has a little bit different use case remember like we have some logic to do some optimistic deleting of the entries when you unhart something so the code on the favorites page is diverging just enough from the gallery page that our generic component is not good enough okay so we need to figure out a way to either a just duplicate code and have it in two different places or B we can try to make a generic component which allows us to basically set on unheart over here so just for experience let's try like having image grid instead of it hard coding with this cloudinary image is let's actually have that be a function also get image we'll call it git image all right so this is going to take in a git image and then we're going to type that over here we'll say get image it's going to be a function that returns a react node I believe hopefully and we're going to pull this out okay so instead of like mapping it here we're going to go ahead and pull that out to the gallery first of all and we're going to try to pass it in as a dynamic Constructor of the component okay so this will be a function that needs to return the cloudinary image and we're going to get back a image here which we're going to call search result I'm not a fan of calling it search result at this point but it's that's what we're using um and let's go back to the image definition here this is going to take an image data I guess we could say go back over here we'll call this image data and we're going to go ahead and use the image data and we'll pass that in there does that make sense so like we wanted to allow this image grid to be used on various different pages but different pages have different functionality when you click the heart or when you do different things okay so let us try to do the same logic on um the favorites page so we have the image grid here I'm going to go ahead and try to return this instead this go ahead and paste that in make sure that we have image data here and pass in image data and then I'm going to delete some of this stuff and it looks like this just takes resources all right so I don't know we'll check this out if we did this correctly what we should see is both should be showing the masonry grid they should both all work as they did before um unfortunately this is not updating when you click on stuff okay it used to delete it from the array but now it's not working so we got to kind of figure that out I think what's Happening Here is I should probably say use client I don't know if that'll make a difference um oh secondly I know what's going on we set it up to take in a git image but we have it hard coded here still okay so what we want to do is we're going to actually pass a map to get image like this and I think that should take care of it there we go okay this one's having some issues let's go ahead and see what's going on the image is a function functions cannot be passed directly to client components unless you're explicitly expose it by marking it with your server so I think um let's go to the gallery all right so this is kind of painful like we can't pass a function here because we're in a server component so we actually need to like figure out a way to go over here and we need to make a I'll call a gallery grid dot PSX um and we basically want to take this code and we're going to delete a lot of this we're going to get rid of the async stuff so it's a server component we're going to delete all of this and the whole reason we're doing this again is because like you can't do certain things uh on the react server components so we're gonna go ahead and have this thing be passed in images like that we'll just go ahead and say this is images and hopefully this will work better now instead of same image grid because we have to like dynamically pass it a function I'm going to say Gallery grid and we're going to say Auto Import that images will be results.resources like that and go ahead and delete all that um so let's let's see if this works I'm going to click Gallery hit refresh too happens there we go favorites does it work click off like the gallery can I heart some things okay so if you were to ask me like what is the main thing I hate about the app router it's probably what we just did the fact that I had to like take some code literally put it in a separate file so that I can just put use client at the top it's just a pain I'm trying to accept it but overall I just don't like how we have to do this with our components it's just not fun unless there's a way to kind of work around that which I don't think there is okay so I think the code is a little bit cleaner we have like this nice grid that we can actually use on various pages I don't think it's very responsive at some point like when the page gets too small you know we're gonna have to probably reduce the grids but we're not going to focus on the responsiveness of the app just yet I think let's just focus on getting some good functionality going so I think at this point what I want to try to work on is the ability to add images to albums okay so like if you're on a gallery or you're on your favorites page having a button that you can click that'll make a drop down pop up where you can add it to a new album so let's try to see if we can at least get a drop down showing up so let's go to uh go to the Shad CN UI and let's try to find if there's like a menu like a drop down menu okay we have drop down menu here this looks pretty good so let us go ahead and install this okay if you look at the code for how this is done um this is how it's done drop down menu we got a drop down menu trigger as child you make a button so let's try to grab we obviously don't need all of this drop down menu logic we just need like some of it so I guess I'll start with all of it for right now yeah let's just start with all of it and I'll delete the stuff sometimes it's easier to do things like that so inside of the image grid that is creating a platinary image which technically this is used in multiple places so I would kind of think that we could actually pull that out to a share component as well let's try doing that and hopefully it doesn't break everything but inside this components directory I'm also going to make a image menu okay I'm going to paste in all that code that we just copied and the idea is we're going to try to get this menu to show up next to the heart on those images okay now I did change the location of this a little bit so I think we're going to have to like refactor where some of the stuff is I'm actually just going to delete stuff in like weakness Auto Import in a bit okay go over here let's delete that go ahead and Auto Import that again and we probably broke it in various places so let's do this cool now let's focus so on the cloudinary image remember that's like the main image component what we want to do here is we have hearts but I also want to try to add in a menu okay so I'm gonna go ahead and just put a image menu like this and let's see what happens when you do that go back to our app here all right so now we click this button we get a profile button now I want to try to change this to be a hamburger menu and I'm going to put it in the top right and I'm actually going to move the heart over to the left so if we go to Hero icons and say like menu this one looks like it'd be pretty good let's just copy the jsx here and inside of icons I'm going to go ahead and just put one called menu ESX export function menu and just return that jsx there okay and we're going to go ahead and put menu inside the button okay there we have a little menu there and we have to do some more stuff to kind of position that so I'm going to clean this up a little bit okay now as far as the menu we want to add a class here I'll say class name and we're going to give it an absolute position and I'm going to say top of two right of 2 and that should kind of place it over the heart in fact this doesn't seem to work so I might actually have to like make a div here like this and give this a class name and wrap this whole menu it's a little big I don't like how big that is so let's see if we can find a way to like maybe not make it a variant let's try ghost and see what happens okay looks a little bit better now can we change the the width and the height of this thing I'll say width of like 12 height of 12 and see what happens okay maybe I'll keep it a secondary is there a way that we can like make this a little bit smaller um I don't know what's causing it to be so large is it the button class I think if I just get rid of the menu the button that work okay let's just make it smaller width and height of eight and see what happens okay that looks pretty good but unfortunately the actual menu icon disappears so I think this thing might have some padding built into it let's just give it a padding of zero and see if that helps there we go I think this looks okay not the best but I'm not a designer so what we're also going to do is find the heart and we want to change this to be instead of top two we're going to say left um actually instead of right two we're going to say left to cool so now we actually have like this drop down menu that exists on every image and we also have the ability to again favorite it in on remove from being favorites so what do we want to add to the menu okay the menu right now I think instead of saying profile we should probably make this thing be a uh add to album button I'm gonna give her the shortcut I don't think we really need a shortcut for that and I don't like how this is hard coded the 56 like I'd rather this be something smaller maybe 24 would be a better thing to use go ahead and fix that spelling there okay so uh 24 is a little bit small let's do 32. and then what we can probably also do is find a folder like here this looks good folder Plus I'll say folder plus TSX Port function folder Plus and then return that looks pretty good you can also use Lucid react which I do believe is like a an icon Library so like I think they probably have like a folder Plus in here so maybe that's easier than using hero icons I apologize for like using hero icons when I have this other library that has fonts in it or that has icons so let's try that instead and see what happens all right so we made a lot of changes I'm gonna go ahead and Commit This real quick and then we will continue on adding more features we'll go ahead and add all these and I'll say grid component and menu image menu go ahead and add those and sync those up all right so now let's try to think through like what does it mean to add to an album so when I click add to album we're going to have to first of all list all the albums that we have maybe we can like load up a modal or something and like you can select which album you want to add it to um so yeah let's just try to go down that path let's see if there is a modal component here they do have a dialogue maybe that's what we want yeah so like we could actually have like a add to album here maybe we could have the ability to like create a new one if it doesn't exist or have some drop downs or something but yeah this one should be good let's just give this a shot this dialog component let's just copy it and let's go ahead and install it like so all right and then if you look at the code for this one we probably just copy all this code and let us go to over here we're gonna make a new component called the add to album dialog and just paste all that in okay now let's make sure that this thing will work um it looks like we need a label and an input for this example to work so let's just go ahead and add a label we're also going to add an input I'm going to rename this to add to album dialog and it looks like this thing actually has like a trigger so like technically this whole thing good take the place of this not too sure what to figure that out do that um let's try to put the add to album inside that image menu here let's just test this out I don't think I've tried this before so we'll see if this works all right so going back to our code if I were to click on this and click add to album do we get anything to pop up thing react children only expect to receive a single react element failed um so I guess this thing oh this thing has too many children all right so we'll have to like maybe what we need to do is uh let's add the button back and we're gonna try to just do this instead of outline we'll say secondary we got a little weird bug here when you click add to album it loads up the thing but then it closes it I wonder if this is some type of condition where like having a nested applicable thing inside this drop down menu item is closing too much all right what I'm going to do is I'm going to add as child here and over here I'm actually going to get rid of the button for right now I'm just gonna make this like a div that and let's see how this looks I'm gonna make sure and I'm going to make this a variant ghost all right so let's try this out um so the ask child is the thing that kind of fixed it I do think there's a weird bug underneath where like the original menu is not closing after you've clicked it so we'll have to look into that later um but now we got a cool way to actually have a dialogue show up that allows us to either select an album like using a select drop down or we could just like type in a name of a new one or something right now we're gonna keep it simple um I always try to take the simplest steps forward before I uh do more difficult things so let's just go ahead and change some of this stuff so instead of edit profile we'll say add to album and I'll say type an album if you want to add this image into actually I'm going to say move this image into and then down here we'll say album and then we are actually just going to stick with one thing right now like that so instead of defaulting this to Pedro we're going to actually put uh family album or something but we probably want to have this be booked into state so that when they click add to album we can actually get some type of like server action firing um all right so there's a couple things we need to do is first of all the input we're gonna have to listen to some State I'll just go ahead and say like album name and set out um name equals you state we'll default that to an empty string and then down here for the input on change we will just go ahead and do this okay and then finally down here when you click on the button on click we're going to go ahead and just call a function that's going to probably invoke a server action to add this image to an album menu now the second thing is we don't know what image they're trying to select right so we should probably have the image be passed in and this one could be a search result okay and now for the album name over here I think we should probably put value is equal to that album name and uh yeah let's just make sure this works so if I were to click add to album we see this modal you can type in you can click add but it's not closing anything yet let's go back to the image menu this thing actually is supposed to take in a what do we call it an image right which should actually be a destructured prop here like this so now we're going to have to basically pass that in whatever image that the user is kind of using the drop down for we're going to pass that in and let's just keep going up this thing image menu needs to be passed in an image so here we'll say image is equal to image data okay I'm guessing there's somewhere else that we need to update this maybe not let's make sure this works so when you click on this I'm going to go ahead and just console log the image here I want to make sure that we did get this all the way past down I'm going to go ahead and click add to album click this button and notice that we do have the full asset showing up which is good we have access to the public ID which is probably what we're going to need to move this from the root folder into an album folder all right so one thing I'm not sure about is after you actually click add to album how do you how do you close this modal I'm not really sure how to do that if you click API reference I do believe this is using your Radix UI under the hood and so you can kind of go over here and there may be a way to dynamically close it interaction with outside the element will be disabled and only instead of fault rendered element so down here it looks like the root dialog you can actually pass it in open state I wonder if that's what we need to do looks like you can so I'm gonna go ahead and say open and on open change set open date I'll go ahead and just put a another state variable here like that and by default I'm going to set it to false and what we could potentially do is down here after you click it I'll add this back in and I'm going to go ahead and say set it to false I just want to verify like can I close the modal after I click save there we go awesome all right so now what we want to do is we want to be able to type in a name of a folder and kind of move that to a different folder um so let's go and look at the API reference here if you go to the admin API it looks like in cloudinary you can actually create folders this is how you can create a new folder going down here it says culinary V2 API create folder and you pass the folder name and the Callback will tell you when it's done being created but I think what we want to do is when someone actually clicks add to album we want to invoke a server action okay now the server action we could potentially Define like here in this components directory we'll do that for now I don't know if this is the best place to put it I'm going to go ahead and say actions TS right now and I'm going to put a use server at the top and I'm going to go ahead and just say export async function create folder and that is going to run this cloudinary code so let's just go ahead and import some of the stuff so import cloudinary from cloudnary and this will be a folder as a string as an argument and then when it's done I know I think we just do a weight here we don't need to do a DOT then call back so we can create the folder um and then we also want to add the image to the folder probably so we'll create the folder and then we'll say like const folder is equal to this and then we also want to take in like an image so I'm going to say search result I'll say existing folder here and what we're going to do is hopefully there's a way to add an image to a folder I have to go look at their Docs let's see if we can just find something by using typescript is there like an ad image no there's a crate folder delete folder search folders Resources by asset folders is there a image a resource it looks like there's a uploader rename function we could try doing that let's do V2 uploader rename and this takes a public ID so luckily we have the image public ID and then we can rename it to a folder so I think we can just basically say folder slash image that public ID go ahead and await on this too I wonder what existing folder has okay so let's let's try this out I don't know I haven't really tried this we'll see if this works we're going to pass in the image and then we're going to pass in the new folder that the person typed which is going to be the album name um and then after that let's go and import that and save this put async here to make sure this works oh I think I have them backwards so I actually need to swap those I need to create a folder image goes first and then the folder name and in fact I think I'd make more sense to call this like add image to album and I'll kind of rename this to album so like we're abstracting away the underlying folder stuff that's kind of baked in the cloud Neri and we're just going to call these albums and we're going to go back to our code and just kind of rename that input a little bit all right so here is the moment of truth if we were to try doing this I'm going to click on this desert scene add to album and I'll type outdoors and click add to album let's see what happens so if we go to Cloud Nary go to Media go to my media library and click on this is this thing added to any type of folder in a location of Outdoors so we did add it to the folder that's pretty cool so obviously this isn't the best dialogue like it'd be better if there was like a drop down with selects but you know at some point there's just too much functionality you can add it to an app and I won't be like you know working on this for 20 hours to make a video but I think that's good enough right now just be able to type in the name of the album and it'll just add it to it but maybe having a type ahead which I do believe cloudnary supports a way to list all of the root level folders that you have so when you create the the dialogue you could just fetch from cloud and area all the folders show that in a type of head or create the folder or the album if it doesn't exist which we might actually do right now so I'm going to go over here on the left and we want to actually have a way to view all the albums so let's go to the side navigation which I think is in layout we're going to add back this albums thing that we had before and we're going to go ahead and just put this all in a link here so I'll just go ahead and say link href is albums like this I'll say as child there we go and the idea is when you click this it should take you to a view that shows all of your albums and then you can kind of click in further into those albums so let's see if we were to click albums what does it take us to takes us to a non-existing page so let's move on to trying to build out the albums view if we go to here an app I'll type albums and we're going to have to create a page which is going to display all the albums I'm going to copy an existing page real quick and right now just comment some stuff out I'll say albums page you don't need state technically we don't need a client yet you know all this we can just delete so I should probably should have created a new one um let's make sure this shows up if I were to type in like albums does this show up on this page now it does and I think maybe we should have copied Gallery I think galleries would have been a better approach going forward yeah let's just copy this instead okay so we got the albums so what we want to do is we want to display cards for all the different albums that we have in the system and when you dive into an album it'll show you all the images that are in that album so the first thing is how do we get the albums that exist in our system well we're gonna have to go and look that up right so let's go over here I do believe there's a way using the admin API to do that and you know in fact I probably don't even need to like look anything up I'm just going to go ahead and just play with typescript typically typescript will help you out I'll do API I will say list no there are folders root folders here we go I think if you just call Root folders that'll actually return you a list of all folders so I'm going to list out those folders real quick and make sure this actually works so let's go to our app I'm going to go ahead and just refresh this page and we should see in our terminal some type of list of folders okay this is what the data structure looks like um it's pagination so like we could just destruct folders that's going to be an array of these which has a name and a path okay so technically I could type this as an object that has folders which is going to be an array of name String path string like this just so we get a little bit of type sense and intellisense and now what we could do is we can actually render those things out in a grid or a list okay so again Shad CN let's see if there is a card we got a card component down here looks pretty good let's just go ahead and see like do we need to install something yeah we got to install a card here so I'll go ahead and grab that I will go here add the card right that's done installing and then if we look at the code uh we don't have to look at the collicious copy it we're going to go ahead and just make a new thing called a album hard TSX paste that in and let's see what we could potentially delete from this to get this kind of working it looks like there's a select here we don't care about a select so I'm actually going to delete that whole select area here we go and that's when I see this show up on the page if I were to kind of like map over those folders so I'm going to say folders.map for every folder that we have in our system let us create a card like this actually not a card we're going to do an album card so let's go over here and change this to album card and we need to pass in the folder here so we have access to it and at this point we should probably say export type folder is equal to this okay and then I'll just type this as a an array of folders album card could take in a folder which is going to be like this there and the idea is that we want to render that out somewhere in the title probably so we can say the name and say all of your folder.name images with that all right going to our app refresh the page and see what happens there we go we've got some albums showing up now we don't need this name input so obviously we can just like delete it um the whole form can go away and then instead of having like cancel and deploy we really just need one button or a link um I'll say as child and I'll say link and I'll say view album and then this thing technically it needs to point to albums and then probably the folder name like that go ahead and import that I think it needs to come from navigation and what is this complaining about the link does not have a Constructor or call signatures oh I'm importing the wrong thing uh let's import them from next link okay um I don't like how this has a hard-coded width let's try getting rid of that and see what happens and then we're going to go ahead and inside of here we're gonna do a div that has a grid grid Falls three and this could have a key of folder.path maybe and then probably some Gap I'll say gap of four now it would be cool to have like the actual like one random image or just an image from that album the idea is when you click view album it should take us to the outdoors album so let's try to add that in so we can show all the images so I'm going to make another folder here called album name like this and then I'm going to make a page TSX now this is going to be very similar to gallery so this page will probably be really similar to Gallery I'm gonna go ahead and just like copy all of gallery and just paste it in here and we probably need to have some type of grid potentially so I'll just go ahead and add that in here we'll just kind of like change stuff around I'll say album grid all right go ahead and import that one need to go ahead and use the album grid down here um we don't need an upload button right now we probably want the actual album name so we'll have to figure out how to get that I do believe your props will be passed in so like this will be album name um like this I do believe so like I think technically I could just do that now it's not happy with the typescript so I'm going to say params is an object with album name ring let's see does that show if I refresh this page will it show outdoor and it does awesome so now it's just a matter of doing search but getting all the images that are like in this album or this folder I don't know how to do that so let's go to admin API let's go to the search API let's see if we can find a folder older equals yeah so we could just do and folder equals album name hopefully this is how you do it nice that's working there's like a giant error over here or warning functioning component cannot be given refs attempts to access this ref will fail did you mean to use react forward ref I don't add to album dialogue add to album dialogue is there a ref somewhere yeah I don't I don't know if I want to go through like debugging that but uh now we actually have the ability to add various things to this album now so like I can click here I can say outdoors I can click add to album and I wish I could automatically close that but let's go to albums and go to Outdoors let's see if we can add something else to the album so I'm gonna say add to album and then I'm going to go ahead and say Outdoors click add to album and let's see did this actually work like any errors down here um yeah it didn't crash there it is now I do think that this albums page will suffer from the same issue where I need to do like a force refresh or else it's just going to load bad data or stale data so I'm gonna go ahead and add that in but I do want to see what happens if I were to like add this to a different out of album like scuba and click add you would hope that this thing would get removed from this album they move to a different album which it is added to scuba I believe we do a hard refresh here um I guess it didn't add it to Scoopa let's go to our media folders over here we have a scuba folder and that has an outdoors folder okay I think the issue is that our action that's like moving things around it's just a pending a prefix right so really what we want to do is like replace the entire album from the public ID so like we're gonna have to rename like we'll have to do this replace we might actually have to split on the the album first so like I'll do Arts is equal to image.public id.split on slashes yeah so it's almost like if Parts dot length is greater than one then we know that it's already part of an album um so what we're actually going to do is I'm going to say Parts is equal to Parts Dot sub not substring I'll say slice of one so that'll just give her the first root album and then I can say response public ID is equal to Parts dot join with slashes and we are going to go ahead and put that here instead there's there's probably a much better way to do that um but let's see if that actually works so what I'm actually going to do is I'm going to go to that Scuba Outdoors image because I think I just kind of messed this one up and say move the folder I'm gonna go ahead and go to the home folder and move it there all right let's try this I'm going to find that Scuba image I'm going to say add to album I'll say Scuba click add and now it should have been added to scuba here it is now if I were to add it to Outdoors that should add it to the outdoor album there it is and it should be gone from scuba now cool so this thing staying open is really bothering me so I'm actually going to go and try to see if I can fix that real quick I'm guessing dad CN is using for the dialogues they're probably using Radix Radix yeah and let's go to the API here I'm guessing it's very similar where you can pass like an open yeah it's very similar so let's let's just fix that real quick because it's just bothering me image menu we're going to say const open set open equals use state of false and then we're going to go ahead and when you click on this thing add to album dialogue we want to close this menu I think we could potentially just do this on click that open false let's try that out first and see if that works I'll click here okay um of course that's not gonna work because I need to actually say open is equal to open and then also and then over here I need to say set open for on open change I think that should fix it okay that's that's not working um let me try adding it to this is that even clickable okay I actually might add it so I'll say on open we're going to go here we're going to pass in um on open and we'll say on open is a function that calls something and we want to go ahead and yeah I'm going to bind into this so I'm going to go ahead and just do a function here and then I'll say like new open state and then I'll say set open is called and then this will be new open state does that make sense so basically I'm just like decorating the function so that it'll invoke the menu so that I know that hey like this dialogue has been opened and then it should close the menu keep our fingers crossed let's see if this works click it I think the issue is that because this thing is nested inside of this component it's getting deleted so now I think what I need to do is on close I need to kind of move this around and I apologize again I don't plan my videos beforehand I kind of just do stuff live so that my viewers can get like a real sense of like software engineering but I think what we want to do is if this is set to false I'm going to call on close let's see if that fixes it but it'll stay open in the background but when I close out it'll close awesome kind of it almost worked but not really just because I'm manually setting it here right so I need to actually say I want to make a function over here say function close which is going to run this that open a false and you know actually I'm going to say on close let's just do that go back to our albums let's go to scuba let's move this one to Outdoors click out to album it closed them all right so we have some pretty good progress now we have Gallery albums and favorites we have drop downs we have the modal popping up to add things to different albums ability to upload I want to go ahead and commit what I have before we make too much more progress I'm going to go ahead and say adding image menu albums and dialogue and I think I added the dialogue here yep go ahead and commit those all right so now I think it'd be cool to start diving more into like the generative AI that binary can provide you and the filtering and various things so once you have like all your things uploaded to your gallery uh it'll be cool if we could have another button here called like edit right and take us to like an editing mode or something let's try to do that let's go and find the menu we've got the image menu here and I'm going to go ahead and add another drop down item called edit and this is just going to be a link that'll take you to another page I'll go ahead and make sure I Auto Import the next link here href is going to be um we'll call it edit and then we'll do a image.public ID here like that and I'll say edit now let's make sure this shows up it'd be nice if we had a pencil icon or something so let's go up here and see with my icons where the icons now let's just see if I can just add one and see if I see what happens I'll say pencil from a lucid react hopefully that's good enough there we go we got a little pencil um class name we're going to go ahead and say margin right of two and it's a little bit big so maybe a width of eight height of eight let's see if that'll help oh that's too big let's go four okay I'm not sure why it's kind of off we'll fix this later I'm not going to waste much time on making this thing perfect if you click on edit we want to make sure that takes you to an edit page now for some reason it took you the outdoors and that is not what we want we want oh okay um unfortunately the ID has a slash in it so that might be a little bit difficult to handle we might be able to go to the edit page and pass in a query string and then grab that query string um for that data so let's actually try doing that I'm gonna go to edit and then I'm gonna put public ID is equal to that and then we also might want to URL encode public ID um I'm actually going to wrap this and encode your I URI component because it does have a slash in it I think it'd make more sense to encode it all right let's see if I click edit here it takes us to here and the idea is that we need an edit page now so let's go and make a new edit page I'll make page ESX export function edit page and I do believe we get props here that should have that query string let's just print this out and see what happens make sure I add default there I'll just put async as well all right so notice here in search params we get search prams public ID I'm gonna go ahead and just type this whole thing like this I'll say search params and we're going to say public ID then over here we're going to type it we'll say search Rams we'll say public ID as a string there so that will allow us to actually get the public ID which is here okay so let's go ahead and grab some stuff from the gallery let's grab like this whole section thing like that and we're going to render out edit a public ID so that will show up in the header and let's refresh the page and make sure it works awesome a little bit verbose but it works so the first thing I want to do is I want to display that image somewhere I want to display that Public Image so what we could potentially do is we're going to have to probably make a new use client component so that we can display that and this might be a scenario where this actually made more sense with a used client on this entire page I don't know if we need to do any type of server fetching yet um we'll see let's just go to the async here and I'm going to go ahead and just make a cloud an area I guess CDI CID ald image like this we're going to say source is equal to public ID I think we need to provide like a width we'll say 300 height could be 200 make sure I Auto Import that oh alt is missing so alt is some image all right does this show up the image it does so now we have the image that we're trying to edit so some things that you can potentially do to this image would be doing some generative fill so let's try to start with that um we could add a button maybe some buttons up here or buttons here which will kind of like apply some type of editing on this image all right let's just go and like add a button that can at least do some generative fill right and what a generative fill does is it basically takes the outsides of the image and it'll use AI to fill in the blanks right so if you wanted to have like a more expansive desert with mountains you just let AI do its thing and you'll have a cool little image so how could we potentially do that well we need to have a button over here probably so I'll just make a button and I'll call it generative generative bill okay let's make sure we import that from our components and I'm going to go ahead and say variant is default that what does secondary look like all right and what we're going to do is when you click on this image we basically just want to apply some type of transformation and show it over here so how can we do that let's just go ahead and keep track of what filter you're trying to do and we could just go ahead and put like a const um transformation and set transformation equals you state like this we'll set it equal to empty for right now but when you click on these different things so for example generative fill I'm going to say set transformation I'm going to go ahead and say set transformation generative inner generative Bill okay now to make this better we could probably just type these things like we could do an empty string or generative fill just so we have a better type sense autocomplete okay maybe undefined would be a better thing to do undefined and what I'm trying to do here is when you click on this button I want to basically show that image over here so I'm going to say if the transformation is equal to generative fill then I'm going to show another image on the right okay so I'm going to copy this and we're going to go ahead and just paste in let's go over to nextcliner.dev and let's look at the generative fill so you basically just need to put build background return the given size without and we'll add some crop of pad Okay so we'll do this and uh let's see what happens and before I run that let's just go ahead and put like a div here that has a grid grid calls to and gap of 12 so that we have space between the two different images that we're showing all right so moment of truth if I click this it should hopefully show an image over here that has some generative fill there you have it sometimes AI is hit and miss so actually if I went to the gallery and I tried a different image all right so test this out I went ahead and selected this guy and what my idea is I'm going to go ahead and try to generate his lower half like maybe show like his legs or something if I click in order to fill there you have the AI trying to fill in the bottom half obviously AI can only do so much but I think that's a good start with the generative fill just I do want to note that the generative fill is in beta so if the results don't look that great it's because it is in beta but there are some other cool filters that we could potentially add by just creating some different buttons right so let's just go ahead and say like apply generate to fill we could have another one called clear all and we could just go ahead and have this set to like undefined and that variant could be that like a ghost variant so I can say clear all and then we're going to go ahead and wrap all these in some type of grid I'll say gap of four all right so we do generative fill we could also apply some different type of styles if you wanted to do one for example we could potentially do a blur that might be pretty cool so let's just go here get over the fill background the CR on the crop and we're going to say blur is equal to 800. we can add that type over here all right let's give that a shot I think I forgot to add a button for blur let's just go ahead and do that okay apply blur and there you have it the image on the right will be blurred what other cool thing is you have zoom and pan grayscale pixelate hint opacity Brim let's just add pixelate and grayscale but I think you um get the picture by now so gray scale apply Gray I'll say convert you Gray go up here and add that as a type and then down here we could also just change this be Gray and we'll say gray scale I think that's what it was yep pixelate is another one all right so let's do pixelate and uh yeah let's make sure something's work so while I'm missing the pixelate button go ahead and add that in all right let's try this there we go convert the gray apply blur apply dinner to fill clear all so now we have like a really basic page that we can actually like modify these things with filters and you can get like as experimental as you want go through all their different filters they you can do cropping you can do image overlays if you wanted to you could potentially remove the background of images that might be a cool one to do so the background removal I do think you have to turn that on with an add-on so let's try that out let's go over here and I think you only get a certain amount of these on the free plan so let's go to the cloudner dashboard and I'll do the cloudinary AI background removal so you get 15 monthly edits so I won't be able to dimmer this too much but I'm going to go ahead and turn it on all right so I click this that says successfully subscribe to cloudner's AI background removal let's see if this works let's go back here um a little bit of technical difficulties but I'm gonna refresh the page and then I'm going to click on the background removal and there you have it successfully remove the background from this if I go ahead and open up a new image you can see that his background has been removed cool so good progress I'm gonna go ahead and commit um what we have we have a lot of cool different Transformations here that you can kind of use to edit your images and download them later if I go here and I'll say add an edit page for transforming images alright so one thing I want to fix real quick is this Ed button notice how these things are like not looking similar and this also doesn't have like a pointer cursor when you hover over it so let's go and copy this button right here and I'm going to wrap this whole thing in the button and I'm going to say as child on the button so that I can put the link inside of it and that should hopefully give us a link I would probably also give this a class name of hover or cursor pointer not sure why battlefold doesn't have that all right and then we need to align it to the left so I think we could potentially just say text to the left and let's see what happens if we do that all right that didn't work so maybe we can give it a flex and say justify start no okay this one might be a little bit more difficult so if you look here the button the link and then inside the link it has an SVG in this so I would think that text left would have moved it to the left um I'm gonna try adding the class to the button itself okay maybe that's what he needs to do all right so I think it just needs to be moved over maybe like padding adding left of two let's see what happens no adding left to four there we go all right I don't know if that's the proper way to fix that but I mean it looks better now okay but at this point I think one thing to be really nice to work on is the ability to search your gallery based on images that have certain tags on them okay so I may add a search box up here or maybe even one underneath this where a user can type in a tag name and it'll filter down to only show the ones that match that tag so let's go ahead and load up the gallery and on the gallery page let's go ahead and open that up we're going to go ahead and add a search I'm going to say search form something like that and we're probably going to be needing a client components let's just make a search form TSX I'll say use client it'll say export function search form and this form needs to return I mean we could obviously have a return to form but we need an input so we did do an input on the dialog let's go to the dialog real quick and let's just see how we did the input go down here looks like we just did this essentially just grab those two and I'm going to paste that in here and then let's just go ahead and Auto Import that one from the UI label Auto Import that from UI input and then we're going to need some state right so let's just go ahead and grab state like this but at the top of the search form and we're going to call this tag name that tag name all right so go ahead and say search by tag and then over here we're going to just import that in let's see if this shows up we should at least see like an input show up there we go um I don't like how you can't click on the label so let's go ahead and say this will be html4 tag name and when you click on this it'll actually select the input and then we probably also want a search button okay so let's add a button down here say button and we'll say search need to Auto Import that don't forget that and let's verify if this works there we go now this thing we probably want to wrap here I'll say Flex like that so they will be on the same line and we also add some gap between them so I'll say gap of two there we go a little bit of space now the idea is that when they search something and press enter it should basically filter down these to only show things that are matching with X so how would we do that okay first of all we probably want to have an on submit here so that when someone submits the form we can do something and I'm just going to go ahead and say prevent default I'll say type submit here you don't have to put that but it does just help it be easier to read through to know that this is a submit button so what do we want to do when someone types in an outdoor tag or outdoors and presses search we want it to basically filter this down because the gallery is a react server component page it might make sense to actually have this page reload with the query string so we can say search and then we'll say Outdoors okay and then the react server component could basically rerun and filter down that data for us so let's let's try that out what I'm going to do is I'm going to bring in the router here I'll say cons to router he's able to use router we're going to react to navigation and then over here I'm going to say router Dot replace and then we are on the gallery and then I'm going to go ahead and say search by and then I think it's uh make sure you do the encode URL let's go ahead and find that encode URI component and we're going to say acne does that make sense so we're going to replace the URL with Gallery search then we're going to say router.refresh that'll tell react to kind of like rerun the react server component which should use whatever the search parameter is and we're going to get that data so here we already did a search param so let's just kind of grab that from another page here's a good example of where we did that if I were to copy that in we could get access to the search and this is going to be a string that's passed in from the query string and what we need to do is change this expression so if search is defined I'm going to say ternary if it's defined we want to take this basically this expression if you look over here you do and tags is equal to something so what we're actually going to do is if this is a defined I'm going to do a ternary and then I'm going to go ahead and just put over here I'll say search if it's defined we'll do and tags is equal to search otherwise we'll do an empty string and uh let's figure out why this is failing oh I gotta do another ternary here sorry I gotta do another string interpolation so not the cleanest but I mean I think this will work so depending on what you put in that query string it'll actually return you back data that's related to the tag you typed in so let's try this out let's see if this works I think we do have some tags I don't remember what we called them um I think we're tagging stuff by favorites so like we could potentially say favorite and click search and notice up here it does add the query string it does add favorites but we got some type of error so let's try to figure that out what's going on um I think the backend is actually crashing so let's go to the server this is expected colon and got tags so what am I doing wrong I'm not really sure why this is crashing let's let's go and check another page real quick and tags equals that um so I don't think it's an actual like issue with the syntax I'm doing oh maybe I need a space here let's add a space that's what's going on right again go ahead and refresh and see if it crashes okay now it's working so I just forgot to add a little space simple little human error um secondly you'll notice that when this thing loads it doesn't say favorites here it probably should put whatever's here inside the default value of the search by tag so let's go back to the search form and we're going to have to have that pass in the initial search okay we'll just go ahead and say search and let's go and add that so I'll say initial search and we'll say initial search is a string and we're going to pass that in here and if it's not defined we'll default to an empty string all right so let's try that out so notice that when I change this if I say like dog it does change it to match okay if I go to Gallery I should reset it actually notice I did not reset that so that's kind of a pain right we got to figure out how to get that thing reset one thing I've noticed that you can do is you can actually use an effect like this I think we kind of already covered this before where you can pass in the search I'm sorry you can pass an initial search and we can say set tag name is equal to initial search so if for whatever reason the tag name does change it'll like clear out awesome so hopefully that kind of makes sense when you're dealing with react server components you kind of have to like do some interesting things when you're mixing Ram server components with the client components because the client component doesn't know when to update right it has like a mind of its own and you have to kind of like use effects to force state things to update when these props are changed at least that's what I've seen there might be a better work around for that but that's what I've kind of seen with getting the stuff to work not a fan of it but hey it's it is what it is so let's also like um maybe over here like we have this cool ability to search by tag what we could potentially do is I'm going to show you a cool feature with Cloud Nary where you can actually have stuff be Auto tagged when you upload it to cloud and area so if I actually go to my cloudinary dashboard and I think this might actually be if I go to settings here remember if we go down to um upload I do believe I created this special upload thing and if I edit it I think there's a way to say when stuff is uploaded I want you to auto tag it with Google Tags let's see media analysis and AI scroll down oh here it is is that the top so you could potentially Define a hard-coded list of tags if you wanted to um if you look here you can actually do Amazon recognition Auto tagging and I think we could just click save now I don't know if this works by default I think you might actually have to go enable that so if we go to add-ons we have to go to the Amazon recognition uh Rec ignition recognition Auto tagging let's click this now you'll see that you only get 50 monthly ones when you're on the free plan so we'll hit this pretty quick as we're like uploading things well let's just go ahead and enable it okay and that should give us access to like as we upload images that should auto tag um we'll have to test this out I haven't tried it before but I think it should work perfectly fine so let's go back to our app and we'll keep our fingers crossed I'm going to add a new picture this one will be a dog let's just find like a dog and I'm hoping that that is one of the things that'll basically tag with so let's just go ahead and upload that I'll do a picture of a dog laying on some rocks click upload and let's actually see what happens if I were to search dog wow pretty cool huh automatically tagged it looked at the image it found with the object and it just allows us to search for it let's try again with something else let's see I'm sure they have the ability to do cats um let's just do a cat here I'll just do a couple of cats too and keep in mind this is all going to be dependent on how powerful Amazon's recognition engine can tag things I do believe you know it it can only take you so far right so let's just go ahead and upload like two images of cats and I'll click save and I'm going to say cat click search and there you have it pretty cool stuff I like how culinary has like Integrations with other powerful AI services like Google tagging Amazon recognition tagging it has integration with AI for Generation fill I think it also has like generation edit and crop and stuff like that one thing I think it'd be cool to do is all the albums we have in our system we should probably display there right as as sublinks and I think having the user have to click on this and then have to click again not the best user experience right so let's try to add these in addition to showing up here we're gonna have them show as subnav links okay shouldn't be too hard I hope let's go to the layout remember this the layout page is a react server component um so what that allows us to do is we could potentially we could bring in and fetch all of the folders right I think um let's try to find where we fetch stuff from cloud and area I believe we already do this with the albums so let's go to albums page and let's grab this code paste it here and let's also grab cloud and area import paste that there now we're going to do async here and notice that this will give us the root folders right so in the layout itself we're fetching the root folders you could potentially if you don't want to do it in the layout itself because that's going to block the entire page you could potentially do it inside of a sub nav link here if you wanted to but let's do it here for right now and I'm going to go ahead and import that from there so now we should get the folders when this page loads and again like I mentioned before let's just go ahead and loop over them so let's find the album button which I do believe is this one and underneath it we're going to go ahead and just map over all those folders and we're going to create more buttons okay and inside those buttons we're going to add links the link is going to be for the folder dot name okay and this is going to link I can type this correctly this is going to link to slash albums slash folder dot path okay and um save this I believe this will need a key I'll just say folder.name I think it's going to be unique uh probably should say as child so that this will be applied correctly as styling and then we want to indent this just a little bit so we know it's like a nested so I'm going to say class name is equal to padding left of four and now if we go back to the app hopefully there they go they show up now if I click Outdoors it should take us to the outdoors section if I click uba takes us to scuba now these these uh buttons I think we should change them to be ghost so I'll say variant variant equals ghost all right and I don't like how they're like not full width so maybe we should kind of just copy the existing styles that we have and then we could do some type of like padding left of two or something let's see if that helps that did not help uh what about margin left too that is a little bit better I might do four um that's also bad because you see on the right it's like going off the page so we're gonna not do the margin or the padding on here instead what we're going to do is we're going to put it inside of here like on the link itself I think we could potentially just say this heading left of four still not liking that right okay um does that even doing anything let's try 12. okay that was doing something so uh I'll do it like eight cool all right so now it's a better user experience I mean you could click albums if you want to view into all these um and potentially if you wanted to delete an album we could add that feature um I think this is kind of good enough right now I'm gonna try to add one more thing and I want to kind of demo a little bit better about the um the generative AI here so if I go over to edit let's pretend like someone wanted to add in some additional things on this image so right now if you go to apply dinner to fill all it does is kind of expand the image images height a little bit as you can see it kind of kind of expanded it um but let's say you wanted to actually change something like let's say you wanted to put a person inside of this chair or like remove the chair we could potentially do that with cloudnary's AI generative fill let's go back to the edit page and if we go down to the generative fill so instead of just putting a bullying for fill background we can actually give this an object and we can actually change gravity prompt and crop so that we can position things and like add various things to the picture so if I were to put prompt here and I could just go ahead and say Christmas tree I think that's a good example that I've seen now if I were to basically apply the generative fill let's just refresh the page and I'll click apply a generative fill and I might also increase the width of this a little bit let's go in 1800 and change the height a little bit and click save let's see if this will add some type of Christmas tree inside of the picture there you have it so it actually added Christmas tree a little a fireplace and a reef okay I just wanted to kind of demo that because I kind of skipped over that part when I was previously making the edit page and if you wanted to we could potentially make a input box here so that people can change what the prompt is so maybe we could do that real quick if we have this section I'll give this a flex and I'll say Flex call and then we're going to go ahead and put that inside of here I'll do an input and we want to basically add in a prompt so I'll say value is equal to prompt and then over here I'll say on change e set prompt e dot current target.value this is off the page let me go ahead and do that and then of course we're going to need a little bit of State up here so I'm going to go ahead and say Advanced prompt that prompt equals new state and I'll just go ahead and set that as an empty string now this thing it looks like I'm I have a syntax error somewhere so e set prompt this needs a parenthesis and I need to delete that Okay cool so now there's a little prompt here and also we should probably add some gap between this it's a little a little close and then also a placeholder and a label so we need to get a label do this and I'll just go ahead and say like prompt and uh let's see if this shows up okay so I forgot to hook this in to the actual prompt down here so let's just go ahead and delete that and not have a default as Christmas tree and I'm gonna go ahead and clear all and I'll click apply gener to fill you know another thing about it this is a generative fill it doesn't remove right so if you wanted to fill in on the right we could potentially oh wow just added a bit after I deleted that um let's just go ahead and type in kitchen I think one thing that's also happening is that like as you type it's going to keep on making requests to do the generative fill maybe that's not the best approach maybe we should actually only change the prompt when you click on apply generative fill so I might actually make a second variable here um I'll call it the pending prompt and the input will change the pending prompt and then when you click on this we're going to actually call set transformation but then we're going to say set prompt is equal to pending prompt so that'll only kick off a new image when you have clicked on this button so if I type in like a Christmas tree actually I'm going to say a kitchen and then now I want to click it it should kick that off and change what the image looks like I wish there's a way to add a loader here or a spinner I'm not really sure how to do that maybe that's something you guys can look into but now that we have a nice little kitchen they change this little living room area to an actual kitchen and it looks pretty cool to add some finishing touches I'm going to go ahead and just like create a little icon for this that we're working on I'm gonna go ahead and say a photo album and we're gonna make it um let's make it black and then I'm going to go ahead and just say create eight of these hopefully we can get a cool looking photo album icon all right that's not too bad um I kind of like this one over here the shadow one's pretty cool too but what I'm gonna do is I'm going to go ahead and just click on remove background and we're going to take that and we're going to put it as an icon for our application that we're building and let's go to the header let's find the photos app I'm going to put a little image here now technically I could upload this to cloudinary too but I'm just going to go ahead and do this on a next image so I'll say image and we will import this I'll say source is equal to slash album I think it's a PNG I went ahead and added it here so we got an album with no background and then we're going to go ahead and give this a width of 100 height of 80. you know I'm gonna make it 8080. let's do that and then we should probably give it an ALT um icon of this photo album app all right so let's see if it shows up there it is it's a little big I might actually reduce this to 50 50. and I'm going to say web dev Cody rename it to web dev Cody photos about that so I think this is as far as I'm going to take this application we added a decent amount of features all from using purely cloudinary I didn't make any secondary database I'm just using all of the cloudinary supported apis to do queries to do inserts the widgets for uploading the ability to favorite and stuff that's all added with just tags and albums are added with the public prefixes of the images so as you can tell Flannery has tons of different things you can do with images I'm just going through here you'll start to see like how much they actually support and then they also have tons of add-ons as you saw where you can actually do various AI related things with um your images and on top of what I did I'd even show you how to do videos you can upload videos of cloudinery as well they have a bunch of features that are built into doing video transcriptions you can do video tagging ultimately I would say if you need a good place to store all your media assets like images and videos and you need a robust way to query over those manage them tag them go check out quaternary I had a lot of fun building this application and I was actually surprised I was able to build all this without bringing in postgres or any type of database some final thoughts if you actually want to practice um building onto this application some things I would probably do is I would add a load more button or some type of like lazy scroll so as you scroll more it would fetch more which means that you may actually want to like convert this to just a client component it from the get-go because I think the pagination and dynamic loading in more images might be kind of more difficult with the react server components I think the ability to delete albums would be kind of useful right now when you have an album there's no way to delete it which means that you basically have to Loop through all the images and move them to your base gallery and I'm sure there's some other things that you could probably look into also like authentication I do believe cloudnary provides you a way to do pre-signed URLs so like if you need to actually authenticate these images so that no one can just view them there are ways to create signed URLs on your API so that these images will only show up for certain users that are attached to their account right that wraps it up so I hope you guys enjoyed watching this video and you learned something new about cloudinary like always I have a Discord channel that you welcome to join if you want to find a place to hang out or ask other developers questions if you're stuck on anything other than that have a good day and happy coding
Info
Channel: Web Dev Cody
Views: 39,273
Rating: undefined out of 5
Keywords: web development, programming, coding, code, learn to code, tutorial, software engineering, nextjs, next.js, cloudinary, web app
Id: MC6D4vylKTc
Channel Id: undefined
Length: 178min 3sec (10683 seconds)
Published: Thu Aug 10 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.