feat(dashboard,js-sdk,types): Update app layout, and add user sdk methods (#8182)
**What** - Updates app layout (sidebar and topbar) - Adds "System" option to theme toggle (we now default to system) - Adds sdk methods for user endpoints (RESOLVES CC-67)
This commit is contained in:
committed by
GitHub
parent
07205e4249
commit
75c5d5ad9e
@@ -1,15 +1,20 @@
|
||||
import {
|
||||
BuildingStorefront,
|
||||
Buildings,
|
||||
ChevronDownMini,
|
||||
CogSixTooth,
|
||||
CurrencyDollar,
|
||||
EllipsisHorizontal,
|
||||
MagnifyingGlass,
|
||||
MinusMini,
|
||||
OpenRectArrowOut,
|
||||
ReceiptPercent,
|
||||
ShoppingCart,
|
||||
SquaresPlus,
|
||||
Tag,
|
||||
Users,
|
||||
} from "@medusajs/icons"
|
||||
import { Avatar, Text } from "@medusajs/ui"
|
||||
import { Avatar, DropdownMenu, Text, clx } from "@medusajs/ui"
|
||||
import * as Collapsible from "@radix-ui/react-collapsible"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
@@ -20,7 +25,12 @@ import { Skeleton } from "../../common/skeleton"
|
||||
import { NavItem, NavItemProps } 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 { useLogout } from "../../../hooks/api"
|
||||
import { queryClient } from "../../../lib/query-client"
|
||||
import { useSearch } from "../../../providers/search-provider"
|
||||
import { UserMenu } from "../user-menu"
|
||||
|
||||
export const MainLayout = () => {
|
||||
return (
|
||||
@@ -40,41 +50,129 @@ const MainSidebar = () => {
|
||||
<Divider variant="dashed" />
|
||||
</div>
|
||||
</div>
|
||||
<CoreRouteSection />
|
||||
<ExtensionRouteSection />
|
||||
<div className="flex flex-1 flex-col justify-between">
|
||||
<div className="flex flex-1 flex-col">
|
||||
<CoreRouteSection />
|
||||
<ExtensionRouteSection />
|
||||
</div>
|
||||
<UtilitySection />
|
||||
</div>
|
||||
<div className="bg-ui-bg-subtle sticky bottom-0">
|
||||
<UserSection />
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
)
|
||||
}
|
||||
|
||||
const Logout = () => {
|
||||
const { t } = useTranslation()
|
||||
const navigate = useNavigate()
|
||||
|
||||
const { mutateAsync: logoutMutation } = useLogout()
|
||||
|
||||
const handleLogout = async () => {
|
||||
await logoutMutation(undefined, {
|
||||
onSuccess: () => {
|
||||
/**
|
||||
* When the user logs out, we want to clear the query cache
|
||||
*/
|
||||
queryClient.clear()
|
||||
navigate("/login")
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<DropdownMenu.Item onClick={handleLogout}>
|
||||
<div className="flex items-center gap-x-2">
|
||||
<OpenRectArrowOut className="text-ui-fg-subtle" />
|
||||
<span>{t("app.menus.actions.logout")}</span>
|
||||
</div>
|
||||
</DropdownMenu.Item>
|
||||
)
|
||||
}
|
||||
|
||||
const Header = () => {
|
||||
const { store, isError, error } = useStore()
|
||||
const { t } = useTranslation()
|
||||
const { store, isPending, isError, error } = useStore()
|
||||
|
||||
const name = store?.name
|
||||
const fallback = store?.name?.slice(0, 1).toUpperCase()
|
||||
|
||||
const isLoaded = !isPending && !!store && !!name && !!fallback
|
||||
|
||||
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">
|
||||
<div className="w-full p-3">
|
||||
<DropdownMenu>
|
||||
<DropdownMenu.Trigger
|
||||
disabled={!isLoaded}
|
||||
className={clx(
|
||||
"bg-ui-bg-subtle transition-fg grid w-full grid-cols-[24px_1fr_15px] items-center gap-x-3 rounded-md p-0.5 pr-2 outline-none",
|
||||
"hover:bg-ui-bg-subtle-hover",
|
||||
"data-[state=open]:bg-ui-bg-subtle-hover",
|
||||
"focus-visible:shadow-borders-focus"
|
||||
)}
|
||||
>
|
||||
{fallback ? (
|
||||
<Avatar variant="squared" fallback={fallback} />
|
||||
<Avatar variant="squared" size="xsmall" fallback={fallback} />
|
||||
) : (
|
||||
<Skeleton className="h-8 w-8 rounded-md" />
|
||||
<Skeleton className="h-6 w-6 rounded-md" />
|
||||
)}
|
||||
{name ? (
|
||||
<Text size="small" weight="plus" leading="compact">
|
||||
{store.name}
|
||||
</Text>
|
||||
) : (
|
||||
<Skeleton className="h-[9px] w-[120px]" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="block overflow-hidden">
|
||||
{name ? (
|
||||
<Text
|
||||
size="small"
|
||||
weight="plus"
|
||||
leading="compact"
|
||||
className="truncate"
|
||||
>
|
||||
{store.name}
|
||||
</Text>
|
||||
) : (
|
||||
<Skeleton className="h-[9px] w-[120px]" />
|
||||
)}
|
||||
</div>
|
||||
<EllipsisHorizontal className="text-ui-fg-muted" />
|
||||
</DropdownMenu.Trigger>
|
||||
{isLoaded && (
|
||||
<DropdownMenu.Content className="w-[var(--radix-dropdown-menu-trigger-width)] min-w-0">
|
||||
<div className="flex items-center gap-x-3 px-2 py-1">
|
||||
<Avatar variant="squared" size="small" fallback={fallback} />
|
||||
<div className="flex flex-col overflow-hidden">
|
||||
<Text
|
||||
size="small"
|
||||
weight="plus"
|
||||
leading="compact"
|
||||
className="truncate"
|
||||
>
|
||||
{name}
|
||||
</Text>
|
||||
<Text
|
||||
size="xsmall"
|
||||
leading="compact"
|
||||
className="text-ui-fg-subtle"
|
||||
>
|
||||
{t("app.nav.main.store")}
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item className="gap-x-2" asChild>
|
||||
<Link to="/settings/store">
|
||||
<BuildingStorefront className="text-ui-fg-subtle" />
|
||||
{t("app.nav.main.storeSettings")}
|
||||
</Link>
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
<Logout />
|
||||
</DropdownMenu.Content>
|
||||
)}
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -156,11 +254,40 @@ const useCoreRoutes = (): Omit<NavItemProps, "pathname">[] => {
|
||||
]
|
||||
}
|
||||
|
||||
const Searchbar = () => {
|
||||
const { t } = useTranslation()
|
||||
const { toggleSearch } = useSearch()
|
||||
|
||||
return (
|
||||
<div className="px-3">
|
||||
<button
|
||||
onClick={toggleSearch}
|
||||
className={clx(
|
||||
"bg-ui-bg-subtle text-ui-fg-subtle flex w-full items-center gap-x-2.5 rounded-md px-2 py-1 outline-none",
|
||||
"hover:bg-ui-bg-subtle-hover",
|
||||
"focus-visible:shadow-borders-focus"
|
||||
)}
|
||||
>
|
||||
<MagnifyingGlass />
|
||||
<div className="flex-1 text-left">
|
||||
<Text size="small" leading="compact" weight="plus">
|
||||
{t("app.search.label")}
|
||||
</Text>
|
||||
</div>
|
||||
<Text size="small" leading="compact" className="text-ui-fg-muted">
|
||||
⌘K
|
||||
</Text>
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const CoreRouteSection = () => {
|
||||
const coreRoutes = useCoreRoutes()
|
||||
|
||||
return (
|
||||
<nav className="flex flex-col gap-y-1 py-3">
|
||||
<Searchbar />
|
||||
{coreRoutes.map((route) => {
|
||||
return <NavItem key={route.to} {...route} />
|
||||
})}
|
||||
@@ -192,7 +319,7 @@ const ExtensionRouteSection = () => {
|
||||
<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">
|
||||
{t("nav.extensions")}
|
||||
{t("app.nav.common.extensions")}
|
||||
</Text>
|
||||
<div className="text-ui-fg-muted">
|
||||
<ChevronDownMini className="group-data-[state=open]/trigger:hidden" />
|
||||
@@ -202,7 +329,7 @@ const ExtensionRouteSection = () => {
|
||||
</Collapsible.Trigger>
|
||||
</div>
|
||||
<Collapsible.Content>
|
||||
<div className="flex flex-col gap-y-1 py-1 pb-4">
|
||||
<nav className="flex flex-col gap-y-0.5 py-1 pb-4">
|
||||
{extensionLinks.map((link) => {
|
||||
return (
|
||||
<NavItem
|
||||
@@ -214,10 +341,37 @@ const ExtensionRouteSection = () => {
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</nav>
|
||||
</Collapsible.Content>
|
||||
</Collapsible.Root>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const UtilitySection = () => {
|
||||
const location = useLocation()
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-y-0.5 py-3">
|
||||
<NavItem
|
||||
label={t("app.nav.settings.header")}
|
||||
to="/settings"
|
||||
from={location.pathname}
|
||||
icon={<CogSixTooth />}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const UserSection = () => {
|
||||
return (
|
||||
<div>
|
||||
<div className="px-3">
|
||||
<Divider variant="dashed" />
|
||||
</div>
|
||||
<UserMenu />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user