From f65373b02ffe58163d3aad946a0916efc80b7e85 Mon Sep 17 00:00:00 2001 From: Ian Krieger <48930920+IanKrieger@users.noreply.github.com> Date: Fri, 26 Jan 2024 12:24:30 -0500 Subject: [PATCH] feat: organize campaign creation better (#1031) * feat: re-organize campaign configuration * fix: update min campaign * fix: add await --- audit-resolve.json | 2 +- .../Campaigns/CampaignDateRange.tsx | 12 +-- src/components/Segment/SegmentPicker.tsx | 9 ++- src/user/ads/ShowAdsButton.tsx | 6 +- src/user/views/adsManager/types/index.ts | 4 +- .../adSet/fields/ConversionField.tsx | 25 +++--- .../components/adSet/fields/PickerFields.tsx | 21 ++--- .../components/campaign/BudgetSettings.tsx | 12 --- .../components/campaign/CampaignSettings.tsx | 17 ++++- .../components/BillingModelSelect.tsx | 3 +- .../campaign/fields/BudgetField.tsx | 76 ++++++++----------- .../components/form/components/BaseForm.tsx | 6 -- .../advanced/components/review/Review.tsx | 3 + src/validation/CampaignSchema.tsx | 2 +- 14 files changed, 91 insertions(+), 107 deletions(-) delete mode 100644 src/user/views/adsManager/views/advanced/components/campaign/BudgetSettings.tsx diff --git a/audit-resolve.json b/audit-resolve.json index d68ba48c..d5eb2afd 100644 --- a/audit-resolve.json +++ b/audit-resolve.json @@ -8,4 +8,4 @@ }, "rules": {}, "version": 1 -} \ No newline at end of file +} diff --git a/src/components/Campaigns/CampaignDateRange.tsx b/src/components/Campaigns/CampaignDateRange.tsx index 76deb961..b5c27afa 100644 --- a/src/components/Campaigns/CampaignDateRange.tsx +++ b/src/components/Campaigns/CampaignDateRange.tsx @@ -23,9 +23,9 @@ export const CampaignDateRange = () => { value={parseISO(startMeta.value)} error={!!startMeta.error} helperText={startMeta.error} - onChange={(dt) => { - startHelper.setValue(formatISO(dt)); - startHelper.setTouched(true); + onChange={async (dt) => { + await startHelper.setValue(formatISO(dt)); + await startHelper.setTouched(true); }} disabled={!isDraft} /> @@ -38,9 +38,9 @@ export const CampaignDateRange = () => { value={parseISO(endMeta.value)} error={!!endMeta.error} helperText={endMeta.error} - onChange={(dt) => { - endHelper.setValue(formatISO(dt)); - endHelper.setTouched(true); + onChange={async (dt) => { + await endHelper.setValue(formatISO(dt)); + await endHelper.setTouched(true); }} /> diff --git a/src/components/Segment/SegmentPicker.tsx b/src/components/Segment/SegmentPicker.tsx index 248e4e53..d44b08de 100644 --- a/src/components/Segment/SegmentPicker.tsx +++ b/src/components/Segment/SegmentPicker.tsx @@ -41,7 +41,7 @@ export const SegmentPicker = ({ idx }: Props) => { {!targetMeta.value && ( @@ -71,10 +71,11 @@ export const SegmentPicker = ({ idx }: Props) => { {...params} label="Audiences" helperText={ - meta.error ?? - "Select the audience segments to target. Brave will decide if left untargeted." + meta.touched && !!meta.error + ? meta.error + : "Select the audience segments to target. Brave will decide if left untargeted." } - error={!!meta.error} + error={meta.touched && !!meta.error} /> )} isOptionEqualToValue={(option, value) => option.code === value.code} diff --git a/src/user/ads/ShowAdsButton.tsx b/src/user/ads/ShowAdsButton.tsx index 00415e17..6a1faea8 100644 --- a/src/user/ads/ShowAdsButton.tsx +++ b/src/user/ads/ShowAdsButton.tsx @@ -13,12 +13,12 @@ export function ShowAdsButton() { underline="none" variant="subtitle1" sx={{ cursor: "pointer" }} - onClick={() => { + onClick={async () => { setIsShowingAds(true); - helper.setValue(false); + await helper.setValue(false); }} > - Use previously created Ads + Use existing Ads ); } diff --git a/src/user/views/adsManager/types/index.ts b/src/user/views/adsManager/types/index.ts index e69b53cf..c0e1b80e 100644 --- a/src/user/views/adsManager/types/index.ts +++ b/src/user/views/adsManager/types/index.ts @@ -101,8 +101,8 @@ export const initialCreative: Creative = { export const initialAdSet: AdSetForm = { name: "", - isNotTargeting: true, - segments: [{ code: "Svp7l-zGN", name: "untargeted" }], + isNotTargeting: false, + segments: [], conversions: [], oses: [], creatives: [], diff --git a/src/user/views/adsManager/views/advanced/components/adSet/fields/ConversionField.tsx b/src/user/views/adsManager/views/advanced/components/adSet/fields/ConversionField.tsx index f345c5a0..9f5bfb0f 100644 --- a/src/user/views/adsManager/views/advanced/components/adSet/fields/ConversionField.tsx +++ b/src/user/views/adsManager/views/advanced/components/adSet/fields/ConversionField.tsx @@ -1,8 +1,9 @@ -import { Link, Stack, Typography } from "@mui/material"; +import { Button, Link, Stack, Typography } from "@mui/material"; import { ConversionFields } from "components/Conversion/ConversionFields"; import { FieldArray, FieldArrayRenderProps, useField } from "formik"; import { Conversion, initialConversion } from "../../../../../types"; import { CardContainer } from "components/Card/CardContainer"; +import { Add } from "@mui/icons-material"; interface Props { index: number; @@ -11,27 +12,31 @@ interface Props { export function ConversionField({ index }: Props) { const [, meta] = useField(`adSets.${index}.conversions`); const conversions = meta.value ?? []; + const hasConversions = conversions.length > 0; return ( {(helper: FieldArrayRenderProps) => ( <> - + Define post-engagement analytics. - {conversions.length === 0 && ( - helper.push(initialConversion)} - sx={{ cursor: "pointer" }} + sx={{ + maxWidth: 300, + borderRadius: "16px", + }} + endIcon={} > - Add Conversion Tracking + - + Add Conversion tracking + )} - {conversions.length === 1 && ( + {hasConversions && ( ("format"); return ( - <> + + + Select the interest segments and platforms you would like to target. + {format.value !== CampaignFormat.NewsDisplayAd && ( - - - Select the audience you would like to advertise to by interests. - - - + )} - - - Select the devices and platforms you would like to advertise to. - - - - + + ); } diff --git a/src/user/views/adsManager/views/advanced/components/campaign/BudgetSettings.tsx b/src/user/views/adsManager/views/advanced/components/campaign/BudgetSettings.tsx deleted file mode 100644 index b4175ea0..00000000 --- a/src/user/views/adsManager/views/advanced/components/campaign/BudgetSettings.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { BudgetField } from "user/views/adsManager/views/advanced/components/campaign/fields/BudgetField"; -import { PaymentMethodField } from "user/views/adsManager/views/advanced/components/campaign/fields/PaymentMethodField"; - -export function BudgetSettings() { - return ( - <> - - - - - ); -} diff --git a/src/user/views/adsManager/views/advanced/components/campaign/CampaignSettings.tsx b/src/user/views/adsManager/views/advanced/components/campaign/CampaignSettings.tsx index 4c233333..dcfacce0 100644 --- a/src/user/views/adsManager/views/advanced/components/campaign/CampaignSettings.tsx +++ b/src/user/views/adsManager/views/advanced/components/campaign/CampaignSettings.tsx @@ -5,24 +5,39 @@ import { LocationField } from "user/views/adsManager/views/advanced/components/c import { Typography } from "@mui/material"; import { FormatField } from "user/views/adsManager/views/advanced/components/campaign/fields/FormatField"; import { AdvertiserPrice } from "user/hooks/useAdvertiserWithPrices"; +import { BudgetField } from "user/views/adsManager/views/advanced/components/campaign/fields/BudgetField"; +import { BillingModelSelect } from "user/views/adsManager/views/advanced/components/campaign/components/BillingModelSelect"; +import { CustomPriceSelect } from "user/views/adsManager/views/advanced/components/campaign/components/CustomPriceSelect"; +import { useAdvertiser } from "auth/hooks/queries/useAdvertiser"; export function CampaignSettings(props: { prices: AdvertiserPrice[] }) { const { isDraft } = useIsEdit(); + const { advertiser } = useAdvertiser(); return ( <> - Define when you want your campaign to run. + Define how you want your campaign to run. + + + + {!advertiser.selfServiceSetPrice && ( + + )} + + {advertiser.selfServiceSetPrice && } + + {isDraft && } ); diff --git a/src/user/views/adsManager/views/advanced/components/campaign/components/BillingModelSelect.tsx b/src/user/views/adsManager/views/advanced/components/campaign/components/BillingModelSelect.tsx index 731a46c0..171a1b04 100644 --- a/src/user/views/adsManager/views/advanced/components/campaign/components/BillingModelSelect.tsx +++ b/src/user/views/adsManager/views/advanced/components/campaign/components/BillingModelSelect.tsx @@ -17,7 +17,8 @@ export function BillingModelSelect(props: { prices: AdvertiserPrice[] }) { return ( - {uiLabelsForCampaignFormat(format.value)} billing configuration + {uiLabelsForCampaignFormat(format.value)} pricing configuration + option(s) {props.prices diff --git a/src/user/views/adsManager/views/advanced/components/campaign/fields/BudgetField.tsx b/src/user/views/adsManager/views/advanced/components/campaign/fields/BudgetField.tsx index eab3b405..4cdcd40b 100644 --- a/src/user/views/adsManager/views/advanced/components/campaign/fields/BudgetField.tsx +++ b/src/user/views/adsManager/views/advanced/components/campaign/fields/BudgetField.tsx @@ -1,20 +1,17 @@ -import { InputAdornment, Stack, Typography } from "@mui/material"; -import { FormikTextField, useIsEdit } from "form/FormikHelpers"; -import { useEffect, useState } from "react"; -import { useField, useFormikContext } from "formik"; -import { CampaignForm } from "../../../../../types"; -import { differenceInHours } from "date-fns"; -import { MIN_PER_CAMPAIGN, MIN_PER_DAY } from "validation/CampaignSchema"; -import { CardContainer } from "components/Card/CardContainer"; -import { useAdvertiserWithPrices } from "user/hooks/useAdvertiserWithPrices"; -import { BillingModelSelect } from "../components/BillingModelSelect"; -import { CustomPriceSelect } from "../components/CustomPriceSelect"; +import {InputAdornment} from "@mui/material"; +import {FormikTextField, useIsEdit} from "form/FormikHelpers"; +import {useEffect, useState} from "react"; +import {useField, useFormikContext} from "formik"; +import {CampaignForm} from "../../../../../types"; +import {differenceInHours} from "date-fns"; +import {MIN_PER_CAMPAIGN, MIN_PER_DAY} from "validation/CampaignSchema"; +import {useAdvertiserWithPrices} from "user/hooks/useAdvertiserWithPrices"; export function BudgetField() { const [, , dailyBudget] = useField("dailyBudget"); - const { isDraft } = useIsEdit(); - const { data } = useAdvertiserWithPrices(); - const { values, errors } = useFormikContext(); + const {isDraft} = useIsEdit(); + const {data} = useAdvertiserWithPrices(); + const {values, errors} = useFormikContext(); const [minBudget, setMinBudget] = useState(MIN_PER_CAMPAIGN); const campaignRuntime = Math.floor( differenceInHours(new Date(values.endAt), new Date(values.startAt)) / 24, @@ -38,37 +35,24 @@ export function BudgetField() { }, [campaignRuntime, values.budget, minBudget]); return ( - - - Set a limit on how much your campaign will spend. - - - $, - endAdornment: ( - {values.currency} - ), - }} - helperText={ - errors.budget || errors.dailyBudget - ? `${errors.dailyBudget}. Minimum $${minBudget}.` - : undefined - } - error={!!errors.budget || !!errors.dailyBudget} - disabled={!isDraft && !data.selfServiceSetPrice} - /> - - {!data.selfServiceSetPrice && ( - - )} - - {data.selfServiceSetPrice && } - - + $, + endAdornment: ( + {values.currency} + ), + }} + helperText={ + errors.budget || errors.dailyBudget + ? `${errors.dailyBudget}. Minimum $${minBudget}.` + : undefined + } + error={!!errors.budget || !!errors.dailyBudget} + disabled={!isDraft && !data.selfServiceSetPrice} + /> ); } diff --git a/src/user/views/adsManager/views/advanced/components/form/components/BaseForm.tsx b/src/user/views/adsManager/views/advanced/components/form/components/BaseForm.tsx index 2bdd6335..d7b3ac81 100644 --- a/src/user/views/adsManager/views/advanced/components/form/components/BaseForm.tsx +++ b/src/user/views/adsManager/views/advanced/components/form/components/BaseForm.tsx @@ -6,7 +6,6 @@ import { PaymentButton } from "user/views/adsManager/views/advanced/components/f import { AdSetFields } from "user/views/adsManager/views/advanced/components/adSet/AdSetFields"; import { NewAdSet } from "user/views/adsManager/views/advanced/components/adSet/NewAdSet"; import { Route, Switch, useRouteMatch } from "react-router-dom"; -import { BudgetSettings } from "user/views/adsManager/views/advanced/components/campaign/BudgetSettings"; import { FormContext } from "state/context"; import { useState } from "react"; import { AdvertiserPrice } from "user/hooks/useAdvertiserWithPrices"; @@ -26,11 +25,6 @@ export function BaseForm({ hasPaymentIntent, prices }: Props) { path: `${url}/settings`, component: , }, - { - label: "Budget", - path: `${url}/budget`, - component: , - }, { label: "Ad Sets", path: `${url}/adSets`, diff --git a/src/user/views/adsManager/views/advanced/components/review/Review.tsx b/src/user/views/adsManager/views/advanced/components/review/Review.tsx index 80bc4b13..6e3de461 100644 --- a/src/user/views/adsManager/views/advanced/components/review/Review.tsx +++ b/src/user/views/adsManager/views/advanced/components/review/Review.tsx @@ -4,6 +4,7 @@ import { Box } from "@mui/material"; import { useEffect } from "react"; import { CampaignReview } from "./components/CampaignReview"; import { AdSetReview } from "./components/AdSetReview"; +import { PaymentMethodField } from "user/views/adsManager/views/advanced/components/campaign/fields/PaymentMethodField"; export function Review() { const { values, errors, setTouched } = useFormikContext(); @@ -28,6 +29,8 @@ export function Review() { errors={errors.adSets?.[adSetIdx]} /> ))} + + ); } diff --git a/src/validation/CampaignSchema.tsx b/src/validation/CampaignSchema.tsx index e42eed7e..bd0962f0 100644 --- a/src/validation/CampaignSchema.tsx +++ b/src/validation/CampaignSchema.tsx @@ -20,7 +20,7 @@ import { Billing } from "user/views/adsManager/types"; import { uiLabelsForCampaignFormat } from "util/campaign"; export const MIN_PER_DAY = 33; -export const MIN_PER_CAMPAIGN = 100; +export const MIN_PER_CAMPAIGN = 500; export const CampaignSchema = (prices: AdvertiserPrice[]) => object().shape({