feat(dashboard,admin-vite-plugin,admin-bundler,admin-sdk): Rework admin extensions and introduce custom fields API (#9338)

This commit is contained in:
Kasper Fabricius Kristensen
2024-10-09 11:44:40 +00:00
committed by GitHub
parent 35e69d32f2
commit d71343d6ab
159 changed files with 5264 additions and 2224 deletions
@@ -19,14 +19,13 @@ import * as Collapsible from "@radix-ui/react-collapsible"
import { useTranslation } from "react-i18next"
import { useStore } from "../../../hooks/api/store"
import { settingsRouteRegex } from "../../../lib/extension-helpers"
import { Divider } from "../../common/divider"
import { Skeleton } from "../../common/skeleton"
import { NavItem, NavItemProps } from "../../layout/nav-item"
import { INavItem, NavItem } from "../../layout/nav-item"
import { Shell } from "../../layout/shell"
import { Link, useLocation, useNavigate } from "react-router-dom"
import routes from "virtual:medusa/routes/links"
import { useDashboardExtension } from "../../../extensions"
import { useLogout } from "../../../hooks/api"
import { queryClient } from "../../../lib/query-client"
import { useSearch } from "../../../providers/search-provider"
@@ -177,7 +176,7 @@ const Header = () => {
)
}
const useCoreRoutes = (): Omit<NavItemProps, "pathname">[] => {
const useCoreRoutes = (): Omit<INavItem, "pathname">[] => {
const { t } = useTranslation()
return [
@@ -297,14 +296,11 @@ const CoreRouteSection = () => {
const ExtensionRouteSection = () => {
const { t } = useTranslation()
const { getMenu } = useDashboardExtension()
const links = routes.links
const menuItems = getMenu("coreExtensions")
const extensionLinks = links
.filter((link) => !settingsRouteRegex.test(link.path))
.sort((a, b) => a.label.localeCompare(b.label))
if (!extensionLinks.length) {
if (!menuItems.length) {
return null
}
@@ -330,13 +326,14 @@ const ExtensionRouteSection = () => {
</div>
<Collapsible.Content>
<nav className="flex flex-col gap-y-0.5 py-1 pb-4">
{extensionLinks.map((link) => {
{menuItems.map((item, i) => {
return (
<NavItem
key={link.path}
to={link.path}
label={link.label}
icon={link.icon ? <link.icon /> : <SquaresPlus />}
key={i}
to={item.to}
label={item.label}
icon={item.icon ? item.icon : <SquaresPlus />}
items={item.items}
type="extension"
/>
)
@@ -19,7 +19,7 @@ type NestedItemProps = {
to: string
}
export type NavItemProps = {
export type INavItem = {
icon?: ReactNode
label: string
to: string
@@ -29,11 +29,11 @@ export type NavItemProps = {
}
const BASE_NAV_LINK_CLASSES =
"text-ui-fg-subtle transition-fg hover:bg-ui-bg-subtle-hover flex items-center gap-x-2 rounded-md py-1 pl-0.5 pr-2 outline-none [&>svg]:text-ui-fg-subtle focus-visible:shadow-borders-focus"
"text-ui-fg-subtle transition-fg hover:bg-ui-bg-subtle-hover flex items-center gap-x-2 rounded-md py-0.5 pl-0.5 pr-2 outline-none [&>svg]:text-ui-fg-subtle focus-visible:shadow-borders-focus"
const ACTIVE_NAV_LINK_CLASSES =
"bg-ui-bg-base shadow-elevation-card-rest text-ui-fg-base hover:bg-ui-bg-base"
const NESTED_NAV_LINK_CLASSES = "pl-[34px] pr-2 w-full text-ui-fg-muted"
const SETTING_NAV_LINK_CLASSES = "pl-2"
const NESTED_NAV_LINK_CLASSES = "pl-[34px] pr-2 py-1 w-full text-ui-fg-muted"
const SETTING_NAV_LINK_CLASSES = "pl-2 py-1"
const getIsOpen = (
to: string,
@@ -89,7 +89,7 @@ export const NavItem = ({
items,
type = "core",
from,
}: NavItemProps) => {
}: INavItem) => {
const { pathname } = useLocation()
const [open, setOpen] = useState(getIsOpen(to, items, pathname))
@@ -149,7 +149,7 @@ export const NavItem = ({
<Collapsible.Root open={open} onOpenChange={setOpen}>
<Collapsible.Trigger
className={clx(
"text-ui-fg-subtle hover:text-ui-fg-base transition-fg hover:bg-ui-bg-subtle-hover flex w-full items-center gap-x-2 rounded-md py-1 pl-0.5 pr-2 outline-none lg:hidden",
"text-ui-fg-subtle hover:text-ui-fg-base transition-fg hover:bg-ui-bg-subtle-hover flex w-full items-center gap-x-2 rounded-md py-0.5 pl-0.5 pr-2 outline-none lg:hidden",
{ "pl-2": isSetting }
)}
>
@@ -126,17 +126,17 @@ const Notification = ({
return (
<>
<div className="flex items-start justify-center gap-3 border-b p-6 relative">
<div className="relative flex items-start justify-center gap-3 border-b p-6">
<div className="text-ui-fg-muted flex size-5 items-center justify-center">
<InformationCircleSolid />
</div>
<div className="flex w-full flex-col gap-y-3">
<div className="flex flex-col">
<div className="items-center flex justify-between">
<div className="flex items-center justify-between">
<Text size="small" leading="compact" weight="plus">
{data.title}
</Text>
<div className="items-center flex justify-center align-center gap-2">
<div className="align-center flex items-center justify-center gap-2">
<Text
as={"span"}
className={clx("text-ui-fg-subtle", {
@@ -152,7 +152,7 @@ const Notification = ({
</Text>
{unread && (
<div
className="h-2 w-2 rounded bg-ui-bg-interactive"
className="bg-ui-bg-interactive h-2 w-2 rounded"
role="status"
/>
)}
@@ -201,7 +201,7 @@ const useUnreadNotifications = () => {
const [hasUnread, setHasUnread] = useState(false)
const { notifications } = useNotifications(
{ limit: 1, offset: 0, fields: "created_at" },
{ refetchInterval: 3000 }
{ refetchInterval: 60_000 }
)
const lastNotification = notifications?.[0]
@@ -48,12 +48,12 @@ export const SingleColumnPage = <TData,>({
return (
<div className="flex flex-col gap-y-3">
{before.widgets.map((w, i) => {
return <w.Component {...widgetProps} key={i} />
{before.map((Component, i) => {
return <Component {...widgetProps} key={i} />
})}
{children}
{after.widgets.map((w, i) => {
return <w.Component {...widgetProps} key={i} />
{after.map((Component, i) => {
return <Component {...widgetProps} key={i} />
})}
{showMetadata && <MetadataSection data={data!} />}
{showJSON && <JsonViewSection data={data!} />}
@@ -1,13 +1,13 @@
import { clx } from "@medusajs/ui"
import { Children, ComponentPropsWithoutRef } from "react"
import { Children, ComponentPropsWithoutRef, ComponentType } from "react"
import { Outlet } from "react-router-dom"
import { JsonViewSection } from "../../../common/json-view-section"
import { MetadataSection } from "../../../common/metadata-section"
import { PageProps, WidgetImport, WidgetProps } from "../types"
import { PageProps, WidgetProps } from "../types"
interface TwoColumnWidgetProps extends WidgetProps {
sideBefore: WidgetImport
sideAfter: WidgetImport
sideBefore: ComponentType<any>[]
sideAfter: ComponentType<any>[]
}
interface TwoColumnPageProps<TData> extends PageProps<TData> {
@@ -71,14 +71,14 @@ const Root = <TData,>({
return (
<div className="flex flex-col gap-y-3">
{before.widgets.map((w, i) => {
return <w.Component {...widgetProps} key={i} />
{before.map((Component, i) => {
return <Component {...widgetProps} key={i} />
})}
<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">
{main}
{after.widgets.map((w, i) => {
return <w.Component {...widgetProps} key={i} />
{after.map((Component, i) => {
return <Component {...widgetProps} key={i} />
})}
{showExtraData && (
<div className="hidden flex-col gap-y-3 xl:flex">
@@ -88,12 +88,12 @@ const Root = <TData,>({
)}
</div>
<div className="flex w-full max-w-[100%] flex-col gap-y-3 xl:mt-0 xl:max-w-[440px]">
{sideBefore.widgets.map((w, i) => {
return <w.Component {...widgetProps} key={i} />
{sideBefore.map((Component, i) => {
return <Component {...widgetProps} key={i} />
})}
{sidebar}
{sideAfter.widgets.map((w, i) => {
return <w.Component {...widgetProps} key={i} />
{sideAfter.map((Component, i) => {
return <Component {...widgetProps} key={i} />
})}
{showExtraData && (
<div className="flex flex-col gap-y-3 xl:hidden">
@@ -1,16 +1,8 @@
import { ReactNode } from "react"
export type Widget = {
Component: React.ComponentType<any>
}
export type WidgetImport = {
widgets: Widget[]
}
import { ComponentType, ReactNode } from "react"
export interface WidgetProps {
before: WidgetImport
after: WidgetImport
before: ComponentType<any>[]
after: ComponentType<any>[]
}
export interface PageProps<TData> {
@@ -1,14 +1,5 @@
import { Outlet } from "react-router-dom";
import { Outlet } from "react-router-dom"
export const PublicLayout = () => {
return (
<div className="min-h-screen flex items-center justify-center px-4 py-6">
<div className="bg-ui-bg-base text-ui-fg-subtle w-[520px] px-16 py-20 rounded-[32px] shadow-elevation-modal flex flex-col gap-y-12 items-center">
<div className="w-24 h-24 rounded-3xl bg-ui-bg-subtle shadow-elevation-card-hover"></div>
<div className="w-full">
<Outlet />
</div>
</div>
</div>
);
};
return <Outlet />
}
@@ -5,12 +5,11 @@ import { Fragment, useEffect, useMemo, useState } from "react"
import { useTranslation } from "react-i18next"
import { Link, useLocation } from "react-router-dom"
import { settingsRouteRegex } from "../../../lib/extension-helpers"
import { Divider } from "../../common/divider"
import { NavItem, NavItemProps } from "../nav-item"
import { INavItem, NavItem } from "../nav-item"
import { Shell } from "../shell"
import routes from "virtual:medusa/routes/links"
import { useDashboardExtension } from "../../../extensions"
import { UserMenu } from "../user-menu"
export const SettingsLayout = () => {
@@ -21,7 +20,7 @@ export const SettingsLayout = () => {
)
}
const useSettingRoutes = (): NavItemProps[] => {
const useSettingRoutes = (): INavItem[] => {
const { t } = useTranslation()
return useMemo(
@@ -67,7 +66,7 @@ const useSettingRoutes = (): NavItemProps[] => {
)
}
const useDeveloperRoutes = (): NavItemProps[] => {
const useDeveloperRoutes = (): INavItem[] => {
const { t } = useTranslation()
return useMemo(
@@ -89,7 +88,7 @@ const useDeveloperRoutes = (): NavItemProps[] => {
)
}
const useMyAccountRoutes = (): NavItemProps[] => {
const useMyAccountRoutes = (): INavItem[] => {
const { t } = useTranslation()
return useMemo(
@@ -103,21 +102,6 @@ const useMyAccountRoutes = (): NavItemProps[] => {
)
}
const useExtensionRoutes = (): NavItemProps[] => {
const links = routes.links
return useMemo(() => {
const settingsLinks = links.filter((link) =>
settingsRouteRegex.test(link.path)
)
return settingsLinks.map((link) => ({
label: link.label,
to: link.path,
}))
}, [links])
}
/**
* Ensure that the `from` prop is not another settings route, to avoid
* the user getting stuck in a navigation loop.
@@ -131,10 +115,12 @@ const getSafeFromValue = (from: string) => {
}
const SettingsSidebar = () => {
const { getMenu } = useDashboardExtension()
const routes = useSettingRoutes()
const developerRoutes = useDeveloperRoutes()
const extensionRoutes = useExtensionRoutes()
const myAccountRoutes = useMyAccountRoutes()
const extensionRoutes = getMenu("settingsExtensions")
const { t } = useTranslation()
@@ -227,7 +213,7 @@ const CollapsibleSection = ({
items,
}: {
label: string
items: NavItemProps[]
items: INavItem[]
}) => {
return (
<Collapsible.Root defaultOpen className="py-3">