feat(dashboard, medusa) add metadata to pages (#9433)

**WIP**
- add metadata component and edit route to missing pages
- fix API issues around metadata in certain domains

---

FIXES CC-554
This commit is contained in:
Frane Polić
2024-10-06 13:57:56 +02:00
committed by GitHub
parent ffc35f2b6e
commit d6b452b734
25 changed files with 358 additions and 98 deletions

View File

@@ -234,6 +234,11 @@ export const RouteMap: RouteObject[] = [
lazy: () =>
import("../../routes/categories/category-organize"),
},
{
path: "metadata/edit",
lazy: () =>
import("../../routes/categories/categories-metadata"),
},
],
},
],
@@ -623,6 +628,11 @@ export const RouteMap: RouteObject[] = [
"../../routes/reservations/reservation-detail/components/edit-reservation"
),
},
{
path: "metadata/edit",
lazy: () =>
import("../../routes/reservations/reservation-metadata"),
},
],
},
],
@@ -681,6 +691,11 @@ export const RouteMap: RouteObject[] = [
"../../routes/inventory/inventory-detail/components/adjust-inventory"
),
},
{
path: "metadata/edit",
lazy: () =>
import("../../routes/inventory/inventory-metadata"),
},
{
// TODO: create reservation
path: "reservations",
@@ -765,6 +780,10 @@ export const RouteMap: RouteObject[] = [
lazy: () =>
import("../../routes/regions/region-add-countries"),
},
{
path: "metadata/edit",
lazy: () => import("../../routes/regions/region-metadata"),
},
],
},
],
@@ -820,6 +839,10 @@ export const RouteMap: RouteObject[] = [
path: "edit",
lazy: () => import("../../routes/users/user-edit"),
},
{
path: "metadata/edit",
lazy: () => import("../../routes/users/user-metadata"),
},
],
},
],
@@ -867,6 +890,13 @@ export const RouteMap: RouteObject[] = [
"../../routes/sales-channels/sales-channel-add-products"
),
},
{
path: "metadata/edit",
lazy: () =>
import(
"../../routes/sales-channels/sales-channel-metadata"
),
},
],
},
],

View File

@@ -0,0 +1,30 @@
import { useParams } from "react-router-dom"
import {
useProductCategory,
useUpdateProductCategory,
} from "../../../hooks/api"
import { MetadataForm } from "../../../components/forms/metadata-form"
import { RouteDrawer } from "../../../components/modals"
export const CategoriesMetadata = () => {
const { id } = useParams()
const { product_category, isPending, isError, error } = useProductCategory(id)
const { mutateAsync, isPending: isMutating } = useUpdateProductCategory(id)
if (isError) {
throw error
}
return (
<RouteDrawer>
<MetadataForm
isPending={isPending}
isMutating={isMutating}
hook={mutateAsync}
metadata={product_category?.metadata}
/>
</RouteDrawer>
)
}

View File

@@ -0,0 +1 @@
export { CategoriesMetadata as Component } from "./categories-metadata"

View File

