feat(dashboard,medusa): Add updated Metadata Form (#8084)
**What** - Adds new Metadata form component. - Adds the Metadata section as an option to the Page layouts <img width="576" alt="Skærmbillede 2024-07-11 kl 11 34 06" src="https://github.com/medusajs/medusa/assets/45367945/417810ee-26e2-4c8a-86e3-58ef327054af"> <img width="580" alt="Skærmbillede 2024-07-11 kl 11 34 33" src="https://github.com/medusajs/medusa/assets/45367945/437a5e01-01e2-4ff7-8c7e-42a86d1ce2b3"> **Note** - When Metadata contains non-primitive data, we disable those rows, and show a placeholder value, a tooltip and an alert describing that the row can be edited through the API. I want to add a JSON editor to allow editing these things in admin, but awaiting approval on that. - This PR only adds the new form to a couple of pages, to keep the PR light, especially since metadata is not implemented correctly in all validators so also needs some changes to the core. This still show some examples of how its used with the new Page layout components. Will follow up with more pages in future PRs. - We try to convert the inputs to the best fitting primitive, so if a user types "true" then we save the value as a boolean, "130" as number, "testing" as a string, etc.
This commit is contained in:
committed by
GitHub
parent
66acb3023e
commit
b5a44ef6b1
@@ -1,5 +1,5 @@
|
||||
import {
|
||||
ArrowsPointingOut,
|
||||
ArrowUpRightOnBox,
|
||||
Check,
|
||||
SquareTwoStack,
|
||||
TriangleDownMini,
|
||||
@@ -30,7 +30,7 @@ export const JsonViewSection = ({ data }: JsonViewSectionProps) => {
|
||||
<Container className="flex items-center justify-between px-6 py-4">
|
||||
<div className="flex items-center gap-x-4">
|
||||
<Heading level="h2">{t("json.header")}</Heading>
|
||||
<Badge size="2xsmall">
|
||||
<Badge size="2xsmall" rounded="full">
|
||||
{t("json.numberOfKeys", {
|
||||
count: numberOfKeys,
|
||||
})}
|
||||
@@ -41,9 +41,9 @@ export const JsonViewSection = ({ data }: JsonViewSectionProps) => {
|
||||
<IconButton
|
||||
size="small"
|
||||
variant="transparent"
|
||||
className="text-ui-fg-subtle"
|
||||
className="text-ui-fg-muted hover:text-ui-fg-subtle"
|
||||
>
|
||||
<ArrowsPointingOut />
|
||||
<ArrowUpRightOnBox />
|
||||
</IconButton>
|
||||
</Drawer.Trigger>
|
||||
<Drawer.Content className="bg-ui-contrast-bg-base text-ui-code-fg-subtle !shadow-elevation-commandbar overflow-hidden border border-none max-md:inset-x-2 max-md:max-w-[calc(100%-16px)]">
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./metadata-section"
|
||||
@@ -0,0 +1,49 @@
|
||||
import { ArrowUpRightOnBox } from "@medusajs/icons"
|
||||
import { Badge, Container, Heading, IconButton } from "@medusajs/ui"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { Link } from "react-router-dom"
|
||||
|
||||
type MetadataSectionProps<TData extends object> = {
|
||||
data: TData
|
||||
href?: string
|
||||
}
|
||||
|
||||
export const MetadataSection = <TData extends object>({
|
||||
data,
|
||||
href = "metadata/edit",
|
||||
}: MetadataSectionProps<TData>) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
if (!data) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (!("metadata" in data)) {
|
||||
return null
|
||||
}
|
||||
|
||||
const numberOfKeys = data.metadata ? Object.keys(data.metadata).length : 0
|
||||
|
||||
return (
|
||||
<Container className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-x-3">
|
||||
<Heading level="h2">{t("metadata.header")}</Heading>
|
||||
<Badge size="2xsmall" rounded="full">
|
||||
{t("metadata.numberOfKeys", {
|
||||
count: numberOfKeys,
|
||||
})}
|
||||
</Badge>
|
||||
</div>
|
||||
<IconButton
|
||||
size="small"
|
||||
variant="transparent"
|
||||
className="text-ui-fg-muted hover:text-ui-fg-subtle"
|
||||
asChild
|
||||
>
|
||||
<Link to={href}>
|
||||
<ArrowUpRightOnBox />
|
||||
</Link>
|
||||
</IconButton>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
@@ -229,3 +229,99 @@ export const JsonViewSectionSkeleton = () => {
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
type SingleColumnPageSkeletonProps = {
|
||||
sections?: number
|
||||
showJSON?: boolean
|
||||
showMetadata?: boolean
|
||||
}
|
||||
|
||||
export const SingleColumnPageSkeleton = ({
|
||||
sections = 2,
|
||||
showJSON = false,
|
||||
showMetadata = false,
|
||||
}: SingleColumnPageSkeletonProps) => {
|
||||
return (
|
||||
<div className="flex flex-col gap-y-3">
|
||||
{Array.from({ length: sections }, (_, i) => i).map((section) => {
|
||||
return (
|
||||
<Skeleton
|
||||
key={section}
|
||||
className={clx("h-full max-h-[460px] w-full rounded-lg", {
|
||||
// First section is smaller on most pages, this gives us less
|
||||
// layout shifting in general,
|
||||
"max-h-[219px]": section === 0,
|
||||
})}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
{showMetadata && <Skeleton className="h-[60px] w-full rounded-lg" />}
|
||||
{showJSON && <Skeleton className="h-[60px] w-full rounded-lg" />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
type TwoColumnPageSkeletonProps = {
|
||||
mainSections?: number
|
||||
sidebarSections?: number
|
||||
showJSON?: boolean
|
||||
showMetadata?: boolean
|
||||
}
|
||||
|
||||
export const TwoColumnPageSkeleton = ({
|
||||
mainSections = 2,
|
||||
sidebarSections = 1,
|
||||
showJSON = false,
|
||||
showMetadata = true,
|
||||
}: TwoColumnPageSkeletonProps) => {
|
||||
const showExtraData = showJSON || showMetadata
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-y-3">
|
||||
<div className="flex flex-col gap-x-4 gap-y-3 xl:flex-row xl:items-start">
|
||||
<div className="flex w-full flex-col gap-y-3">
|
||||
{Array.from({ length: mainSections }, (_, i) => i).map((section) => {
|
||||
return (
|
||||
<Skeleton
|
||||
key={section}
|
||||
className={clx("h-full max-h-[460px] w-full rounded-lg", {
|
||||
"max-h-[219px]": section === 0,
|
||||
})}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
{showExtraData && (
|
||||
<div className="hidden flex-col gap-y-3 xl:flex">
|
||||
{showMetadata && (
|
||||
<Skeleton className="h-[60px] w-full rounded-lg" />
|
||||
)}
|
||||
{showJSON && <Skeleton className="h-[60px] w-full rounded-lg" />}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex w-full max-w-[100%] flex-col gap-y-3 xl:mt-0 xl:max-w-[440px]">
|
||||
{Array.from({ length: sidebarSections }, (_, i) => i).map(
|
||||
(section) => {
|
||||
return (
|
||||
<Skeleton
|
||||
key={section}
|
||||
className={clx("h-full max-h-[320px] w-full rounded-lg", {
|
||||
"max-h-[140px]": section === 0,
|
||||
})}
|
||||
/>
|
||||
)
|
||||
}
|
||||
)}
|
||||
{showExtraData && (
|
||||
<div className="flex flex-col gap-y-3 xl:hidden">
|
||||
{showMetadata && (
|
||||
<Skeleton className="h-[60px] w-full rounded-lg" />
|
||||
)}
|
||||
{showJSON && <Skeleton className="h-[60px] w-full rounded-lg" />}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user