Admin Development | Medusa Development Course
By Viktor Holik
By Viktor Holik
Welcome to our Medusa Development Course! In this series, you'll learn how to get your development environment ready and take your first steps in building a Medusa-powered e-commerce application. We'll walk you through everything step by step, helping you master Medusa’s modular architecture and build the key features you need.
This course was made by Viktor Holik, our Software Engineer and Medusa Expert.
In the last episode, we will focus on building the user interface for managing suppliers and updating product suppliers in Medusa’s admin panel.
You can find the transcription of the guide below.
In this chapter, we're going to build the UI for managing suppliers and changing the supplier for a particular product.
But before we dive into that, let's make a few changes to the backend. First, let's remove subscribers. Also, as you might remember, we extended the product creation validator and added an additional field. We will rename that field to supplier ID, because, as you know, we have established a menu to one relationship between product and suppliers.
Next, we'll clean up the code by removing any unused middlewares and imports. We'll also remove any unused resources in supplier service, like loggedInCustomer. Also, let's not forget about removing it from the injected dependencies type.
In earlier chapters, we added the supplier relationships to the store product endpoints. They extend the product responses with the supplier fields. Now, let's do the same for the admin product endpoints.
For the admin product case, we just need to add the supplier field to the default admin product relations array. This will ensure that when we hit the admin products endpoints, the supplier field is included in the response.
I will look in as an admin postman and check the admin products endpoint response, and as we can see here, we have the supplier field. We will also modify the update product endpoint validator to include supplier ID as an option. To do this, we'll extend the admin postproducts product requests from the @medusajs/medusa package.
Similarly, we will make the supplier ID field optional in the create product validator as well. Don't forget to register the updated product validator in the API index.ts file.
Once those changes are in place, let's test everything postman. I will log in as an admin, and now let's check the current supplier ID in the product update response.
Now, let's pick your random supplier ID from the store supplier's response, and update the product with the supplier field using the selected supplier ID. And yes, it works fine.
Next, let's uncomment the admin plugin from the medusa-config.js, start the backend, and go to localhost7001, and log in as an admin. My idea is to add the supplier selection widget on the product details page over here. But first, we need to create a page where we can manage suppliers that is where we can create and delete suppliers in our store.
To begin, we'll create the supplier's folder inside the admin routes, and add the page.tsx file. Inside this file, we'll define a component called SupplierListTable, and also as a props, it would take the RouteProps from medusajs admin. Next, let's export this component by default.
Additionally, we will export the page configuration as a config type let's use RouteConfig, also from medusajs admin package. And for this side by label, we will name it Suppliers. We should also define an icon. For this particular case, I'll be using the Medusa.js icon library. So let's head over to Medusa UI icon library, and click on the icons. I think RocketLaunch icon is a perfect fit for this. We will import it from @medusajs/icons, and add it to the configuration.
Next, ensure that the component returns and div instead of a simple string to avoid errors when rendering, and once that's done, we can start the backend server. Now, when we'll log in, we should see a supplier's button in the sidebar displaying the selected icon and the text that's inside the div.
The next step is to create the supplier's table. For this, we'll use the Medusa UI library table components. I will copy an example from the library and paste it into our code base. Let's just copy the component body and paste it in the supplier's destable body. We will then modify the table headers to make the supplier data. For this supplier reference, we'll use the supplier model. The columns will include name, contact person, and the last column will be reserved for actions, such as deleting supplier routes.
Let's also modify the table cell we'll use. The supplier data will set up a custom hook using useAdminCustomQuery from the medusa-react package. The first argument for this hook will be the admin backend route, which in our case is suppliers. The second argument will be the query key and the third one query parameters, won't be needed for this case.
Now, let's define a type for the API response, which we will call SupplierListResponse. The supplier's fields will have the type of a supplier model, we will then map this data to populate the table body. The first argument of useAdminCustomQuery generic would be the query parameters type. In our case, it would be just the record string unknown. And the second type would be the API endpoint response. In our case, it would be the supplier's response. Let's use the data from this hook to populate the table body.
Lastly, I have found a useful loading state snippet from the Medusa repo, which I will copy and incorporate for handling the loading state. This is how it looks like on the UI side. To make the table more presentable, I would wrap it with the container component from the Medusa.js UI package. Additionally, I would add title labeled suppliers inside the container. This is how it looks like.
Let's also add some top margins to the table for spacing. To style the title, we will use the text component from the same Medusa UI package and define the weight and size using its component props. This makes the table look much more organized in the Admin panel. Next, we will remove the unused text write class from the header cell.
Now, let's create dropdown action menu for the supplier rows. For this, I would use the dropdown menu component from the documentation and let's copy this example. I would rename it from dropdown menu to actions menu and move the imports to the top file, ensuring everything is correctly imported. Since we are only implementing a delete action, I would remove any unused triggers.
Next, I will define an ID prop in the component and use the useAdminCustomDelete hook to create a mutation function to delete the supplier. For proper revalidation, we will use the same query key as defined in use admin query key, which in our case is supplier.
Additionally, I would add a function to the action component props to provide a feedback. We will also define a success callback in the mutation hook, display a message saying supplier deleted successfully. Let's also add this mutate function to the on-click handler on the dropdown menu item.
Finally, we will add this actions menu as a table cell instead of feature actions text and pass the required props to make everything functional. Now let's see how it works in the admin panel. Let's delete some supplier and as you can see here, the whole list of suppliers is getting revalidated.
Now, I want to define a widget on a product details page that allows us to select a particular supplier. According to the admin widget documentation in Medusa, we can find an injection zone that corresponds with the product details page, in our case the injection zone as product details after.
First, under the widget folder, I will create a new file and name it product-suppliers.tsx. In this file, I will define a new component. As props, it will take product details widget props from the Medusa.js admin package. With this, we can extract the correct data from the widget props.
Next, we will define a config object and its type will be widget config, also from Medusa.js admin package. And let's add our zone, in our case it will be product details after. After seeing this up, we will export the component as a default and return some placeholder text in a div within the component body. Once this is done, when we navigate to the product details page, we should see our component displayed.
Now, let's visit the Medusa UI library page to look up the select component. We'll copy the example and paste it inside the value of our widget component. Let's import select component from Medusa.js library. And also, let's modify the placeholder to something more relevant like select supplier.
Next, we'll access the current supplier from the product data provided in the component props. However, we currently see that the supplier type is defined as any. We need to fix this by navigating to the index.tts file and correcting the imports relative pass. After this adjustment, when we go back to the widget file, we'll have the proper supplier definition in the data.
Now, let's import use admin update product from the Medusa react package and use it to update the product supplier. As the first argument of that hook, we should pass the product ID. We'll also add a message to notify the user when the product supplier is successfully updated.
To fetch all suppliers, we'll use the use AdminCustomCreate hook. To keep our code clean, we'll refactor the code base by moving the supplier list endpoint type into a shared types file. Then, we'll import the supplier model and fix the relative import pass. Finally, we'll import the supplier type into both the supplier stable and our new widget ensure consistency.
Now, let's use data from the supplier list endpoint to display all available suppliers. We'll also use the on-value change handler to keep track of the supplier changes. Currently, the update product mutate function doesn't recognize the supplier ID fields extending in the validators of an endpoint. If we look at the Medusa react package, we can see that the function uses the AdminPostProducts product request class. To handle this, we'll extend type definition of that class by updating index.ts file and add the supplier ID fields to it.
Now, as you can see, I can pass the supplier ID as a string and I will do that, but also when I change the supplier ID type to number, I would get an error type.
In addition, we'll add a loading state for the listing endpoint to improve user experience. Let's also import the icon from the suggest icons. Finally, let's add a default value for the supplier select using the current supplier variable to prep the field. Also, let's not forget that current supplier could be no.
Let's now check if everything is working correctly. Let's change the supplier and now we should be able to see the supplier change reflected in the body.
To improve the layout, let's wrap the whole select with the container components and add a title using the text component that we have used before. We'll also wrap the supplier select fields inside the diff and apply it soft margin to make it look cleaner. This is how the result looks like.
Next, instead of placing the supplier list table under the sidebar, we will move it to the settings page. To do this, we need to adjust the folder routes under the admin directory, changing it to the settings. We also should modify the config type to settings config and moving the label and icon into the cart object,and also we should add a description. Nw, after refreshing the page and navigating to the settings, we should see our changes reflected.
With these enhancements, you now have a fully functional UI for managing suppliers in Medusa’s admin panel, making it easy to add, view, and delete suppliers. We’ve also successfully integrated a supplier selection widget on the product details page, streamlining the process of assigning suppliers to products.
We hope you found Viktor's tutorial insightful and helpful.
Welcome to our Medusa Development Course! Create and customize subscribers to respond to key events, like sending an email when an order is placed.
Welcome to our Medusa Development Course! Explore how loaders work within Medusa core and create a custom loader.