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:
Kasper Fabricius Kristensen
2024-01-08 09:26:46 +00:00
committed by GitHub
parent 479a8b82a9
commit f868775861
491 changed files with 11332 additions and 428 deletions
@@ -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;
};