chore: move next admin packages to core repo (#5983)
**What** - Move packages for `next` version of admin to core repo **Other** - Since this PR introduces packages that depend on Vite 5, it also introduces @types/node@^20. We have never had a direct dependency on the types package for Node, and as far as I can see that has resulted in us using the types from Node.js@8, as those are a dependency of one of our dependencies. With the introduction of @types/node@^20, two of our packages had TS errors because they were using the NodeJS.Timer type, which was deprecated in Node.js@14. We should add specific @types/node packages to all our packages, but I haven't done so in this PR to keep it as clean as possible. - Q: @olivermrbl I've added the new packages to the ignore list for changeset, is this enough to prevent them from being published?
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
import { AdminAuthRes, User } from "@medusajs/medusa"
|
||||
import { createContext } from "react"
|
||||
|
||||
type AuthContextValue = {
|
||||
login: (email: string, password: string) => Promise<AdminAuthRes>
|
||||
user: Omit<User, "password_hash"> | null
|
||||
isLoading: boolean
|
||||
}
|
||||
|
||||
export const AuthContext = createContext<AuthContextValue | null>(null)
|
||||
@@ -0,0 +1,24 @@
|
||||
import { useAdminGetSession, useAdminLogin } from "medusa-react"
|
||||
import { PropsWithChildren } from "react"
|
||||
import { AuthContext } from "./auth-context"
|
||||
|
||||
export const AuthProvider = ({ children }: PropsWithChildren) => {
|
||||
const { mutateAsync: loginMutation } = useAdminLogin()
|
||||
const { user, isLoading } = useAdminGetSession()
|
||||
|
||||
const login = async (email: string, password: string) => {
|
||||
return await loginMutation({ email, password })
|
||||
}
|
||||
|
||||
return (
|
||||
<AuthContext.Provider
|
||||
value={{
|
||||
login,
|
||||
user: user ?? null,
|
||||
isLoading,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./auth-provider";
|
||||
export * from "./use-auth";
|
||||
@@ -0,0 +1,10 @@
|
||||
import { useContext } from "react";
|
||||
import { AuthContext } from "./auth-context";
|
||||
|
||||
export const useAuth = () => {
|
||||
const context = useContext(AuthContext);
|
||||
if (!context) {
|
||||
throw new Error("useAuth must be used within an AuthProvider");
|
||||
}
|
||||
return context;
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
import { createContext } from "react";
|
||||
import { Feature } from "./types";
|
||||
|
||||
type FeatureContextValue = {
|
||||
isFeatureEnabled: (feature: Feature) => boolean;
|
||||
};
|
||||
|
||||
export const FeatureContext = createContext<FeatureContextValue | null>(null);
|
||||
@@ -0,0 +1,33 @@
|
||||
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[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!store || isLoading) {
|
||||
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);
|
||||
|
||||
setFeatures(enabled as Feature[]);
|
||||
}, [store, isLoading]);
|
||||
|
||||
function isFeatureEnabled(feature: Feature) {
|
||||
return features.includes(feature);
|
||||
}
|
||||
|
||||
return (
|
||||
<FeatureContext.Provider value={{ isFeatureEnabled }}>
|
||||
{children}
|
||||
</FeatureContext.Provider>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,2 @@
|
||||
export { FeatureProvider } from "./feature-provider";
|
||||
export { useFeature } from "./use-feature";
|
||||
@@ -0,0 +1,16 @@
|
||||
const featureFlags = [
|
||||
"analytics",
|
||||
"order_editing",
|
||||
"product_categories",
|
||||
"publishable_api_keys",
|
||||
"sales_channels",
|
||||
"tax_inclusive_pricing",
|
||||
] as const;
|
||||
|
||||
type FeatureFlag = (typeof featureFlags)[number];
|
||||
|
||||
const modules = ["inventory"] as const;
|
||||
|
||||
type Module = (typeof modules)[number];
|
||||
|
||||
export type Feature = FeatureFlag | Module;
|
||||
@@ -0,0 +1,10 @@
|
||||
import { useContext } from "react";
|
||||
import { FeatureContext } from "./feature-context";
|
||||
|
||||
export const useFeature = () => {
|
||||
const context = useContext(FeatureContext);
|
||||
if (context === null) {
|
||||
throw new Error("useFeature must be used within a FeatureProvider");
|
||||
}
|
||||
return context;
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./router-provider";
|
||||
@@ -0,0 +1,319 @@
|
||||
import {
|
||||
RouterProvider as Provider,
|
||||
RouteObject,
|
||||
createBrowserRouter,
|
||||
} from "react-router-dom"
|
||||
|
||||
import { RequireAuth } from "../../components/authentication/require-auth"
|
||||
import { AppLayout } from "../../components/layout/app-layout"
|
||||
import { PublicLayout } from "../../components/layout/public-layout"
|
||||
|
||||
import { AdminProductsRes } from "@medusajs/medusa"
|
||||
import routes from "medusa-admin:routes/pages"
|
||||
import settings from "medusa-admin:settings/pages"
|
||||
import { ErrorBoundary } from "../../components/error/error-boundary"
|
||||
import { SearchProvider } from "../search-provider"
|
||||
|
||||
const routeExtensions: RouteObject[] = routes.pages.map((ext) => {
|
||||
return {
|
||||
path: ext.path,
|
||||
async lazy() {
|
||||
const { default: Component } = await import(/* @vite-ignore */ ext.file)
|
||||
return { Component }
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
const settingsExtensions: RouteObject[] = settings.pages.map((ext) => {
|
||||
return {
|
||||
path: `/settings${ext.path}`,
|
||||
async lazy() {
|
||||
const { default: Component } = await import(/* @vite-ignore */ ext.file)
|
||||
return { Component }
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
const router = createBrowserRouter([
|
||||
{
|
||||
element: <PublicLayout />,
|
||||
children: [
|
||||
{
|
||||
path: "/login",
|
||||
lazy: () => import("../../routes/login"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
element: (
|
||||
<RequireAuth>
|
||||
<SearchProvider>
|
||||
<AppLayout />
|
||||
</SearchProvider>
|
||||
</RequireAuth>
|
||||
),
|
||||
errorElement: <ErrorBoundary />,
|
||||
children: [
|
||||
{
|
||||
path: "/",
|
||||
lazy: () => import("../../routes/home"),
|
||||
},
|
||||
{
|
||||
path: "/orders",
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
lazy: () => import("../../routes/orders/list"),
|
||||
},
|
||||
{
|
||||
path: ":id",
|
||||
lazy: () => import("../../routes/orders/details"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/draft-orders",
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
lazy: () => import("../../routes/draft-orders/list"),
|
||||
},
|
||||
{
|
||||
path: ":id",
|
||||
lazy: () => import("../../routes/draft-orders/details"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/products",
|
||||
handle: {
|
||||
crumb: () => "Products",
|
||||
},
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
lazy: () => import("../../routes/products/views/product-list"),
|
||||
},
|
||||
{
|
||||
path: ":id",
|
||||
lazy: () => import("../../routes/products/views/product-details"),
|
||||
handle: {
|
||||
crumb: (data: AdminProductsRes) => data.product.title,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/categories",
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
lazy: () => import("../../routes/categories/list"),
|
||||
},
|
||||
{
|
||||
path: ":id",
|
||||
lazy: () => import("../../routes/categories/details"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/collections",
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
lazy: () => import("../../routes/collections/list"),
|
||||
},
|
||||
{
|
||||
path: ":id",
|
||||
lazy: () => import("../../routes/collections/details"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/customers",
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
lazy: () => import("../../routes/customers/list"),
|
||||
},
|
||||
{
|
||||
path: ":id",
|
||||
lazy: () => import("../../routes/customers/details"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/customer-groups",
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
lazy: () => import("../../routes/customer-groups/list"),
|
||||
},
|
||||
{
|
||||
path: ":id",
|
||||
lazy: () => import("../../routes/customer-groups/details"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/gift-cards",
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
lazy: () => import("../../routes/gift-cards/list"),
|
||||
},
|
||||
{
|
||||
path: ":id",
|
||||
lazy: () => import("../../routes/gift-cards/details"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/inventory",
|
||||
lazy: () => import("../../routes/inventory/list"),
|
||||
},
|
||||
{
|
||||
path: "/discounts",
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
lazy: () => import("../../routes/discounts/list"),
|
||||
},
|
||||
{
|
||||
path: ":id",
|
||||
lazy: () => import("../../routes/discounts/details"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/pricing",
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
lazy: () => import("../../routes/pricing/list"),
|
||||
},
|
||||
{
|
||||
path: ":id",
|
||||
lazy: () => import("../../routes/pricing/details"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/settings",
|
||||
handle: {
|
||||
crumb: () => "Settings",
|
||||
},
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
lazy: () => import("../../routes/settings"),
|
||||
},
|
||||
{
|
||||
path: "profile",
|
||||
lazy: () => import("../../routes/profile/views/profile-details"),
|
||||
handle: {
|
||||
crumb: () => "Profile",
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "store",
|
||||
lazy: () => import("../../routes/store/views/store-details"),
|
||||
handle: {
|
||||
crumb: () => "Store",
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "locations",
|
||||
lazy: () => import("../../routes/locations/list"),
|
||||
},
|
||||
{
|
||||
path: "regions",
|
||||
handle: {
|
||||
crumb: () => "Regions",
|
||||
},
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
lazy: () => import("../../routes/regions/views/region-list"),
|
||||
},
|
||||
{
|
||||
path: ":id",
|
||||
lazy: () => import("../../routes/regions/views/region-details"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "users",
|
||||
lazy: () => import("../../routes/users"),
|
||||
handle: {
|
||||
crumb: () => "Users",
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "currencies",
|
||||
lazy: () =>
|
||||
import("../../routes/currencies/views/currencies-details"),
|
||||
handle: {
|
||||
crumb: () => "Currencies",
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "taxes",
|
||||
handle: {
|
||||
crumb: () => "Taxes",
|
||||
},
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
lazy: () => import("../../routes/taxes/views/tax-list"),
|
||||
},
|
||||
{
|
||||
path: ":id",
|
||||
lazy: () => import("../../routes/taxes/views/tax-details"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "sales-channels",
|
||||
handle: {
|
||||
crumb: () => "Sales Channels",
|
||||
},
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
lazy: () =>
|
||||
import(
|
||||
"../../routes/sales-channels/views/sales-channel-list"
|
||||
),
|
||||
},
|
||||
{
|
||||
path: ":id",
|
||||
lazy: () =>
|
||||
import(
|
||||
"../../routes/sales-channels/views/sales-channel-details"
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "api-key-management",
|
||||
lazy: () => import("../../routes/api-key-management"),
|
||||
handle: {
|
||||
crumb: () => "API Key Management",
|
||||
},
|
||||
},
|
||||
...settingsExtensions,
|
||||
],
|
||||
},
|
||||
...routeExtensions,
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "*",
|
||||
lazy: () => import("../../routes/no-match"),
|
||||
},
|
||||
])
|
||||
|
||||
export const RouterProvider = () => {
|
||||
return <Provider router={router} />
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export { SearchProvider } from "./search-provider"
|
||||
export { useSearch } from "./use-search"
|
||||
@@ -0,0 +1,9 @@
|
||||
import { createContext } from "react"
|
||||
|
||||
type SearchContextValue = {
|
||||
open: boolean
|
||||
onOpenChange: (open: boolean) => void
|
||||
toggleSearch: () => void
|
||||
}
|
||||
|
||||
export const SearchContext = createContext<SearchContextValue | null>(null)
|
||||
@@ -0,0 +1,38 @@
|
||||
import { PropsWithChildren, useEffect, useState } from "react"
|
||||
import { Search } from "../../components/search"
|
||||
import { SearchContext } from "./search-context"
|
||||
|
||||
export const SearchProvider = ({ children }: PropsWithChildren) => {
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
const toggleSearch = () => {
|
||||
setOpen(!open)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const onKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
|
||||
setOpen((prev) => !prev)
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("keydown", onKeyDown)
|
||||
|
||||
return () => {
|
||||
document.removeEventListener("keydown", onKeyDown)
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<SearchContext.Provider
|
||||
value={{
|
||||
open,
|
||||
onOpenChange: setOpen,
|
||||
toggleSearch,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
<Search />
|
||||
</SearchContext.Provider>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { useContext } from "react"
|
||||
import { SearchContext } from "./search-context"
|
||||
|
||||
export const useSearch = () => {
|
||||
const context = useContext(SearchContext)
|
||||
if (!context) {
|
||||
throw new Error("useSearch must be used within a SearchProvider")
|
||||
}
|
||||
return context
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export type { Theme } from "./theme-context";
|
||||
export * from "./theme-provider";
|
||||
export * from "./use-theme";
|
||||
@@ -0,0 +1,10 @@
|
||||
import { createContext } from "react";
|
||||
|
||||
export type Theme = "light" | "dark";
|
||||
|
||||
type ThemeContextValue = {
|
||||
theme: Theme;
|
||||
setTheme: (theme: Theme) => void;
|
||||
};
|
||||
|
||||
export const ThemeContext = createContext<ThemeContextValue | null>(null);
|
||||
@@ -0,0 +1,54 @@
|
||||
import { PropsWithChildren, useEffect, useState } from "react";
|
||||
import { Theme, ThemeContext } from "./theme-context";
|
||||
|
||||
const THEME_KEY = "medusa_admin_theme";
|
||||
|
||||
export const ThemeProvider = ({ children }: PropsWithChildren) => {
|
||||
const [state, setState] = useState<Theme>(
|
||||
(localStorage?.getItem(THEME_KEY) as Theme) || "light"
|
||||
);
|
||||
|
||||
const setTheme = (theme: Theme) => {
|
||||
localStorage.setItem(THEME_KEY, theme);
|
||||
setState(theme);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const html = document.querySelector("html");
|
||||
if (html) {
|
||||
/**
|
||||
* Temporarily disable transitions to prevent
|
||||
* the theme change from flashing.
|
||||
*/
|
||||
const css = document.createElement("style");
|
||||
css.appendChild(
|
||||
document.createTextNode(
|
||||
`* {
|
||||
-webkit-transition: none !important;
|
||||
-moz-transition: none !important;
|
||||
-o-transition: none !important;
|
||||
-ms-transition: none !important;
|
||||
transition: none !important;
|
||||
}`
|
||||
)
|
||||
);
|
||||
document.head.appendChild(css);
|
||||
|
||||
html.classList.remove(state === "light" ? "dark" : "light");
|
||||
html.classList.add(state);
|
||||
|
||||
/**
|
||||
* Re-enable transitions after the theme has been set,
|
||||
* and force the browser to repaint.
|
||||
*/
|
||||
window.getComputedStyle(css).opacity;
|
||||
document.head.removeChild(css);
|
||||
}
|
||||
}, [state]);
|
||||
|
||||
return (
|
||||
<ThemeContext.Provider value={{ theme: state, setTheme }}>
|
||||
{children}
|
||||
</ThemeContext.Provider>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
import { useContext } from "react";
|
||||
import { ThemeContext } from "./theme-context";
|
||||
|
||||
export const useTheme = () => {
|
||||
const context = useContext(ThemeContext);
|
||||
if (!context) {
|
||||
throw new Error("useTheme must be used within a ThemeProvider");
|
||||
}
|
||||
return context;
|
||||
};
|
||||
Reference in New Issue
Block a user