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:
Kasper Fabricius Kristensen
2024-07-19 13:18:48 +02:00
committed by GitHub
parent 07205e4249
commit 75c5d5ad9e
31 changed files with 1346 additions and 2400 deletions

View File

@@ -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>
)
}