@@ -1,5 +1,4 @@
import { Outlet, useLoaderData, useParams } from "react-router-dom"
import { JsonViewSection } from "../../../components/common/json-view-section"
import { useLoaderData, useParams } from "react-router-dom"
import { useProductCategory } from "../../../hooks/api/categories"
import { CategoryGeneralSection } from "./components/category-general-section"
import { CategoryOrganizeSection } from "./components/category-organize-section"
@@ -10,6 +9,7 @@ import after from "virtual:medusa/widgets/product_category/details/after"
import before from "virtual:medusa/widgets/product_category/details/before"
import sideAfter from "virtual:medusa/widgets/product_category/details/side/after"
import sideBefore from "virtual:medusa/widgets/product_category/details/side/before"
import { TwoColumnPage } from "../../../components/layout/pages"
export const CategoryDetail = () => {
const { id } = useParams()
@@ -35,7 +35,18 @@ export const CategoryDetail = () => {
}
return (
<div className="flex flex-col gap-y-3">
<TwoColumnPage
widgets={{
after,
before,
sideAfter,
sideBefore,
}}
data={product_category}
showJSON
showMetadata
hasOutlet
>
{before.widgets.map((w, i) => {
return (
<div key={i}>
@@ -43,43 +54,34 @@ export const CategoryDetail = () => {
</div>
)
})}
<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">
<CategoryGeneralSection category={product_category} />
<CategoryProductSection category={product_category} />
{after.widgets.map((w, i) => {
return (
<div key={i}>
<w.Component data={product_category} />
</div>
)
})}
<div className="hidden xl:block">
<JsonViewSection data={product_category} />
</div>
</div>
<div className="flex w-full max-w-[100%] flex-col gap-y-3 xl:max-w-[400px]">
{sideBefore.widgets.map((w, i) => {
return (
<div key={i}>
<w.Component data={product_category} />
</div>
)
})}
<CategoryOrganizeSection category={product_category} />
{sideAfter.widgets.map((w, i) => {
return (
<div key={i}>
<w.Component data={product_category} />
</div>
)
})}
<div className="xl:hidden">
<JsonViewSection data={product_category} />
</div>
</div>
</div>
<Outlet />
</div>
<TwoColumnPage.Main>
<CategoryGeneralSection category={product_category} />
<CategoryProductSection category={product_category} />
{after.widgets.map((w, i) => {
return (
<div key={i}>
<w.Component data={product_category} />
</div>
)
})}
</TwoColumnPage.Main>
<TwoColumnPage.Sidebar>
{sideBefore.widgets.map((w, i) => {
return (
<div key={i}>
<w.Component data={product_category} />
</div>
)
})}
<CategoryOrganizeSection category={product_category} />
{sideAfter.widgets.map((w, i) => {
return (
<div key={i}>
<w.Component data={product_category} />
</div>
)
})}
</TwoColumnPage.Sidebar>
</TwoColumnPage>
)
}

View File

@@ -1,10 +1,10 @@
import { Container, Heading } from "@medusajs/ui"
import { HttpTypes } from "@medusajs/types"
import { PencilSquare } from "@medusajs/icons"
import { useTranslation } from "react-i18next"
import { ActionMenu } from "../../../../components/common/action-menu"
import { PencilSquare } from "@medusajs/icons"
import { SectionRow } from "../../../../components/common/section"
import { useTranslation } from "react-i18next"
type InventoryItemGeneralSectionProps = {
inventoryItem: HttpTypes.AdminInventoryItemResponse["inventory_item"]

View File

@@ -0,0 +1 @@
export { InventoryItemMetadata as Component } from "./inventory-metadata"

View File

@@ -0,0 +1,27 @@
import { useParams } from "react-router-dom"
import { useInventoryItem, useUpdateInventoryItem } from "../../../hooks/api"
import { MetadataForm } from "../../../components/forms/metadata-form"
import { RouteDrawer } from "../../../components/modals"
export const InventoryItemMetadata = () => {
const { id } = useParams()
const { inventory_item, isPending, isError, error } = useInventoryItem(id)
const { mutateAsync, isPending: isMutating } = useUpdateInventoryItem(id)
if (isError) {
throw error
}
return (
<RouteDrawer>
<MetadataForm
isPending={isPending}
isMutating={isMutating}
hook={mutateAsync}
metadata={inventory_item?.metadata}
/>
</RouteDrawer>
)
}

View File

@@ -9,6 +9,8 @@ import { regionLoader } from "./loader"
import after from "virtual:medusa/widgets/region/details/after"
import before from "virtual:medusa/widgets/region/details/before"
import { usePricePreferences } from "../../../hooks/api/price-preferences"
import { SingleColumnPage } from "../../../components/layout/pages"
import { UserGeneralSection } from "../../users/user-detail/components/user-general-section"
export const RegionDetail = () => {
const initialData = useLoaderData() as Awaited<
@@ -56,7 +58,16 @@ export const RegionDetail = () => {
}
return (
<div className="flex flex-col gap-y-2">
<SingleColumnPage
widgets={{
before,
after,
}}
data={region}
hasOutlet
showMetadata
showJSON
>
{before.widgets.map((w, i) => {
return (
<div key={i}>
@@ -76,8 +87,6 @@ export const RegionDetail = () => {
</div>
)
})}
<JsonViewSection data={region} />
<Outlet />
</div>
</SingleColumnPage>
)
}

View File

@@ -0,0 +1 @@
export { RegionMetadata as Component } from "./region-metadata.tsx"

View File

@@ -0,0 +1,27 @@
import { useParams } from "react-router-dom"
import { MetadataForm } from "../../../components/forms/metadata-form"
import { RouteDrawer } from "../../../components/modals"
import { useRegion, useUpdateRegion } from "../../../hooks/api"
export const RegionMetadata = () => {
const { id } = useParams()
const { region, isPending, isError, error } = useRegion(id)
const { mutateAsync, isPending: isMutating } = useUpdateRegion(id)
if (isError) {
throw error
}
return (
<RouteDrawer>
<MetadataForm
isPending={isPending}
isMutating={isMutating}
hook={mutateAsync}
metadata={region?.metadata}
/>
</RouteDrawer>
)
}

View File

@@ -1,8 +1,6 @@
import { Outlet, useLoaderData, useParams } from "react-router-dom"
import { useLoaderData, useParams } from "react-router-dom"
import { JsonViewSection } from "../../../components/common/json-view-section"
import { useReservationItem } from "../../../hooks/api/reservations"
import { InventoryItemGeneralSection } from "../../inventory/inventory-detail/components/inventory-item-general-section"
import { ReservationGeneralSection } from "./components/reservation-general-section"
import { reservationItemLoader } from "./loader"
@@ -10,6 +8,8 @@ import after from "virtual:medusa/widgets/reservation/details/after"
import before from "virtual:medusa/widgets/reservation/details/before"
import sideAfter from "virtual:medusa/widgets/reservation/details/side/after"
import sideBefore from "virtual:medusa/widgets/reservation/details/side/before"
import { TwoColumnPage } from "../../../components/layout/pages"
import { InventoryItemGeneralSection } from "../../inventory/inventory-detail/components/inventory-item-general-section"
export const ReservationDetail = () => {
const { id } = useParams()
@@ -35,7 +35,18 @@ export const ReservationDetail = () => {
}
return (
<div className="flex flex-col gap-y-2">
<TwoColumnPage
widgets={{
after,
before,
sideAfter,
sideBefore,
}}
data={reservation}
showJSON
showMetadata
hasOutlet
>
{before.widgets.map((w, i) => {
return (
<div key={i}>
@@ -43,44 +54,21 @@ export const ReservationDetail = () => {
</div>
)
})}
<div className="flex flex-col gap-x-4 xl:flex-row xl:items-start">
<div className="flex w-full flex-col gap-y-3">
<ReservationGeneralSection reservation={reservation} />
{after.widgets.map((w, i) => {
return (
<div key={i}>
<w.Component data={reservation} />
</div>
)
})}
<div className="hidden xl:block">
<JsonViewSection data={reservation} />
</div>
</div>
<div className="mt-2 flex w-full max-w-[100%] flex-col gap-y-2 xl:mt-0 xl:max-w-[400px]">
{sideBefore.widgets.map((w, i) => {
return (
<div key={i}>
<w.Component data={reservation} />
</div>
)
})}
<InventoryItemGeneralSection
inventoryItem={reservation.inventory_item}
/>
{sideAfter.widgets.map((w, i) => {
return (
<div key={i}>
<w.Component data={reservation} />
</div>
)
})}
<div className="xl:hidden">
<JsonViewSection data={reservation} />
</div>
<Outlet />
</div>
</div>
</div>
<TwoColumnPage.Main>
<ReservationGeneralSection reservation={reservation} />
{after.widgets.map((w, i) => {
return (
<div key={i}>
<w.Component data={reservation} />
</div>
)
})}
</TwoColumnPage.Main>
<TwoColumnPage.Sidebar>
<InventoryItemGeneralSection
inventoryItem={reservation.inventory_item!}
/>
</TwoColumnPage.Sidebar>
</TwoColumnPage>
)
}

View File

@@ -0,0 +1 @@
export { ReservationMetadata as Component } from "./reservation-metadata"

View File

@@ -0,0 +1,30 @@
import { useParams } from "react-router-dom"
import {
useReservationItem,
useUpdateReservationItem,
} from "../../../hooks/api"
import { MetadataForm } from "../../../components/forms/metadata-form"
import { RouteDrawer } from "../../../components/modals"
export const ReservationMetadata = () => {
const { id } = useParams()
const { reservation, isPending, isError, error } = useReservationItem(id)
const { mutateAsync, isPending: isMutating } = useUpdateReservationItem(id)
if (isError) {
throw error
}
return (
<RouteDrawer>
<MetadataForm
isPending={isPending}
isMutating={isMutating}
hook={mutateAsync}
metadata={reservation?.metadata}
/>
</RouteDrawer>
)
}

View File

@@ -8,6 +8,7 @@ import { salesChannelLoader } from "./loader"
import after from "virtual:medusa/widgets/sales_channel/details/after"
import before from "virtual:medusa/widgets/sales_channel/details/before"
import { SingleColumnPage } from "../../../components/layout/pages"
export const SalesChannelDetail = () => {
const initialData = useLoaderData() as Awaited<
@@ -24,7 +25,16 @@ export const SalesChannelDetail = () => {
}
return (
<div className="flex flex-col gap-y-2">
<SingleColumnPage
widgets={{
before,
after,
}}
data={sales_channel}
hasOutlet
showMetadata
showJSON
>
{before.widgets.map((w, i) => {
return (
<div key={i}>
@@ -41,8 +51,6 @@ export const SalesChannelDetail = () => {
</div>
)
})}
<JsonViewSection data={sales_channel} />
<Outlet />
</div>
</SingleColumnPage>
)
}

View File

@@ -0,0 +1 @@
export { SalesChannelMetadata as Component } from "./sales-channel-metadata"

View File

@@ -0,0 +1,32 @@
import { useParams } from "react-router-dom"
import { RouteDrawer } from "../../../components/modals"
import { MetadataForm } from "../../../components/forms/metadata-form"
import { useSalesChannel, useUpdateSalesChannel } from "../../../hooks/api"
export const SalesChannelMetadata = () => {
const { id } = useParams()
const {
sales_channel: salesChannel,
isPending,
isError,
error,
} = useSalesChannel(id)
const { mutateAsync, isPending: isMutating } = useUpdateSalesChannel(id)
if (isError) {
throw error
}
return (
<RouteDrawer>
<MetadataForm
isPending={isPending}
isMutating={isMutating}
hook={mutateAsync}
metadata={salesChannel?.metadata}
/>
</RouteDrawer>
)
}

View File

@@ -40,6 +40,7 @@ export const TaxRegionDetail = () => {
data={taxRegion}
showJSON
hasOutlet
// showMetadata // TOOD -> enable when tax region update is added to the API
widgets={{
after,
before,

View File

@@ -0,0 +1 @@
export { TaxRegionMetadata as Component } from "./tax-region-metadata.tsx"

View File

@@ -0,0 +1,31 @@
import { useParams } from "react-router-dom"
import { useTaxRegion } from "../../../hooks/api"
import { MetadataForm } from "../../../components/forms/metadata-form"
import { RouteDrawer } from "../../../components/modals"
/**
* TODO: Tax region update endpoint is missing
*/
export const TaxRegionMetadata = () => {
const { id } = useParams()
const { tax_region, isPending, isError, error } = useTaxRegion(id)
const { mutateAsync, isPending: isMutating } = {} // useUpdateTaxRegion(id)
if (isError) {
throw error
}
return (
<RouteDrawer>
<MetadataForm
isPending={isPending}
isMutating={isMutating}
hook={mutateAsync}
metadata={tax_region?.metadata}
/>
</RouteDrawer>
)
}

View File

@@ -7,6 +7,7 @@ import { userLoader } from "./loader"
import after from "virtual:medusa/widgets/user/details/after"
import before from "virtual:medusa/widgets/user/details/before"
import { SingleColumnPage } from "../../../components/layout/pages"
export const UserDetail = () => {
const initialData = useLoaderData() as Awaited<ReturnType<typeof userLoader>>
@@ -34,7 +35,16 @@ export const UserDetail = () => {
}
return (
<div className="flex flex-col gap-y-2">
<SingleColumnPage
widgets={{
before,
after,
}}
data={user}
hasOutlet
showMetadata
showJSON
>
{before.widgets.map((w, i) => (
<div key={i}>
<w.Component data={user} />
@@ -46,8 +56,6 @@ export const UserDetail = () => {
<w.Component data={user} />
</div>
))}
<JsonViewSection data={user} />
<Outlet />
</div>
</SingleColumnPage>
)
}

View File

@@ -0,0 +1 @@
export { UserMetadata as Component } from "./user-metadata"

View File

@@ -0,0 +1,27 @@
import { useParams } from "react-router-dom"
import { MetadataForm } from "../../../components/forms/metadata-form"
import { RouteDrawer } from "../../../components/modals"
import { useUpdateUser, useUser } from "../../../hooks/api"
export const UserMetadata = () => {
const { id } = useParams()
const { user, isPending, isError, error } = useUser(id)
const { mutateAsync, isPending: isMutating } = useUpdateUser(id)
if (isError) {
throw error
}
return (
<RouteDrawer>
<MetadataForm
isPending={isPending}
isMutating={isMutating}
hook={mutateAsync}
metadata={user?.metadata}
/>
</RouteDrawer>
)
}

View File

@@ -9,4 +9,5 @@ export interface AdminUpdateUser {
first_name?: string | null
last_name?: string | null
avatar_url?: string | null
metadata?: Record<string, unknown> | null
}

View File

@@ -6,6 +6,7 @@ export const defaultAdminSalesChannelFields = [
"created_at",
"updated_at",
"deleted_at",
"metadata",
]
export const retrieveTransformQueryConfig = {

View File

@@ -37,4 +37,5 @@ export const AdminUpdateUser = z.object({
first_name: z.string().nullish(),
last_name: z.string().nullish(),
avatar_url: z.string().nullish(),
metadata: z.record(z.unknown()).nullish().optional(),
})