fix(dashboard): Minor cleanup and improve text legibility (#7045)
**What** - Cleans up some artifacts from the V1 -> V2 migrations. - Removes the MedusaProvider from the root in favor of a plain QueryClient. - Applies font styles to make the text in admin resemble designs in Figma more closely.
This commit is contained in:
committed by
GitHub
parent
27387b7cf1
commit
51acd1da5b
@@ -1,24 +1,18 @@
|
||||
import { Toaster } from "@medusajs/ui"
|
||||
import { MedusaProvider } from "medusa-react"
|
||||
import { QueryClientProvider } from "@tanstack/react-query"
|
||||
|
||||
import { queryClient } from "./lib/medusa"
|
||||
import { RouterProvider } from "./providers/router-provider"
|
||||
import { ThemeProvider } from "./providers/theme-provider"
|
||||
|
||||
import { MEDUSA_BACKEND_URL, queryClient } from "./lib/medusa"
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<MedusaProvider
|
||||
baseUrl={MEDUSA_BACKEND_URL}
|
||||
queryClientProviderProps={{
|
||||
client: queryClient,
|
||||
}}
|
||||
>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<ThemeProvider>
|
||||
<RouterProvider />
|
||||
<Toaster />
|
||||
</ThemeProvider>
|
||||
</MedusaProvider>
|
||||
</QueryClientProvider>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./protected-route"
|
||||
@@ -0,0 +1,30 @@
|
||||
import { Spinner } from "@medusajs/icons"
|
||||
import { Navigate, Outlet, useLocation } from "react-router-dom"
|
||||
import { useMe } from "../../../hooks/api/users"
|
||||
import { SearchProvider } from "../../../providers/search-provider"
|
||||
import { SidebarProvider } from "../../../providers/sidebar-provider"
|
||||
|
||||
export const ProtectedRoute = () => {
|
||||
const { user, isLoading } = useMe()
|
||||
const location = useLocation()
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center">
|
||||
<Spinner className="text-ui-fg-interactive animate-spin" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
return <Navigate to="/login" state={{ from: location }} replace />
|
||||
}
|
||||
|
||||
return (
|
||||
<SidebarProvider>
|
||||
<SearchProvider>
|
||||
<Outlet />
|
||||
</SearchProvider>
|
||||
</SidebarProvider>
|
||||
)
|
||||
}
|
||||
@@ -5,6 +5,9 @@ import { useAdminGetSession } from "medusa-react"
|
||||
import { SearchProvider } from "../../../providers/search-provider"
|
||||
import { SidebarProvider } from "../../../providers/sidebar-provider"
|
||||
|
||||
/**
|
||||
* @deprecated - Delete once all V1 domains have been migrated to V2.
|
||||
*/
|
||||
export const ProtectedRoute = () => {
|
||||
const { user, isLoading } = useAdminGetSession()
|
||||
const location = useLocation()
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export * from "./main-layout"
|
||||
@@ -1,205 +0,0 @@
|
||||
import {
|
||||
Buildings,
|
||||
ChevronDownMini,
|
||||
CurrencyDollar,
|
||||
MinusMini,
|
||||
ReceiptPercent,
|
||||
ShoppingCart,
|
||||
SquaresPlus,
|
||||
Tag,
|
||||
Users,
|
||||
} from "@medusajs/icons"
|
||||
import { Avatar, Text } from "@medusajs/ui"
|
||||
import * as Collapsible from "@radix-ui/react-collapsible"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
import { Skeleton } from "../../common/skeleton"
|
||||
import { NavItem, NavItemProps } from "../../layout/nav-item"
|
||||
import { Shell } from "../../layout/shell"
|
||||
|
||||
import extensions from "medusa-admin:routes/links"
|
||||
import { useStore } from "../../../hooks/api/store"
|
||||
|
||||
export const MainLayout = () => {
|
||||
return (
|
||||
<Shell>
|
||||
<MainSidebar />
|
||||
</Shell>
|
||||
)
|
||||
}
|
||||
|
||||
const MainSidebar = () => {
|
||||
return (
|
||||
<aside className="flex flex-1 flex-col justify-between overflow-y-auto">
|
||||
<div className="flex flex-1 flex-col">
|
||||
<div className="bg-ui-bg-subtle sticky top-0">
|
||||
<Header />
|
||||
<div className="px-3">
|
||||
<div className="border-ui-border-strong h-px w-full border-b border-dashed" />
|
||||
</div>
|
||||
</div>
|
||||
<CoreRouteSection />
|
||||
<ExtensionRouteSection />
|
||||
</div>
|
||||
</aside>
|
||||
)
|
||||
}
|
||||
|
||||
const Header = () => {
|
||||
const { store, isError, error } = useStore()
|
||||
|
||||
const name = store?.name
|
||||
const fallback = store?.name?.slice(0, 1).toUpperCase()
|
||||
|
||||
if (isError) {
|
||||
throw error
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-full px-3 py-2">
|
||||
<div className="flex items-center p-1 md:pr-2">
|
||||
<div className="flex items-center gap-x-3">
|
||||
{fallback ? (
|
||||
<Avatar variant="squared" fallback={fallback} />
|
||||
) : (
|
||||
<Skeleton className="h-8 w-8 rounded-md" />
|
||||
)}
|
||||
{name ? (
|
||||
<Text size="small" weight="plus" leading="compact">
|
||||
{store.name}
|
||||
</Text>
|
||||
) : (
|
||||
<Skeleton className="h-[9px] w-[120px]" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const useCoreRoutes = (): Omit<NavItemProps, "pathname">[] => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return [
|
||||
{
|
||||
icon: <ShoppingCart />,
|
||||
label: t("orders.domain"),
|
||||
to: "/orders",
|
||||
items: [
|
||||
{
|
||||
label: t("draftOrders.domain"),
|
||||
to: "/draft-orders",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
icon: <Tag />,
|
||||
label: t("products.domain"),
|
||||
to: "/products",
|
||||
items: [
|
||||
{
|
||||
label: t("collections.domain"),
|
||||
to: "/collections",
|
||||
},
|
||||
{
|
||||
label: t("categories.domain"),
|
||||
to: "/categories",
|
||||
},
|
||||
{
|
||||
label: t("giftCards.domain"),
|
||||
to: "/gift-cards",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
icon: <Buildings />,
|
||||
label: t("inventory.domain"),
|
||||
to: "/inventory",
|
||||
items: [
|
||||
{
|
||||
label: t("reservations.domain"),
|
||||
to: "/reservations",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
icon: <Users />,
|
||||
label: t("customers.domain"),
|
||||
to: "/customers",
|
||||
items: [
|
||||
{
|
||||
label: t("customerGroups.domain"),
|
||||
to: "/customer-groups",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
icon: <ReceiptPercent />,
|
||||
label: t("promotions.domain"),
|
||||
to: "/promotions",
|
||||
},
|
||||
{
|
||||
icon: <CurrencyDollar />,
|
||||
label: t("pricing.domain"),
|
||||
to: "/pricing",
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
const CoreRouteSection = () => {
|
||||
const coreRoutes = useCoreRoutes()
|
||||
|
||||
return (
|
||||
<nav className="flex flex-col gap-y-1 py-2">
|
||||
{coreRoutes.map((route) => {
|
||||
return <NavItem key={route.to} {...route} />
|
||||
})}
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
|
||||
const ExtensionRouteSection = () => {
|
||||
if (!extensions.links || extensions.links.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="px-3">
|
||||
<div className="border-ui-border-strong h-px w-full border-b border-dashed" />
|
||||
</div>
|
||||
<div className="flex flex-col gap-y-1 py-2">
|
||||
<Collapsible.Root defaultOpen>
|
||||
<div className="px-4">
|
||||
<Collapsible.Trigger asChild className="group/trigger">
|
||||
<button className="text-ui-fg-subtle flex w-full items-center justify-between px-2">
|
||||
<Text size="xsmall" weight="plus" leading="compact">
|
||||
Extensions
|
||||
</Text>
|
||||
<div className="text-ui-fg-muted">
|
||||
<ChevronDownMini className="group-data-[state=open]/trigger:hidden" />
|
||||
<MinusMini className="group-data-[state=closed]/trigger:hidden" />
|
||||
</div>
|
||||
</button>
|
||||
</Collapsible.Trigger>
|
||||
</div>
|
||||
<Collapsible.Content>
|
||||
<div className="flex flex-col gap-y-1 py-1 pb-4">
|
||||
{extensions.links.map((link) => {
|
||||
return (
|
||||
<NavItem
|
||||
key={link.path}
|
||||
to={link.path}
|
||||
label={link.label}
|
||||
icon={link.icon ? <link.icon /> : <SquaresPlus />}
|
||||
type="extension"
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</Collapsible.Content>
|
||||
</Collapsible.Root>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -11,12 +11,12 @@ import {
|
||||
} from "@medusajs/icons"
|
||||
import { Avatar, Text } from "@medusajs/ui"
|
||||
import * as Collapsible from "@radix-ui/react-collapsible"
|
||||
import { useAdminStore } from "medusa-react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
import { useStore } from "../../../hooks/api/store"
|
||||
import { Skeleton } from "../../common/skeleton"
|
||||
import { NavItem, NavItemProps } from "../nav-item"
|
||||
import { Shell } from "../shell"
|
||||
import { NavItem, NavItemProps } from "../../layout/nav-item"
|
||||
import { Shell } from "../../layout/shell"
|
||||
|
||||
import extensions from "medusa-admin:routes/links"
|
||||
|
||||
@@ -46,7 +46,7 @@ const MainSidebar = () => {
|
||||
}
|
||||
|
||||
const Header = () => {
|
||||
const { store, isError, error } = useAdminStore()
|
||||
const { store, isError, error } = useStore()
|
||||
|
||||
const name = store?.name
|
||||
const fallback = store?.name?.slice(0, 1).toUpperCase()
|
||||
@@ -135,8 +135,8 @@ const useCoreRoutes = (): Omit<NavItemProps, "pathname">[] => {
|
||||
},
|
||||
{
|
||||
icon: <ReceiptPercent />,
|
||||
label: t("discounts.domain"),
|
||||
to: "/discounts",
|
||||
label: t("promotions.domain"),
|
||||
to: "/promotions",
|
||||
},
|
||||
{
|
||||
icon: <CurrencyDollar />,
|
||||
|
||||
@@ -105,7 +105,6 @@ const Breadcrumbs = () => {
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{/* {!isLast && <TriangleRightMini className="-mt-0.5 mx-2" />} */}
|
||||
{!isLast && <span className="mx-2 -mt-0.5">›</span>}
|
||||
</li>
|
||||
)
|
||||
@@ -140,7 +139,7 @@ const UserBadge = () => {
|
||||
<button
|
||||
disabled={!user}
|
||||
className={clx(
|
||||
"shadow-borders-base flex max-w-[192px] select-none items-center gap-x-2 overflow-hidden text-ellipsis whitespace-nowrap rounded-full py-1 pl-1 pr-2.5"
|
||||
"shadow-borders-base flex max-w-[192px] select-none items-center gap-x-2 overflow-hidden text-ellipsis whitespace-nowrap rounded-full py-1 pl-1 pr-2.5 outline-none"
|
||||
)}
|
||||
>
|
||||
{fallback ? (
|
||||
@@ -384,7 +383,7 @@ const MobileSidebarContainer = ({ children }: PropsWithChildren) => {
|
||||
<Dialog.Root open={mobile} onOpenChange={() => toggle("mobile")}>
|
||||
<Dialog.Portal>
|
||||
<Dialog.Overlay className="bg-ui-bg-overlay fixed inset-0" />
|
||||
<Dialog.Content className="bg-ui-bg-subtle fixed inset-y-0 left-0 h-screen w-[220px] border-r">
|
||||
<Dialog.Content className="bg-ui-bg-subtle fixed inset-y-0 left-0 h-screen w-full max-w-[240px] border-r">
|
||||
{children}
|
||||
</Dialog.Content>
|
||||
</Dialog.Portal>
|
||||
|
||||
@@ -28,7 +28,8 @@
|
||||
}
|
||||
|
||||
:root {
|
||||
@apply bg-ui-bg-subtle text-ui-fg-base;
|
||||
@apply bg-ui-bg-subtle text-ui-fg-base antialiased;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
type Options = {
|
||||
leading?: boolean
|
||||
}
|
||||
|
||||
export const debounce = (
|
||||
func: (...args: any[]) => void,
|
||||
delay: number,
|
||||
{ leading }: Options = {}
|
||||
): ((...args: any[]) => void) => {
|
||||
let timerId: NodeJS.Timeout | undefined
|
||||
|
||||
return (...args) => {
|
||||
if (!timerId && leading) {
|
||||
func(...args)
|
||||
}
|
||||
clearTimeout(timerId)
|
||||
|
||||
timerId = setTimeout(() => func(...args), delay)
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
export function upperCaseFirst(str: string): string {
|
||||
return str.charAt(0).toUpperCase() + str.slice(1)
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { createContext } from "react";
|
||||
import { Feature } from "./types";
|
||||
import { createContext } from "react"
|
||||
import { Feature } from "./types"
|
||||
|
||||
type FeatureContextValue = {
|
||||
isFeatureEnabled: (feature: Feature) => boolean;
|
||||
};
|
||||
isFeatureEnabled: (feature: Feature) => boolean
|
||||
}
|
||||
|
||||
export const FeatureContext = createContext<FeatureContextValue | null>(null);
|
||||
export const FeatureContext = createContext<FeatureContextValue | null>(null)
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
import { useAdminStore } from "medusa-react";
|
||||
import { PropsWithChildren, useEffect, useState } from "react";
|
||||
import { FeatureContext } from "./feature-context";
|
||||
import { Feature } from "./types";
|
||||
import { useAdminStore } from "medusa-react"
|
||||
import { PropsWithChildren, useEffect, useState } from "react"
|
||||
import { FeatureContext } from "./feature-context"
|
||||
import { Feature } from "./types"
|
||||
|
||||
export const FeatureProvider = ({ children }: PropsWithChildren) => {
|
||||
const { store, isLoading } = useAdminStore();
|
||||
const [features, setFeatures] = useState<Feature[]>([]);
|
||||
const { store, isLoading } = useAdminStore()
|
||||
const [features, setFeatures] = useState<Feature[]>([])
|
||||
|
||||
useEffect(() => {
|
||||
if (!store || isLoading) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
const flags = store.feature_flags
|
||||
.filter((f) => f.value === true)
|
||||
.map((f) => f.key);
|
||||
const modules = store.modules.map((m) => m.module);
|
||||
const enabled = flags.concat(modules);
|
||||
.map((f) => f.key)
|
||||
const modules = store.modules.map((m) => m.module)
|
||||
const enabled = flags.concat(modules)
|
||||
|
||||
setFeatures(enabled as Feature[]);
|
||||
}, [store, isLoading]);
|
||||
setFeatures(enabled as Feature[])
|
||||
}, [store, isLoading])
|
||||
|
||||
function isFeatureEnabled(feature: Feature) {
|
||||
return features.includes(feature);
|
||||
return features.includes(feature)
|
||||
}
|
||||
|
||||
return (
|
||||
<FeatureContext.Provider value={{ isFeatureEnabled }}>
|
||||
{children}
|
||||
</FeatureContext.Provider>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
@@ -5,12 +5,12 @@ const featureFlags = [
|
||||
"publishable_api_keys",
|
||||
"sales_channels",
|
||||
"tax_inclusive_pricing",
|
||||
] as const;
|
||||
] as const
|
||||
|
||||
type FeatureFlag = (typeof featureFlags)[number];
|
||||
type FeatureFlag = (typeof featureFlags)[number]
|
||||
|
||||
const modules = ["inventory"] as const;
|
||||
const modules = ["inventory"] as const
|
||||
|
||||
type Module = (typeof modules)[number];
|
||||
type Module = (typeof modules)[number]
|
||||
|
||||
export type Feature = FeatureFlag | Module;
|
||||
export type Feature = FeatureFlag | Module
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
import { AdminCustomersRes } from "@medusajs/client-types"
|
||||
import {
|
||||
AdminCollectionsRes,
|
||||
AdminProductsRes,
|
||||
AdminPromotionRes,
|
||||
AdminRegionsRes,
|
||||
} from "@medusajs/medusa"
|
||||
import {
|
||||
AdminApiKeyResponse,
|
||||
AdminCustomerGroupResponse,
|
||||
@@ -5,49 +12,13 @@ import {
|
||||
SalesChannelDTO,
|
||||
UserDTO,
|
||||
} from "@medusajs/types"
|
||||
import {
|
||||
AdminCollectionsRes,
|
||||
AdminProductsRes,
|
||||
AdminPromotionRes,
|
||||
AdminRegionsRes,
|
||||
} from "@medusajs/medusa"
|
||||
import { Navigate, Outlet, RouteObject, useLocation } from "react-router-dom"
|
||||
import { Outlet, RouteObject } from "react-router-dom"
|
||||
|
||||
import { AdminCustomersRes } from "@medusajs/client-types"
|
||||
import { ProtectedRoute } from "../../components/authentication/protected-route"
|
||||
import { ErrorBoundary } from "../../components/error/error-boundary"
|
||||
import { InventoryItemRes } from "../../types/api-responses"
|
||||
import { MainLayout } from "../../components/layout-v2/main-layout"
|
||||
import { PriceListRes } from "../../types/api-responses"
|
||||
import { SearchProvider } from "../search-provider"
|
||||
import { MainLayout } from "../../components/layout/main-layout"
|
||||
import { SettingsLayout } from "../../components/layout/settings-layout"
|
||||
import { SidebarProvider } from "../sidebar-provider"
|
||||
import { Spinner } from "@medusajs/icons"
|
||||
import { useMe } from "../../hooks/api/users"
|
||||
|
||||
export const ProtectedRoute = () => {
|
||||
const { user, isLoading } = useMe()
|
||||
const location = useLocation()
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center">
|
||||
<Spinner className="text-ui-fg-interactive animate-spin" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
return <Navigate to="/login" state={{ from: location }} replace />
|
||||
}
|
||||
|
||||
return (
|
||||
<SidebarProvider>
|
||||
<SearchProvider>
|
||||
<Outlet />
|
||||
</SearchProvider>
|
||||
</SidebarProvider>
|
||||
)
|
||||
}
|
||||
import { InventoryItemRes, PriceListRes } from "../../types/api-responses"
|
||||
|
||||
/**
|
||||
* Experimental V2 routes.
|
||||
@@ -473,9 +444,6 @@ export const v2Routes: RouteObject[] = [
|
||||
{
|
||||
path: "/settings",
|
||||
element: <SettingsLayout />,
|
||||
handle: {
|
||||
crumb: () => "Settings",
|
||||
},
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { PropsWithChildren, useState } from "react"
|
||||
import { PropsWithChildren, useEffect, useState } from "react"
|
||||
import { useLocation } from "react-router-dom"
|
||||
import { SidebarContext } from "./sidebar-context"
|
||||
|
||||
export const SidebarProvider = ({ children }: PropsWithChildren) => {
|
||||
const [desktop, setDesktop] = useState(true)
|
||||
const [mobile, setMobile] = useState(false)
|
||||
|
||||
const { pathname } = useLocation()
|
||||
|
||||
const toggle = (view: "desktop" | "mobile") => {
|
||||
if (view === "desktop") {
|
||||
setDesktop(!desktop)
|
||||
@@ -13,6 +16,13 @@ export const SidebarProvider = ({ children }: PropsWithChildren) => {
|
||||
}
|
||||
}
|
||||
|
||||
// close the mobile sidebar on route change
|
||||
// this is to prevent the sidebar from staying open
|
||||
// when navigating to a new page
|
||||
useEffect(() => {
|
||||
setMobile(false)
|
||||
}, [pathname])
|
||||
|
||||
return (
|
||||
<SidebarContext.Provider value={{ desktop, mobile, toggle }}>
|
||||
{children}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// / <reference types="vite/client" />
|
||||
|
||||
interface ImportMetaEnv {
|
||||
readonly MEDUSA_ADMIN_BACKEND_URL: string
|
||||
readonly VITE_MEDUSA_ADMIN_BACKEND_URL: string
|
||||
readonly VITE_MEDUSA_V2: "true" | "false"
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user