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ć authored and GitHub committed 2024-10-06 11:57:56 +00:00
1 parent ffc35f2b6e
commit d6b452b734
25 files changed
+324 -64

No files matched your search

@@ -234,6 +234,11 @@ export const RouteMap: RouteObject[] = [
lazy: () => lazy: () =>
import("../../routes/categories/category-organize"), 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" "../../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" "../../routes/inventory/inventory-detail/components/adjust-inventory"
), ),
}, },
{
path: "metadata/edit",
lazy: () =>
import("../../routes/inventory/inventory-metadata"),
},
{ {
// TODO: create reservation // TODO: create reservation
path: "reservations", path: "reservations",
@@ -765,6 +780,10 @@ export const RouteMap: RouteObject[] = [
lazy: () => lazy: () =>
import("../../routes/regions/region-add-countries"), 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", path: "edit",
lazy: () => import("../../routes/users/user-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" "../../routes/sales-channels/sales-channel-add-products"
), ),
}, },
{
path: "metadata/edit",
lazy: () =>
import(
"../../routes/sales-channels/sales-channel-metadata"
),
},
], ],
}, },
], ],
@@ -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>
)
}
@@ -0,0 +1 @@
export { CategoriesMetadata as Component } from "./categories-metadata"
@@ -1,5 +1,4 @@
import { Outlet, useLoaderData, useParams } from "react-router-dom" import { useLoaderData, useParams } from "react-router-dom"
import { JsonViewSection } from "../../../components/common/json-view-section"
import { useProductCategory } from "../../../hooks/api/categories" import { useProductCategory } from "../../../hooks/api/categories"
import { CategoryGeneralSection } from "./components/category-general-section" import { CategoryGeneralSection } from "./components/category-general-section"
import { CategoryOrganizeSection } from "./components/category-organize-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 before from "virtual:medusa/widgets/product_category/details/before"
import sideAfter from "virtual:medusa/widgets/product_category/details/side/after" import sideAfter from "virtual:medusa/widgets/product_category/details/side/after"
import sideBefore from "virtual:medusa/widgets/product_category/details/side/before" import sideBefore from "virtual:medusa/widgets/product_category/details/side/before"
import { TwoColumnPage } from "../../../components/layout/pages"
export const CategoryDetail = () => { export const CategoryDetail = () => {
const { id } = useParams() const { id } = useParams()
@@ -35,7 +35,18 @@ export const CategoryDetail = () => {
} }
return ( 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) => { {before.widgets.map((w, i) => {
return ( return (
<div key={i}> <div key={i}>
@@ -43,8 +54,7 @@ export const CategoryDetail = () => {
</div> </div>
) )
})} })}
<div className="flex flex-col gap-x-4 gap-y-3 xl:flex-row xl:items-start"> <TwoColumnPage.Main>
<div className="flex w-full flex-col gap-y-3">
<CategoryGeneralSection category={product_category} /> <CategoryGeneralSection category={product_category} />
<CategoryProductSection category={product_category} /> <CategoryProductSection category={product_category} />
{after.widgets.map((w, i) => { {after.widgets.map((w, i) => {
@@ -54,11 +64,8 @@ export const CategoryDetail = () => {
</div> </div>
) )
})} })}
<div className="hidden xl:block"> </TwoColumnPage.Main>
<JsonViewSection data={product_category} /> <TwoColumnPage.Sidebar>
</div>
</div>
<div className="flex w-full max-w-[100%] flex-col gap-y-3 xl:max-w-[400px]">
{sideBefore.widgets.map((w, i) => { {sideBefore.widgets.map((w, i) => {
return ( return (
<div key={i}> <div key={i}>
@@ -74,12 +81,7 @@ export const CategoryDetail = () => {
</div> </div>
) )
})} })}
<div className="xl:hidden"> </TwoColumnPage.Sidebar>
<JsonViewSection data={product_category} /> </TwoColumnPage>
</div>
</div>
</div>
<Outlet />
</div>
) )
} }
@@ -1,10 +1,10 @@
import { Container, Heading } from "@medusajs/ui" import { Container, Heading } from "@medusajs/ui"
import { HttpTypes } from "@medusajs/types" import { HttpTypes } from "@medusajs/types"
import { PencilSquare } from "@medusajs/icons"
import { useTranslation } from "react-i18next"
import { ActionMenu } from "../../../../components/common/action-menu" import { ActionMenu } from "../../../../components/common/action-menu"
import { PencilSquare } from "@medusajs/icons"
import { SectionRow } from "../../../../components/common/section" import { SectionRow } from "../../../../components/common/section"
import { useTranslation } from "react-i18next"
type InventoryItemGeneralSectionProps = { type InventoryItemGeneralSectionProps = {
inventoryItem: HttpTypes.AdminInventoryItemResponse["inventory_item"] inventoryItem: HttpTypes.AdminInventoryItemResponse["inventory_item"]
@@ -0,0 +1 @@
export { InventoryItemMetadata as Component } from "./inventory-metadata"
@@ -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>
)
}
@@ -9,6 +9,8 @@ import { regionLoader } from "./loader"
import after from "virtual:medusa/widgets/region/details/after" import after from "virtual:medusa/widgets/region/details/after"
import before from "virtual:medusa/widgets/region/details/before" import before from "virtual:medusa/widgets/region/details/before"
import { usePricePreferences } from "../../../hooks/api/price-preferences" 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 = () => { export const RegionDetail = () => {
const initialData = useLoaderData() as Awaited< const initialData = useLoaderData() as Awaited<
@@ -56,7 +58,16 @@ export const RegionDetail = () => {
} }
return ( return (
<div className="flex flex-col gap-y-2"> <SingleColumnPage
widgets={{
before,
after,
}}
data={region}
hasOutlet
showMetadata
showJSON
>
{before.widgets.map((w, i) => { {before.widgets.map((w, i) => {
return ( return (
<div key={i}> <div key={i}>
@@ -76,8 +87,6 @@ export const RegionDetail = () => {
</div> </div>
) )
})} })}
<JsonViewSection data={region} /> </SingleColumnPage>
<Outlet />
</div>
) )
} }
@@ -0,0 +1 @@
export { RegionMetadata as Component } from "./region-metadata.tsx"
@@ -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>
)
}
@@ -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 { useReservationItem } from "../../../hooks/api/reservations"
import { InventoryItemGeneralSection } from "../../inventory/inventory-detail/components/inventory-item-general-section"
import { ReservationGeneralSection } from "./components/reservation-general-section" import { ReservationGeneralSection } from "./components/reservation-general-section"
import { reservationItemLoader } from "./loader" 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 before from "virtual:medusa/widgets/reservation/details/before"
import sideAfter from "virtual:medusa/widgets/reservation/details/side/after" import sideAfter from "virtual:medusa/widgets/reservation/details/side/after"
import sideBefore from "virtual:medusa/widgets/reservation/details/side/before" 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 = () => { export const ReservationDetail = () => {
const { id } = useParams() const { id } = useParams()
@@ -35,7 +35,18 @@ export const ReservationDetail = () => {
} }
return ( 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) => { {before.widgets.map((w, i) => {
return ( return (
<div key={i}> <div key={i}>
@@ -43,8 +54,7 @@ export const ReservationDetail = () => {
</div> </div>
) )
})} })}
<div className="flex flex-col gap-x-4 xl:flex-row xl:items-start"> <TwoColumnPage.Main>
<div className="flex w-full flex-col gap-y-3">
<ReservationGeneralSection reservation={reservation} /> <ReservationGeneralSection reservation={reservation} />
{after.widgets.map((w, i) => { {after.widgets.map((w, i) => {
return ( return (
@@ -53,34 +63,12 @@ export const ReservationDetail = () => {
</div> </div>
) )
})} })}
<div className="hidden xl:block"> </TwoColumnPage.Main>
<JsonViewSection data={reservation} /> <TwoColumnPage.Sidebar>
</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 <InventoryItemGeneralSection
inventoryItem={reservation.inventory_item} inventoryItem={reservation.inventory_item!}
/> />
{sideAfter.widgets.map((w, i) => { </TwoColumnPage.Sidebar>
return ( </TwoColumnPage>
<div key={i}>
<w.Component data={reservation} />
</div>
)
})}
<div className="xl:hidden">
<JsonViewSection data={reservation} />
</div>
<Outlet />
</div>
</div>
</div>
) )
} }
@@ -0,0 +1 @@
export { ReservationMetadata as Component } from "./reservation-metadata"
@@ -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>
)
}
@@ -8,6 +8,7 @@ import { salesChannelLoader } from "./loader"
import after from "virtual:medusa/widgets/sales_channel/details/after" import after from "virtual:medusa/widgets/sales_channel/details/after"
import before from "virtual:medusa/widgets/sales_channel/details/before" import before from "virtual:medusa/widgets/sales_channel/details/before"
import { SingleColumnPage } from "../../../components/layout/pages"
export const SalesChannelDetail = () => { export const SalesChannelDetail = () => {
const initialData = useLoaderData() as Awaited< const initialData = useLoaderData() as Awaited<
@@ -24,7 +25,16 @@ export const SalesChannelDetail = () => {
} }
return ( return (
<div className="flex flex-col gap-y-2"> <SingleColumnPage
widgets={{
before,
after,
}}
data={sales_channel}
hasOutlet
showMetadata
showJSON
>
{before.widgets.map((w, i) => { {before.widgets.map((w, i) => {
return ( return (
<div key={i}> <div key={i}>
@@ -41,8 +51,6 @@ export const SalesChannelDetail = () => {
</div> </div>
) )
})} })}
<JsonViewSection data={sales_channel} /> </SingleColumnPage>
<Outlet />
</div>
) )
} }
@@ -0,0 +1 @@
export { SalesChannelMetadata as Component } from "./sales-channel-metadata"
@@ -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>
)
}
@@ -40,6 +40,7 @@ export const TaxRegionDetail = () => {
data={taxRegion} data={taxRegion}
showJSON showJSON
hasOutlet hasOutlet
// showMetadata // TOOD -> enable when tax region update is added to the API
widgets={{ widgets={{
after, after,
before, before,
@@ -0,0 +1 @@
export { TaxRegionMetadata as Component } from "./tax-region-metadata.tsx"
@@ -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>
)
}
@@ -7,6 +7,7 @@ import { userLoader } from "./loader"
import after from "virtual:medusa/widgets/user/details/after" import after from "virtual:medusa/widgets/user/details/after"
import before from "virtual:medusa/widgets/user/details/before" import before from "virtual:medusa/widgets/user/details/before"
import { SingleColumnPage } from "../../../components/layout/pages"
export const UserDetail = () => { export const UserDetail = () => {
const initialData = useLoaderData() as Awaited<ReturnType<typeof userLoader>> const initialData = useLoaderData() as Awaited<ReturnType<typeof userLoader>>
@@ -34,7 +35,16 @@ export const UserDetail = () => {
} }
return ( return (
<div className="flex flex-col gap-y-2"> <SingleColumnPage
widgets={{
before,
after,
}}
data={user}
hasOutlet
showMetadata
showJSON
>
{before.widgets.map((w, i) => ( {before.widgets.map((w, i) => (
<div key={i}> <div key={i}>
<w.Component data={user} /> <w.Component data={user} />
@@ -46,8 +56,6 @@ export const UserDetail = () => {
<w.Component data={user} /> <w.Component data={user} />
</div> </div>
))} ))}
<JsonViewSection data={user} /> </SingleColumnPage>
<Outlet />
</div>
) )
} }
@@ -0,0 +1 @@
export { UserMetadata as Component } from "./user-metadata"
@@ -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>
)
}
@@ -9,4 +9,5 @@ export interface AdminUpdateUser {
first_name?: string | null first_name?: string | null
last_name?: string | null last_name?: string | null
avatar_url?: string | null avatar_url?: string | null
metadata?: Record<string, unknown> | null
} }
@@ -6,6 +6,7 @@ export const defaultAdminSalesChannelFields = [
"created_at", "created_at",
"updated_at", "updated_at",
"deleted_at", "deleted_at",
"metadata",
] ]
export const retrieveTransformQueryConfig = { export const retrieveTransformQueryConfig = {
@@ -37,4 +37,5 @@ export const AdminUpdateUser = z.object({
first_name: z.string().nullish(), first_name: z.string().nullish(),
last_name: z.string().nullish(), last_name: z.string().nullish(),
avatar_url: z.string().nullish(), avatar_url: z.string().nullish(),
metadata: z.record(z.unknown()).nullish().optional(),
}) })