diff --git a/packages/admin/dashboard/src/components/forms/metadata-form/metadata-form.tsx b/packages/admin/dashboard/src/components/forms/metadata-form/metadata-form.tsx index f19ff2d33a..c59374f055 100644 --- a/packages/admin/dashboard/src/components/forms/metadata-form/metadata-form.tsx +++ b/packages/admin/dashboard/src/components/forms/metadata-form/metadata-form.tsx @@ -24,6 +24,7 @@ import { Form } from "../../common/form" import { InlineTip } from "../../common/inline-tip" import { Skeleton } from "../../common/skeleton" import { RouteDrawer, useRouteModal } from "../../modals" +import { KeyboundForm } from "../../utilities/keybound-form" type MetaDataSubmitHook = ( params: { metadata?: Record | null }, @@ -125,7 +126,7 @@ const InnerForm = ({ return ( -
@@ -277,7 +278,7 @@ const InnerForm = ({ - +
) } diff --git a/packages/admin/dashboard/src/components/inputs/combobox/combobox.tsx b/packages/admin/dashboard/src/components/inputs/combobox/combobox.tsx index 38414c3287..e1670cea96 100644 --- a/packages/admin/dashboard/src/components/inputs/combobox/combobox.tsx +++ b/packages/admin/dashboard/src/components/inputs/combobox/combobox.tsx @@ -32,7 +32,7 @@ import { } from "react" import { useTranslation } from "react-i18next" -import { genericForwardRef } from "../../common/generic-forward-ref" +import { genericForwardRef } from "../../utilities/generic-forward-ref" type ComboboxOption = { value: string diff --git a/packages/admin/dashboard/src/components/layout/split-view/index.ts b/packages/admin/dashboard/src/components/layout/split-view/index.ts deleted file mode 100644 index b717c0fded..0000000000 --- a/packages/admin/dashboard/src/components/layout/split-view/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./split-view" diff --git a/packages/admin/dashboard/src/components/layout/split-view/split-view.tsx b/packages/admin/dashboard/src/components/layout/split-view/split-view.tsx deleted file mode 100644 index decade8003..0000000000 --- a/packages/admin/dashboard/src/components/layout/split-view/split-view.tsx +++ /dev/null @@ -1,103 +0,0 @@ -import { Button, clx } from "@medusajs/ui" -import * as Dialog from "@radix-ui/react-dialog" -import { - ComponentPropsWithoutRef, - PropsWithChildren, - createContext, - useContext, - useRef, -} from "react" - -type SplitViewContextValue = { - open: boolean - onOpenChange: (open: boolean) => void -} - -const SplitViewContext = createContext(null) - -const useSplitViewContext = () => { - const context = useContext(SplitViewContext) - - if (!context) { - throw new Error("useSplitViewContext must be used within a SplitView") - } - - return context -} - -type SplitViewProps = PropsWithChildren<{ - open?: boolean - onOpenChange?: (open: boolean) => void -}> - -const Root = ({ open, onOpenChange, children }: SplitViewProps) => { - const containerRef = useRef(null) - - return ( - -
- {children} -
-
- ) -} - -const Content = ({ - children, - className, - ...props -}: ComponentPropsWithoutRef<"div">) => { - return ( -
- {children} -
- ) -} - -const Drawer = ({ children }: PropsWithChildren) => { - return ( -
- - - {children} - -
- ) -} - -const Close = ({ - variant = "secondary", - size = "small", - children, - ...props -}: ComponentPropsWithoutRef) => { - return ( - - - - ) -} - -/** - * SplitView is a layout component that allows you to create a split view layout within a FocusModal. - */ -export const SplitView = Object.assign(Root, { - Content, - Drawer, - Close, -}) diff --git a/packages/admin/dashboard/src/components/common/generic-forward-ref/generic-forward-ref.tsx b/packages/admin/dashboard/src/components/utilities/generic-forward-ref/generic-forward-ref.tsx similarity index 100% rename from packages/admin/dashboard/src/components/common/generic-forward-ref/generic-forward-ref.tsx rename to packages/admin/dashboard/src/components/utilities/generic-forward-ref/generic-forward-ref.tsx diff --git a/packages/admin/dashboard/src/components/common/generic-forward-ref/index.ts b/packages/admin/dashboard/src/components/utilities/generic-forward-ref/index.ts similarity index 100% rename from packages/admin/dashboard/src/components/common/generic-forward-ref/index.ts rename to packages/admin/dashboard/src/components/utilities/generic-forward-ref/index.ts diff --git a/packages/admin/dashboard/src/components/utilities/keybound-form/index.ts b/packages/admin/dashboard/src/components/utilities/keybound-form/index.ts new file mode 100644 index 0000000000..b310b055fb --- /dev/null +++ b/packages/admin/dashboard/src/components/utilities/keybound-form/index.ts @@ -0,0 +1 @@ +export * from "./keybound-form" diff --git a/packages/admin/dashboard/src/components/utilities/keybound-form/keybound-form.tsx b/packages/admin/dashboard/src/components/utilities/keybound-form/keybound-form.tsx new file mode 100644 index 0000000000..6315b80c35 --- /dev/null +++ b/packages/admin/dashboard/src/components/utilities/keybound-form/keybound-form.tsx @@ -0,0 +1,35 @@ +import React from "react" + +/** + * A form that can only be submitted when using the meta or control key. + */ +export const KeyboundForm = React.forwardRef< + HTMLFormElement, + React.FormHTMLAttributes +>(({ onSubmit, onKeyDown, ...rest }, ref) => { + const handleSubmit = (event: React.FormEvent) => { + event.preventDefault() + onSubmit?.(event) + } + + const handleKeyDown = (event: React.KeyboardEvent) => { + if (event.key === "Enter") { + event.preventDefault() + + if (event.metaKey || event.ctrlKey) { + handleSubmit(event) + } + } + } + + return ( +
+ ) +}) + +KeyboundForm.displayName = "KeyboundForm" diff --git a/packages/admin/dashboard/src/components/utilities/visually-hidden/index.ts b/packages/admin/dashboard/src/components/utilities/visually-hidden/index.ts new file mode 100644 index 0000000000..2a2698d67f --- /dev/null +++ b/packages/admin/dashboard/src/components/utilities/visually-hidden/index.ts @@ -0,0 +1 @@ +export * from "./visually-hidden" diff --git a/packages/admin/dashboard/src/components/utilities/visually-hidden/visually-hidden.tsx b/packages/admin/dashboard/src/components/utilities/visually-hidden/visually-hidden.tsx new file mode 100644 index 0000000000..d55b1c464a --- /dev/null +++ b/packages/admin/dashboard/src/components/utilities/visually-hidden/visually-hidden.tsx @@ -0,0 +1,5 @@ +import { PropsWithChildren } from "react" + +export const VisuallyHidden = ({ children }: PropsWithChildren) => { + return {children} +} diff --git a/packages/admin/dashboard/src/i18n/translations/en.json b/packages/admin/dashboard/src/i18n/translations/en.json index ec3c6b9b83..6b4a0a3e13 100644 --- a/packages/admin/dashboard/src/i18n/translations/en.json +++ b/packages/admin/dashboard/src/i18n/translations/en.json @@ -1877,6 +1877,7 @@ }, "edit": { "header": "Edit Campaign", + "description": "Edit the details of the campaign.", "successToast": "Campaign '{{name}}' was successfully updated." }, "configuration": { @@ -1888,6 +1889,8 @@ } }, "create": { + "title": "Create Campaign", + "description": "Create a promotional campaign.", "hint": "Create a promotional campaign.", "header": "Create Campaign", "successToast": "Campaign '{{name}}' was successfully created." @@ -2301,9 +2304,12 @@ }, "edit": { "header": "Edit API Key", + "description": "Edit the API key's title.", "successToast": "API key {{title}} was successfully updated." }, "salesChannels": { + "title": "Add Sales Channels", + "description": "Add the sales channels that the API key should be limited to.", "successToast_one": "{{count}} sales channel was successfully added to the API key.", "successToast_other": "{{count}} sales channels were successfully added to the API key.", "alreadyAddedTooltip": "The sales channel has already been added to the API key.", diff --git a/packages/admin/dashboard/src/routes/api-key-management/api-key-management-create/components/api-key-create-form/api-key-create-form.tsx b/packages/admin/dashboard/src/routes/api-key-management/api-key-management-create/components/api-key-create-form/api-key-create-form.tsx index 7de281ece1..defd3a6949 100644 --- a/packages/admin/dashboard/src/routes/api-key-management/api-key-management-create/components/api-key-create-form/api-key-create-form.tsx +++ b/packages/admin/dashboard/src/routes/api-key-management/api-key-management-create/components/api-key-create-form/api-key-create-form.tsx @@ -12,6 +12,7 @@ import { RouteFocusModal, useRouteModal, } from "../../../../../components/modals" +import { KeyboundForm } from "../../../../../components/utilities/keybound-form" import { useCreateApiKey } from "../../../../../hooks/api/api-keys" import { ApiKeyType } from "../../../common/constants" @@ -97,36 +98,29 @@ export const ApiKeyCreateForm = ({ keyType }: ApiKeyCreateFormProps) => { return ( - - -
- - - - -
-
+
- - {keyType === ApiKeyType.PUBLISHABLE - ? t("apiKeyManagement.create.createPublishableHeader") - : t("apiKeyManagement.create.createSecretHeader")} - - - {keyType === ApiKeyType.PUBLISHABLE - ? t("apiKeyManagement.create.createPublishableHint") - : t("apiKeyManagement.create.createSecretHint")} - + + + {keyType === ApiKeyType.PUBLISHABLE + ? t("apiKeyManagement.create.createPublishableHeader") + : t("apiKeyManagement.create.createSecretHeader")} + + + + + {keyType === ApiKeyType.PUBLISHABLE + ? t("apiKeyManagement.create.createPublishableHint") + : t("apiKeyManagement.create.createSecretHint")} + +
{
- + +
+ + + + +
+
+ diff --git a/packages/admin/dashboard/src/routes/api-key-management/api-key-management-edit/api-key-management-edit.tsx b/packages/admin/dashboard/src/routes/api-key-management/api-key-management-edit/api-key-management-edit.tsx index 43ebb25378..f549ee29d7 100644 --- a/packages/admin/dashboard/src/routes/api-key-management/api-key-management-edit/api-key-management-edit.tsx +++ b/packages/admin/dashboard/src/routes/api-key-management/api-key-management-edit/api-key-management-edit.tsx @@ -2,6 +2,7 @@ import { Heading } from "@medusajs/ui" import { useTranslation } from "react-i18next" import { useParams } from "react-router-dom" import { RouteDrawer } from "../../../components/modals" +import { VisuallyHidden } from "../../../components/utilities/visually-hidden" import { useApiKey } from "../../../hooks/api/api-keys" import { EditApiKeyForm } from "./components/edit-api-key-form" @@ -18,7 +19,14 @@ export const ApiKeyManagementEdit = () => { return ( - {t("apiKeyManagement.edit.header")} + + {t("apiKeyManagement.edit.header")} + + + + {t("apiKeyManagement.edit.description")} + + {!isLoading && !!api_key && } diff --git a/packages/admin/dashboard/src/routes/api-key-management/api-key-management-edit/components/edit-api-key-form/edit-api-key-form.tsx b/packages/admin/dashboard/src/routes/api-key-management/api-key-management-edit/components/edit-api-key-form/edit-api-key-form.tsx index 977604cd6a..607f4cfdd7 100644 --- a/packages/admin/dashboard/src/routes/api-key-management/api-key-management-edit/components/edit-api-key-form/edit-api-key-form.tsx +++ b/packages/admin/dashboard/src/routes/api-key-management/api-key-management-edit/components/edit-api-key-form/edit-api-key-form.tsx @@ -7,6 +7,7 @@ import * as zod from "zod" import { ApiKeyDTO } from "@medusajs/types" import { Form } from "../../../../../components/common/form" import { RouteDrawer, useRouteModal } from "../../../../../components/modals" +import { KeyboundForm } from "../../../../../components/utilities/keybound-form" import { useUpdateApiKey } from "../../../../../hooks/api/api-keys" type EditApiKeyFormProps = { @@ -48,7 +49,7 @@ export const EditApiKeyForm = ({ apiKey }: EditApiKeyFormProps) => { return ( -
+
{
- +
) } diff --git a/packages/admin/dashboard/src/routes/api-key-management/api-key-management-sales-channels/components/api-key-sales-channels-form/api-key-sales-channels-form.tsx b/packages/admin/dashboard/src/routes/api-key-management/api-key-management-sales-channels/components/api-key-sales-channels-form/api-key-sales-channels-form.tsx index 61fd4486ce..21455f62c8 100644 --- a/packages/admin/dashboard/src/routes/api-key-management/api-key-management-sales-channels/components/api-key-sales-channels-form/api-key-sales-channels-form.tsx +++ b/packages/admin/dashboard/src/routes/api-key-management/api-key-management-sales-channels/components/api-key-sales-channels-form/api-key-sales-channels-form.tsx @@ -16,9 +16,12 @@ import { useRouteModal, } from "../../../../../components/modals" import { DataTable } from "../../../../../components/table/data-table" +import { KeyboundForm } from "../../../../../components/utilities/keybound-form" +import { VisuallyHidden } from "../../../../../components/utilities/visually-hidden" import { useBatchAddSalesChannelsToApiKey } from "../../../../../hooks/api/api-keys" import { useSalesChannels } from "../../../../../hooks/api/sales-channels" import { useSalesChannelTableColumns } from "../../../../../hooks/table/columns/use-sales-channel-table-columns" +import { useSalesChannelTableFilters } from "../../../../../hooks/table/filters" import { useSalesChannelTableQuery } from "../../../../../hooks/table/query/use-sales-channel-table-query" import { useDataTable } from "../../../../../hooks/use-data-table" @@ -58,6 +61,7 @@ export const ApiKeySalesChannelsForm = ({ }) const columns = useColumns() + const filters = useSalesChannelTableFilters() const { sales_channels, count, isLoading } = useSalesChannels( { ...searchParams }, @@ -114,33 +118,33 @@ export const ApiKeySalesChannelsForm = ({ return ( -
+ + + + {t("apiKeyManagement.salesChannels.title")} + + + + + {t("apiKeyManagement.salesChannels.description")} + +
{form.formState.errors.sales_channel_ids && ( {form.formState.errors.sales_channel_ids.message} )} - - - -
- + - + +
+ + + + +
+
+
) } diff --git a/packages/admin/dashboard/src/routes/campaigns/add-campaign-promotions/components/add-campaign-promotions-form.tsx b/packages/admin/dashboard/src/routes/campaigns/add-campaign-promotions/components/add-campaign-promotions-form.tsx index d968f45bfb..ab9f89f1b3 100644 --- a/packages/admin/dashboard/src/routes/campaigns/add-campaign-promotions/components/add-campaign-promotions-form.tsx +++ b/packages/admin/dashboard/src/routes/campaigns/add-campaign-promotions/components/add-campaign-promotions-form.tsx @@ -13,6 +13,7 @@ import { useTranslation } from "react-i18next" import * as zod from "zod" import { RouteFocusModal, useRouteModal } from "../../../../components/modals" import { DataTable } from "../../../../components/table/data-table" +import { KeyboundForm } from "../../../../components/utilities/keybound-form" import { useAddOrRemoveCampaignPromotions } from "../../../../hooks/api/campaigns" import { usePromotions } from "../../../../hooks/api/promotions" import { usePromotionTableColumns } from "../../../../hooks/table/columns/use-promotion-table-columns" @@ -109,7 +110,7 @@ export const AddCampaignPromotionsForm = ({ return ( -
@@ -120,14 +121,6 @@ export const AddCampaignPromotionsForm = ({ {form.formState.errors.promotion_ids.message} )} - - - -
@@ -148,7 +141,19 @@ export const AddCampaignPromotionsForm = ({ }} /> - + +
+ + + + +
+
+
) } diff --git a/packages/admin/dashboard/src/routes/campaigns/campaign-budget-edit/campaign-budget-edit.tsx b/packages/admin/dashboard/src/routes/campaigns/campaign-budget-edit/campaign-budget-edit.tsx index 2de7752828..ba1b4e7013 100644 --- a/packages/admin/dashboard/src/routes/campaigns/campaign-budget-edit/campaign-budget-edit.tsx +++ b/packages/admin/dashboard/src/routes/campaigns/campaign-budget-edit/campaign-budget-edit.tsx @@ -18,7 +18,9 @@ export const CampaignBudgetEdit = () => { return ( - {t("campaigns.budget.edit.header")} + + {t("campaigns.budget.edit.header")} + {!isLoading && campaign && } diff --git a/packages/admin/dashboard/src/routes/campaigns/campaign-budget-edit/components/edit-campaign-budget-form/edit-campaign-budget-form.tsx b/packages/admin/dashboard/src/routes/campaigns/campaign-budget-edit/components/edit-campaign-budget-form/edit-campaign-budget-form.tsx index 871ae05f14..c920bd5ea2 100644 --- a/packages/admin/dashboard/src/routes/campaigns/campaign-budget-edit/components/edit-campaign-budget-form/edit-campaign-budget-form.tsx +++ b/packages/admin/dashboard/src/routes/campaigns/campaign-budget-edit/components/edit-campaign-budget-form/edit-campaign-budget-form.tsx @@ -6,6 +6,7 @@ import { useTranslation } from "react-i18next" import * as zod from "zod" import { Form } from "../../../../../components/common/form" import { RouteDrawer, useRouteModal } from "../../../../../components/modals" +import { KeyboundForm } from "../../../../../components/utilities/keybound-form" import { useUpdateCampaign } from "../../../../../hooks/api/campaigns" import { getCurrencySymbol } from "../../../../../lib/data/currencies" @@ -58,7 +59,7 @@ export const EditCampaignBudgetForm = ({ return ( -
+
- +
) } diff --git a/packages/admin/dashboard/src/routes/campaigns/campaign-configuration/components/campaign-configuration-form/campaign-configuration-form.tsx b/packages/admin/dashboard/src/routes/campaigns/campaign-configuration/components/campaign-configuration-form/campaign-configuration-form.tsx index 5159523b9d..eee0d6cbc4 100644 --- a/packages/admin/dashboard/src/routes/campaigns/campaign-configuration/components/campaign-configuration-form/campaign-configuration-form.tsx +++ b/packages/admin/dashboard/src/routes/campaigns/campaign-configuration/components/campaign-configuration-form/campaign-configuration-form.tsx @@ -7,6 +7,7 @@ import { z } from "zod" import { Form } from "../../../../../components/common/form" import { RouteDrawer, useRouteModal } from "../../../../../components/modals" +import { KeyboundForm } from "../../../../../components/utilities/keybound-form" import { useUpdateCampaign } from "../../../../../hooks/api/campaigns" type CampaignConfigurationFormProps = { @@ -59,7 +60,7 @@ export const CampaignConfigurationForm = ({ return ( -
+
- +
) } diff --git a/packages/admin/dashboard/src/routes/campaigns/campaign-create/components/create-campaign-form/create-campaign-form.tsx b/packages/admin/dashboard/src/routes/campaigns/campaign-create/components/create-campaign-form/create-campaign-form.tsx index d6fb17e932..057149dd11 100644 --- a/packages/admin/dashboard/src/routes/campaigns/campaign-create/components/create-campaign-form/create-campaign-form.tsx +++ b/packages/admin/dashboard/src/routes/campaigns/campaign-create/components/create-campaign-form/create-campaign-form.tsx @@ -4,20 +4,22 @@ import { useForm } from "react-hook-form" import { useTranslation } from "react-i18next" import * as zod from "zod" -import { CampaignBudgetTypeValues } from "@medusajs/types" import { RouteFocusModal, useRouteModal, } from "../../../../../components/modals" +import { KeyboundForm } from "../../../../../components/utilities/keybound-form" +import { VisuallyHidden } from "../../../../../components/utilities/visually-hidden" import { useCreateCampaign } from "../../../../../hooks/api/campaigns" import { CreateCampaignFormFields } from "../../../common/components/create-campaign-form-fields" +import { DEFAULT_CAMPAIGN_VALUES } from "../../../common/constants" export const CreateCampaignSchema = zod.object({ name: zod.string().min(1), description: zod.string().optional(), campaign_identifier: zod.string().min(1), - starts_at: zod.date().optional(), - ends_at: zod.date().optional(), + starts_at: zod.date().nullable(), + ends_at: zod.date().nullable(), budget: zod.object({ limit: zod.number().min(0).nullish(), type: zod.enum(["spend", "usage"]), @@ -25,24 +27,13 @@ export const CreateCampaignSchema = zod.object({ }), }) -export const defaultCampaignValues = { - name: "", - description: "", - campaign_identifier: "", - budget: { - type: "usage" as CampaignBudgetTypeValues, - currency_code: null, - limit: null, - }, -} - export const CreateCampaignForm = () => { const { t } = useTranslation() const { handleSuccess } = useRouteModal() const { mutateAsync, isPending } = useCreateCampaign() const form = useForm>({ - defaultValues: defaultCampaignValues, + defaultValues: DEFAULT_CAMPAIGN_VALUES, resolver: zodResolver(CreateCampaignSchema), }) @@ -78,11 +69,18 @@ export const CreateCampaignForm = () => { return ( -
- + + + {t("campaigns.create.title")} + + + {t("campaigns.create.description")} + + @@ -103,7 +101,7 @@ export const CreateCampaignForm = () => { - +
) } diff --git a/packages/admin/dashboard/src/routes/campaigns/campaign-edit/campaign-edit.tsx b/packages/admin/dashboard/src/routes/campaigns/campaign-edit/campaign-edit.tsx index 80ee8355de..8659deb69a 100644 --- a/packages/admin/dashboard/src/routes/campaigns/campaign-edit/campaign-edit.tsx +++ b/packages/admin/dashboard/src/routes/campaigns/campaign-edit/campaign-edit.tsx @@ -2,6 +2,7 @@ import { Heading } from "@medusajs/ui" import { useTranslation } from "react-i18next" import { useParams } from "react-router-dom" import { RouteDrawer } from "../../../components/modals" +import { VisuallyHidden } from "../../../components/utilities/visually-hidden" import { useCampaign } from "../../../hooks/api/campaigns" import { EditCampaignForm } from "./components/edit-campaign-form" @@ -18,7 +19,12 @@ export const CampaignEdit = () => { return ( - {t("campaigns.edit.header")} + + {t("campaigns.edit.header")} + + + {t("campaigns.edit.description")} + {!isLoading && campaign && } diff --git a/packages/admin/dashboard/src/routes/campaigns/campaign-edit/components/edit-campaign-form/edit-campaign-form.tsx b/packages/admin/dashboard/src/routes/campaigns/campaign-edit/components/edit-campaign-form/edit-campaign-form.tsx index 2f4af75eab..0a734aa086 100644 --- a/packages/admin/dashboard/src/routes/campaigns/campaign-edit/components/edit-campaign-form/edit-campaign-form.tsx +++ b/packages/admin/dashboard/src/routes/campaigns/campaign-edit/components/edit-campaign-form/edit-campaign-form.tsx @@ -6,6 +6,7 @@ import { useTranslation } from "react-i18next" import * as zod from "zod" import { Form } from "../../../../../components/common/form" import { RouteDrawer, useRouteModal } from "../../../../../components/modals" +import { KeyboundForm } from "../../../../../components/utilities/keybound-form" import { useUpdateCampaign } from "../../../../../hooks/api/campaigns" type EditCampaignFormProps = { @@ -65,7 +66,7 @@ export const EditCampaignForm = ({ campaign }: EditCampaignFormProps) => { return ( -
+
{
- +
) } diff --git a/packages/admin/dashboard/src/routes/campaigns/common/components/create-campaign-form-fields/create-campaign-form-fields.tsx b/packages/admin/dashboard/src/routes/campaigns/common/components/create-campaign-form-fields/create-campaign-form-fields.tsx index f630974da3..e525d1534c 100644 --- a/packages/admin/dashboard/src/routes/campaigns/common/components/create-campaign-form-fields/create-campaign-form-fields.tsx +++ b/packages/admin/dashboard/src/routes/campaigns/common/components/create-campaign-form-fields/create-campaign-form-fields.tsx @@ -14,7 +14,10 @@ import { useTranslation } from "react-i18next" import { Form } from "../../../../../components/common/form" import { useStore } from "../../../../../hooks/api/store" -import { currencies, getCurrencySymbol } from "../../../../../lib/data/currencies" +import { + currencies, + getCurrencySymbol, +} from "../../../../../lib/data/currencies" export const CreateCampaignFormFields = ({ form, fieldScope = "" }) => { const { t } = useTranslation() @@ -118,7 +121,7 @@ export const CreateCampaignFormFields = ({ form, fieldScope = "" }) => { render={({ field }) => { return ( - {t("fields.description")} + {t("fields.description")}