feat(admin-ui): adds category ui for tree/list, edit, create, delete (#3399)

* chore: adds category ui for tree/list, edit, create, delete

* fix: address feedback

---------

Co-authored-by: Oliver Windall Juhl <59018053+olivermrbl@users.noreply.github.com>
Co-authored-by: fPolic <frane@medusajs.com>
This commit is contained in:
Riqwan Thamir
2023-03-07 13:29:22 +01:00
committed by GitHub
co-authored by Oliver Windall Juhl fPolic
parent 06f567f446
commit b5662d0c6a
16 changed files with 873 additions and 22 deletions
@@ -0,0 +1,29 @@
import React from "react"
import IconProps from "../types/icon-type"
const FolderOpenIcon: React.FC<IconProps> = ({
size = "20px",
color = "currentColor",
...attributes
}) => {
return (
<svg
width={size}
height={size}
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...attributes}
>
<path
d="M3.49557 8.24663C3.58387 8.23322 3.67454 8.22613 3.76678 8.22613H16.2328C16.325 8.22613 16.4157 8.23322 16.504 8.24663M3.49557 8.24663C3.03418 8.31808 2.61956 8.56856 2.34165 8.94373C2.06373 9.31891 1.94494 9.78852 2.01103 10.2507L2.68668 14.9811C2.74703 15.4037 2.95781 15.7905 3.28032 16.0703C3.60284 16.3501 4.01546 16.5041 4.44243 16.5042H15.5579C15.9849 16.5041 16.3975 16.3501 16.72 16.0703C17.0426 15.7905 17.2533 15.4037 17.3137 14.9811L17.9893 10.2507C18.0554 9.78852 17.9366 9.31891 17.6587 8.94373C17.3808 8.56856 16.9654 8.31808 16.504 8.24663M3.49557 8.24663L3.49636 5.26967C3.49636 4.79934 3.68314 4.34827 4.01564 4.01562C4.34813 3.68298 4.79912 3.496 5.26945 3.49579H8.32761C8.64115 3.49606 8.94174 3.62084 9.16331 3.84268L10.8363 5.51407C11.0578 5.73591 11.3584 5.86068 11.672 5.86096H14.7301C15.2006 5.86096 15.6518 6.04785 15.9844 6.38051C16.3171 6.71318 16.504 7.16437 16.504 7.63484V8.24663"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
)
}
export default FolderOpenIcon
@@ -0,0 +1,64 @@
import React from "react"
import IconProps from "../types/icon-type"
const ReorderIcon: React.FC<IconProps> = ({
size = "24px",
color = "currentColor",
...attributes
}) => {
return (
<svg
width={size}
height={size}
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...attributes}
>
<path
d="M7.5 10.75C7.91421 10.75 8.25 10.4142 8.25 10C8.25 9.58579 7.91421 9.25 7.5 9.25C7.08579 9.25 6.75 9.58579 6.75 10C6.75 10.4142 7.08579 10.75 7.5 10.75Z"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M7.5 5.75C7.91421 5.75 8.25 5.41421 8.25 5C8.25 4.58579 7.91421 4.25 7.5 4.25C7.08579 4.25 6.75 4.58579 6.75 5C6.75 5.41421 7.08579 5.75 7.5 5.75Z"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M7.5 15.75C7.91421 15.75 8.25 15.4142 8.25 15C8.25 14.5858 7.91421 14.25 7.5 14.25C7.08579 14.25 6.75 14.5858 6.75 15C6.75 15.4142 7.08579 15.75 7.5 15.75Z"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M12.5 10.75C12.9142 10.75 13.25 10.4142 13.25 10C13.25 9.58579 12.9142 9.25 12.5 9.25C12.0858 9.25 11.75 9.58579 11.75 10C11.75 10.4142 12.0858 10.75 12.5 10.75Z"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M12.5 5.75C12.9142 5.75 13.25 5.41421 13.25 5C13.25 4.58579 12.9142 4.25 12.5 4.25C12.0858 4.25 11.75 4.58579 11.75 5C11.75 5.41421 12.0858 5.75 12.5 5.75Z"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M12.5 15.75C12.9142 15.75 13.25 15.4142 13.25 15C13.25 14.5858 12.9142 14.25 12.5 14.25C12.0858 14.25 11.75 14.5858 11.75 15C11.75 15.4142 12.0858 15.75 12.5 15.75Z"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
)
}
export default ReorderIcon
@@ -0,0 +1,28 @@
import React from "react"
import IconProps from "../types/icon-type"
const TriangleDownIcon: React.FC<IconProps> = ({
size = "20",
color = "currentColor",
...attributes
}) => {
return (
<svg
width={size}
height={size}
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...attributes}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M13.0869 6.25006C13.7804 6.25006 14.22 7.00005 13.8859 7.61318L10.7988 13.2737C10.4525 13.9089 9.54748 13.9089 9.20123 13.2737L6.1141 7.61368C5.78 7.00054 6.21963 6.25055 6.91312 6.25055L13.0869 6.25006Z"
fill={color}
/>
</svg>
)
}
export default TriangleDownIcon
@@ -1,4 +1,4 @@
import React, { PropsWithChildren } from "react"
import { PropsWithChildren } from "react"
import { AnimatePresence, motion } from "framer-motion"
const MODAL_WIDTH = 560
@@ -43,9 +43,9 @@ function SideModal(props: SideModalProps) {
background: "white",
right: 0,
top: 0,
zIndex: 9999,
zIndex: 200,
}}
className="rounded border overflow-hidden"
className="overflow-hidden rounded border"
animate={{ right: 0 }}
exit={{ right: -MODAL_WIDTH }}
>
@@ -1,5 +1,6 @@
import clsx from "clsx"
import React, {
CSSProperties,
useContext,
useEffect,
useImperativeHandle,
@@ -31,6 +32,7 @@ type MultiSelectProps = InputHeaderProps & {
placeholder?: string
isMultiSelect?: boolean
labelledBy?: string
menuPortalStyles?: CSSProperties
options: { label: string; value: string | null; disabled?: boolean }[]
value:
| { label: string; value: string }[]
@@ -69,6 +71,7 @@ const SSelect = React.forwardRef(
placeholder = "Search...",
options,
onCreateOption,
menuPortalStyles = {},
}: MultiSelectProps,
ref
) => {
@@ -135,10 +138,10 @@ const SSelect = React.forwardRef(
<div
key={name}
className={clsx(className, {
"bg-white rounded-t-rounded": isFocussed,
"rounded-t-rounded bg-white": isFocussed,
})}
>
<div className="w-full flex text-grey-50 pr-0.5 justify-between pointer-events-none cursor-pointer mb-2">
<div className="text-grey-50 pointer-events-none mb-2 flex w-full cursor-pointer justify-between pr-0.5">
<InputHeader {...{ label, required, tooltip, tooltipContent }} />
</div>
@@ -176,7 +179,9 @@ const SSelect = React.forwardRef(
}}
closeMenuOnSelect={!isMultiSelect}
blurInputOnSelect={!isMultiSelect}
styles={{ menuPortal: (base) => ({ ...base, zIndex: 60 }) }}
styles={{
menuPortal: (base) => ({ ...base, ...menuPortalStyles }),
}}
hideSelectedOptions={false}
menuPortalTarget={portalRef?.current?.lastChild || document.body}
menuPlacement="auto"
@@ -188,7 +193,7 @@ const SSelect = React.forwardRef(
components={SelectComponents}
/>
}
{isFocussed && enableSearch && <div className="w-full h-5" />}
{isFocussed && enableSearch && <div className="h-5 w-full" />}
</div>
</div>
)
@@ -1,5 +1,6 @@
import { useAdminStore } from "medusa-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"
@@ -0,0 +1,127 @@
import React, { useCallback, useMemo, useState } from "react"
import Nestable from "react-nestable"
import { dropRight, get, flatMap } from "lodash"
import "react-nestable/dist/styles/index.css"
import "../styles/product-categories.css"
import { ProductCategory } from "@medusajs/medusa"
import { adminProductCategoryKeys, useMedusa } from "medusa-react"
import TriangleMiniIcon from "../../../components/fundamentals/icons/triangle-mini-icon"
import ProductCategoryListItemDetails from "./product-category-list-item-details"
import ReorderIcon from "../../../components/fundamentals/icons/reorder-icon"
import { useQueryClient } from "@tanstack/react-query"
import useNotification from "../../../hooks/use-notification"
type ProductCategoriesListProps = {
categories: ProductCategory[]
}
/**
* Draggable list that renders product categories tree view.
*/
function ProductCategoriesList(props: ProductCategoriesListProps) {
const { client } = useMedusa()
const queryClient = useQueryClient()
const notification = useNotification()
const [isUpdating, setIsUpdating] = useState(false)
const { categories } = props
const onItemDrop = useCallback(
async (params: {
item: ProductCategory
items: ProductCategory[]
path: number[]
}) => {
setIsUpdating(true)
let parentId = null
const { dragItem, items, targetPath } = params
const [rank] = targetPath.slice(-1)
if (targetPath.length > 1) {
const path = dropRight(
flatMap(targetPath.slice(0, -1), (item) => [
item,
"category_children",
])
)
const newParent = get(items, path)
parentId = newParent.id
}
try {
await client.admin.productCategories.update(dragItem.id, {
parent_category_id: parentId,
rank,
})
notification("Success", "New order saved", "success")
await queryClient.invalidateQueries(adminProductCategoryKeys.lists())
} catch (e) {
notification("Error", "Failed to save new order", "error")
} finally {
setIsUpdating(false)
}
},
[]
)
const NestableList = useMemo(
() => (
<Nestable
collapsed
items={categories}
onChange={onItemDrop}
childrenProp="category_children"
renderItem={({ item, depth, handler, collapseIcon }) => (
<ProductCategoryListItemDetails
item={item}
depth={depth}
handler={handler}
collapseIcon={collapseIcon}
/>
)}
handler={<ReorderIcon className="cursor-grab" color="#889096" />}
renderCollapseIcon={({ isCollapsed }) => (
<TriangleMiniIcon
style={{
top: -2,
width: 32,
left: -12,
transform: !isCollapsed ? "" : "rotate(270deg)",
}}
color="#889096"
size={18}
/>
)}
/>
),
[categories]
)
return (
<div
style={{
pointerEvents: isUpdating ? "none" : "initial",
position: "relative",
}}
>
{NestableList}
{isUpdating && (
<div
style={{
top: 0,
bottom: 0,
width: "100%",
cursor: "progress",
position: "absolute",
}}
/>
)}
</div>
)
}
export default React.memo(ProductCategoriesList) // Memo prevents list flicker on reorder
@@ -0,0 +1,132 @@
import React, { useContext } from "react"
import clsx from "clsx"
import { ProductCategory } from "@medusajs/medusa"
import { useAdminDeleteProductCategory } from "medusa-react"
import { ProductCategoriesContext } from "../pages"
import Tooltip from "../../../components/atoms/tooltip"
import Button from "../../../components/fundamentals/button"
import Actionables from "../../../components/molecules/actionables"
import TrashIcon from "../../../components/fundamentals/icons/trash-icon"
import EditIcon from "../../../components/fundamentals/icons/edit-icon"
import PlusIcon from "../../../components/fundamentals/icons/plus-icon"
import FolderOpenIcon from "../../../components/fundamentals/icons/folder-open-icon"
import TagIcon from "../../../components/fundamentals/icons/tag-icon"
import MoreHorizontalIcon from "../../../components/fundamentals/icons/more-horizontal-icon"
import useNotification from "../../../hooks/use-notification"
type ProductCategoryListItemDetailsProps = {
depth: number
item: ProductCategory
handler: React.ReactNode
collapseIcon: React.ReactNode
}
function ProductCategoryListItemDetails(
props: ProductCategoryListItemDetailsProps
) {
const { item } = props
const notification = useNotification()
const hasChildren = !!item.category_children?.length
const productCategoriesPageContext = useContext(ProductCategoriesContext)
const { mutateAsync: deleteCategory } = useAdminDeleteProductCategory(item.id)
const actions = [
{
label: "Edit",
onClick: () => productCategoriesPageContext.editCategory(item),
icon: <EditIcon size={20} />,
},
{
label: "Delete",
variant: "danger",
onClick: async () => {
try {
await deleteCategory()
notification("Success", "Category deleted", "success")
} catch (e) {
notification("Error", "Category deletion failed", "error")
}
},
icon: <TrashIcon size={20} />,
disabled: !!item.category_children.length,
},
]
return (
<div className="bg-white">
<div
style={{ marginLeft: props.depth * -8 }}
className="flex h-[40px] items-center"
>
<div className="flex w-[32px] items-center justify-center">
{props.handler}
</div>
<div className="flex w-full items-center justify-between">
<div className="flex items-center">
{hasChildren && (
<div className="flex w-[32px] items-center justify-center">
{props.collapseIcon}
</div>
)}
<div className="flex w-[32px] items-center justify-center">
{hasChildren && <FolderOpenIcon color="#889096" size={18} />}
{!hasChildren && <TagIcon color="#889096" size={18} />}
</div>
<span
className={clsx("ml-2 select-none text-xs font-medium", {
"font-normal text-gray-400": !hasChildren,
})}
>
{item.name}
</span>
</div>
<div className="flex items-center gap-2">
<Tooltip
style={{ zIndex: 1 }}
content={
<>
Add category item to{" "}
<span className="text-grey-80 font-semibold">
{`"${item.name}"`}
</span>
</>
}
>
<Button
size="small"
variant="ghost"
onClick={() =>
productCategoriesPageContext.createSubCategory(item)
}
>
<PlusIcon color="#687076" size={18} />
</Button>
</Tooltip>
<Actionables
forceDropdown
actions={actions}
customTrigger={
<Button
size="small"
variant="ghost"
className="h-xlarge w-xlarge focus-visible:border-violet-60 focus-visible:shadow-input focus:shadow-none focus-visible:outline-none"
>
<MoreHorizontalIcon color="#687076" size={20} />
</Button>
}
/>
</div>
</div>
</div>
</div>
)
}
export default ProductCategoryListItemDetails
@@ -0,0 +1,145 @@
import { useState } from "react"
import { ProductCategory } from "@medusajs/medusa"
import {
adminProductCategoryKeys,
useAdminCreateProductCategory,
} from "medusa-react"
import useNotification from "../../../hooks/use-notification"
import FocusModal from "../../../components/molecules/modal/focus-modal"
import Button from "../../../components/fundamentals/button"
import CrossIcon from "../../../components/fundamentals/icons/cross-icon"
import InputField from "../../../components/molecules/input"
import Select from "../../../components/molecules/select"
import { useQueryClient } from "@tanstack/react-query"
const visibilityOptions = [
{
label: "Public",
value: "public",
},
{ label: "Private", value: "private" },
]
const statusOptions = [
{ label: "Active", value: "active" },
{ label: "Inactive", value: "inactive" },
]
type CreateProductCategoryProps = {
closeModal: () => void
parentCategory?: ProductCategory
}
/**
* Focus modal container for creating Publishable Keys.
*/
function CreateProductCategory(props: CreateProductCategoryProps) {
const { closeModal, parentCategory } = props
const notification = useNotification()
const queryClient = useQueryClient()
const [name, setName] = useState("")
const [handle, setHandle] = useState("")
const [isActive, setIsActive] = useState(true)
const [isPublic, setIsPublic] = useState(true)
const { mutateAsync: createProductCategory } = useAdminCreateProductCategory()
const onSubmit = async () => {
try {
await createProductCategory({
name,
handle,
is_active: isActive,
is_internal: !isPublic,
parent_category_id: parentCategory?.id ?? null,
})
// TODO: temporary here, investigate why `useAdminCreateProductCategory` doesn't invalidate this
await queryClient.invalidateQueries(adminProductCategoryKeys.lists())
closeModal()
notification("Success", "Created a new product category", "success")
} catch (e) {
notification("Error", "Failed to create a new product category", "error")
}
}
return (
<FocusModal>
<FocusModal.Header>
<div className="medium:w-8/12 flex w-full justify-between px-8">
<Button size="small" variant="ghost" onClick={closeModal}>
<CrossIcon size={20} />
</Button>
<div className="gap-x-small flex">
<Button
size="small"
variant="primary"
onClick={onSubmit}
disabled={!name}
className="rounded-rounded"
>
Save category
</Button>
</div>
</div>
</FocusModal.Header>
<FocusModal.Main className="no-scrollbar flex w-full justify-center">
<div className="small:w-4/5 medium:w-7/12 large:w-6/12 my-16 max-w-[700px]">
<h1 className="inter-xlarge-semibold text-grey-90 pb-8">
Add category {parentCategory && `to ${parentCategory.name}`}
</h1>
<h4 className="inter-large-semibold text-grey-90 pb-1">Details</h4>
<div className="mb-8 flex justify-between gap-6">
<InputField
label="Name"
type="string"
name="name"
value={name}
className="w-[338px]"
placeholder="Give this category a name"
onChange={(ev) => setName(ev.target.value)}
/>
<InputField
label="Handle"
type="string"
name="handle"
value={handle}
className="w-[338px]"
placeholder="Custom handle"
onChange={(ev) => setHandle(ev.target.value)}
/>
</div>
<div className="mb-8 flex justify-between gap-6">
<div className="flex-1">
<Select
label="Status"
options={statusOptions}
menuPortalStyles={{ zIndex: 300 }}
value={statusOptions[isActive ? 0 : 1]}
onChange={(o) => setIsActive(o.value === "active")}
/>
</div>
<div className="flex-1">
<Select
label="Visibility"
options={visibilityOptions}
menuPortalStyles={{ zIndex: 300 }}
value={visibilityOptions[isPublic ? 0 : 1]}
onChange={(o) => setIsPublic(o.value === "public")}
/>
</div>
</div>
</div>
</FocusModal.Main>
</FocusModal>
)
}
export default CreateProductCategory
@@ -0,0 +1,158 @@
import { useEffect, useState } from "react"
import { ProductCategory } from "@medusajs/medusa"
import { useAdminUpdateProductCategory } from "medusa-react"
import SideModal from "../../../components/molecules/modal/side-modal"
import Button from "../../../components/fundamentals/button"
import CrossIcon from "../../../components/fundamentals/icons/cross-icon"
import InputField from "../../../components/molecules/input"
import Select from "../../../components/molecules/select"
import useNotification from "../../../hooks/use-notification"
const visibilityOptions = [
{
label: "Public",
value: "public",
},
{ label: "Private", value: "private" },
]
const statusOptions = [
{ label: "Active", value: "active" },
{ label: "Inactive", value: "inactive" },
]
type EditProductCategoriesSideModalProps = {
activeCategory: ProductCategory
close: () => void
isVisible: boolean
}
/**
* Modal for editing product categories
*/
function EditProductCategoriesSideModal(
props: EditProductCategoriesSideModalProps
) {
const { isVisible, close, activeCategory } = props
const [name, setName] = useState("")
const [handle, setHandle] = useState("")
const [isActive, setIsActive] = useState(true)
const [isPublic, setIsPublic] = useState(true)
const notification = useNotification()
const { mutateAsync: updateCategory } = useAdminUpdateProductCategory(
activeCategory?.id
)
useEffect(() => {
if (activeCategory) {
setName(activeCategory.name)
setHandle(activeCategory.handle)
setIsActive(activeCategory.is_active)
setIsPublic(!activeCategory.is_internal)
}
}, [activeCategory])
const onSave = async () => {
try {
await updateCategory({
name,
handle,
is_active: isActive,
is_internal: !isPublic,
})
notification("Success", "Product category updated", "success")
} catch (e) {
notification("Error", "Failed to update the category", "error")
}
close()
}
const onClose = () => {
close()
}
return (
<SideModal close={onClose} isVisible={!!isVisible}>
<div className="flex h-full flex-col justify-between p-6">
{/* === HEADER === */}
<div className="flex items-center justify-between">
<h3 className="inter-large-semibold flex items-center gap-2 text-xl text-gray-900">
Edit product category
</h3>
<Button
variant="secondary"
className="h-8 w-8 p-2"
onClick={props.close}
>
<CrossIcon size={20} className="text-grey-50" />
</Button>
</div>
{/* === DIVIDER === */}
<div className="flex-grow">
<InputField
label="Name"
type="string"
name="name"
value={name}
className="my-6"
placeholder="Give this category a name"
onChange={(ev) => setName(ev.target.value)}
/>
<InputField
label="Handle"
type="string"
name="handle"
value={handle}
className="my-6"
placeholder="Custom handle"
onChange={(ev) => setHandle(ev.target.value)}
/>
<Select
label="Status"
options={statusOptions}
menuPortalStyles={{ zIndex: 300 }}
value={statusOptions[isActive ? 0 : 1]}
onChange={(o) => setIsActive(o.value === "active")}
/>
<Select
className="my-6"
label="Visibility"
options={visibilityOptions}
menuPortalStyles={{ zIndex: 300 }}
value={visibilityOptions[isPublic ? 0 : 1]}
onChange={(o) => setIsPublic(o.value === "public")}
/>
</div>
{/* === DIVIDER === */}
<div
className="block h-[1px] bg-gray-200"
style={{ margin: "24px -24px" }}
/>
{/* === FOOTER === */}
<div className="flex justify-end gap-2">
<Button size="small" variant="ghost" onClick={onClose}>
Cancel
</Button>
<Button size="small" variant="primary" onClick={onSave}>
Save and close
</Button>
</div>
</div>
</SideModal>
)
}
export default EditProductCategoriesSideModal
@@ -1,6 +1,13 @@
import { createContext } from "react"
import { createContext, useState } from "react"
import { ProductCategory } from "@medusajs/medusa"
import { useAdminProductCategories } from "medusa-react"
import useToggleState from "../../../hooks/use-toggle-state"
import BodyCard from "../../../components/organisms/body-card"
import CreateProductCategory from "../modals/add-product-category"
import ProductCategoriesList from "../components/product-categories-list"
import EditProductCategoriesSideModal from "../modals/edit-product-category"
/**
* Product categories empty state placeholder.
@@ -16,20 +23,58 @@ function ProductCategoriesEmptyState() {
)
}
export const ProductCategoriesContext = createContext<{}>({} as any)
export const ProductCategoriesContext = createContext<{
editCategory: (category: ProductCategory) => void
createSubCategory: (category: ProductCategory) => void
}>({} as any)
/**
* Product category index page container.
*/
function ProductCategoryPage() {
const {
state: isCreateModalVisible,
open: showCreateModal,
close: hideCreateModal,
} = useToggleState()
const {
state: isEditModalVisible,
open: showEditModal,
close: hideEditModal,
} = useToggleState()
const [activeCategory, setActiveCategory] = useState<ProductCategory>()
const { product_categories: categories, isLoading } =
useAdminProductCategories({
parent_category_id: "null",
include_descendants_tree: true,
})
const actions = [
{
label: "Add category",
onClick: () => {},
onClick: showCreateModal,
},
]
const context = {}
const showPlaceholder = !isLoading && !categories?.length
const editCategory = (category: ProductCategory) => {
setActiveCategory(category)
showEditModal()
}
const createSubCategory = (category: ProductCategory) => {
setActiveCategory(category)
showCreateModal()
}
const context = {
editCategory,
createSubCategory,
}
return (
<ProductCategoriesContext.Provider value={context}>
@@ -43,8 +88,27 @@ function ProductCategoryPage() {
footerMinHeight={40}
setBorders
>
<ProductCategoriesEmptyState />
{showPlaceholder ? (
<ProductCategoriesEmptyState />
) : isLoading ? null : (
<ProductCategoriesList categories={categories!} />
)}
</BodyCard>
{isCreateModalVisible && (
<CreateProductCategory
parentCategory={activeCategory}
closeModal={() => {
hideCreateModal()
setActiveCategory(undefined)
}}
/>
)}
<EditProductCategoriesSideModal
close={hideEditModal}
activeCategory={activeCategory}
isVisible={!!activeCategory && isEditModalVisible}
/>
</div>
</div>
</ProductCategoriesContext.Provider>
@@ -0,0 +1,23 @@
.nestable-item.is-dragging:before {
content: ' ';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: transparent;
transition: .3s all;
border-radius: 0;
border: 1px dashed #6E56CF;
z-index: 1000;
}
.nestable-item.is-dragging * {
height: 40px!important;
min-height: 40px!important;
max-height: 40px!important;
}
@@ -4,7 +4,6 @@ import { PublishableApiKey, SalesChannel } from "@medusajs/medusa"
import {
useAdminAddPublishableKeySalesChannelsBatch,
useAdminCreatePublishableApiKey,
useAdminPublishableApiKeySalesChannels,
} from "medusa-react"
import Breadcrumb from "../../../components/molecules/breadcrumb"
@@ -40,7 +39,7 @@ function AddSalesChannelsSection(props: AddSalesChannelsSectionProps) {
return (
<div>
<div className="flex justify-between items-center">
<div className="flex items-center justify-between">
<div>
<h5 className="inter-base-semibold text-grey-90 pb-1">
Sales channels
@@ -63,8 +62,8 @@ function AddSalesChannelsSection(props: AddSalesChannelsSectionProps) {
{hasSelectedChannels && (
<div className="mt-10 flex items-center justify-between">
<div className="flex items-center gap-4">
<div className="rounded p-1 border">
<div className="bg-gray-100 p-2 rounded">
<div className="rounded border p-1">
<div className="rounded bg-gray-100 p-2">
<ChannelsIcon />
</div>
</div>
@@ -151,7 +150,7 @@ function CreatePublishableKey(props: CreatePublishableKeyProps) {
return (
<FocusModal>
<FocusModal.Header>
<div className="medium:w-8/12 w-full px-8 flex justify-between">
<div className="medium:w-8/12 flex w-full justify-between px-8">
<Button size="small" variant="ghost" onClick={closeModal}>
<CrossIcon size={20} />
</Button>
@@ -169,8 +168,8 @@ function CreatePublishableKey(props: CreatePublishableKeyProps) {
</div>
</FocusModal.Header>
<FocusModal.Main className="w-full no-scrollbar flex justify-center">
<div className="medium:w-7/12 large:w-6/12 small:w-4/5 max-w-[700px] my-16">
<FocusModal.Main className="no-scrollbar flex w-full justify-center">
<div className="medium:w-7/12 large:w-6/12 small:w-4/5 my-16 max-w-[700px]">
<h1 className="inter-xlarge-semibold text-grey-90 pb-8">
Create API Key
</h1>
@@ -191,7 +190,7 @@ function CreatePublishableKey(props: CreatePublishableKeyProps) {
onChange={(ev) => setName(ev.target.value)}
/>
<div className="w-[100%] h-[1px] bg-gray-200 mt-16 mb-8" />
<div className="mt-16 mb-8 h-[1px] w-[100%] bg-gray-200" />
<AddSalesChannelsSection
selectedChannels={selectedChannels}