feat(admin-ui): ProductCategory list page (#3380)
This commit is contained in:
5
.changeset/green-cows-bow.md
Normal file
5
.changeset/green-cows-bow.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@medusajs/admin-ui": patch
|
||||
---
|
||||
|
||||
feat(admin-ui): add empty state for product categories
|
||||
@@ -0,0 +1,29 @@
|
||||
import React from "react"
|
||||
import IconProps from "../types/icon-type"
|
||||
|
||||
const SwatchIcon: React.FC<IconProps> = ({
|
||||
size = "24px",
|
||||
color = "currentColor",
|
||||
...attributes
|
||||
}) => {
|
||||
return (
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...attributes}
|
||||
>
|
||||
<path
|
||||
d="M3.415 16.585C3.70519 16.8753 4.04973 17.1055 4.42892 17.2626C4.80812 17.4197 5.21455 17.5006 5.625 17.5006M3.415 16.585C4.00105 17.1711 4.7962 17.5006 5.625 17.5006M3.415 16.585C2.82895 15.9989 2.5 15.2038 2.5 14.375V3.4375C2.5 2.92 2.92 2.5 3.4375 2.5H7.8125C8.33 2.5 8.75 2.92 8.75 3.4375V6.83083M5.625 17.5006C6.03545 17.5006 6.44188 17.4197 6.82108 17.2626C7.20027 17.1055 7.54481 16.8753 7.835 16.585M5.625 17.5006C6.4538 17.5006 7.24895 17.1711 7.835 16.585M5.625 17.5006L16.5625 17.5C17.08 17.5 17.5 17.08 17.5 16.5625V12.1875C17.5 11.67 17.08 11.25 16.5625 11.25H13.1692M7.835 16.585L13.1692 11.25M7.835 16.585C8.42105 15.9989 8.75 15.2038 8.75 14.375V6.83083M13.1692 11.25L15.5683 8.85C15.935 8.485 15.935 7.89167 15.5683 7.525L12.475 4.43083C12.1083 4.065 11.515 4.065 11.15 4.43083L8.75 6.83083M5.625 14.375H5.63167V14.3817H5.625V14.375Z"
|
||||
stroke={color}
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default SwatchIcon
|
||||
@@ -32,13 +32,15 @@ const BodyCard: React.FC<BodyCardProps> = ({
|
||||
className,
|
||||
children,
|
||||
compact = false,
|
||||
setBorders = false,
|
||||
footerMinHeight = 24,
|
||||
...rest
|
||||
}) => {
|
||||
const { isScrolled, scrollListener } = useScroll({ threshold: 16 })
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
"rounded-rounded border bg-grey-0 border-grey-20 overflow-hidden flex flex-col h-full w-full",
|
||||
"rounded-rounded bg-grey-0 border-grey-20 flex h-full w-full flex-col overflow-hidden border",
|
||||
{ "min-h-[350px]": !compact },
|
||||
className
|
||||
)}
|
||||
@@ -46,49 +48,63 @@ const BodyCard: React.FC<BodyCardProps> = ({
|
||||
>
|
||||
<div className="relative">
|
||||
{isScrolled && (
|
||||
<div className="absolute rounded-t-rounded top-0 left-0 right-0 bg-gradient-to-b from-grey-0 to-[rgba(255,255,255,0)] h-xlarge z-10" />
|
||||
<div className="rounded-t-rounded from-grey-0 h-xlarge absolute top-0 left-0 right-0 z-10 bg-gradient-to-b to-[rgba(255,255,255,0)]" />
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className="pt-medium px-xlarge flex flex-col grow overflow-y-auto"
|
||||
className={clsx("flex grow flex-col overflow-y-auto", {
|
||||
"border-grey-20 border-b border-solid": setBorders,
|
||||
})}
|
||||
onScroll={scrollListener}
|
||||
>
|
||||
<div className="flex items-center justify-between mt-6 h-xlarge">
|
||||
{customHeader ? (
|
||||
<div>{customHeader}</div>
|
||||
) : title ? (
|
||||
<h1 className="inter-xlarge-semibold text-grey-90">{title}</h1>
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
<div
|
||||
className={clsx("px-xlarge py-large", {
|
||||
"border-grey-20 border-b border-solid": setBorders,
|
||||
})}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
{customHeader ? (
|
||||
<div>{customHeader}</div>
|
||||
) : title ? (
|
||||
<h1 className="inter-xlarge-semibold text-grey-90">{title}</h1>
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
{status && status}
|
||||
<Actionables
|
||||
actions={actionables}
|
||||
forceDropdown={forceDropdown}
|
||||
customTrigger={customActionable}
|
||||
/>
|
||||
{subtitle && (
|
||||
<h3 className="inter-small-regular text-grey-50 pt-1.5">
|
||||
{subtitle}
|
||||
</h3>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
{status && status}
|
||||
<Actionables
|
||||
actions={actionables}
|
||||
forceDropdown={forceDropdown}
|
||||
customTrigger={customActionable}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{subtitle && (
|
||||
<h3 className="inter-small-regular pt-1.5 text-grey-50">
|
||||
{subtitle}
|
||||
</h3>
|
||||
)}
|
||||
{children && (
|
||||
<div
|
||||
className={clsx("flex flex-col", {
|
||||
"my-large grow": !compact,
|
||||
})}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="px-xlarge">
|
||||
{children && (
|
||||
<div
|
||||
className={clsx("flex flex-col", {
|
||||
"my-large grow": !compact,
|
||||
})}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{events && events.length > 0 ? (
|
||||
<div className="pb-large pt-base px-xlarge border-t border-grey-20">
|
||||
<div className="flex items-center flex-row-reverse">
|
||||
<div className="pb-large pt-base px-xlarge border-grey-20 border-t">
|
||||
<div className="flex flex-row-reverse items-center">
|
||||
{events.map((event, i: React.Key) => {
|
||||
return (
|
||||
<Button
|
||||
@@ -106,7 +122,7 @@ const BodyCard: React.FC<BodyCardProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="min-h-[24px]" />
|
||||
<div className={`min-h-[${footerMinHeight}px]`} />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useAdminStore } from "medusa-react"
|
||||
import { useState } from "react"
|
||||
import React, { useState } from "react"
|
||||
import { useFeatureFlag } from "../../../providers/feature-flag-provider"
|
||||
import BuildingsIcon from "../../fundamentals/icons/buildings-icon"
|
||||
import CartIcon from "../../fundamentals/icons/cart-icon"
|
||||
@@ -8,15 +8,17 @@ import GearIcon from "../../fundamentals/icons/gear-icon"
|
||||
import GiftIcon from "../../fundamentals/icons/gift-icon"
|
||||
import SaleIcon from "../../fundamentals/icons/sale-icon"
|
||||
import TagIcon from "../../fundamentals/icons/tag-icon"
|
||||
import SwatchIcon from "../../fundamentals/icons/swatch-icon"
|
||||
import UsersIcon from "../../fundamentals/icons/users-icon"
|
||||
import SidebarMenuItem from "../../molecules/sidebar-menu-item"
|
||||
import UserMenu from "../../molecules/user-menu"
|
||||
|
||||
const ICON_SIZE = 20
|
||||
|
||||
const Sidebar = () => {
|
||||
const Sidebar: React.FC = () => {
|
||||
const [currentlyOpen, setCurrentlyOpen] = useState(-1)
|
||||
|
||||
const { isFeatureEnabled } = useFeatureFlag()
|
||||
const { store } = useAdminStore()
|
||||
|
||||
const triggerHandler = () => {
|
||||
@@ -30,8 +32,6 @@ const Sidebar = () => {
|
||||
// infinite updates, and we do not want the variable to be free floating.
|
||||
triggerHandler.id = 0
|
||||
|
||||
const { isFeatureEnabled } = useFeatureFlag()
|
||||
|
||||
const inventoryEnabled =
|
||||
isFeatureEnabled("inventoryService") &&
|
||||
isFeatureEnabled("stockLocationService")
|
||||
@@ -63,6 +63,14 @@ const Sidebar = () => {
|
||||
text={"Products"}
|
||||
triggerHandler={triggerHandler}
|
||||
/>
|
||||
{isFeatureEnabled("product_categories") && (
|
||||
<SidebarMenuItem
|
||||
pageLink={"/a/product-categories"}
|
||||
icon={<SwatchIcon size={ICON_SIZE} />}
|
||||
text={"Categories"}
|
||||
triggerHandler={triggerHandler}
|
||||
/>
|
||||
)}
|
||||
<SidebarMenuItem
|
||||
pageLink={"/a/customers"}
|
||||
icon={<UsersIcon size={ICON_SIZE} />}
|
||||
|
||||
13
packages/admin-ui/ui/src/domain/product-categories/index.tsx
Normal file
13
packages/admin-ui/ui/src/domain/product-categories/index.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Route, Routes } from "react-router-dom"
|
||||
|
||||
import ProductCategoryIndex from "./pages"
|
||||
|
||||
const ProductCategories = () => {
|
||||
return (
|
||||
<Routes>
|
||||
<Route index element={<ProductCategoryIndex />} />
|
||||
</Routes>
|
||||
)
|
||||
}
|
||||
|
||||
export default ProductCategories
|
||||
@@ -0,0 +1,54 @@
|
||||
import { createContext } from "react"
|
||||
|
||||
import BodyCard from "../../../components/organisms/body-card"
|
||||
|
||||
/**
|
||||
* Product categories empty state placeholder.
|
||||
*/
|
||||
function ProductCategoriesEmptyState() {
|
||||
return (
|
||||
<div className="flex min-h-[600px] items-center justify-center">
|
||||
<p className="text-grey-40">
|
||||
No product categories yet, use the above button to create your first
|
||||
category.
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const ProductCategoriesContext = createContext<{}>({} as any)
|
||||
|
||||
/**
|
||||
* Product category index page container.
|
||||
*/
|
||||
function ProductCategoryPage() {
|
||||
const actions = [
|
||||
{
|
||||
label: "Add category",
|
||||
onClick: () => {},
|
||||
},
|
||||
]
|
||||
|
||||
const context = {}
|
||||
|
||||
return (
|
||||
<ProductCategoriesContext.Provider value={context}>
|
||||
<div className="flex h-full grow flex-col">
|
||||
<div className="flex w-full grow flex-col">
|
||||
<BodyCard
|
||||
className="h-full"
|
||||
title="Product Categories"
|
||||
subtitle="Helps you to keep your products organized."
|
||||
actionables={actions}
|
||||
footerMinHeight={40}
|
||||
setBorders
|
||||
>
|
||||
<ProductCategoriesEmptyState />
|
||||
</BodyCard>
|
||||
</div>
|
||||
</div>
|
||||
</ProductCategoriesContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export default ProductCategoryPage
|
||||
@@ -20,6 +20,7 @@ import PublishableApiKeys from "../domain/publishable-api-keys"
|
||||
import SalesChannels from "../domain/sales-channels"
|
||||
import Settings from "../domain/settings"
|
||||
import { AnalyticsProvider } from "../providers/analytics-provider"
|
||||
import ProductCategories from "../domain/product-categories"
|
||||
|
||||
const IndexPage = () => {
|
||||
const navigate = useNavigate()
|
||||
@@ -42,6 +43,10 @@ const DashboardRoutes = () => {
|
||||
<Routes>
|
||||
<Route path="oauth/:app_name" element={<Oauth />} />
|
||||
<Route path="products/*" element={<ProductsRoute />} />
|
||||
<Route
|
||||
path="product-categories/*"
|
||||
element={<ProductCategories />}
|
||||
/>
|
||||
<Route path="collections/*" element={<Collections />} />
|
||||
<Route path="gift-cards/*" element={<GiftCards />} />
|
||||
<Route path="orders/*" element={<Orders />} />
|
||||
|
||||
Reference in New Issue
Block a user