Skip to content
This repository has been archived by the owner on Jul 10, 2023. It is now read-only.

feat: product categories page #890

Closed
wants to merge 30 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
19ecccc
chore: empty state categories list page
riqwan Feb 8, 2023
c2a0b4e
chore: add empty page state for product categories
riqwan Feb 8, 2023
f76f16f
feat: create category flow
Feb 15, 2023
f299c71
wip: draggable categories list + icons
Feb 16, 2023
111d3c2
wip: category list item, edit category modal wip, logic for rendering…
Feb 16, 2023
f35bde6
wip: draggable list & collapsable items
Feb 18, 2023
fce4078
wip: edit categories UI
Feb 19, 2023
5972a5e
fix: fields width
Feb 19, 2023
79092cf
feat: toggle category on reorder start, hide all descendants on toggle
Feb 20, 2023
85a8e2e
feat: polish line item details
Feb 21, 2023
12f8e72
wip: setup react nestable and render list
Feb 22, 2023
c6463fc
feat: restructure rendering, cleanup components, connect target elements
Feb 22, 2023
4f07c2a
feat: tree levels padding
Feb 23, 2023
b7b7058
wip: saving changes to BD, fix medusa react imports, placeholder sizing
Feb 23, 2023
fb18437
fix: nested children association
Feb 23, 2023
0d7fe24
fix: edit modal spacing, fix z index layers for select
Feb 23, 2023
a4dfe38
fix: order top level categories
Feb 23, 2023
63e9aec
feat: refactor `onItemDrop`, add notifications
Feb 23, 2023
67404a6
feat: change sidebar, fix flickering on load
Feb 27, 2023
f0081da
fix: display select popup above create modal
Feb 27, 2023
e53a3f8
fix: invalidate list endpoint when creating a new category
Feb 28, 2023
46f7e62
chore: added ranking to the tree
riqwan Mar 1, 2023
f99924f
Merge branch 'feat/categories-list-page-empty' into feat/categories-e…
riqwan Mar 2, 2023
1b1ee27
chore: remove duplicate import
riqwan Mar 2, 2023
d5f007d
chore: rename rank to position
riqwan Mar 3, 2023
65e046e
added handle to create
riqwan Mar 3, 2023
c4926cc
fix: prevent list flicker on reorder
Mar 6, 2023
3cd726b
feat: disable reorder while update is running, memo the list
Mar 6, 2023
47f3376
fix: display notification on category deletion
Mar 6, 2023
b65faff
Merge branch 'develop' into feat/categories-empty-state
riqwan Mar 6, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
"react-hotkeys-hook": "^3.4.7",
"react-json-tree": "^0.17.0",
"react-jwt": "^1.1.4",
"react-nestable": "^2.0.0",
"react-router-dom": "^6.4.2",
"react-select": "^5.5.4",
"react-table": "^7.7.0",
Expand Down
29 changes: 29 additions & 0 deletions src/components/fundamentals/icons/folder-open-icon/index.tsx
Original file line number Diff line number Diff line change
@@ -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
64 changes: 64 additions & 0 deletions src/components/fundamentals/icons/reorder-icon/index.tsx
Original file line number Diff line number Diff line change
@@ -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
28 changes: 28 additions & 0 deletions src/components/fundamentals/icons/triangle-mini-icon/index.tsx
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion src/components/molecules/modal/side-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ function SideModal(props: SideModalProps) {
background: "white",
right: 0,
top: 0,
zIndex: 9999,
zIndex: 200,
}}
className="rounded border overflow-hidden"
animate={{ right: 0 }}
Expand Down
7 changes: 6 additions & 1 deletion src/components/molecules/select/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import clsx from "clsx"
import React, {
CSSProperties,
useContext,
useEffect,
useImperativeHandle,
Expand Down Expand Up @@ -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 }[]
Expand Down Expand Up @@ -69,6 +71,7 @@ const SSelect = React.forwardRef(
placeholder = "Search...",
options,
onCreateOption,
menuPortalStyles = {},
}: MultiSelectProps,
ref
) => {
Expand Down Expand Up @@ -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"
Expand Down
3 changes: 2 additions & 1 deletion src/components/organisms/sidebar/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useAdminStore } from "medusa-react"
import React, { useState } from "react"

import { useFeatureFlag } from "../../../context/feature-flag"
import BuildingsIcon from "../../fundamentals/icons/buildings-icon"
import CartIcon from "../../fundamentals/icons/cart-icon"
Expand Down Expand Up @@ -67,7 +68,7 @@ const Sidebar: React.FC = () => {
<SidebarMenuItem
pageLink={"/a/product-categories"}
icon={<SwatchIcon size={ICON_SIZE} />}
text={"Product Categories"}
text={"Categories"}
triggerHandler={triggerHandler}
/>
)}
Expand Down
127 changes: 127 additions & 0 deletions src/domain/product-categories/components/product-categories-list.tsx
Original file line number Diff line number Diff line change
@@ -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
Loading