Skip to content

Commit

Permalink
feat(dashboard, medusa, medusa-js, medusa-react, icons): DataGrid, pa…
Browse files Browse the repository at this point in the history
…rtial Product domain, and ProductVariant hook (#6428)

The PR for the Products section is growing quite large, so I would like to merge this PR that contains a lot of the ground work before moving onto finalizing the rest of the domain.

**Note**
Since the PR contains changes to the core, that the dashboard depends on, the staging env will not work. To preview this PR, you will need to run it locally. 

## `@medusajs/medusa`

**What**
- Adds missing query params to `GET /admin/products/:id/variants`
- `options.values` has been added to the default relations of admin product endpoints.

## `medusa-react`

**What**
- Adds missing hook for `GET /admin/products/:id/variants`

## `@medusajs/dashboard`
- Adds base implementation for `DataGrid` component (formerly `BulkEditor`) (WIP)
- Adds `/products` overview page
- Adds partial `/products/create` page for creating new products (WIP - need to go over design w/ Ludvig before continuing)
- Adds `/products/:id` details page
- Adds `/products/:id/gallery` page for inspecting a products images in fullscreen.
- Adds `/products/:id/edit` page for editing the general information of a product
- Adds `/products/:id/attributes` page for editing the attributes information of a product
- Adds `/products/:id/sales-channels` page for editing which sales channels a product is available in
- Fixes a bug in `DataTable` where a table with two fixed columns would not display correctly

For the review its not important to test the DataGrid, as it is still WIP, and I need to go through some minor changes to the behaviour with Ludvig, as virtualizing it adds some constraints.

## `@medusajs/icons`

**What**
- Pulls latest icons from Figma

## TODO in next PR
- [ ] Fix the typing of POST /admin/products/:id as it is currently not possible to delete any of the nullable fields once they have been added. Be aware of this when reviewing this PR.
- [ ] Wrap up `/products/create` page
- [ ] Add `/products/:id/media` page for managing media associated with the product.
- [ ] Add `/products/id/options` for managing product options (need Ludvig to rethink this as the current API is very limited and we can implement the current design as is.)
- [ ] Add `/products/:id/variants/:id` page for editing a variant. (Possibly concat all of these into one BulkEditor page?)
  • Loading branch information
kasperkristensen authored Feb 21, 2024
1 parent c3e3022 commit 44d43e8
Show file tree
Hide file tree
Showing 131 changed files with 5,798 additions and 655 deletions.
9 changes: 9 additions & 0 deletions .changeset/three-spiders-flash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@medusajs/client-types": patch
"@medusajs/icons": patch
"medusa-react": patch
"@medusajs/medusa-js": patch
"@medusajs/medusa": patch
---

feat(medusa,medusa-js,medusa-react,icons): Fixes GET /admin/products/:id/variants endpoint in the core, and medusa-js and medusa-react. Pulls latest icons from Figma into `@medusajs/icons`.
2 changes: 2 additions & 0 deletions packages/admin-next/dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"@radix-ui/react-hover-card": "^1.0.7",
"@tanstack/react-query": "4.22.0",
"@tanstack/react-table": "8.10.7",
"@tanstack/react-virtual": "^3.0.4",
"@uiw/react-json-view": "2.0.0-alpha.10",
"cmdk": "^0.2.0",
"date-fns": "^3.2.0",
Expand All @@ -38,6 +39,7 @@
"react-hook-form": "7.49.1",
"react-i18next": "13.5.0",
"react-jwt": "^1.2.0",
"react-resizable-panels": "^2.0.9",
"react-router-dom": "6.20.1",
"zod": "3.22.4"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,47 @@
"cancel": "Cancel",
"save": "Save",
"continue": "Continue",
"edit": "Edit"
"edit": "Edit",
"download": "Download"
},
"errorBoundary": {
"badRequestTitle": "Bad request",
"badRequestMessage": "The request was invalid.",
"notFoundTitle": "Not found",
"notFoundMessage": "The page you are looking for does not exist.",
"internalServerErrorTitle": "Internal server error",
"internalServerErrorMessage": "An error occurred on the server.",
"defaultTitle": "An error occurred",
"defaultMessage": "An error occurred while rendering this page."
},
"products": {
"domain": "Products",
"createProductTitle": "Create Product",
"createProductHint": "Create a new product to sell in your store.",
"deleteWarning": "You are about to delete the product {{title}}. This action cannot be undone.",
"variants": "Variants",
"attributes": "Attributes",
"editProduct": "Edit Product",
"editAttributes": "Edit Attributes",
"organization": "Organization",
"editOrganization": "Edit Organization",
"options": "Options",
"editOptions": "Edit Options",
"media": "Media",
"editMedia": "Edit Media",
"deleteMedia_one": "You are about to delete {{count}} media item. This action cannot be undone.",
"deleteMedia_other": "You are about to delete {{count}} media items. This action cannot be undone.",
"deleteMediaAndThumbnail_one": "You are about to delete {{count}} media item including the thumbnail. This action cannot be undone.",
"deleteMediaAndThumbnail_other": "You are about to delete {{count}} media items including the thumbnail. This action cannot be undone.",
"gallery": "Gallery",
"titleHint": "Give your product a short and clear title.<0/>50-60 characters is the recommended length for search engines.",
"descriptionHint": "Give your product a short and clear description.<0/>120-160 characters is the recommended length for search engines.",
"handleTooltip": "The handle is used to reference the product in your storefront. If not specified, the handle will be generated from the product title.",
"availableInSalesChannels": "Available in <0>{{x}}</0> of <1>{{y}}</1> sales channels",
"noSalesChannels": "Not available in any sales channels",
"variantCount_one": "{{count}} variant",
"variantCount_other": "{{count}} variants",
"deleteVariantWarning": "You are about to delete the variant {{title}}. This action cannot be undone.",
"productStatus": {
"draft": "Draft",
"published": "Published",
Expand Down Expand Up @@ -365,6 +398,17 @@
"tag": "Tag",
"dateIssued": "Date issued",
"issuedDate": "Issued date",
"expiryDate": "Expiry date"
"expiryDate": "Expiry date",
"height": "Height",
"width": "Width",
"length": "Length",
"weight": "Weight",
"midCode": "MID Code",
"hsCode": "HS Code",
"countryOfOrigin": "Country of Origin",
"material": "Material",
"thumbnail": "Thumbnail",
"sku": "SKU",
"managedInventory": "Managed inventory"
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { forwardRef } from "react"
import { ComponentPropsWithoutRef, forwardRef } from "react"

import { TrianglesMini } from "@medusajs/icons"
import { clx } from "@medusajs/ui"
Expand All @@ -7,15 +7,15 @@ import { countries } from "../../../lib/countries"

export const CountrySelect = forwardRef<
HTMLSelectElement,
React.ComponentPropsWithoutRef<"select"> & { placeholder?: string }
ComponentPropsWithoutRef<"select"> & { placeholder?: string }
>(({ className, disabled, placeholder, ...props }, ref) => {
const { t } = useTranslation()

return (
<div className="relative">
<TrianglesMini
className={clx(
"absolute right-2 top-1/2 -translate-y-1/2 text-ui-fg-muted transition-fg pointer-events-none",
"text-ui-fg-muted transition-fg pointer-events-none absolute right-2 top-1/2 -translate-y-1/2",
{
"text-ui-fg-disabled": disabled,
}
Expand All @@ -24,7 +24,7 @@ export const CountrySelect = forwardRef<
<select
disabled={disabled}
className={clx(
"appearance-none bg-ui-bg-field shadow-buttons-neutral transition-fg flex w-full select-none items-center justify-between rounded-md outline-none px-2 py-1 txt-compact-small",
"bg-ui-bg-field shadow-buttons-neutral transition-fg txt-compact-small flex w-full select-none appearance-none items-center justify-between rounded-md px-2 py-1.5 outline-none",
"placeholder:text-ui-fg-muted text-ui-fg-base",
"hover:bg-ui-bg-field-hover",
"focus-visible:shadow-borders-interactive-with-active data-[state=open]:!shadow-borders-interactive-with-active",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Input, Text } from "@medusajs/ui"
import { ComponentProps, ElementRef, forwardRef } from "react"

export const HandleInput = forwardRef<
ElementRef<typeof Input>,
ComponentProps<typeof Input>
>((props, ref) => {
return (
<div className="relative">
<div className="absolute inset-y-0 left-0 z-10 flex w-8 items-center justify-center border-r">
<Text
className="text-ui-fg-muted"
size="small"
leading="compact"
weight="plus"
>
/
</Text>
</div>
<Input ref={ref} {...props} className="pl-10" />
</div>
)
})
HandleInput.displayName = "HandleInput"
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./handle-input"
Original file line number Diff line number Diff line change
@@ -1,22 +1,59 @@
import { Navigate, useLocation, useRouteError } from "react-router-dom"

import { ExclamationCircle } from "@medusajs/icons"
import { Text } from "@medusajs/ui"
import { useTranslation } from "react-i18next"
import { isAxiosError } from "../../../lib/is-axios-error"

// WIP - Need to allow wrapping <Outlet> with ErrorBoundary for more granular error handling.
export const ErrorBoundary = () => {
const error = useRouteError()
const location = useLocation()
const { t } = useTranslation()

if (isAxiosError(error)) {
if (error.response?.status === 404) {
return <Navigate to="/404" />
}
let code: number | null = null

if (isAxiosError(error)) {
if (error.response?.status === 401) {
return <Navigate to="/login" state={{ from: location }} replace />
}

// TODO: Catch other server errors
code = error.response?.status ?? null
}

let title: string
let message: string

switch (code) {
case 400:
title = t("errorBoundary.badRequestTitle")
message = t("errorBoundary.badRequestMessage")
break
case 404:
title = t("errorBoundary.notFoundTitle")
message = t("errorBoundary.notFoundMessage")
break
case 500:
title = t("errorBoundary.internalServerErrorTitle")
message = t("errorBoundary.internalServerErrorMessage")
break
default:
title = t("errorBoundary.defaultTitle")
message = t("errorBoundary.defaultMessage")
break
}

// TODO: Actual catch-all error page
return <div>Dang!</div>
return (
<div className="flex size-full min-h-screen items-center justify-center">
<div className="text-ui-fg-subtle flex flex-col items-center gap-y-2">
<ExclamationCircle />
<Text size="small" leading="compact" weight="plus">
{title}
</Text>
<Text size="small" className="text-ui-fg-muted">
{message}
</Text>
</div>
</div>
)
}
Loading

0 comments on commit 44d43e8

Please sign in to comment.