diff --git a/src/routes/promotions/promotion-create/components/create-promotion-form/create-promotion-form.tsx b/src/routes/promotions/promotion-create/components/create-promotion-form/create-promotion-form.tsx index cff49c49..37079dff 100644 --- a/src/routes/promotions/promotion-create/components/create-promotion-form/create-promotion-form.tsx +++ b/src/routes/promotions/promotion-create/components/create-promotion-form/create-promotion-form.tsx @@ -1,12 +1,14 @@ -import { zodResolver } from "@hookform/resolvers/zod" +import { useEffect, useMemo, useState } from 'react'; + +import { zodResolver } from '@hookform/resolvers/zod'; import { ApplicationMethodAllocationValues, ApplicationMethodTargetTypeValues, ApplicationMethodTypeValues, PromotionRuleOperatorValues, PromotionStatusValues, - PromotionTypeValues, -} from "@medusajs/types" + PromotionTypeValues +} from '@medusajs/types'; import { Alert, Badge, @@ -20,72 +22,69 @@ import { ProgressTabs, RadioGroup, Text, - toast, -} from "@medusajs/ui" -import { useEffect, useMemo, useState } from "react" -import { useForm, useWatch } from "react-hook-form" -import { Trans, useTranslation } from "react-i18next" -import { z } from "zod" -import { Form } from "../../../../../components/common/form" -import { DeprecatedPercentageInput } from "../../../../../components/inputs/percentage-input" -import { - RouteFocusModal, - useRouteModal, -} from "../../../../../components/modals" -import { KeyboundForm } from "../../../../../components/utilities/keybound-form" -import { useCampaigns } from "../../../../../hooks/api/campaigns" -import { useCreatePromotion } from "../../../../../hooks/api/promotions" -import { getCurrencySymbol } from "../../../../../lib/data/currencies" -import { DEFAULT_CAMPAIGN_VALUES } from "../../../../campaigns/common/constants" -import { RulesFormField } from "../../../common/edit-rules/components/rules-form-field" -import { AddCampaignPromotionFields } from "../../../promotion-add-campaign/components/add-campaign-promotion-form" -import { Tab } from "./constants" -import { CreatePromotionSchema } from "./form-schema" -import { templates } from "./templates" + toast +} from '@medusajs/ui'; +import { useForm, useWatch } from 'react-hook-form'; +import { Trans, useTranslation } from 'react-i18next'; +import { z } from 'zod'; + +import { Form } from '../../../../../components/common/form'; +import { DeprecatedPercentageInput } from '../../../../../components/inputs/percentage-input'; +import { RouteFocusModal, useRouteModal } from '../../../../../components/modals'; +import { KeyboundForm } from '../../../../../components/utilities/keybound-form'; +import { useCampaigns } from '../../../../../hooks/api/campaigns'; +import { useCreatePromotion } from '../../../../../hooks/api/promotions'; +import { getCurrencySymbol } from '../../../../../lib/data/currencies'; +import { DEFAULT_CAMPAIGN_VALUES } from '../../../../campaigns/common/constants'; +import { RulesFormField } from '../../../common/edit-rules/components/rules-form-field'; +import { AddCampaignPromotionFields } from '../../../promotion-add-campaign/components/add-campaign-promotion-form'; +import { Tab } from './constants'; +import { CreatePromotionSchema } from './form-schema'; +import { templates } from './templates'; const defaultValues = { campaign_id: undefined, template_id: templates[0].id!, - campaign_choice: "none" as "none", - is_automatic: "false", - code: "", - type: "standard" as PromotionTypeValues, - status: "draft" as PromotionStatusValues, + campaign_choice: 'none' as 'none', + is_automatic: 'false', + code: '', + type: 'standard' as PromotionTypeValues, + status: 'draft' as PromotionStatusValues, rules: [], application_method: { - allocation: "each" as ApplicationMethodAllocationValues, - type: "percentage" as ApplicationMethodTypeValues, - target_type: "items" as ApplicationMethodTargetTypeValues, + allocation: 'each' as ApplicationMethodAllocationValues, + type: 'percentage' as ApplicationMethodTypeValues, + target_type: 'items' as ApplicationMethodTargetTypeValues, max_quantity: 1, target_rules: [], - buy_rules: [], + buy_rules: [] }, - campaign: undefined, -} + campaign: undefined +}; -type TabState = Record +type TabState = Record; export const CreatePromotionForm = () => { - const [tab, setTab] = useState(Tab.TYPE) + const [tab, setTab] = useState(Tab.TYPE); const [tabState, setTabState] = useState({ - [Tab.TYPE]: "in-progress", - [Tab.PROMOTION]: "not-started", - [Tab.CAMPAIGN]: "not-started", - }) + [Tab.TYPE]: 'in-progress', + [Tab.PROMOTION]: 'not-started', + [Tab.CAMPAIGN]: 'not-started' + }); - const { t } = useTranslation() - const { handleSuccess } = useRouteModal() + const { t } = useTranslation(); + const { handleSuccess } = useRouteModal(); const form = useForm>({ defaultValues, - resolver: zodResolver(CreatePromotionSchema), - }) - const { setValue, reset, getValues } = form + resolver: zodResolver(CreatePromotionSchema) + }); + const { setValue, reset, getValues } = form; - const { mutateAsync: createPromotion } = useCreatePromotion() + const { mutateAsync: createPromotion } = useCreatePromotion(); const handleSubmit = form.handleSubmit( - async (data) => { + async data => { const { campaign_choice: _campaignChoice, is_automatic, @@ -94,44 +93,42 @@ export const CreatePromotionForm = () => { rules, status, ...promotionData - } = data + } = data; const { target_rules: targetRulesData = [], buy_rules: buyRulesData = [], ...applicationMethodData - } = application_method + } = application_method; const disguisedRules = [ - ...targetRulesData.filter((r) => !!r.disguised), - ...buyRulesData.filter((r) => !!r.disguised), - ...rules.filter((r) => !!r.disguised), - ] + ...targetRulesData.filter(r => !!r.disguised), + ...buyRulesData.filter(r => !!r.disguised), + ...rules.filter(r => !!r.disguised) + ]; - const applicationMethodRuleData: Record = {} + const applicationMethodRuleData: Record = {}; for (const rule of disguisedRules) { applicationMethodRuleData[rule.attribute] = - rule.field_type === "number" - ? parseInt(rule.values as string) - : rule.values + rule.field_type === 'number' ? parseInt(rule.values as string) : rule.values; } const buildRulesData = ( rules: { - operator: string - attribute: string - values: any[] | any - disguised?: boolean + operator: string; + attribute: string; + values: any[] | any; + disguised?: boolean; }[] ) => { return rules - .filter((r) => !r.disguised) - .map((rule) => ({ + .filter(r => !r.disguised) + .map(rule => ({ operator: rule.operator as PromotionRuleOperatorValues, attribute: rule.attribute, - values: rule.values, - })) - } + values: rule.values + })); + }; createPromotion( { @@ -142,245 +139,237 @@ export const CreatePromotionForm = () => { ...applicationMethodData, ...applicationMethodRuleData, target_rules: buildRulesData(targetRulesData), - type: "percentage", + type: 'percentage' }, - is_automatic: is_automatic === "true", + is_automatic: is_automatic === 'true' }, { onSuccess: ({ promotion }) => { toast.success( - t("promotions.toasts.promotionCreateSuccess", { - code: promotion.code, + t('promotions.toasts.promotionCreateSuccess', { + code: promotion.code }) - ) + ); - handleSuccess(`/promotions/${promotion.id}`) - }, - onError: (e) => { - toast.error(e.message) + handleSuccess(`/promotions/${promotion.id}`); }, + onError: e => { + toast.error(e.message); + } } - ) + ); }, - async (error) => { - const { campaign: _campaign, ...rest } = error || {} - const errorInPromotionTab = !!Object.keys(rest || {}).length + async error => { + const { campaign: _campaign, ...rest } = error || {}; + const errorInPromotionTab = !!Object.keys(rest || {}).length; if (errorInPromotionTab) { - toast.error(t("promotions.errors.promotionTabError")) + toast.error(t('promotions.errors.promotionTabError')); } } - ) + ); const handleTabChange = async (tab: Tab) => { switch (tab) { case Tab.TYPE: - setTabState((prev) => ({ + setTabState(prev => ({ ...prev, - [Tab.TYPE]: "in-progress", - })) - setTab(tab) - break + [Tab.TYPE]: 'in-progress' + })); + setTab(tab); + break; case Tab.PROMOTION: - setTabState((prev) => ({ + setTabState(prev => ({ ...prev, - [Tab.TYPE]: "completed", - [Tab.PROMOTION]: "in-progress", - })) - setTab(tab) - break + [Tab.TYPE]: 'completed', + [Tab.PROMOTION]: 'in-progress' + })); + setTab(tab); + break; case Tab.CAMPAIGN: { - // const valid = await form.trigger() - - // if (!valid) { - // // If the promotion tab is not valid, we want to set the tab state to in-progress - // // and set the tab to the promotion tab - // setTabState({ - // [Tab.TYPE]: "completed", - // [Tab.PROMOTION]: "in-progress", - // [Tab.CAMPAIGN]: "not-started", - // }) - // setTab(Tab.PROMOTION) - // break - // } - - setTabState((prev) => ({ + const valid = await form.trigger(['code', 'application_method.value']); + + if (!valid) { + setTabState({ + [Tab.TYPE]: 'completed', + [Tab.PROMOTION]: 'in-progress', + [Tab.CAMPAIGN]: 'not-started' + }); + setTab(Tab.PROMOTION); + break; + } + + setTabState(prev => ({ ...prev, - [Tab.PROMOTION]: "completed", - [Tab.CAMPAIGN]: "in-progress", - })) - setTab(tab) - break + [Tab.PROMOTION]: 'completed', + [Tab.CAMPAIGN]: 'in-progress' + })); + setTab(tab); + break; } } - } + }; const handleContinue = async () => { switch (tab) { case Tab.TYPE: - handleTabChange(Tab.PROMOTION) - break + handleTabChange(Tab.PROMOTION); + break; case Tab.PROMOTION: { - const valid = - !!form.getValues("code") || - !!form.getValues("application_method.value") + const valid = !!form.getValues('code') && !!form.getValues('application_method.value'); if (valid) { - handleTabChange(Tab.CAMPAIGN) + handleTabChange(Tab.CAMPAIGN); } - if (!form.getValues("code")) { - form.setError("code", { - message: "error", - }) + if (!form.getValues('code')) { + form.setError('code', { + message: t('promotions.errors.requiredField') + }); } - if (!form.getValues("application_method.value")) { - form.setError("application_method.value", { - message: "error", - }) + if (!form.getValues('application_method.value')) { + form.setError('application_method.value', { + message: t('promotions.errors.requiredField') + }); } - break + break; } case Tab.CAMPAIGN: - break + break; } - } + }; const watchTemplateId = useWatch({ control: form.control, - name: "template_id", - }) + name: 'template_id' + }); const currentTemplate = useMemo(() => { - const currentTemplate = templates.find( - (template) => template.id === watchTemplateId - ) + const currentTemplate = templates.find(template => template.id === watchTemplateId); if (!currentTemplate) { - return + return; } reset({ ...defaultValues, - template_id: watchTemplateId, - }) + template_id: watchTemplateId + }); for (const [key, value] of Object.entries(currentTemplate.defaults)) { - if (typeof value === "object") { + if (typeof value === 'object') { for (const [subKey, subValue] of Object.entries(value)) { - setValue( - `application_method.${subKey}` as keyof typeof defaultValues, - subValue - ) + setValue(`application_method.${subKey}` as keyof typeof defaultValues, subValue); } } else { - setValue(key as keyof typeof defaultValues, value) + setValue(key as keyof typeof defaultValues, value); } } - return currentTemplate - }, [watchTemplateId, setValue, reset]) + return currentTemplate; + }, [watchTemplateId, setValue, reset]); const watchValueType = useWatch({ control: form.control, - name: "application_method.type", - }) + name: 'application_method.type' + }); - const isFixedValueType = watchValueType === "fixed" + const isFixedValueType = watchValueType === 'fixed'; const watchAllocation = useWatch({ control: form.control, - name: "application_method.allocation", - }) + name: 'application_method.allocation' + }); useEffect(() => { - if (watchAllocation === "across") { - setValue("application_method.max_quantity", null) + if (watchAllocation === 'across') { + setValue('application_method.max_quantity', null); } - }, [watchAllocation, setValue]) + }, [watchAllocation, setValue]); const watchType = useWatch({ control: form.control, - name: "type", - }) + name: 'type' + }); - const isTypeStandard = watchType === "standard" + const isTypeStandard = watchType === 'standard'; const targetType = useWatch({ control: form.control, - name: "application_method.target_type", - }) + name: 'application_method.target_type' + }); - const isTargetTypeOrder = targetType === "order" + const isTargetTypeOrder = targetType === 'order'; - const formData = form.getValues() - let campaignQuery: object = {} + const formData = form.getValues(); + let campaignQuery: object = {}; if (isFixedValueType && formData.application_method.currency_code) { campaignQuery = { budget: { - currency_code: formData.application_method.currency_code, - }, - } + currency_code: formData.application_method.currency_code + } + }; } - const { campaigns } = useCampaigns(campaignQuery) + const { campaigns } = useCampaigns(campaignQuery); const watchCampaignChoice = useWatch({ control: form.control, - name: "campaign_choice", - }) + name: 'campaign_choice' + }); useEffect(() => { - const formData = getValues() + const formData = getValues(); - if (watchCampaignChoice !== "existing") { - setValue("campaign_id", undefined) + if (watchCampaignChoice !== 'existing') { + setValue('campaign_id', undefined); } - if (watchCampaignChoice !== "new") { - setValue("campaign", undefined) + if (watchCampaignChoice !== 'new') { + setValue('campaign', undefined); } - if (watchCampaignChoice === "new") { + if (watchCampaignChoice === 'new') { if (!formData.campaign || !formData.campaign?.budget?.type) { - setValue("campaign", { + setValue('campaign', { ...DEFAULT_CAMPAIGN_VALUES, budget: { ...DEFAULT_CAMPAIGN_VALUES.budget, - currency_code: formData.application_method.currency_code, - }, - }) + currency_code: formData.application_method.currency_code + } + }); } } - }, [watchCampaignChoice, getValues, setValue]) + }, [watchCampaignChoice, getValues, setValue]); const watchRules = useWatch({ control: form.control, - name: "rules", - }) + name: 'rules' + }); - const watchCurrencyRule = watchRules.find( - (rule) => rule.attribute === "currency_code" - ) + const watchCurrencyRule = watchRules.find(rule => rule.attribute === 'currency_code'); if (watchCurrencyRule) { - const formData = form.getValues() - const currencyCode = formData.application_method.currency_code - const ruleValue = watchCurrencyRule.values + const formData = form.getValues(); + const currencyCode = formData.application_method.currency_code; + const ruleValue = watchCurrencyRule.values; if (!Array.isArray(ruleValue) && currencyCode !== ruleValue) { - form.setValue("application_method.currency_code", ruleValue as string) + form.setValue('application_method.currency_code', ruleValue as string); } } return ( - + handleTabChange(tab as Tab)} + onValueChange={tab => handleTabChange(tab as Tab)} className="flex h-full flex-col overflow-hidden" > @@ -392,7 +381,7 @@ export const CreatePromotionForm = () => { value={Tab.TYPE} status={tabState[Tab.TYPE]} > - {t("promotions.tabs.template")} + {t('promotions.tabs.template')} { value={Tab.PROMOTION} status={tabState[Tab.PROMOTION]} > - {t("promotions.tabs.details")} + {t('promotions.tabs.details')} { value={Tab.CAMPAIGN} status={tabState[Tab.CAMPAIGN]} > - {t("promotions.tabs.campaign")} + {t('promotions.tabs.campaign')} @@ -428,16 +417,16 @@ export const CreatePromotionForm = () => { render={({ field }) => { return ( - {t("promotions.fields.type")} + {t('promotions.fields.type')} - {templates.map((template) => { + {templates.map(template => { return ( { label={template.title} description={template.description} /> - ) + ); })} - ) + ); }} /> @@ -464,7 +453,10 @@ export const CreatePromotionForm = () => { >
- + {t(`promotions.sections.details`)} {currentTemplate?.title && ( @@ -495,9 +487,7 @@ export const CreatePromotionForm = () => { render={({ field }) => { return ( - - {t("promotions.form.method.label")} - + {t('promotions.form.method.label')} { onValueChange={field.onChange} > - ) + ); }} /> @@ -539,9 +523,7 @@ export const CreatePromotionForm = () => { render={({ field }) => { return ( - - {t("promotions.form.status.label")} - + {t('promotions.form.status.label')} { onValueChange={field.onChange} > - ) + ); }} /> @@ -582,12 +560,13 @@ export const CreatePromotionForm = () => { render={({ field }) => { return ( - - {t("promotions.form.code.title")} - + {t('promotions.form.code.title')} - + { /> - ) + ); }} />
- {!currentTemplate?.hiddenFields?.includes("type") && ( + {!currentTemplate?.hiddenFields?.includes('type') && ( { return ( - - {t("promotions.fields.type")} - + {t('promotions.fields.type')} { onValueChange={field.onChange} > - ) + ); }} /> )} - + - {!currentTemplate?.hiddenFields?.includes( - "application_method.type" - ) && ( + {!currentTemplate?.hiddenFields?.includes('application_method.type') && ( { return ( - - {t("promotions.fields.value_type")} - + {t('promotions.fields.value_type')} { onValueChange={field.onChange} > - ) + ); }} /> )}
- {!currentTemplate?.hiddenFields?.includes( - "application_method.value" - ) && ( + {!currentTemplate?.hiddenFields?.includes('application_method.value') && ( { - const currencyCode = - form.getValues().application_method.currency_code + const currencyCode = form.getValues().application_method.currency_code; return ( @@ -722,10 +683,10 @@ export const CreatePromotionForm = () => { tooltip={ currencyCode || !isFixedValueType ? undefined - : t("promotions.fields.amount.tooltip") + : t('promotions.fields.amount.tooltip') } > - {t("promotions.form.value.title")} + {t('promotions.form.value.title')} @@ -733,15 +694,11 @@ export const CreatePromotionForm = () => { { - onChange(value ? parseInt(value) : "") + onValueChange={value => { + onChange(value ? parseInt(value) : ''); }} - code={currencyCode || "USD"} - symbol={ - currencyCode - ? getCurrencySymbol(currencyCode) - : "$" - } + code={currencyCode || 'USD'} + symbol={currencyCode ? getCurrencySymbol(currencyCode) : '$'} value={value} // disabled={!currencyCode} /> @@ -753,12 +710,10 @@ export const CreatePromotionForm = () => { max={100} {...field} value={value} - onChange={(e) => { + onChange={e => { onChange( - e.target.value === "" - ? null - : parseInt(e.target.value) - ) + e.target.value === '' ? null : parseInt(e.target.value) + ); }} /> )} @@ -772,38 +727,33 @@ export const CreatePromotionForm = () => { t={t} i18nKey={ isFixedValueType - ? "promotions.form.value_type.fixed.description" - : "promotions.form.value_type.percentage.description" + ? 'promotions.form.value_type.fixed.description' + : 'promotions.form.value_type.percentage.description' } components={[
]} />
- ) + ); }} /> )} - {isTypeStandard && watchAllocation === "each" && ( + {isTypeStandard && watchAllocation === 'each' && ( { return ( - - {t("promotions.form.max_quantity.title")} - + {t('promotions.form.max_quantity.title')} { /> - ) + ); }} /> )}
{isTypeStandard && - !currentTemplate?.hiddenFields?.includes( - "application_method.allocation" - ) && ( + !currentTemplate?.hiddenFields?.includes('application_method.allocation') && ( { return ( - - {t("promotions.fields.allocation")} - + {t('promotions.fields.allocation')} { onValueChange={field.onChange} > - ) + ); }} /> )} @@ -882,7 +820,7 @@ export const CreatePromotionForm = () => { <> @@ -893,7 +831,7 @@ export const CreatePromotionForm = () => { @@ -920,8 +858,11 @@ export const CreatePromotionForm = () => {
- @@ -932,7 +873,7 @@ export const CreatePromotionForm = () => { size="small" isLoading={false} > - {t("actions.save")} + {t('actions.save')} ) : ( )}
- ) -} + ); +};