feat(admin, admin-ui, medusa-js, medusa-react, medusa): Support Admin Extensions (#4761)
Co-authored-by: Rares Stefan <948623+StephixOne@users.noreply.github.com> Co-authored-by: Oli Juhl <59018053+olivermrbl@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
26c78bbc03
commit
f1a05f4725
@@ -3,6 +3,8 @@ import { useEffect } from "react"
|
||||
import { Controller, useWatch } from "react-hook-form"
|
||||
import { NestedForm } from "../../../utils/nested-form"
|
||||
import Switch from "../../atoms/switch"
|
||||
import InfoIcon from "../../fundamentals/icons/info-icon"
|
||||
import Tooltip from "../../atoms/tooltip"
|
||||
|
||||
export type AnalyticsConfigFormType = {
|
||||
anonymize: boolean
|
||||
@@ -11,9 +13,10 @@ export type AnalyticsConfigFormType = {
|
||||
|
||||
type Props = {
|
||||
form: NestedForm<AnalyticsConfigFormType>
|
||||
compact?: boolean
|
||||
}
|
||||
|
||||
const AnalyticsConfigForm = ({ form }: Props) => {
|
||||
const AnalyticsConfigForm = ({ form, compact }: Props) => {
|
||||
const { control, setValue, path } = form
|
||||
|
||||
const watchOptOut = useWatch({
|
||||
@@ -31,17 +34,33 @@ const AnalyticsConfigForm = ({ form }: Props) => {
|
||||
return (
|
||||
<div className="gap-y-xlarge flex flex-col">
|
||||
<div
|
||||
className={clsx("flex items-start transition-opacity", {
|
||||
className={clsx("flex items-center gap-3 transition-opacity", {
|
||||
"opacity-50": watchOptOut,
|
||||
})}
|
||||
>
|
||||
<div className="gap-y-2xsmall flex flex-1 flex-col">
|
||||
<h2 className="inter-base-semibold">Anonymize my usage data</h2>
|
||||
<p className="inter-base-regular text-grey-50">
|
||||
You can choose to anonymize your usage data. If this option is
|
||||
selected, we will not collect your personal information, such as
|
||||
your name and email address.
|
||||
</p>
|
||||
<div className="flex items-center">
|
||||
<h2 className="inter-base-semibold mr-2">
|
||||
Anonymize my usage data{" "}
|
||||
</h2>
|
||||
{compact && (
|
||||
<Tooltip
|
||||
content="You can choose to anonymize your usage data. If this option is
|
||||
selected, we will not collect your personal information, such as
|
||||
your name and email address."
|
||||
side="top"
|
||||
>
|
||||
<InfoIcon size="18px" color={"#889096"} />
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
{!compact && (
|
||||
<p className="inter-base-regular text-grey-50">
|
||||
You can choose to anonymize your usage data. If this option is
|
||||
selected, we will not collect your personal information, such as
|
||||
your name and email address.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<Controller
|
||||
name={path("anonymize")}
|
||||
@@ -57,14 +76,26 @@ const AnalyticsConfigForm = ({ form }: Props) => {
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-start">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="gap-y-2xsmall flex flex-1 flex-col">
|
||||
<h2 className="inter-base-semibold">
|
||||
Opt out of sharing my usage data
|
||||
</h2>
|
||||
<p className="inter-base-regular text-grey-50">
|
||||
You can always opt out of sharing your usage data at any time.
|
||||
</p>
|
||||
<div className="flex items-center">
|
||||
<h2 className="inter-base-semibold mr-2">
|
||||
Opt out of sharing my usage data
|
||||
</h2>
|
||||
{compact && (
|
||||
<Tooltip
|
||||
content="You can always opt out of sharing your usage data at any time."
|
||||
side="top"
|
||||
>
|
||||
<InfoIcon size="18px" color={"#889096"} />
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
{!compact && (
|
||||
<p className="inter-base-regular text-grey-50">
|
||||
You can always opt out of sharing your usage data at any time.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<Controller
|
||||
name={path("opt_out")}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { useAdminLogin } from "medusa-react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { useNavigate } from "react-router-dom"
|
||||
import { useWidgets } from "../../../providers/widget-provider"
|
||||
import InputError from "../../atoms/input-error"
|
||||
import WidgetContainer from "../../extensions/widget-container"
|
||||
import Button from "../../fundamentals/button"
|
||||
import SigninInput from "../../molecules/input-signin"
|
||||
|
||||
@@ -24,6 +26,8 @@ const LoginCard = ({ toResetPassword }: LoginCardProps) => {
|
||||
const navigate = useNavigate()
|
||||
const { mutate, isLoading } = useAdminLogin()
|
||||
|
||||
const { getWidgets } = useWidgets()
|
||||
|
||||
const onSubmit = (values: FormValues) => {
|
||||
mutate(values, {
|
||||
onSuccess: () => {
|
||||
@@ -44,44 +48,66 @@ const LoginCard = ({ toResetPassword }: LoginCardProps) => {
|
||||
})
|
||||
}
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="flex flex-col items-center">
|
||||
<h1 className="inter-xlarge-semibold text-grey-90 mb-large text-[20px]">
|
||||
Log in to Medusa
|
||||
</h1>
|
||||
<div>
|
||||
<SigninInput
|
||||
placeholder="Email"
|
||||
{...register("email", { required: true })}
|
||||
autoComplete="email"
|
||||
className="mb-small"
|
||||
<div className="gap-y-large flex flex-col">
|
||||
{getWidgets("login.before").map((w, i) => {
|
||||
return (
|
||||
<WidgetContainer
|
||||
key={i}
|
||||
widget={w}
|
||||
injectionZone="login.before"
|
||||
entity={undefined}
|
||||
/>
|
||||
<SigninInput
|
||||
placeholder="Password"
|
||||
type={"password"}
|
||||
{...register("password", { required: true })}
|
||||
autoComplete="current-password"
|
||||
className="mb-xsmall"
|
||||
/>
|
||||
<InputError errors={errors} name="password" />
|
||||
)
|
||||
})}
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="flex flex-col items-center">
|
||||
<h1 className="inter-xlarge-semibold text-grey-90 mb-large text-[20px]">
|
||||
Log in to Medusa
|
||||
</h1>
|
||||
<div>
|
||||
<SigninInput
|
||||
placeholder="Email"
|
||||
{...register("email", { required: true })}
|
||||
autoComplete="email"
|
||||
className="mb-small"
|
||||
/>
|
||||
<SigninInput
|
||||
placeholder="Password"
|
||||
type={"password"}
|
||||
{...register("password", { required: true })}
|
||||
autoComplete="current-password"
|
||||
className="mb-xsmall"
|
||||
/>
|
||||
<InputError errors={errors} name="password" />
|
||||
</div>
|
||||
<Button
|
||||
className="rounded-rounded inter-base-regular mt-4 w-[280px]"
|
||||
variant="secondary"
|
||||
size="medium"
|
||||
type="submit"
|
||||
loading={isLoading}
|
||||
>
|
||||
Continue
|
||||
</Button>
|
||||
<span
|
||||
className="inter-small-regular text-grey-50 mt-8 cursor-pointer"
|
||||
onClick={toResetPassword}
|
||||
>
|
||||
Forgot your password?
|
||||
</span>
|
||||
</div>
|
||||
<Button
|
||||
className="rounded-rounded inter-base-regular mt-4 w-[280px]"
|
||||
variant="secondary"
|
||||
size="medium"
|
||||
type="submit"
|
||||
loading={isLoading}
|
||||
>
|
||||
Continue
|
||||
</Button>
|
||||
<span
|
||||
className="inter-small-regular text-grey-50 mt-8 cursor-pointer"
|
||||
onClick={toResetPassword}
|
||||
>
|
||||
Forgot your password?
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
</form>
|
||||
{getWidgets("login.after").map((w, i) => {
|
||||
return (
|
||||
<WidgetContainer
|
||||
key={i}
|
||||
widget={w}
|
||||
injectionZone="login.after"
|
||||
entity={undefined}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -2,14 +2,16 @@ import { useAdminStore } from "medusa-react"
|
||||
import React, { useState } from "react"
|
||||
|
||||
import { useFeatureFlag } from "../../../providers/feature-flag-provider"
|
||||
import { useRoutes } from "../../../providers/route-provider"
|
||||
import BuildingsIcon from "../../fundamentals/icons/buildings-icon"
|
||||
import CartIcon from "../../fundamentals/icons/cart-icon"
|
||||
import CashIcon from "../../fundamentals/icons/cash-icon"
|
||||
import GearIcon from "../../fundamentals/icons/gear-icon"
|
||||
import GiftIcon from "../../fundamentals/icons/gift-icon"
|
||||
import SaleIcon from "../../fundamentals/icons/sale-icon"
|
||||
import TagIcon from "../../fundamentals/icons/tag-icon"
|
||||
import SquaresPlus from "../../fundamentals/icons/squares-plus"
|
||||
import SwatchIcon from "../../fundamentals/icons/swatch-icon"
|
||||
import TagIcon from "../../fundamentals/icons/tag-icon"
|
||||
import UsersIcon from "../../fundamentals/icons/users-icon"
|
||||
import SidebarMenuItem from "../../molecules/sidebar-menu-item"
|
||||
import UserMenu from "../../molecules/user-menu"
|
||||
@@ -22,6 +24,8 @@ const Sidebar: React.FC = () => {
|
||||
const { isFeatureEnabled } = useFeatureFlag()
|
||||
const { store } = useAdminStore()
|
||||
|
||||
const { getLinks } = useRoutes()
|
||||
|
||||
const triggerHandler = () => {
|
||||
const id = triggerHandler.id++
|
||||
return {
|
||||
@@ -104,6 +108,21 @@ const Sidebar: React.FC = () => {
|
||||
triggerHandler={triggerHandler}
|
||||
text={"Pricing"}
|
||||
/>
|
||||
{getLinks().map(({ path, label, icon }, index) => {
|
||||
const cleanLink = path.replace("/a/", "")
|
||||
|
||||
const Icon = icon ? icon : SquaresPlus
|
||||
|
||||
return (
|
||||
<SidebarMenuItem
|
||||
key={index}
|
||||
pageLink={`/a${cleanLink}`}
|
||||
icon={icon ? <Icon /> : <SquaresPlus size={ICON_SIZE} />}
|
||||
triggerHandler={triggerHandler}
|
||||
text={label}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
<SidebarMenuItem
|
||||
pageLink={"/a/settings"}
|
||||
icon={<GearIcon size={ICON_SIZE} />}
|
||||
|
||||
Reference in New Issue
Block a user