From f72cb1cfc2e84d83d8635b4c4f4f92b07fdb5830 Mon Sep 17 00:00:00 2001 From: Ian Krieger <48930920+IanKrieger@users.noreply.github.com> Date: Tue, 29 Aug 2023 14:42:52 -0400 Subject: [PATCH 1/4] chore: generalize how ads are created (#863) * feat: allow use of existing creatives * fix: tests * feat: add new creative form * feat: add existing creative modals to campaign create * fix: remove in sync * chore: move away from notification specific code * chore: prepare for non-specific creative types * chore: be more deliberate about populating form values * fix: pick another field * fix: better convert fragments * fix: reduce changes * fix: merge conflict * fix: remove changes around edit * fix: make sure edit knows they will lose changes * fix: dont prop-drill loading * fix: return component * fix: bring back externalId * fix: keep a copy of the existing ad set creatives * fix: move creatives into one form backing object * fix: edit values * fix: working index means no id necessary * fix: add tests and adjust padding --- src/components/Box/BoxContainer.tsx | 21 +- src/components/Campaigns/Status.tsx | 5 +- .../Creatives/CreateCreativeButton.tsx | 67 +++ .../Creatives/CreativeSpecificFields.tsx | 13 + .../Creatives/CreativeSpecificPreview.tsx | 65 +++ .../Creatives}/NotificationPreview.tsx | 21 +- .../Creatives/NotificationSelect.tsx | 111 +++++ .../Creatives/SelectCreativeHeader.tsx | 47 ++ src/components/Navigation/Navbar.tsx | 1 - .../Navigation/NewCampaignButton.tsx | 31 ++ src/graphql/ad-set.generated.tsx | 124 +++++ src/graphql/campaign.generated.tsx | 132 +++++ src/graphql/creative.generated.tsx | 259 ++++++---- src/graphql/creative.graphql | 58 ++- src/state/context.ts | 5 + src/user/ads/AdsExistingAd.tsx | 144 ++++++ src/user/ads/NewAd.tsx | 73 +-- src/user/ads/NotificationAd.tsx | 74 +-- src/user/hooks/useAdvertiserCreatives.ts | 40 +- src/user/library/index.test.ts | 455 +++++++++++++++++- src/user/library/index.ts | 172 ++++--- src/user/views/adsManager/types/index.ts | 43 +- .../advanced/components/adSet/NewAdSet.tsx | 9 +- .../components/adSet/fields/AdSetAds.tsx | 57 +-- .../advanced/components/form/EditCampaign.tsx | 7 +- .../components/form/components/BaseForm.tsx | 43 +- .../form/components/PaymentButton.tsx | 9 +- .../advanced/components/review/Review.tsx | 4 +- .../components/review/components/AdReview.tsx | 29 -- .../review/components/AdSetReview.tsx | 12 +- src/validation/CampaignSchema.tsx | 41 +- src/validation/CreativeSchema.test.ts | 65 +++ src/validation/CreativeSchema.tsx | 48 ++ src/validation/regex.ts | 4 + 34 files changed, 1805 insertions(+), 484 deletions(-) create mode 100644 src/components/Creatives/CreateCreativeButton.tsx create mode 100644 src/components/Creatives/CreativeSpecificFields.tsx create mode 100644 src/components/Creatives/CreativeSpecificPreview.tsx rename src/{user/ads => components/Creatives}/NotificationPreview.tsx (66%) create mode 100644 src/components/Creatives/NotificationSelect.tsx create mode 100644 src/components/Creatives/SelectCreativeHeader.tsx create mode 100644 src/components/Navigation/NewCampaignButton.tsx create mode 100644 src/user/ads/AdsExistingAd.tsx delete mode 100644 src/user/views/adsManager/views/advanced/components/review/components/AdReview.tsx create mode 100644 src/validation/CreativeSchema.test.ts create mode 100644 src/validation/CreativeSchema.tsx create mode 100644 src/validation/regex.ts diff --git a/src/components/Box/BoxContainer.tsx b/src/components/Box/BoxContainer.tsx index 4b152a48..d03c2c29 100644 --- a/src/components/Box/BoxContainer.tsx +++ b/src/components/Box/BoxContainer.tsx @@ -2,16 +2,23 @@ import { PropsWithChildren, ReactNode } from "react"; import { Box, Typography } from "@mui/material"; export function BoxContainer( - props: { header?: ReactNode } & PropsWithChildren, + props: { useTypography?: boolean; header?: ReactNode } & PropsWithChildren, ) { + let header; + if (props.header) { + header = props.useTypography ? ( + + {props.header} + + ) : ( + props.header + ); + } + return ( - {props.header && ( - - {props.header} - - )} - + {header} + {props.children} diff --git a/src/components/Campaigns/Status.tsx b/src/components/Campaigns/Status.tsx index c8dd1065..337a4892 100644 --- a/src/components/Campaigns/Status.tsx +++ b/src/components/Campaigns/Status.tsx @@ -7,11 +7,11 @@ interface Props { state: string; start?: string; end?: string; + opaque?: boolean; } -export const Status = ({ state, start, end }: Props) => { +export const Status = ({ state, start, end, opaque }: Props) => { let color = calcColorForState(state); - let label = _.startCase(state); if (start) { @@ -36,6 +36,7 @@ export const Status = ({ state, start, end }: Props) => { sx={{ backgroundColor: color, fontSize: "0.7rem", + opacity: opaque === false ? "0.3" : 1, }} /> diff --git a/src/components/Creatives/CreateCreativeButton.tsx b/src/components/Creatives/CreateCreativeButton.tsx new file mode 100644 index 00000000..79e59561 --- /dev/null +++ b/src/components/Creatives/CreateCreativeButton.tsx @@ -0,0 +1,67 @@ +import SaveIcon from "@mui/icons-material/Save"; +import { + CampaignForm, + Creative, + initialCreative, +} from "user/views/adsManager/types"; +import _ from "lodash"; +import { + refetchAdvertiserCreativesQuery, + useCreateCreativeMutation, +} from "graphql/creative.generated"; +import { useField, useFormikContext } from "formik"; +import { useAdvertiser } from "auth/hooks/queries/useAdvertiser"; +import { LoadingButton } from "@mui/lab"; +import { validCreativeFields } from "user/library"; + +export function CreateCreativeButton() { + const { values, setFieldValue } = useFormikContext(); + const [, , isCreating] = useField("isCreating"); + const [, newMeta, newHelper] = useField("newCreative"); + const { advertiser } = useAdvertiser(); + + const [create, { loading }] = useCreateCreativeMutation({ + async onCompleted(data) { + newHelper.setValue(initialCreative); + newHelper.setTouched(false); + values.adSets.forEach((adSet, idx) => { + void setFieldValue(`adSets.${idx}.creatives`, [ + ...adSet.creatives, + validCreativeFields(data.createCreative, advertiser.id), + ]); + }); + isCreating.setValue(false); + }, + refetchQueries: [ + { + ...refetchAdvertiserCreativesQuery({ advertiserId: advertiser.id }), + }, + ], + }); + + return ( + } + onClick={(e) => { + e.preventDefault(); + create({ + variables: { + input: { + ..._.omit(newMeta.value, "included"), + advertiserId: advertiser.id, + }, + }, + }); + }} + disabled={ + newMeta.value?.targetUrlValid !== undefined || + !_.isEmpty(newMeta.error) || + loading + } + loading={loading} + > + Add + + ); +} diff --git a/src/components/Creatives/CreativeSpecificFields.tsx b/src/components/Creatives/CreativeSpecificFields.tsx new file mode 100644 index 00000000..f6463db6 --- /dev/null +++ b/src/components/Creatives/CreativeSpecificFields.tsx @@ -0,0 +1,13 @@ +import { useFormikContext } from "formik"; +import { CampaignForm } from "user/views/adsManager/types"; +import { CampaignFormat } from "graphql/types"; +import { NotificationAd } from "user/ads/NotificationAd"; + +export const CreativeSpecificFields = () => { + const { values } = useFormikContext(); + + if (values.format === CampaignFormat.PushNotification) + return ; + + return null; +}; diff --git a/src/components/Creatives/CreativeSpecificPreview.tsx b/src/components/Creatives/CreativeSpecificPreview.tsx new file mode 100644 index 00000000..f094e8bd --- /dev/null +++ b/src/components/Creatives/CreativeSpecificPreview.tsx @@ -0,0 +1,65 @@ +import { CampaignFormat } from "graphql/types"; +import { BoxContainer } from "components/Box/BoxContainer"; +import { NotificationPreview } from "components/Creatives/NotificationPreview"; +import { Stack, Typography } from "@mui/material"; +import { PropsWithChildren } from "react"; +import { useField } from "formik"; +import { Creative } from "user/views/adsManager/types"; +import { DisplayError } from "user/views/adsManager/views/advanced/components/review/components/ReviewField"; + +interface Props extends PropsWithChildren { + options: Creative[]; + useSimpleHeader?: boolean; + error?: string; +} + +export function CreativeSpecificPreview({ + options, + useSimpleHeader, + error, + children, +}: Props) { + const [, format] = useField("format"); + + let component; + if (format.value === CampaignFormat.PushNotification) { + component = options.map((c, idx) => ( + + + + )); + } + + if (error) { + return ( + <> + + Ads + + + + ); + } + + return ( + <> + {useSimpleHeader && ( + + Ads + + )} + + {component} + {children} + + + ); +} diff --git a/src/user/ads/NotificationPreview.tsx b/src/components/Creatives/NotificationPreview.tsx similarity index 66% rename from src/user/ads/NotificationPreview.tsx rename to src/components/Creatives/NotificationPreview.tsx index 460d070e..0591dd26 100644 --- a/src/user/ads/NotificationPreview.tsx +++ b/src/components/Creatives/NotificationPreview.tsx @@ -1,10 +1,14 @@ -import { useField } from "formik"; -import { Creative } from "user/views/adsManager/types"; import { Box, Paper, Stack, Typography } from "@mui/material"; import logo from "../../../brave_logo_icon.png"; +import { useField } from "formik"; +import { CreativeInput } from "graphql/types"; -export function NotificationPreview(props: { title?: string; body?: string }) { - const [, meta] = useField("newCreative"); +export function NotificationPreview(props: { + title?: string; + body?: string; + selected?: boolean; +}) { + const [, meta, ,] = useField("newCreative"); return ( @@ -18,6 +22,7 @@ export function NotificationPreview(props: { title?: string; body?: string }) { display: "flex", justifyContent: "left", flexDirection: "row", + opacity: props.selected === false ? 0.5 : 1, }} > @@ -27,10 +32,14 @@ export function NotificationPreview(props: { title?: string; body?: string }) { /> - {props.title || meta.value.title || "Title Preview"} + {props.title || + meta.value?.payloadNotification?.title || + "Title Preview"} - {props.body || meta.value.body || "Body Preview"} + {props.body || + meta.value?.payloadNotification?.body || + "Body Preview"} diff --git a/src/components/Creatives/NotificationSelect.tsx b/src/components/Creatives/NotificationSelect.tsx new file mode 100644 index 00000000..4b6d552d --- /dev/null +++ b/src/components/Creatives/NotificationSelect.tsx @@ -0,0 +1,111 @@ +import { Box, Button, Stack, Typography } from "@mui/material"; +import { BoxContainer } from "components/Box/BoxContainer"; +import { NotificationPreview } from "components/Creatives/NotificationPreview"; +import moment from "moment"; +import { SelectCreativeHeader } from "components/Creatives/SelectCreativeHeader"; +import { CampaignForm, Creative } from "user/views/adsManager/types"; +import _ from "lodash"; +import { useContext, useState } from "react"; +import { FormContext } from "state/context"; +import { useFormikContext } from "formik"; + +export function NotificationSelect(props: { + options: Creative[]; + useSelectedAdStyle?: boolean; + showState?: boolean; + index?: number; + hideCreated?: boolean; +}) { + const index = props.index; + const { values, setFieldValue } = useFormikContext(); + const { setIsShowingAds } = useContext(FormContext); + const [curr, setCurr] = useState([]); + + const onSelectCreative = (c: Creative, selected: boolean) => { + let value; + if (selected) { + value = [...curr, c]; + } else { + value = _.filter(curr, (n) => n.id !== c.id); + } + + if (index !== undefined) { + const foundIndex = values.adSets[index].creatives.findIndex( + (co) => c.id === co.id, + ); + if (foundIndex !== undefined) { + void setFieldValue( + `adSets.${index}.creatives.${foundIndex}.included`, + selected, + ); + } + } + + setCurr(_.uniqBy(value, "id")); + }; + + const isSelected = (co: Creative) => + props.useSelectedAdStyle === false || co.included; + + return ( + + 3 ? "scroll" : "hidden" }} + > + {props.options.map((co, idx) => ( + + } + key={idx} + > + + {!(props.hideCreated ?? false) && ( + + created {moment(co.createdAt).fromNow()} + + )} + + ))} + + {props.index === undefined && ( + + )} + + ); +} diff --git a/src/components/Creatives/SelectCreativeHeader.tsx b/src/components/Creatives/SelectCreativeHeader.tsx new file mode 100644 index 00000000..6dc151f9 --- /dev/null +++ b/src/components/Creatives/SelectCreativeHeader.tsx @@ -0,0 +1,47 @@ +import { Box, IconButton, Typography } from "@mui/material"; +import CheckBoxIcon from "@mui/icons-material/CheckBox"; +import CheckBoxOutlineBlankIcon from "@mui/icons-material/CheckBoxOutlineBlank"; +import { Creative } from "user/views/adsManager/types"; +import { Status } from "components/Campaigns/Status"; +import { useEffect, useState } from "react"; + +export const SelectCreativeHeader = (props: { + creative: Creative; + onSelectCreative: (c: Creative, selected: boolean) => void; + showState?: boolean; +}) => { + const [selected, setSelected] = useState(); + useEffect(() => { + setSelected(props.creative.included); + }, [props.creative]); + + return ( + + { + const s = !selected; + setSelected(s); + props.onSelectCreative(props.creative, s); + }} + sx={{ p: 0 }} + > + {selected ? ( + + ) : ( + + )} + + {props.creative.name} +
+ {props.showState !== false && ( + + )} + + ); +}; diff --git a/src/components/Navigation/Navbar.tsx b/src/components/Navigation/Navbar.tsx index b6172e7e..8e1bc60f 100644 --- a/src/components/Navigation/Navbar.tsx +++ b/src/components/Navigation/Navbar.tsx @@ -1,5 +1,4 @@ import { useRouteMatch, Link as RouterLink } from "react-router-dom"; - import { AppBar, Button, Divider, Stack, Toolbar } from "@mui/material"; import { DraftMenu } from "components/Navigation/DraftMenu"; diff --git a/src/components/Navigation/NewCampaignButton.tsx b/src/components/Navigation/NewCampaignButton.tsx new file mode 100644 index 00000000..93674608 --- /dev/null +++ b/src/components/Navigation/NewCampaignButton.tsx @@ -0,0 +1,31 @@ +import { useAdvertiser } from "auth/hooks/queries/useAdvertiser"; +import { Button } from "@mui/material"; +import moment from "moment/moment"; +import { Link as RouterLink, useRouteMatch } from "react-router-dom"; + +export function NewCampaignButton() { + const { url } = useRouteMatch(); + const { advertiser } = useAdvertiser(); + const isCompletePage = url.includes("/user/main/complete/new"); + const isNewCampaignPage = url.includes("/user/main/adsmanager/advanced"); + const newUrl = `/user/main/adsmanager/advanced/new/${moment() + .utc() + .valueOf()}/settings`; + + if (!advertiser.selfServiceCreate) { + return null; + } + + return ( + + ); +} diff --git a/src/graphql/ad-set.generated.tsx b/src/graphql/ad-set.generated.tsx index 0adb3893..4b947c7e 100644 --- a/src/graphql/ad-set.generated.tsx +++ b/src/graphql/ad-set.generated.tsx @@ -42,6 +42,35 @@ export type AdSetFragment = { title: string; targetUrl: string; } | null; + payloadNewTabPage?: { + logo?: { + imageUrl: string; + alt: string; + companyName: string; + destinationUrl: string; + } | null; + wallpapers?: Array<{ + imageUrl: string; + focalPoint: { x: number; y: number }; + }> | null; + } | null; + payloadInlineContent?: { + title: string; + ctaText: string; + imageUrl: string; + targetUrl: string; + dimensions: string; + description: string; + } | null; + payloadSearch?: { body: string; title: string; targetUrl: string } | null; + payloadSearchHomepage?: { + body: string; + imageUrl: string; + imageDarkModeUrl?: string | null; + targetUrl: string; + title: string; + ctaText: string; + } | null; }; }> | null; }; @@ -63,6 +92,35 @@ export type AdFragment = { title: string; targetUrl: string; } | null; + payloadNewTabPage?: { + logo?: { + imageUrl: string; + alt: string; + companyName: string; + destinationUrl: string; + } | null; + wallpapers?: Array<{ + imageUrl: string; + focalPoint: { x: number; y: number }; + }> | null; + } | null; + payloadInlineContent?: { + title: string; + ctaText: string; + imageUrl: string; + targetUrl: string; + dimensions: string; + description: string; + } | null; + payloadSearch?: { body: string; title: string; targetUrl: string } | null; + payloadSearchHomepage?: { + body: string; + imageUrl: string; + imageDarkModeUrl?: string | null; + targetUrl: string; + title: string; + ctaText: string; + } | null; }; }; @@ -109,6 +167,39 @@ export type CreateAdSetMutation = { title: string; targetUrl: string; } | null; + payloadNewTabPage?: { + logo?: { + imageUrl: string; + alt: string; + companyName: string; + destinationUrl: string; + } | null; + wallpapers?: Array<{ + imageUrl: string; + focalPoint: { x: number; y: number }; + }> | null; + } | null; + payloadInlineContent?: { + title: string; + ctaText: string; + imageUrl: string; + targetUrl: string; + dimensions: string; + description: string; + } | null; + payloadSearch?: { + body: string; + title: string; + targetUrl: string; + } | null; + payloadSearchHomepage?: { + body: string; + imageUrl: string; + imageDarkModeUrl?: string | null; + targetUrl: string; + title: string; + ctaText: string; + } | null; }; }> | null; }; @@ -157,6 +248,39 @@ export type UpdateAdSetMutation = { title: string; targetUrl: string; } | null; + payloadNewTabPage?: { + logo?: { + imageUrl: string; + alt: string; + companyName: string; + destinationUrl: string; + } | null; + wallpapers?: Array<{ + imageUrl: string; + focalPoint: { x: number; y: number }; + }> | null; + } | null; + payloadInlineContent?: { + title: string; + ctaText: string; + imageUrl: string; + targetUrl: string; + dimensions: string; + description: string; + } | null; + payloadSearch?: { + body: string; + title: string; + targetUrl: string; + } | null; + payloadSearchHomepage?: { + body: string; + imageUrl: string; + imageDarkModeUrl?: string | null; + targetUrl: string; + title: string; + ctaText: string; + } | null; }; }> | null; }; diff --git a/src/graphql/campaign.generated.tsx b/src/graphql/campaign.generated.tsx index d0be5d05..ee73e9ab 100644 --- a/src/graphql/campaign.generated.tsx +++ b/src/graphql/campaign.generated.tsx @@ -72,6 +72,39 @@ export type CampaignFragment = { title: string; targetUrl: string; } | null; + payloadNewTabPage?: { + logo?: { + imageUrl: string; + alt: string; + companyName: string; + destinationUrl: string; + } | null; + wallpapers?: Array<{ + imageUrl: string; + focalPoint: { x: number; y: number }; + }> | null; + } | null; + payloadInlineContent?: { + title: string; + ctaText: string; + imageUrl: string; + targetUrl: string; + dimensions: string; + description: string; + } | null; + payloadSearch?: { + body: string; + title: string; + targetUrl: string; + } | null; + payloadSearchHomepage?: { + body: string; + imageUrl: string; + imageDarkModeUrl?: string | null; + targetUrl: string; + title: string; + ctaText: string; + } | null; }; }> | null; }>; @@ -150,6 +183,39 @@ export type CampaignAdsFragment = { title: string; targetUrl: string; } | null; + payloadNewTabPage?: { + logo?: { + imageUrl: string; + alt: string; + companyName: string; + destinationUrl: string; + } | null; + wallpapers?: Array<{ + imageUrl: string; + focalPoint: { x: number; y: number }; + }> | null; + } | null; + payloadInlineContent?: { + title: string; + ctaText: string; + imageUrl: string; + targetUrl: string; + dimensions: string; + description: string; + } | null; + payloadSearch?: { + body: string; + title: string; + targetUrl: string; + } | null; + payloadSearchHomepage?: { + body: string; + imageUrl: string; + imageDarkModeUrl?: string | null; + targetUrl: string; + title: string; + ctaText: string; + } | null; }; }> | null; }>; @@ -228,6 +294,39 @@ export type LoadCampaignQuery = { title: string; targetUrl: string; } | null; + payloadNewTabPage?: { + logo?: { + imageUrl: string; + alt: string; + companyName: string; + destinationUrl: string; + } | null; + wallpapers?: Array<{ + imageUrl: string; + focalPoint: { x: number; y: number }; + }> | null; + } | null; + payloadInlineContent?: { + title: string; + ctaText: string; + imageUrl: string; + targetUrl: string; + dimensions: string; + description: string; + } | null; + payloadSearch?: { + body: string; + title: string; + targetUrl: string; + } | null; + payloadSearchHomepage?: { + body: string; + imageUrl: string; + imageDarkModeUrl?: string | null; + targetUrl: string; + title: string; + ctaText: string; + } | null; }; }> | null; }>; @@ -288,6 +387,39 @@ export type LoadCampaignAdsQuery = { title: string; targetUrl: string; } | null; + payloadNewTabPage?: { + logo?: { + imageUrl: string; + alt: string; + companyName: string; + destinationUrl: string; + } | null; + wallpapers?: Array<{ + imageUrl: string; + focalPoint: { x: number; y: number }; + }> | null; + } | null; + payloadInlineContent?: { + title: string; + ctaText: string; + imageUrl: string; + targetUrl: string; + dimensions: string; + description: string; + } | null; + payloadSearch?: { + body: string; + title: string; + targetUrl: string; + } | null; + payloadSearchHomepage?: { + body: string; + imageUrl: string; + imageDarkModeUrl?: string | null; + targetUrl: string; + title: string; + ctaText: string; + } | null; }; }> | null; }>; diff --git a/src/graphql/creative.generated.tsx b/src/graphql/creative.generated.tsx index b67e1f46..8f46b4d8 100644 --- a/src/graphql/creative.generated.tsx +++ b/src/graphql/creative.generated.tsx @@ -15,6 +15,35 @@ export type CreativeFragment = { title: string; targetUrl: string; } | null; + payloadNewTabPage?: { + logo?: { + imageUrl: string; + alt: string; + companyName: string; + destinationUrl: string; + } | null; + wallpapers?: Array<{ + imageUrl: string; + focalPoint: { x: number; y: number }; + }> | null; + } | null; + payloadInlineContent?: { + title: string; + ctaText: string; + imageUrl: string; + targetUrl: string; + dimensions: string; + description: string; + } | null; + payloadSearch?: { body: string; title: string; targetUrl: string } | null; + payloadSearchHomepage?: { + body: string; + imageUrl: string; + imageDarkModeUrl?: string | null; + targetUrl: string; + title: string; + ctaText: string; + } | null; }; export type AdvertiserCreativesQueryVariables = Types.Exact<{ @@ -36,33 +65,88 @@ export type AdvertiserCreativesQuery = { title: string; targetUrl: string; } | null; + payloadNewTabPage?: { + logo?: { + imageUrl: string; + alt: string; + companyName: string; + destinationUrl: string; + } | null; + wallpapers?: Array<{ + imageUrl: string; + focalPoint: { x: number; y: number }; + }> | null; + } | null; + payloadInlineContent?: { + title: string; + ctaText: string; + imageUrl: string; + targetUrl: string; + dimensions: string; + description: string; + } | null; + payloadSearch?: { body: string; title: string; targetUrl: string } | null; + payloadSearchHomepage?: { + body: string; + imageUrl: string; + imageDarkModeUrl?: string | null; + targetUrl: string; + title: string; + ctaText: string; + } | null; }>; } | null; }; -export type CreateNotificationCreativeMutationVariables = Types.Exact<{ - input: Types.CreateNotificationCreativeInput; +export type CreateCreativeMutationVariables = Types.Exact<{ + input: Types.CreativeInput; }>; -export type CreateNotificationCreativeMutation = { - createNotificationCreative: { +export type CreateCreativeMutation = { + createCreative: { id: string; + createdAt: any; + modifiedAt: any; + name: string; + state: string; + type: { code: string }; payloadNotification?: { body: string; title: string; targetUrl: string; } | null; + payloadNewTabPage?: { + logo?: { + imageUrl: string; + alt: string; + companyName: string; + destinationUrl: string; + } | null; + wallpapers?: Array<{ + imageUrl: string; + focalPoint: { x: number; y: number }; + }> | null; + } | null; + payloadInlineContent?: { + title: string; + ctaText: string; + imageUrl: string; + targetUrl: string; + dimensions: string; + description: string; + } | null; + payloadSearch?: { body: string; title: string; targetUrl: string } | null; + payloadSearchHomepage?: { + body: string; + imageUrl: string; + imageDarkModeUrl?: string | null; + targetUrl: string; + title: string; + ctaText: string; + } | null; }; }; -export type UpdateNotificationCreativeMutationVariables = Types.Exact<{ - input: Types.UpdateNotificationCreativeInput; -}>; - -export type UpdateNotificationCreativeMutation = { - updateNotificationCreative: { id: string }; -}; - export const CreativeFragmentDoc = gql` fragment Creative on Creative { id @@ -78,6 +162,47 @@ export const CreativeFragmentDoc = gql` title targetUrl } + payloadNewTabPage { + logo { + imageUrl + alt + companyName + destinationUrl + } + wallpapers { + imageUrl + focalPoint { + x + y + } + } + } + payloadInlineContent { + title + ctaText + imageUrl + targetUrl + dimensions + description + } + payloadNotification { + body + title + targetUrl + } + payloadSearch { + body + title + targetUrl + } + payloadSearchHomepage { + body + imageUrl + imageDarkModeUrl + targetUrl + title + ctaText + } } `; export const AdvertiserCreativesDocument = gql` @@ -147,114 +272,54 @@ export function refetchAdvertiserCreativesQuery( ) { return { query: AdvertiserCreativesDocument, variables: variables }; } -export const CreateNotificationCreativeDocument = gql` - mutation createNotificationCreative( - $input: CreateNotificationCreativeInput! - ) { - createNotificationCreative(createNotificationCreativeInput: $input) { - id - payloadNotification { - body - title - targetUrl - } +export const CreateCreativeDocument = gql` + mutation createCreative($input: CreativeInput!) { + createCreative(creative: $input) { + ...Creative } } + ${CreativeFragmentDoc} `; -export type CreateNotificationCreativeMutationFn = Apollo.MutationFunction< - CreateNotificationCreativeMutation, - CreateNotificationCreativeMutationVariables +export type CreateCreativeMutationFn = Apollo.MutationFunction< + CreateCreativeMutation, + CreateCreativeMutationVariables >; /** - * __useCreateNotificationCreativeMutation__ + * __useCreateCreativeMutation__ * - * To run a mutation, you first call `useCreateNotificationCreativeMutation` within a React component and pass it any options that fit your needs. - * When your component renders, `useCreateNotificationCreativeMutation` returns a tuple that includes: + * To run a mutation, you first call `useCreateCreativeMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useCreateCreativeMutation` returns a tuple that includes: * - A mutate function that you can call at any time to execute the mutation * - An object with fields that represent the current status of the mutation's execution * * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; * * @example - * const [createNotificationCreativeMutation, { data, loading, error }] = useCreateNotificationCreativeMutation({ + * const [createCreativeMutation, { data, loading, error }] = useCreateCreativeMutation({ * variables: { * input: // value for 'input' * }, * }); */ -export function useCreateNotificationCreativeMutation( +export function useCreateCreativeMutation( baseOptions?: Apollo.MutationHookOptions< - CreateNotificationCreativeMutation, - CreateNotificationCreativeMutationVariables + CreateCreativeMutation, + CreateCreativeMutationVariables >, ) { const options = { ...defaultOptions, ...baseOptions }; return Apollo.useMutation< - CreateNotificationCreativeMutation, - CreateNotificationCreativeMutationVariables - >(CreateNotificationCreativeDocument, options); + CreateCreativeMutation, + CreateCreativeMutationVariables + >(CreateCreativeDocument, options); } -export type CreateNotificationCreativeMutationHookResult = ReturnType< - typeof useCreateNotificationCreativeMutation +export type CreateCreativeMutationHookResult = ReturnType< + typeof useCreateCreativeMutation >; -export type CreateNotificationCreativeMutationResult = - Apollo.MutationResult; -export type CreateNotificationCreativeMutationOptions = - Apollo.BaseMutationOptions< - CreateNotificationCreativeMutation, - CreateNotificationCreativeMutationVariables - >; -export const UpdateNotificationCreativeDocument = gql` - mutation updateNotificationCreative( - $input: UpdateNotificationCreativeInput! - ) { - updateNotificationCreative(updateNotificationCreativeInput: $input) { - id - } - } -`; -export type UpdateNotificationCreativeMutationFn = Apollo.MutationFunction< - UpdateNotificationCreativeMutation, - UpdateNotificationCreativeMutationVariables ->; - -/** - * __useUpdateNotificationCreativeMutation__ - * - * To run a mutation, you first call `useUpdateNotificationCreativeMutation` within a React component and pass it any options that fit your needs. - * When your component renders, `useUpdateNotificationCreativeMutation` returns a tuple that includes: - * - A mutate function that you can call at any time to execute the mutation - * - An object with fields that represent the current status of the mutation's execution - * - * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; - * - * @example - * const [updateNotificationCreativeMutation, { data, loading, error }] = useUpdateNotificationCreativeMutation({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ -export function useUpdateNotificationCreativeMutation( - baseOptions?: Apollo.MutationHookOptions< - UpdateNotificationCreativeMutation, - UpdateNotificationCreativeMutationVariables - >, -) { - const options = { ...defaultOptions, ...baseOptions }; - return Apollo.useMutation< - UpdateNotificationCreativeMutation, - UpdateNotificationCreativeMutationVariables - >(UpdateNotificationCreativeDocument, options); -} -export type UpdateNotificationCreativeMutationHookResult = ReturnType< - typeof useUpdateNotificationCreativeMutation +export type CreateCreativeMutationResult = + Apollo.MutationResult; +export type CreateCreativeMutationOptions = Apollo.BaseMutationOptions< + CreateCreativeMutation, + CreateCreativeMutationVariables >; -export type UpdateNotificationCreativeMutationResult = - Apollo.MutationResult; -export type UpdateNotificationCreativeMutationOptions = - Apollo.BaseMutationOptions< - UpdateNotificationCreativeMutation, - UpdateNotificationCreativeMutationVariables - >; diff --git a/src/graphql/creative.graphql b/src/graphql/creative.graphql index 80ad3102..a7b10adf 100644 --- a/src/graphql/creative.graphql +++ b/src/graphql/creative.graphql @@ -12,6 +12,47 @@ fragment Creative on Creative { title targetUrl } + payloadNewTabPage { + logo { + imageUrl + alt + companyName + destinationUrl + } + wallpapers { + imageUrl + focalPoint { + x + y + } + } + } + payloadInlineContent { + title + ctaText + imageUrl + targetUrl + dimensions + description + } + payloadNotification { + body + title + targetUrl + } + payloadSearch { + body + title + targetUrl + } + payloadSearchHomepage { + body + imageUrl + imageDarkModeUrl + targetUrl + title + ctaText + } } query advertiserCreatives($advertiserId: String!) { @@ -23,19 +64,8 @@ query advertiserCreatives($advertiserId: String!) { } } -mutation createNotificationCreative($input: CreateNotificationCreativeInput!) { - createNotificationCreative(createNotificationCreativeInput: $input) { - id - payloadNotification { - body - title - targetUrl - } - } -} - -mutation updateNotificationCreative($input: UpdateNotificationCreativeInput!) { - updateNotificationCreative(updateNotificationCreativeInput: $input) { - id +mutation createCreative($input: CreativeInput!) { + createCreative(creative: $input) { + ...Creative } } diff --git a/src/state/context.ts b/src/state/context.ts index 0c7e0b58..e5bb4ae2 100644 --- a/src/state/context.ts +++ b/src/state/context.ts @@ -31,3 +31,8 @@ export const FilterContext = createContext({ fromDate: null as Date | null, setFromDate: (_d: Date | null) => {}, }); + +export const FormContext = createContext({ + isShowingAds: false as boolean, + setIsShowingAds: (_b: boolean) => {}, +}); diff --git a/src/user/ads/AdsExistingAd.tsx b/src/user/ads/AdsExistingAd.tsx new file mode 100644 index 00000000..4dc45a71 --- /dev/null +++ b/src/user/ads/AdsExistingAd.tsx @@ -0,0 +1,144 @@ +import { + Alert, + Box, + InputAdornment, + LinearProgress, + TextField, + Typography, +} from "@mui/material"; +import { useFormikContext } from "formik"; +import { CampaignFormat } from "graphql/types"; +import _ from "lodash"; +import { + CreativeFragment, + useAdvertiserCreativesQuery, +} from "graphql/creative.generated"; +import { isCreativeTypeApplicableToCampaignFormat } from "user/library"; +import { useAdvertiser } from "auth/hooks/queries/useAdvertiser"; +import { CampaignForm } from "user/views/adsManager/types"; +import { CardContainer } from "components/Card/CardContainer"; +import SearchIcon from "@mui/icons-material/Search"; +import { useContext, useRef, useState } from "react"; +import { NotificationSelect } from "components/Creatives/NotificationSelect"; +import { FormContext } from "state/context"; +import { useAdvertiserCreatives } from "user/hooks/useAdvertiserCreatives"; + +function filterCreativesBasedOnCampaignFormat( + creatives: CreativeFragment[], + campaignFormat: CampaignFormat | null, +): CreativeFragment[] { + if (!campaignFormat) return creatives; + + return creatives.filter((c) => + isCreativeTypeApplicableToCampaignFormat(c.type, campaignFormat), + ); +} + +export function AdsExistingAd() { + const { setIsShowingAds } = useContext(FormContext); + const { creatives } = useAdvertiserCreatives(); + const { values } = useFormikContext(); + const { advertiser } = useAdvertiser(); + const original = useRef([]); + const [options, setOptions] = useState(); + const { loading } = useAdvertiserCreativesQuery({ + variables: { advertiserId: advertiser.id }, + onCompleted(data) { + const creativeOptionList = _.orderBy( + filterCreativesBasedOnCampaignFormat( + data.advertiser?.creatives ?? [], + values.format, + ), + ["type.code", "createdAt"], + ["asc", "desc"], + ) as CreativeFragment[]; + + const filtered = creativeOptionList.filter((c) => c.state === "active"); + const exludeExisting = filtered.filter((e) => { + const associatedOptions = creatives ?? []; + return associatedOptions.find((ao) => ao.id === e.id) === undefined; + }); + original.current = exludeExisting; + setOptions(exludeExisting); + }, + }); + + if (loading) { + return ; + } + + if (options && options.length === 0) { + return ( + setIsShowingAds(false)}> + No previous Ads available + + ); + } + + return ( + + + Add an existing Ad + + + + Ads are modular building blocks that can be paired with ad sets to build + unique combinations. Your previously approved ads will show here. Select + by using the box next to the name. Use the "Complete + selection" button to finish. + + + + + + + ), + }} + onChange={(e) => { + const value = e.target.value.toLowerCase(); + if (!value || value.trim() !== "") { + setOptions( + original.current.filter((co) => + co.name.toLowerCase().includes(value), + ), + ); + } else { + setOptions(original.current); + } + }} + /> + + + + + ); +} + +const CreativeSpecificSelect = (props: { + format: CampaignFormat; + options: CreativeFragment[]; +}) => { + const { advertiser } = useAdvertiser(); + + if (props.format === CampaignFormat.PushNotification) + return ( + ({ + ...o, + advertiserId: advertiser.id, + included: false, + }))} + useSelectedAdStyle={false} + showState={false} + /> + ); + + return null; +}; diff --git a/src/user/ads/NewAd.tsx b/src/user/ads/NewAd.tsx index 74aaa35f..4538b7f1 100644 --- a/src/user/ads/NewAd.tsx +++ b/src/user/ads/NewAd.tsx @@ -1,34 +1,38 @@ -import { useRecentlyCreatedAdvertiserCreatives } from "user/hooks/useAdvertiserCreatives"; import { CardContainer } from "components/Card/CardContainer"; -import { Box, Button, Stack } from "@mui/material"; -import { useState } from "react"; +import { Box, Button, Link } from "@mui/material"; +import { useContext, useEffect } from "react"; import { BoxContainer } from "components/Box/BoxContainer"; import AddCircleOutlineIcon from "@mui/icons-material/AddCircleOutline"; import RemoveCircleOutlineIcon from "@mui/icons-material/RemoveCircleOutline"; -import { NotificationPreview } from "user/ads/NotificationPreview"; -import { NotificationAd } from "user/ads/NotificationAd"; +import { CreativeSpecificFields } from "components/Creatives/CreativeSpecificFields"; import { useField } from "formik"; +import { Creative, initialCreative } from "user/views/adsManager/types"; +import { FormContext } from "state/context"; +import { AdsExistingAd } from "user/ads/AdsExistingAd"; +import { CreativeSpecificPreview } from "components/Creatives/CreativeSpecificPreview"; +import { useAdvertiserCreatives } from "user/hooks/useAdvertiserCreatives"; export function NewAd() { + const { creatives } = useAdvertiserCreatives(); + const [, , newCreative] = useField("newCreative"); const [, meta, helper] = useField("isCreating"); - const [showForm, setShowForm] = useState(false); - const creatives = useRecentlyCreatedAdvertiserCreatives(); + const { isShowingAds, setIsShowingAds } = useContext(FormContext); + + useEffect(() => { + if (!meta.value) { + newCreative.setValue(initialCreative); + newCreative.setTouched(false); + } + }, [meta.value]); return ( <> - - - {(creatives ?? []).map((c, idx) => ( - - - - ))} - + + + { helper.setValue(!meta.value); - setShowForm(!showForm); + setIsShowingAds(false); }} > - {showForm ? ( + {meta.value ? ( ) : ( )} - + + {!isShowingAds && ( + { + setIsShowingAds(true); + helper.setValue(false); + }} + > + Use previously created Ads + + )} - {showForm && ( - { - helper.setValue(false); - setShowForm(false); - }} - /> - )} + {isShowingAds && } + {meta.value && } ); } diff --git a/src/user/ads/NotificationAd.tsx b/src/user/ads/NotificationAd.tsx index 58ac1735..f5d850fd 100644 --- a/src/user/ads/NotificationAd.tsx +++ b/src/user/ads/NotificationAd.tsx @@ -2,45 +2,17 @@ import { CardContainer } from "components/Card/CardContainer"; import { FormikTextField } from "form/FormikHelpers"; import { Stack } from "@mui/material"; import { UrlResolver } from "components/Url/UrlResolver"; -import { LoadingButton } from "@mui/lab"; -import SaveIcon from "@mui/icons-material/Save"; -import { creativeInput } from "user/library"; -import { CreateNotificationCreativeInput } from "graphql/types"; import { useField } from "formik"; -import { Creative, initialCreative } from "user/views/adsManager/types"; -import { useAdvertiser } from "auth/hooks/queries/useAdvertiser"; -import { useUser } from "auth/hooks/queries/useUser"; -import { NotificationPreview } from "user/ads/NotificationPreview"; -import { - refetchAdvertiserCreativesQuery, - useCreateNotificationCreativeMutation, -} from "graphql/creative.generated"; +import { NotificationPreview } from "components/Creatives/NotificationPreview"; +import { CreateCreativeButton } from "components/Creatives/CreateCreativeButton"; +import { useEffect } from "react"; -interface Props { - onCreate: () => void; -} +export function NotificationAd() { + const [, , code] = useField("newCreative.type.code"); -export function NotificationAd({ onCreate }: Props) { - const [, meta, newCreativeHelper] = useField("newCreative"); - const [, creativesMeta, creativesHelper] = useField("creatives"); - const { advertiser } = useAdvertiser(); - const { userId } = useUser(); - const [create, { loading }] = useCreateNotificationCreativeMutation({ - async onCompleted(data) { - newCreativeHelper.setValue(initialCreative); - newCreativeHelper.setTouched(false); - creativesHelper.setValue([ - ...(creativesMeta.value ?? []), - data.createNotificationCreative.id, - ]); - onCreate(); - }, - refetchQueries: [ - { - ...refetchAdvertiserCreativesQuery({ advertiserId: advertiser.id }), - }, - ], - }); + useEffect(() => { + code.setValue("notification_all_v1"); + }, []); return ( @@ -48,14 +20,14 @@ export function NotificationAd({ onCreate }: Props) {
- } - onClick={(e) => { - e.preventDefault(); - const input = creativeInput( - advertiser.id, - meta.value, - userId, - ) as CreateNotificationCreativeInput; - create({ variables: { input } }); - }} - disabled={ - !!meta.error || - meta.value?.targetUrlValidationResult !== undefined || - loading - } - loading={loading} - > - Add - + ); diff --git a/src/user/hooks/useAdvertiserCreatives.ts b/src/user/hooks/useAdvertiserCreatives.ts index 3117fe85..451f7231 100644 --- a/src/user/hooks/useAdvertiserCreatives.ts +++ b/src/user/hooks/useAdvertiserCreatives.ts @@ -1,34 +1,20 @@ -import { useAdvertiserCreativesQuery } from "graphql/creative.generated"; -import { useAdvertiser } from "auth/hooks/queries/useAdvertiser"; import { useFormikContext } from "formik"; import { CampaignForm, Creative } from "user/views/adsManager/types"; import _ from "lodash"; -export function useAdvertiserCreatives(): Creative[] { - const { advertiser } = useAdvertiser(); - const { data } = useAdvertiserCreativesQuery({ - variables: { advertiserId: advertiser.id }, - }); - return (data?.advertiser?.creatives ?? []).map((c) => ({ - id: c.id, - name: c.name, - title: c.payloadNotification?.title ?? "New Ad", - body: c.payloadNotification?.body ?? "Body Preview", - targetUrl: c.payloadNotification?.targetUrl ?? "", - state: c.state, - })); -} - -export function useRecentlyCreatedAdvertiserCreatives() { +export function useAdvertiserCreatives() { const { values } = useFormikContext(); - const creatives = useAdvertiserCreatives(); - const inCampaign = creatives.filter((c) => { - if (c.id) { - return (values.creatives ?? []).includes(c.id); - } - - return false; - }); + const inAdSet: Creative[] = _.flatMap(values.adSets, "creatives").map( + (c: Creative) => ({ + type: c.type, + payloadNotification: c.payloadNotification, + id: c.id, + advertiserId: c.advertiserId, + name: c.name, + state: c.state, + included: false, + }), + ); - return _.uniqBy(inCampaign, "id"); + return { creatives: _.uniqBy(inAdSet, "id") }; } diff --git a/src/user/library/index.test.ts b/src/user/library/index.test.ts index aad02ce5..bd92261f 100644 --- a/src/user/library/index.test.ts +++ b/src/user/library/index.test.ts @@ -1,6 +1,11 @@ import { CampaignFragment } from "graphql/campaign.generated"; import { describe, expect, it } from "vitest"; -import { editCampaignValues, transformCreative } from "."; +import { + editCampaignValues, + transformCreative, + transformEditForm, + transformNewForm, +} from "."; import { CampaignFormat, CampaignPacingStrategies, @@ -9,7 +14,10 @@ import { PaymentType, } from "graphql/types"; import { produce } from "immer"; -import { Creative } from "user/views/adsManager/types"; +import { AdSetForm, CampaignForm, Creative } from "user/views/adsManager/types"; +import _ from "lodash"; +import { AdFragment, AdSetFragment } from "graphql/ad-set.generated"; +import { CreativeFragment } from "graphql/creative.generated"; const BASE_CPM_CAMPAIGN_FRAGMENT: Readonly = { id: "3495317a-bb47-4daf-8d3e-14cdc0e87457", @@ -160,10 +168,16 @@ describe("pricing logic (read)", () => { describe("pricing logic (write)", () => { const creative: Creative = { + payloadNotification: { + title: "some title", + body: "body", + targetUrl: "some url", + }, + advertiserId: "some id", + state: "draft", + type: { code: "notification_all_v1" }, name: "some name", - title: "some title", - body: "body", - targetUrl: "https://some.example.org", + included: true, }; it("should convert from CPM to per-impression values when populating a CPM creative", () => { @@ -196,3 +210,434 @@ describe("pricing logic (write)", () => { expect(inputObject.priceType).toEqual(ConfirmationType.Landed); }); }); + +describe("new form tests", () => { + const dateString = new Date().toLocaleString(); + + const creative: Creative = { + id: "11111", + advertiserId: "123456", + included: true, + name: "Test", + state: "draft", + type: { code: "test" }, + }; + + const creative2: Creative = { + id: "33333", + advertiserId: "123456", + included: false, + name: "Dont include", + state: "draft", + type: { code: "test" }, + }; + + const adSetForm: AdSetForm = { + conversions: [], + creatives: [creative, creative2], + isNotTargeting: false, + name: "", + oses: [{ name: "macos", code: "1234" }], + segments: [{ name: "test", code: "5678" }], + }; + + const form: CampaignForm = { + adSets: [adSetForm], + advertiserId: "12345", + billingType: "cpm", + budget: 1000, + currency: "USD", + dailyBudget: 10, + endAt: dateString, + format: CampaignFormat.PushNotification, + geoTargets: [{ code: "US", name: "United States" }], + isCreating: false, + name: "Test", + paymentType: PaymentType.Radom, + price: 6, + startAt: dateString, + state: "draft", + type: "paid", + validateStart: false, + }; + + it("should transform campaign form", () => { + const res = _.omit(transformNewForm(form, "me"), ["startAt", "endAt"]); + expect(res).toMatchInlineSnapshot(` + { + "adSets": [ + { + "ads": [ + { + "creativeId": "11111", + "price": "0.006", + "priceType": "VIEW", + }, + ], + "billingType": "cpm", + "conversions": [], + "name": "", + "oses": [ + { + "code": "1234", + "name": "macos", + }, + ], + "perDay": 1, + "segments": [ + { + "code": "5678", + "name": "test", + }, + ], + "totalMax": 10, + }, + ], + "advertiserId": "12345", + "budget": 1000, + "currency": "USD", + "dailyBudget": 10, + "dailyCap": 1, + "externalId": "", + "format": "PUSH_NOTIFICATION", + "geoTargets": [ + { + "code": "US", + "name": "United States", + }, + ], + "name": "Test", + "pacingStrategy": "MODEL_V1", + "paymentType": "RADOM", + "source": "self_serve", + "state": "draft", + "type": "paid", + "userId": "me", + } + `); + }); + + it("should transform a creative", () => { + creative.payloadNotification = { + title: "valid", + targetUrl: "valid", + body: "valid", + }; + + creative.payloadSearch = { + title: "invalid", + targetUrl: "invalid", + body: "invalid", + }; + + const res = transformCreative(creative, form); + expect(res).toMatchInlineSnapshot(` + { + "creativeId": "11111", + "price": "0.006", + "priceType": "VIEW", + } + `); + }); +}); + +describe("edit form tests", () => { + const creative: CreativeFragment = { + createdAt: undefined, + id: "1234", + modifiedAt: undefined, + name: "a creative", + state: "active", + payloadNotification: { + targetUrl: "valid", + title: "valid", + body: "valid", + }, + type: { code: "notification_v1_all" }, + }; + + const ad: AdFragment = { + id: "1", + creative: creative, + state: "active", + price: "6", + priceType: ConfirmationType.View, + }; + + const ad2: AdFragment = { + id: "2", + creative: creative, + state: "deleted", + price: "6", + priceType: ConfirmationType.View, + }; + + const ad3: AdFragment = { + id: "3", + creative: { + ...creative, + id: "1235", + name: "a different creative", + }, + state: "active", + price: "6", + priceType: ConfirmationType.View, + }; + + const adSet: AdSetFragment = { + ads: [ad, ad2], + billingType: "cpm", + conversions: [], + createdAt: undefined, + id: "11111", + perDay: 1, + oses: [{ name: "macos", code: "1234" }], + segments: [{ name: "test", code: "5678" }], + state: "active", + totalMax: 100, + }; + + const adSet2: AdSetFragment = { + ads: [ad, ad3], + billingType: "cpm", + conversions: [], + createdAt: undefined, + id: "22222", + perDay: 1, + oses: [{ name: "linux", code: "1234" }], + segments: [{ name: "help", code: "5678" }], + state: "active", + totalMax: 100, + }; + + const campaignFragment: CampaignFragment = { + adSets: [adSet, adSet2], + advertiser: { id: "12345" }, + budget: 100, + createdAt: undefined, + currency: "USD", + dailyBudget: 0, + dailyCap: 0, + endAt: undefined, + externalId: "", + format: CampaignFormat.PushNotification, + id: "000001", + name: "My first campaign", + pacingOverride: false, + pacingStrategy: CampaignPacingStrategies.ModelV1, + passThroughRate: 0, + paymentType: PaymentType.Radom, + priority: 1, + source: CampaignSource.SelfServe, + spent: 0, + startAt: undefined, + state: "active", + type: "paid", + }; + + const editForm = editCampaignValues( + campaignFragment, + campaignFragment.advertiser.id, + ); + it("should result in a valid campaign form", () => { + const omitted = _.omit(editForm, ["newCreative"]); + expect(omitted).toMatchInlineSnapshot(` + { + "adSets": [ + { + "conversions": [], + "creatives": [ + { + "advertiserId": "12345", + "id": "1234", + "included": true, + "name": "a creative", + "payloadNotification": { + "body": "valid", + "targetUrl": "valid", + "title": "valid", + }, + "state": "active", + "targetUrlValid": "", + "type": { + "code": "notification_v1_all", + }, + }, + { + "advertiserId": "12345", + "id": "1235", + "included": false, + "name": "a different creative", + "payloadNotification": { + "body": "valid", + "targetUrl": "valid", + "title": "valid", + }, + "state": "active", + "targetUrlValid": "", + "type": { + "code": "notification_v1_all", + }, + }, + ], + "id": "11111", + "isNotTargeting": false, + "name": "11111", + "oses": [ + { + "code": "1234", + "name": "macos", + }, + ], + "segments": [ + { + "code": "5678", + "name": "test", + }, + ], + }, + { + "conversions": [], + "creatives": [ + { + "advertiserId": "12345", + "id": "1234", + "included": true, + "name": "a creative", + "payloadNotification": { + "body": "valid", + "targetUrl": "valid", + "title": "valid", + }, + "state": "active", + "targetUrlValid": "", + "type": { + "code": "notification_v1_all", + }, + }, + { + "advertiserId": "12345", + "id": "1235", + "included": true, + "name": "a different creative", + "payloadNotification": { + "body": "valid", + "targetUrl": "valid", + "title": "valid", + }, + "state": "active", + "targetUrlValid": "", + "type": { + "code": "notification_v1_all", + }, + }, + ], + "id": "22222", + "isNotTargeting": false, + "name": "22222", + "oses": [ + { + "code": "1234", + "name": "linux", + }, + ], + "segments": [ + { + "code": "5678", + "name": "help", + }, + ], + }, + ], + "advertiserId": "12345", + "billingType": "cpm", + "budget": 100, + "currency": "USD", + "dailyBudget": 0, + "endAt": undefined, + "format": "PUSH_NOTIFICATION", + "geoTargets": [], + "id": "000001", + "isCreating": false, + "name": "My first campaign", + "paymentType": "RADOM", + "price": 6000, + "startAt": undefined, + "state": "active", + "type": "paid", + "validateStart": false, + } + `); + }); + + it("should resolve to update input", () => { + const update = transformEditForm(editForm, editForm.id ?? ""); + expect(update).toMatchInlineSnapshot(` + { + "adSets": [ + { + "ads": [ + { + "creativeId": "1234", + "creativeSetId": "11111", + "price": "6", + "priceType": "VIEW", + }, + ], + "id": "11111", + "oses": [ + { + "code": "1234", + "name": "macos", + }, + ], + "segments": [ + { + "code": "5678", + "name": "test", + }, + ], + }, + { + "ads": [ + { + "creativeId": "1234", + "creativeSetId": "22222", + "price": "6", + "priceType": "VIEW", + }, + { + "creativeId": "1235", + "creativeSetId": "22222", + "price": "6", + "priceType": "VIEW", + }, + ], + "id": "22222", + "oses": [ + { + "code": "1234", + "name": "linux", + }, + ], + "segments": [ + { + "code": "5678", + "name": "help", + }, + ], + }, + ], + "budget": 100, + "dailyBudget": 0, + "endAt": undefined, + "id": "000001", + "name": "My first campaign", + "paymentType": "RADOM", + "startAt": undefined, + "state": "active", + "type": "paid", + } + `); + }); +}); diff --git a/src/user/library/index.ts b/src/user/library/index.ts index 3a7d3781..85be5261 100644 --- a/src/user/library/index.ts +++ b/src/user/library/index.ts @@ -1,26 +1,25 @@ import { CampaignFormat, + CampaignPacingStrategies, ConfirmationType, CreateAdInput, CreateCampaignInput, - CreateNotificationCreativeInput, - GeocodeInput, UpdateCampaignInput, - UpdateNotificationCreativeInput, } from "graphql/types"; import { CampaignFragment } from "graphql/campaign.generated"; import { AdFragment } from "graphql/ad-set.generated"; import { + AdSetForm, Billing, CampaignForm, Conversion, Creative, initialCreative, - OS, Segment, } from "user/views/adsManager/types"; import _ from "lodash"; import BigNumber from "bignumber.js"; +import { CreativeFragment } from "graphql/creative.generated"; const TYPE_CODE_LOOKUP: Record = { notification_all_v1: "Push Notification", @@ -36,14 +35,14 @@ export function transformNewForm( ): CreateCampaignInput { return { currency: form.currency, - dailyCap: form.dailyCap, + externalId: "", + dailyCap: 1, dailyBudget: form.dailyBudget, endAt: form.endAt, - pacingStrategy: form.pacingStrategy, + pacingStrategy: CampaignPacingStrategies.ModelV1, geoTargets: form.geoTargets.map((g) => ({ code: g.code, name: g.name })), name: form.name, advertiserId: form.advertiserId, - externalId: "", format: form.format, userId: userId, source: "self_serve", @@ -54,13 +53,14 @@ export function transformNewForm( adSets: form.adSets.map((adSet) => ({ name: adSet.name, billingType: form.billingType, - execution: "per_click", perDay: 1, segments: adSet.segments.map((s) => ({ code: s.code, name: s.name })), oses: adSet.oses, totalMax: 10, conversions: transformConversion(adSet.conversions), - ads: adSet.creatives.map((ad) => transformCreative(ad, form)), + ads: adSet.creatives + .filter((c) => c.included) + .map((ad) => transformCreative(ad, form)), })), paymentType: form.paymentType, }; @@ -96,44 +96,14 @@ export function transformCreative( priceType = ConfirmationType.Click; } - return { - webhooks: [], - creativeId: creative.id!, + const createInput: CreateAdInput = { price: price.toString(), priceType: priceType, }; -} - -export function creativeInput( - advertiserId: string, - creative: Creative, - userId?: string, -): CreateNotificationCreativeInput | UpdateNotificationCreativeInput { - const baseNotification = { - advertiserId, - userId, - name: creative.name, - payload: { - title: creative.title, - body: creative.body, - targetUrl: creative.targetUrl, - }, - state: creative.state, - }; - if (creative.id) { - return { - ...baseNotification, - creativeId: creative.id, - }; - } + createInput.creativeId = creative.id; - return { - ...baseNotification, - type: { - code: "notification_all_v1", - }, - }; + return createInput; } export function editCampaignValues( @@ -153,33 +123,39 @@ export function editCampaignValues( const seg = adSet.segments ?? ([] as Segment[]); return { - ...adSet, id: adSet.id, - conversions: adSet.conversions ?? [], - oses: adSet.oses ?? ([] as OS[]), - segments: adSet.segments ?? ([] as Segment[]), + conversions: (adSet.conversions ?? []).map((c) => ({ + id: c.id, + type: c.type, + observationWindow: c.observationWindow, + urlPattern: c.urlPattern, + })), + oses: (adSet.oses ?? []).map((o) => ({ name: o.name, code: o.code })), + segments: (adSet.segments ?? []).map((o) => ({ + name: o.name, + code: o.code, + })), isNotTargeting: seg.length === 1 && seg[0].code === "Svp7l-zGN", name: adSet.name || adSet.id.split("-")[0], - creatives: creativeList(adSet.ads), - }; + creatives: creativeList(advertiserId, adSet.ads, ads), + } as AdSetForm; }), + isCreating: false, advertiserId, - hasPaymentIntent: campaign.hasPaymentIntent ?? false, - creatives: creativeList(ads).map((a) => a.id!), newCreative: initialCreative, - isCreating: false, + currency: campaign.currency, price: price.toNumber(), billingType: billingType, validateStart: false, budget: campaign.budget, - currency: campaign.currency, dailyBudget: campaign.dailyBudget, - dailyCap: campaign.dailyCap, endAt: campaign.endAt, format: campaign.format, - geoTargets: campaign.geoTargets ?? ([] as GeocodeInput[]), + geoTargets: (campaign.geoTargets ?? []).map((g) => ({ + code: g.code, + name: g.name, + })), name: campaign.name, - pacingStrategy: campaign.pacingStrategy, startAt: campaign.startAt, state: campaign.state, type: "paid", @@ -187,36 +163,58 @@ export function editCampaignValues( }; } -function creativeList(ads?: AdFragment[] | null): Creative[] { - return _.uniqBy( - (ads ?? []) - .filter((ad) => ad.creative != null && ad.state !== "deleted") +function creativeList( + advertiserId: string, + adSetAds?: AdFragment[] | null, + allAds?: AdFragment[] | null, +): Creative[] { + const filterAds = (a?: AdFragment[] | null, included?: boolean) => { + return (a ?? []) + .filter((ad) => ad.creative !== null && ad.state !== "deleted") .map((ad) => { const c = ad.creative; return { - creativeInstanceId: ad.id, - id: c.id, - name: c.name, - targetUrl: c.payloadNotification!.targetUrl, - title: c.payloadNotification!.title, - body: c.payloadNotification!.body, - targetUrlValidationResult: "", - state: c.state, + ...validCreativeFields(c, advertiserId, included), }; - }), + }); + }; + + return _.uniqBy( + [...filterAds(adSetAds, true), ...filterAds(allAds, false)], "id", ); } +export function validCreativeFields( + c: CreativeFragment | Creative, + advertiserId: string, + included?: boolean, +): Creative { + return { + advertiserId, + id: c.id, + included: included ?? false, + name: c.name, + targetUrlValid: "", + state: c.state, + type: { code: c.type.code }, + payloadNotification: c.payloadNotification + ? { + title: c.payloadNotification.title, + body: c.payloadNotification.body, + targetUrl: c.payloadNotification.targetUrl, + } + : undefined, + }; +} + export function transformEditForm( form: CampaignForm, id: string, ): UpdateCampaignInput { return { budget: form.budget, - currency: form.currency, dailyBudget: form.dailyBudget, - dailyCap: form.dailyCap, endAt: form.endAt, id, name: form.name, @@ -228,11 +226,12 @@ export function transformEditForm( id: adSet.id, segments: adSet.segments.map((v) => ({ code: v.code, name: v.name })), oses: adSet.oses.map((v) => ({ code: v.code, name: v.name })), - ads: adSet.creatives.map((ad) => ({ - ...transformCreative(ad, form), - id: ad.creativeInstanceId, - creativeSetId: adSet.id, - })), + ads: adSet.creatives + .filter((c) => c.included) + .map((ad) => ({ + ...transformCreative(ad, form), + creativeSetId: adSet.id, + })), })), }; } @@ -258,3 +257,26 @@ export function uiTextForCreativeTypeCode(creativeTypeCode: { }): string { return uiTextForCreativeType(creativeTypeCode.code); } + +export function isCreativeTypeApplicableToCampaignFormat( + creativeTypeCode: { + code: string; + }, + format: CampaignFormat, +): boolean { + const { code } = creativeTypeCode; + switch (code) { + case "notification_all_v1": + return format === CampaignFormat.PushNotification; + case "new_tab_page_all_v1": + return format === CampaignFormat.NtpSi; + case "inline_content_all_v1": + return format === CampaignFormat.NewsDisplayAd; + case "search_all_v1": + return format === CampaignFormat.Search; + case "search_homepage_all_v1": + return format === CampaignFormat.SearchHomepage; + default: + return false; + } +} diff --git a/src/user/views/adsManager/types/index.ts b/src/user/views/adsManager/types/index.ts index ff5a0ebc..fef57b7d 100644 --- a/src/user/views/adsManager/types/index.ts +++ b/src/user/views/adsManager/types/index.ts @@ -1,8 +1,4 @@ -import { - CampaignFormat, - CampaignPacingStrategies, - PaymentType, -} from "graphql/types"; +import { CampaignFormat, CreativeInput, PaymentType } from "graphql/types"; import { defaultEndDate, defaultStartDate } from "form/DateFieldHelpers"; import { MIN_PER_CAMPAIGN } from "validation/CampaignSchema"; import { IAdvertiser } from "auth/context/auth.interface"; @@ -20,20 +16,16 @@ export type CampaignForm = { isCreating: boolean; currency: string; dailyBudget: number; - dailyCap: number; geoTargets: GeoTarget[]; adSets: AdSetForm[]; format: CampaignFormat; newCreative?: Creative; - creatives?: string[]; name: string; state: string; type: "paid"; // this is per click for CPC campaigns, but per thousand views for CPM campaigns price: number; billingType: Billing; - pacingStrategy: CampaignPacingStrategies; - hasPaymentIntent: boolean; paymentType: PaymentType; }; @@ -68,15 +60,13 @@ export type Segment = { name: string; }; -export type Creative = { +export type Creative = CreativeInput & { id?: string; - name: string; - title: string; - body: string; - targetUrl: string; - targetUrlValidationResult?: string; + targetUrlValid?: string; state?: string; - creativeInstanceId?: string; + createdAt?: string; + modifiedAt?: string; + included: boolean; }; export const initialConversion: Conversion = { @@ -87,10 +77,15 @@ export const initialConversion: Conversion = { export const initialCreative: Creative = { name: "", - title: "", - body: "", - targetUrl: "", + advertiserId: "", + payloadNotification: { + title: "", + targetUrl: "", + body: "", + }, + type: { code: "" }, state: "draft", + included: false, }; export const initialAdSet: AdSetForm = { @@ -104,18 +99,17 @@ export const initialAdSet: AdSetForm = { export const initialCampaign = (advertiser: IAdvertiser): CampaignForm => { return { + isCreating: false, advertiserId: advertiser.id, startAt: defaultStartDate(), endAt: defaultEndDate(), validateStart: true, - isCreating: false, budget: MIN_PER_CAMPAIGN, - hasPaymentIntent: false, - currency: "USD", dailyBudget: MIN_PER_CAMPAIGN, - dailyCap: 1, geoTargets: [], + newCreative: initialCreative, billingType: "cpm", + currency: "USD", price: 6, adSets: [ { @@ -126,9 +120,6 @@ export const initialCampaign = (advertiser: IAdvertiser): CampaignForm => { name: "", state: "draft", type: "paid", - pacingStrategy: CampaignPacingStrategies.ModelV1, paymentType: advertiser.selfServicePaymentType, - newCreative: initialCreative, - creatives: [], }; }; diff --git a/src/user/views/adsManager/views/advanced/components/adSet/NewAdSet.tsx b/src/user/views/adsManager/views/advanced/components/adSet/NewAdSet.tsx index 0847965e..f36d63ec 100644 --- a/src/user/views/adsManager/views/advanced/components/adSet/NewAdSet.tsx +++ b/src/user/views/adsManager/views/advanced/components/adSet/NewAdSet.tsx @@ -13,8 +13,10 @@ import { CampaignForm, initialAdSet } from "user/views/adsManager/types"; import { useRef } from "react"; import RemoveCircleOutlineIcon from "@mui/icons-material/RemoveCircleOutline"; import { useIsEdit } from "form/FormikHelpers"; +import { useAdvertiserCreatives } from "user/hooks/useAdvertiserCreatives"; export function NewAdSet() { + const { creatives } = useAdvertiserCreatives(); const { isEdit } = useIsEdit(); const history = useHistory(); const { values } = useFormikContext(); @@ -22,6 +24,11 @@ export function NewAdSet() { const selected = useRef(0); selected.current = Number(params.get("current") ?? 0); + const initial = { + ...initialAdSet, + creatives, + }; + return ( <> @@ -77,7 +84,7 @@ export function NewAdSet() { pb={0} pt={0} component={Button} - onClick={() => helper.push(initialAdSet)} + onClick={() => helper.push(initial)} border="1px solid #ededed" > diff --git a/src/user/views/adsManager/views/advanced/components/adSet/fields/AdSetAds.tsx b/src/user/views/adsManager/views/advanced/components/adSet/fields/AdSetAds.tsx index 1289451a..123e825f 100644 --- a/src/user/views/adsManager/views/advanced/components/adSet/fields/AdSetAds.tsx +++ b/src/user/views/adsManager/views/advanced/components/adSet/fields/AdSetAds.tsx @@ -1,54 +1,29 @@ import { CardContainer } from "components/Card/CardContainer"; -import { Autocomplete, Checkbox, TextField } from "@mui/material"; -import { useRecentlyCreatedAdvertiserCreatives } from "user/hooks/useAdvertiserCreatives"; -import CheckBoxOutlineBlankIcon from "@mui/icons-material/CheckBoxOutlineBlank"; -import CheckBoxIcon from "@mui/icons-material/CheckBox"; -import { useField } from "formik"; -import { Creative } from "user/views/adsManager/types"; -import _ from "lodash"; +import { Typography } from "@mui/material"; +import { CampaignForm } from "user/views/adsManager/types"; +import { useFormikContext } from "formik"; +import { CampaignFormat } from "graphql/types"; +import { NotificationSelect } from "components/Creatives/NotificationSelect"; interface Props { index: number; } export function AdSetAds({ index }: Props) { - const creatives = useRecentlyCreatedAdvertiserCreatives(); - const [, meta, helper] = useField(`adSets.${index}.creatives`); + const { values } = useFormikContext(); return ( - option.name} - renderOption={(props, option, { selected }) => ( -
  • - } - checkedIcon={} - style={{ marginRight: 8 }} - checked={selected} - /> - {option.name} -
  • - )} - renderInput={(params) => ( - - )} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={meta.value} - onChange={(_ev, value) => { - helper.setValue(_.sortBy(value, "name")); - }} - onBlur={() => helper.setTouched(true)} - /> + + Select the Ads you would like to include in this ad set. + + + {values.format === CampaignFormat.PushNotification && ( + + )}
    ); } diff --git a/src/user/views/adsManager/views/advanced/components/form/EditCampaign.tsx b/src/user/views/adsManager/views/advanced/components/form/EditCampaign.tsx index a5bdadd0..a9e259ae 100644 --- a/src/user/views/adsManager/views/advanced/components/form/EditCampaign.tsx +++ b/src/user/views/adsManager/views/advanced/components/form/EditCampaign.tsx @@ -36,14 +36,15 @@ export function EditCampaign() { fetchPolicy: "cache-and-network", }); + const hasPaymentIntent = initialData?.campaign?.hasPaymentIntent; const [mutation] = useUpdateCampaignMutation({ onCompleted(data) { - if (initialData?.campaign?.hasPaymentIntent) { + if (hasPaymentIntent) { history.push( `/user/main/complete/edit?referenceId=${data.updateCampaign.id}`, ); } else { - createPaymentSession(data.updateCampaign.id); + void createPaymentSession(data.updateCampaign.id); } }, onError() { @@ -85,7 +86,7 @@ export function EditCampaign() { }} validationSchema={CampaignSchema} > - + ); 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 a2240756..bf964cb2 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 @@ -8,9 +8,16 @@ import { AdSetFields } from "user/views/adsManager/views/advanced/components/adS 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"; -export function BaseForm() { +interface Props { + hasPaymentIntent?: boolean | null; +} + +export function BaseForm({ hasPaymentIntent }: Props) { const { url } = useRouteMatch(); + const [isShowingAds, setIsShowingAds] = useState(false); const steps = [ { @@ -43,16 +50,28 @@ export function BaseForm() { ]; return ( -
    - }> - - {steps.map((s) => ( - - {s.component} - - ))} - - -
    + +
    + + } + > + + {steps.map((s) => ( + + {s.component} + + ))} + + +
    +
    ); } diff --git a/src/user/views/adsManager/views/advanced/components/form/components/PaymentButton.tsx b/src/user/views/adsManager/views/advanced/components/form/components/PaymentButton.tsx index 665b3d16..aa9c8fb1 100644 --- a/src/user/views/adsManager/views/advanced/components/form/components/PaymentButton.tsx +++ b/src/user/views/adsManager/views/advanced/components/form/components/PaymentButton.tsx @@ -1,17 +1,14 @@ import { FormikSubmitButton, useIsEdit } from "form/FormikHelpers"; -import { useFormikContext } from "formik"; -import { CampaignForm } from "user/views/adsManager/types"; -export function PaymentButton() { +export function PaymentButton(props: { hasPaymentIntent: boolean }) { const { isEdit } = useIsEdit(); - const { values } = useFormikContext(); const paymentText = "Make payment & submit for approval"; return ( (); @@ -20,13 +19,12 @@ export function Review() { - - {values.adSets.map((adSet, adSetIdx) => ( ))} diff --git a/src/user/views/adsManager/views/advanced/components/review/components/AdReview.tsx b/src/user/views/adsManager/views/advanced/components/review/components/AdReview.tsx deleted file mode 100644 index ce40d382..00000000 --- a/src/user/views/adsManager/views/advanced/components/review/components/AdReview.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { Stack, Typography } from "@mui/material"; -import { useRecentlyCreatedAdvertiserCreatives } from "user/hooks/useAdvertiserCreatives"; -import { BoxContainer } from "components/Box/BoxContainer"; -import { NotificationPreview } from "user/ads/NotificationPreview"; -import { ReviewContainer } from "user/views/adsManager/views/advanced/components/review/components/ReviewContainer"; - -export function AdReview() { - const creatives = useRecentlyCreatedAdvertiserCreatives(); - - return ( - - {creatives.length === 0 && ( - No Recently Created Ads - )} - - {creatives.map((c) => ( - - - - ))} - - - ); -} diff --git a/src/user/views/adsManager/views/advanced/components/review/components/AdSetReview.tsx b/src/user/views/adsManager/views/advanced/components/review/components/AdSetReview.tsx index c988b55c..1917ae07 100644 --- a/src/user/views/adsManager/views/advanced/components/review/components/AdSetReview.tsx +++ b/src/user/views/adsManager/views/advanced/components/review/components/AdSetReview.tsx @@ -3,19 +3,23 @@ import { FormikErrors } from "formik"; import { ConversionDisplay } from "components/Conversion/ConversionDisplay"; import { ReviewField } from "./ReviewField"; import { ReviewContainer } from "user/views/adsManager/views/advanced/components/review/components/ReviewContainer"; +import { CampaignFormat } from "graphql/types"; +import { CreativeSpecificPreview } from "components/Creatives/CreativeSpecificPreview"; interface Props { idx: number; adSet: AdSetForm; + format: CampaignFormat; errors?: string | FormikErrors; } export function AdSetReview({ adSet, idx, errors }: Props) { + const included = adSet.creatives.filter((c) => c.included); + const hasErrors = !!errors; if (typeof errors === "string") { return <>{errors}; } - const hasErrors = !!errors; const adSetError = errors; const mapToString = (arr: Segment[] | OS[] | Creative[]) => { @@ -47,9 +51,9 @@ export function AdSetReview({ adSet, idx, errors }: Props) { conversions={adSet.conversions} convErrors={adSetError?.conversions} /> - diff --git a/src/validation/CampaignSchema.tsx b/src/validation/CampaignSchema.tsx index 33f2fbb4..a1b9efa8 100644 --- a/src/validation/CampaignSchema.tsx +++ b/src/validation/CampaignSchema.tsx @@ -1,12 +1,8 @@ import { array, boolean, date, number, object, ref, string } from "yup"; import { startOfDay } from "date-fns"; import { twoDaysOut } from "form/DateFieldHelpers"; -import _ from "lodash"; - -export const SimpleUrlRegexp = /https:\/\/.+\.[a-zA-Z]{2,}\/?.*/g; -const NoSpacesRegex = /^\S*$/; -const TrailingAsteriskRegex = /.*\*$/; -const HttpsRegex = /^https:\/\//; +import { TrailingAsteriskRegex } from "validation/regex"; +import { CreativeSchema } from "validation/CreativeSchema"; export const MIN_PER_DAY = 33; export const MIN_PER_CAMPAIGN = 100; @@ -20,34 +16,9 @@ export const CampaignSchema = object().shape({ MIN_PER_CAMPAIGN, `Lifetime budget must be $${MIN_PER_CAMPAIGN} or more`, ), - isCreating: boolean().default(false), newCreative: object().when("isCreating", { is: true, - then: (schema) => - schema.shape({ - name: string().label("Creative Name").required("Ad Name is required"), - title: string() - .label("Title") - .max(30, "Maximum 30 Characters") - .required("Ad Title is required"), - body: string() - .label("Body") - .max(60, "Maximum 60 Characters") - .required("Ad Body is required"), - targetUrlValidationResult: string().test({ - test: (value) => _.isEmpty(value), - message: ({ value }) => value, - }), - targetUrl: string() - .label("Target Url") - .required("Ad URL is required") - .matches(NoSpacesRegex, `Ad URL must not contain any whitespace`) - .matches(HttpsRegex, `URL must start with https://`) - .matches( - SimpleUrlRegexp, - `Please enter a valid Ad URL, for example https://brave.com`, - ), - }), + then: () => CreativeSchema, }), validateStart: boolean(), dailyBudget: number() @@ -148,7 +119,11 @@ export const CampaignSchema = object().shape({ .required("Conversion Type required."), }), ), - creatives: array().min(1, "Ad Sets must have at least one Ad"), + creatives: array().test( + "min-length", + "Ad Sets must have at least one Ad", + (value) => (value ?? []).filter((c) => c.included).length > 0, + ), }), ), }); diff --git a/src/validation/CreativeSchema.test.ts b/src/validation/CreativeSchema.test.ts new file mode 100644 index 00000000..a5242735 --- /dev/null +++ b/src/validation/CreativeSchema.test.ts @@ -0,0 +1,65 @@ +import { CreativeSchema } from "./CreativeSchema"; +import { produce } from "immer"; + +const validPushCreative = { + name: "some creative", + type: { code: "notification_all_v1", name: "" }, + state: "under_review", + payloadNotification: { + body: "abc", + title: "xyz", + targetUrl: "https://hello.com", + }, +}; + +it("should pass on a valid object", () => { + CreativeSchema.validateSync(validPushCreative); +}); + +it.each([ + "https://example.com", + "https://www.secure2.sophos.com/en-us/security-news-trends/whitepapers/gated-wp/endpoint-buyers-guide.aspx?cmp=134766&utm_source=Brave&utm_campaign=ASEAN%7CBrave%7CEndpointBuyer%27sGuide%7CITFocus&utm_medium=cpc&utm_content=SM116529", + "https://test.io?bar=baz#foo", +])("should pass if push notification is selected for %s", (value) => { + const c = produce(validPushCreative, (draft) => { + draft.payloadNotification.targetUrl = value; + }); + + expect(() => CreativeSchema.validateSync(c)); +}); + +it.each(["notAUrl", "gopher://blah.com", "httpx://balh.com"])( + "should reject as invalid url if push notification is selected for %s", + (value) => { + const c = produce(validPushCreative, (draft) => { + draft.payloadNotification.targetUrl = value; + }); + expect(() => CreativeSchema.validateSync(c)).toThrowError( + "URL must start with https://", + ); + }, +); + +it.each(["https://with a space"])( + "should reject as invalid input if push notification is selected for %s", + (value) => { + const c = produce(validPushCreative, (draft) => { + draft.payloadNotification.targetUrl = value; + }); + expect(() => CreativeSchema.validateSync(c)).toThrowError( + "URL must not contain any whitespace", + ); + }, +); + +it.each(["http://example.com"])( + "should reject as not secure if push notification is selected for %s", + (value) => { + const c = produce(validPushCreative, (draft) => { + draft.payloadNotification.targetUrl = value; + }); + expect(() => CreativeSchema.validateSync(c)).toThrowError( + "URL must start with https://", + ); + }, +); diff --git a/src/validation/CreativeSchema.tsx b/src/validation/CreativeSchema.tsx new file mode 100644 index 00000000..1abbbc11 --- /dev/null +++ b/src/validation/CreativeSchema.tsx @@ -0,0 +1,48 @@ +import { object, string } from "yup"; +import { HttpsRegex, NoSpacesRegex, SimpleUrlRegexp } from "validation/regex"; +import _ from "lodash"; + +export const CreativeSchema = object().shape({ + name: string().label("Creative Name").required(), + type: object().shape({ + code: string() + .oneOf([ + "notification_all_v1", + "new_tab_page_all_v1", + "inline_content_all_v1", + "search_all_v1", + "search_homepage_all_v1", + ]) + .label("Creative Type") + .required("Creative Type is required"), + name: string(), + }), + state: string() + .oneOf(["draft", "under_review"]) + .label("State") + .required() + .default("draft"), + targetUrlValid: string().test({ + test: (value) => _.isEmpty(value), + message: ({ value }) => value, + }), + payloadNotification: object() + .nullable() + .when("type.code", { + is: "notification_all_v1", + then: (schema) => + schema.required().shape({ + body: string().label("Body").required().max(60), + targetUrl: string() + .label("Target Url") + .required("URL is required") + .matches(NoSpacesRegex, `URL must not contain any whitespace`) + .matches(HttpsRegex, `URL must start with https://`) + .matches( + SimpleUrlRegexp, + `Please enter a valid Ad URL, for example https://brave.com`, + ), + title: string().label("Title").required().max(30), + }), + }), +}); diff --git a/src/validation/regex.ts b/src/validation/regex.ts new file mode 100644 index 00000000..4d171332 --- /dev/null +++ b/src/validation/regex.ts @@ -0,0 +1,4 @@ +export const SimpleUrlRegexp = /https:\/\/.+\.[a-zA-Z]{2,}\/?.*/g; +export const NoSpacesRegex = /^\S*$/; +export const TrailingAsteriskRegex = /.*\*$/; +export const HttpsRegex = /^https:\/\//; From 1b9955e4e6afdb51b587086c6edc9db26a685dac Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 1 Sep 2023 09:59:24 -0400 Subject: [PATCH 2/4] fix(deps): update all non-major dependencies (#844) * fix(deps): update all non-major dependencies * fix: switch adapters * fix: re-rerun install --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Ian Krieger --- package-lock.json | 825 +++++++++++++------- package.json | 50 +- src/auth/views/components/AuthContainer.tsx | 7 +- src/components/Date/DateRangePicker.tsx | 4 +- 4 files changed, 561 insertions(+), 325 deletions(-) diff --git a/package-lock.json b/package-lock.json index aa3f4d8c..82fe9fc4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,23 +8,23 @@ "name": "ads-ui", "version": "0.1.0", "dependencies": { - "@apollo/client": "3.7.17", - "@date-io/moment": "2.16.1", + "@apollo/client": "3.8.1", + "@date-io/moment": "2.17.0", "@emotion/react": "11.11.1", "@emotion/styled": "11.11.0", - "@fontsource/mulish": "5.0.5", - "@fontsource/poppins": "5.0.5", - "@mui/icons-material": "5.14.0", - "@mui/lab": "5.0.0-alpha.136", - "@mui/material": "5.14.0", + "@fontsource/mulish": "5.0.8", + "@fontsource/poppins": "5.0.8", + "@mui/icons-material": "5.14.6", + "@mui/lab": "5.0.0-alpha.141", + "@mui/material": "5.14.6", "@mui/x-date-pickers": "5.0.20", - "axios": "1.4.0", + "axios": "1.5.0", "base64url": "3.0.1", - "bignumber.js": "9.1.1", + "bignumber.js": "9.1.2", "date-fns": "2.30.0", "date-fns-tz": "2.0.0", - "formik": "2.4.2", - "graphql": "16.7.1", + "formik": "2.4.3", + "graphql": "16.8.0", "highcharts": "11.1.0", "highcharts-react-official": "3.2.0", "immer": "10.0.2", @@ -49,26 +49,26 @@ "@graphql-codegen/visitor-plugin-common": "2.13.8", "@types/chart.js": "2.9.37", "@types/classnames": "2.3.0", - "@types/jest": "29.5.3", + "@types/jest": "29.5.4", "@types/jwt-decode": "2.2.1", - "@types/lodash": "4.14.195", - "@types/react": "18.2.15", + "@types/lodash": "4.14.197", + "@types/react": "18.2.21", "@types/react-dom": "18.2.7", "@types/react-router-dom": "5.3.3", - "@typescript-eslint/eslint-plugin": "6.3.0", - "@typescript-eslint/parser": "6.3.0", + "@typescript-eslint/eslint-plugin": "6.4.1", + "@typescript-eslint/parser": "6.4.1", "@vitejs/plugin-basic-ssl": "1.0.1", - "@vitejs/plugin-react": "4.0.3", - "eslint": "8.46.0", + "@vitejs/plugin-react": "4.0.4", + "eslint": "8.48.0", "eslint-config-prettier": "9.0.0", - "eslint-plugin-react": "7.33.1", + "eslint-plugin-react": "7.33.2", "husky": "8.0.3", - "prettier": "3.0.0", - "typescript": "5.1.6", - "vite": "4.4.4", - "vite-plugin-checker": "0.6.1", + "prettier": "3.0.2", + "typescript": "5.2.2", + "vite": "4.4.9", + "vite-plugin-checker": "0.6.2", "vite-tsconfig-paths": "4.2.0", - "vitest": "0.33.0" + "vitest": "0.34.3" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -107,17 +107,17 @@ } }, "node_modules/@apollo/client": { - "version": "3.7.17", - "resolved": "https://registry.npmjs.org/@apollo/client/-/client-3.7.17.tgz", - "integrity": "sha512-0EErSHEtKPNl5wgWikHJbKFAzJ/k11O0WO2QyqZSHpdxdAnw7UWHY4YiLbHCFG7lhrD+NTQ3Z/H9Jn4rcikoJA==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@apollo/client/-/client-3.8.1.tgz", + "integrity": "sha512-JGGj/9bdoLEqzatRikDeN8etseY5qeFAY0vSAx/Pd0ePNsaflKzHx6V2NZ0NsGkInq+9IXXX3RLVDf0EotizMA==", "dependencies": { "@graphql-typed-document-node/core": "^3.1.1", - "@wry/context": "^0.7.0", - "@wry/equality": "^0.5.0", - "@wry/trie": "^0.4.0", + "@wry/context": "^0.7.3", + "@wry/equality": "^0.5.6", + "@wry/trie": "^0.4.3", "graphql-tag": "^2.12.6", "hoist-non-react-statics": "^3.3.2", - "optimism": "^0.16.2", + "optimism": "^0.17.5", "prop-types": "^15.7.2", "response-iterator": "^0.2.6", "symbol-observable": "^4.0.0", @@ -147,17 +147,6 @@ } } }, - "node_modules/@apollo/client/node_modules/@wry/trie": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@wry/trie/-/trie-0.4.3.tgz", - "integrity": "sha512-I6bHwH0fSf6RqQcnnXLJKhkSXG45MFral3GxPaY4uAl0LYDZM+YDVDAiU9bYwjTuysy1S0IeecWtmq1SZA3M1w==", - "dependencies": { - "tslib": "^2.3.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@ardatan/relay-compiler": { "version": "12.0.0", "resolved": "https://registry.npmjs.org/@ardatan/relay-compiler/-/relay-compiler-12.0.0.tgz", @@ -1180,11 +1169,11 @@ } }, "node_modules/@babel/runtime": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.6.tgz", - "integrity": "sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ==", + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.11.tgz", + "integrity": "sha512-ee7jVNlWN09+KftVOu9n7S8gQzD/Z6hN/I8VBRXW4P1+Xe7kJGXMwu8vds4aGIMHZnNbdpSWCfZZtinytpcAvA==", "dependencies": { - "regenerator-runtime": "^0.13.11" + "regenerator-runtime": "^0.14.0" }, "engines": { "node": ">=6.9.0" @@ -1261,9 +1250,9 @@ } }, "node_modules/@date-io/core": { - "version": "2.16.0", - "resolved": "https://registry.npmjs.org/@date-io/core/-/core-2.16.0.tgz", - "integrity": "sha512-DYmSzkr+jToahwWrsiRA2/pzMEtz9Bq1euJwoOuYwuwIYXnZFtHajY2E6a1VNVDc9jP8YUXK1BvnZH9mmT19Zg==" + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/@date-io/core/-/core-2.17.0.tgz", + "integrity": "sha512-+EQE8xZhRM/hsY0CDTVyayMDDY5ihc4MqXCrPxooKw19yAzUIC6uUqsZeaOFNL9YKTNxYKrJP5DFgE8o5xRCOw==" }, "node_modules/@date-io/date-fns": { "version": "2.16.0", @@ -1314,11 +1303,11 @@ } }, "node_modules/@date-io/moment": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/@date-io/moment/-/moment-2.16.1.tgz", - "integrity": "sha512-JkxldQxUqZBfZtsaCcCMkm/dmytdyq5pS1RxshCQ4fHhsvP5A7gSqPD22QbVXMcJydi3d3v1Y8BQdUKEuGACZQ==", + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/@date-io/moment/-/moment-2.17.0.tgz", + "integrity": "sha512-e4nb4CDZU4k0WRVhz1Wvl7d+hFsedObSauDHKtZwU9kt7gdYEAzKgnrSCTHsEaXrDumdrkCYTeZ0Tmyk7uV4tw==", "dependencies": { - "@date-io/core": "^2.16.0" + "@date-io/core": "^2.17.0" }, "peerDependencies": { "moment": "^2.24.0" @@ -1858,9 +1847,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.1.tgz", - "integrity": "sha512-9t7ZA7NGGK8ckelF0PQCfcxIUzs1Md5rrO6U/c+FIQNanea5UZC0wqKXH4vHBccmu4ZJgZ2idtPeW7+Q2npOEA==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", + "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", "dev": true, "dependencies": { "ajv": "^6.12.4", @@ -1908,23 +1897,57 @@ } }, "node_modules/@eslint/js": { - "version": "8.46.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.46.0.tgz", - "integrity": "sha512-a8TLtmPi8xzPkCbp/OGFUo5yhRkHM2Ko9kOWP4znJr0WAhWyThaw3PnwX4vOTWOAMsV2uRt32PPDcEz63esSaA==", + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.48.0.tgz", + "integrity": "sha512-ZSjtmelB7IJfWD2Fvb7+Z+ChTIKWq6kjda95fLcQKNS5aheVHn4IkfgRQE3sIIzTcSLwLcLZUD9UBt+V7+h+Pw==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@floating-ui/core": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.4.1.tgz", + "integrity": "sha512-jk3WqquEJRlcyu7997NtR5PibI+y5bi+LS3hPmguVClypenMsCY3CBa3LAQnozRCtCrYWSEtAdiskpamuJRFOQ==", + "dependencies": { + "@floating-ui/utils": "^0.1.1" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.5.1.tgz", + "integrity": "sha512-KwvVcPSXg6mQygvA1TjbN/gh///36kKtllIF8SUm0qpFj8+rvYrpvlYdL1JoA71SHpDqgSSdGOSoQ0Mp3uY5aw==", + "dependencies": { + "@floating-ui/core": "^1.4.1", + "@floating-ui/utils": "^0.1.1" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.2.tgz", + "integrity": "sha512-5qhlDvjaLmAst/rKb3VdlCinwTF4EYMiVxuuc/HVUjs46W0zgtbMmAZ1UTsDrRTxRmUEzl92mOtWbeeXL26lSQ==", + "dependencies": { + "@floating-ui/dom": "^1.5.1" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.1.tgz", + "integrity": "sha512-m0G6wlnhm/AX0H12IOWtK8gASEMffnX08RtKkCgTdHb9JpHKGloI7icFfLg9ZmQeavcvR0PKmzxClyuFPSjKWw==" + }, "node_modules/@fontsource/mulish": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/@fontsource/mulish/-/mulish-5.0.5.tgz", - "integrity": "sha512-C2eHZ11poELitOhh/73s+NI7PyAAbLMm0FVgnnAesI51ntpDX6bnK4nU+2j/xrajGmSjeKtqFKWjbAVsKkfPzA==" + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@fontsource/mulish/-/mulish-5.0.8.tgz", + "integrity": "sha512-3XWTEYs9FYAL5mgF60NCo9b8+D1OgURPMcmQYkEZt7BEGlRveTwSIRvTcqRjD0UEM9Vzv1MkeolsBytvveL4GA==" }, "node_modules/@fontsource/poppins": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/@fontsource/poppins/-/poppins-5.0.5.tgz", - "integrity": "sha512-Y/AGmD6iavfobRb7f2l49q0dQSyisvRrUfJ7F0AWOlTNFty8bbnPBbg+5Qsq5EN6xWaIAn++50aLfOCEe7zu3w==" + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@fontsource/poppins/-/poppins-5.0.8.tgz", + "integrity": "sha512-P8owfYWluoUY5Nyzk4gT/L6LmLmseP6ezFWhj6VBUa5pRIdnCvNJpoQ6i/vhekjtJOfqX6nKlB+LCttoUl2GQQ==" }, "node_modules/@graphql-codegen/add": { "version": "3.2.3", @@ -2841,19 +2864,19 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", "dev": true }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.18", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", - "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", + "version": "0.3.19", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz", + "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==", "dev": true, "dependencies": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, "node_modules/@korzio/djv-draft-04": { @@ -2863,16 +2886,17 @@ "optional": true }, "node_modules/@mui/base": { - "version": "5.0.0-beta.7", - "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.7.tgz", - "integrity": "sha512-Pjbwm6gjiS96kOMF7E5fjEJsenc0tZBesrLQ4rrdi3eT/c/yhSWnPbCUkHSz8bnS0l3/VQ8bA+oERSGSV2PK6A==", + "version": "5.0.0-beta.12", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.12.tgz", + "integrity": "sha512-tZjjXNAyUpwSDT1uRliZMhRQkWYzELJ8Qi61EuOMRpi36HIwnK2T7Nr4RI423Sv8G2EEikDAZj7je33eNd73NQ==", "dependencies": { - "@babel/runtime": "^7.22.5", + "@babel/runtime": "^7.22.10", "@emotion/is-prop-valid": "^1.2.1", + "@floating-ui/react-dom": "^2.0.1", "@mui/types": "^7.2.4", - "@mui/utils": "^5.13.7", + "@mui/utils": "^5.14.6", "@popperjs/core": "^2.11.8", - "clsx": "^1.2.1", + "clsx": "^2.0.0", "prop-types": "^15.8.1", "react-is": "^18.2.0" }, @@ -2894,21 +2918,29 @@ } } }, + "node_modules/@mui/base/node_modules/clsx": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", + "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==", + "engines": { + "node": ">=6" + } + }, "node_modules/@mui/core-downloads-tracker": { - "version": "5.14.1", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.14.1.tgz", - "integrity": "sha512-mIa1WmDmNr1LoupV1Rbxt9bTFKMbIn10RHG1bnZ/FJCkAYpuU/D4n+R+ttiycgcZNngU++zyh/OQeJblzbQPzg==", + "version": "5.14.7", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.14.7.tgz", + "integrity": "sha512-sCWTUNElBPgB30iLvWe3PU7SIlTKZNf6/E/sko85iHVeHCM6WPkDw+y89CrZYjhFNmPqt2fIQM/pZu+rP2lFLA==", "funding": { "type": "opencollective", "url": "https://opencollective.com/mui" } }, "node_modules/@mui/icons-material": { - "version": "5.14.0", - "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.14.0.tgz", - "integrity": "sha512-z7lYNteDi1GMkF9JP/m2RWuCYK1M/FlaeBSUK7/IhIYzIXNhAVjfD8jRq5vFBV31qkEi2aGBS2z5SfLXwH6U0A==", + "version": "5.14.6", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.14.6.tgz", + "integrity": "sha512-7Cujy7lRGTj2T3SvY9C9ZOTFDtrXJogeNnRcU/ODyNoxwskMNPFOcc15F+98MAdJenBVLJPYu+vPP6DUvEpNrA==", "dependencies": { - "@babel/runtime": "^7.22.5" + "@babel/runtime": "^7.22.10" }, "engines": { "node": ">=12.0.0" @@ -2929,16 +2961,16 @@ } }, "node_modules/@mui/lab": { - "version": "5.0.0-alpha.136", - "resolved": "https://registry.npmjs.org/@mui/lab/-/lab-5.0.0-alpha.136.tgz", - "integrity": "sha512-ai6LspCkyKRD8rL2HEtaTPBbeYkARNNKvSbjOLVJtro9bIFpZ0noH21Pp7t50Y2eFemuYeC+D5Tto3OvGKrhnQ==", + "version": "5.0.0-alpha.141", + "resolved": "https://registry.npmjs.org/@mui/lab/-/lab-5.0.0-alpha.141.tgz", + "integrity": "sha512-PsW55xX2ieNLldca2hLxL1SYtZgRQv++lj1W/Jyi5Z2MHuFDcdqI7yKGrOzyIWw7ctQrmHa1FTShBiCa2wkEoQ==", "dependencies": { - "@babel/runtime": "^7.22.5", - "@mui/base": "5.0.0-beta.7", - "@mui/system": "^5.14.0", + "@babel/runtime": "^7.22.10", + "@mui/base": "5.0.0-beta.12", + "@mui/system": "^5.14.6", "@mui/types": "^7.2.4", - "@mui/utils": "^5.13.7", - "clsx": "^1.2.1", + "@mui/utils": "^5.14.6", + "clsx": "^2.0.0", "prop-types": "^15.8.1", "react-is": "^18.2.0" }, @@ -2969,19 +3001,27 @@ } } }, + "node_modules/@mui/lab/node_modules/clsx": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", + "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==", + "engines": { + "node": ">=6" + } + }, "node_modules/@mui/material": { - "version": "5.14.0", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.14.0.tgz", - "integrity": "sha512-HP7CP71NhMkui2HUIEKl2/JfuHMuoarSUWAKlNw6s17bl/Num9rN61EM6uUzc2A2zHjj/00A66GnvDnmixEJEw==", - "dependencies": { - "@babel/runtime": "^7.22.5", - "@mui/base": "5.0.0-beta.7", - "@mui/core-downloads-tracker": "^5.14.0", - "@mui/system": "^5.14.0", + "version": "5.14.6", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.14.6.tgz", + "integrity": "sha512-C3UgGrmtvcGkQkm0ONBU7bTdapTjQc2Se3b2354xMmU7lgSgW7VM6EP9wIH5XqqoJ60m9l/s9kbTWX0Y+EaWvA==", + "dependencies": { + "@babel/runtime": "^7.22.10", + "@mui/base": "5.0.0-beta.12", + "@mui/core-downloads-tracker": "^5.14.6", + "@mui/system": "^5.14.6", "@mui/types": "^7.2.4", - "@mui/utils": "^5.13.7", + "@mui/utils": "^5.14.6", "@types/react-transition-group": "^4.4.6", - "clsx": "^1.2.1", + "clsx": "^2.0.0", "csstype": "^3.1.2", "prop-types": "^15.8.1", "react-is": "^18.2.0", @@ -3013,13 +3053,21 @@ } } }, + "node_modules/@mui/material/node_modules/clsx": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", + "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==", + "engines": { + "node": ">=6" + } + }, "node_modules/@mui/private-theming": { - "version": "5.13.7", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.13.7.tgz", - "integrity": "sha512-qbSr+udcij5F9dKhGX7fEdx2drXchq7htLNr2Qg2Ma+WJ6q0ERlEqGSBiPiVDJkptcjeVL4DGmcf1wl5+vD4EA==", + "version": "5.14.7", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.14.7.tgz", + "integrity": "sha512-Y86+hmDnJab2Ka42PgxKpK3oL7EiacbeeX3X/lG9LGO0wSc45wZjHeTfIlVSkkUCkexiMKEJp5NlSjZhr27NRQ==", "dependencies": { - "@babel/runtime": "^7.22.5", - "@mui/utils": "^5.13.7", + "@babel/runtime": "^7.22.10", + "@mui/utils": "^5.14.7", "prop-types": "^15.8.1" }, "engines": { @@ -3040,11 +3088,11 @@ } }, "node_modules/@mui/styled-engine": { - "version": "5.13.2", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.13.2.tgz", - "integrity": "sha512-VCYCU6xVtXOrIN8lcbuPmoG+u7FYuOERG++fpY74hPpEWkyFQG97F+/XfTQVYzlR2m7nPjnwVUgATcTCMEaMvw==", + "version": "5.14.7", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.14.7.tgz", + "integrity": "sha512-hKBETEDsIAkL8/mBwPiQj/vw28OeIhMXC3Tvj4J2bb9snxAKpiZioR1PwqP+6P41twsC/GKBd0Vr9oaWYaHuMg==", "dependencies": { - "@babel/runtime": "^7.21.0", + "@babel/runtime": "^7.22.10", "@emotion/cache": "^11.11.0", "csstype": "^3.1.2", "prop-types": "^15.8.1" @@ -3071,16 +3119,16 @@ } }, "node_modules/@mui/system": { - "version": "5.14.1", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.14.1.tgz", - "integrity": "sha512-u+xlsU34Jdkgx1CxmBnIC4Y08uPdVX5iEd3S/1dggDFtOGp+Lj8xmKRJAQ8PJOOJLOh8pDwaZx4AwXikL4l1QA==", + "version": "5.14.7", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.14.7.tgz", + "integrity": "sha512-jeZtHglc+Pi6qjGoopT6O4RqYXVBMqHVOsjMGP0hxGSSPm1T4gsAu7jU8eqGx9YwwjvvJ0eotTjFqw7iJ6qE2Q==", "dependencies": { - "@babel/runtime": "^7.22.6", - "@mui/private-theming": "^5.13.7", - "@mui/styled-engine": "^5.13.2", + "@babel/runtime": "^7.22.10", + "@mui/private-theming": "^5.14.7", + "@mui/styled-engine": "^5.14.7", "@mui/types": "^7.2.4", - "@mui/utils": "^5.14.1", - "clsx": "^1.2.1", + "@mui/utils": "^5.14.7", + "clsx": "^2.0.0", "csstype": "^3.1.2", "prop-types": "^15.8.1" }, @@ -3109,6 +3157,14 @@ } } }, + "node_modules/@mui/system/node_modules/clsx": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", + "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==", + "engines": { + "node": ">=6" + } + }, "node_modules/@mui/types": { "version": "7.2.4", "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.4.tgz", @@ -3123,11 +3179,11 @@ } }, "node_modules/@mui/utils": { - "version": "5.14.1", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.14.1.tgz", - "integrity": "sha512-39KHKK2JeqRmuUcLDLwM+c2XfVC136C5/yUyQXmO2PVbOb2Bol4KxtkssEqCbTwg87PSCG3f1Tb0keRsK7cVGw==", + "version": "5.14.7", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.14.7.tgz", + "integrity": "sha512-RtheP/aBoPogVdi8vj8Vo2IFnRa4mZVmnD0RGlVZ49yF60rZs+xP4/KbpIrTr83xVs34QmHQ2aQ+IX7I0a0dDw==", "dependencies": { - "@babel/runtime": "^7.22.6", + "@babel/runtime": "^7.22.10", "@types/prop-types": "^15.7.5", "@types/react-is": "^18.2.1", "prop-types": "^15.8.1", @@ -3403,9 +3459,9 @@ } }, "node_modules/@types/jest": { - "version": "29.5.3", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.3.tgz", - "integrity": "sha512-1Nq7YrO/vJE/FYnqYyw0FS8LdrjExSgIiHyKg7xPpn+yi8Q4huZryKnkJatN1ZRH89Kw2v33/8ZMB7DuZeSLlA==", + "version": "29.5.4", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.4.tgz", + "integrity": "sha512-PhglGmhWeD46FYOVLt3X7TiWjzwuVGW9wG/4qocPevXMjCmrIc5b6db9WjeGE4QYVpUAWMDv3v0IiBwObY289A==", "dev": true, "dependencies": { "expect": "^29.0.0", @@ -3446,9 +3502,9 @@ "dev": true }, "node_modules/@types/lodash": { - "version": "4.14.195", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.195.tgz", - "integrity": "sha512-Hwx9EUgdwf2GLarOjQp5ZH8ZmblzcbTBC2wtQWNKARBSxM9ezRIAUpeDTgoQRAFB0+8CNWXVA9+MaSOzOF3nPg==", + "version": "4.14.197", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.197.tgz", + "integrity": "sha512-BMVOiWs0uNxHVlHBgzTIqJYmj+PgCo4euloGF+5m4okL3rEYzM2EEv78mw8zWSMM57dM7kVIgJ2QDvwHSoCI5g==", "dev": true }, "node_modules/@types/node": { @@ -3468,9 +3524,9 @@ "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" }, "node_modules/@types/react": { - "version": "18.2.15", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.15.tgz", - "integrity": "sha512-oEjE7TQt1fFTFSbf8kkNuc798ahTUzn3Le67/PWjE8MAfYAD/qB7O8hSTcromLFqHCt9bcdOg5GXMokzTjJ5SA==", + "version": "18.2.21", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.21.tgz", + "integrity": "sha512-neFKG/sBAwGxHgXiIxnbm3/AAVQ/cMRS93hvBpg8xYRbeQSPVABp9U2bRnPf0iI4+Ucdv3plSxKK+3CW2ENJxA==", "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -3529,9 +3585,9 @@ "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" }, "node_modules/@types/semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==", + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.1.tgz", + "integrity": "sha512-cJRQXpObxfNKkFAZbJl2yjWtJCqELQIdShsogr1d2MilP8dKD9TE/nEKHkJgUNHdGKCQaf9HbIynuV2csLGVLg==", "dev": true }, "node_modules/@types/stack-utils": { @@ -3565,21 +3621,20 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.3.0.tgz", - "integrity": "sha512-IZYjYZ0ifGSLZbwMqIip/nOamFiWJ9AH+T/GYNZBWkVcyNQOFGtSMoWV7RvY4poYCMZ/4lHzNl796WOSNxmk8A==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.4.1.tgz", + "integrity": "sha512-3F5PtBzUW0dYlq77Lcqo13fv+58KDwUib3BddilE8ajPJT+faGgxmI9Sw+I8ZS22BYwoir9ZhNXcLi+S+I2bkw==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.3.0", - "@typescript-eslint/type-utils": "6.3.0", - "@typescript-eslint/utils": "6.3.0", - "@typescript-eslint/visitor-keys": "6.3.0", + "@typescript-eslint/scope-manager": "6.4.1", + "@typescript-eslint/type-utils": "6.4.1", + "@typescript-eslint/utils": "6.4.1", + "@typescript-eslint/visitor-keys": "6.4.1", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", "natural-compare": "^1.4.0", - "natural-compare-lite": "^1.4.0", "semver": "^7.5.4", "ts-api-utils": "^1.0.1" }, @@ -3601,15 +3656,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.3.0.tgz", - "integrity": "sha512-ibP+y2Gr6p0qsUkhs7InMdXrwldjxZw66wpcQq9/PzAroM45wdwyu81T+7RibNCh8oc0AgrsyCwJByncY0Ongg==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.4.1.tgz", + "integrity": "sha512-610G6KHymg9V7EqOaNBMtD1GgpAmGROsmfHJPXNLCU9bfIuLrkdOygltK784F6Crboyd5tBFayPB7Sf0McrQwg==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "6.3.0", - "@typescript-eslint/types": "6.3.0", - "@typescript-eslint/typescript-estree": "6.3.0", - "@typescript-eslint/visitor-keys": "6.3.0", + "@typescript-eslint/scope-manager": "6.4.1", + "@typescript-eslint/types": "6.4.1", + "@typescript-eslint/typescript-estree": "6.4.1", + "@typescript-eslint/visitor-keys": "6.4.1", "debug": "^4.3.4" }, "engines": { @@ -3629,13 +3684,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.3.0.tgz", - "integrity": "sha512-WlNFgBEuGu74ahrXzgefiz/QlVb+qg8KDTpknKwR7hMH+lQygWyx0CQFoUmMn1zDkQjTBBIn75IxtWss77iBIQ==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.4.1.tgz", + "integrity": "sha512-p/OavqOQfm4/Hdrr7kvacOSFjwQ2rrDVJRPxt/o0TOWdFnjJptnjnZ+sYDR7fi4OimvIuKp+2LCkc+rt9fIW+A==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.3.0", - "@typescript-eslint/visitor-keys": "6.3.0" + "@typescript-eslint/types": "6.4.1", + "@typescript-eslint/visitor-keys": "6.4.1" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -3646,13 +3701,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.3.0.tgz", - "integrity": "sha512-7Oj+1ox1T2Yc8PKpBvOKWhoI/4rWFd1j7FA/rPE0lbBPXTKjdbtC+7Ev0SeBjEKkIhKWVeZSP+mR7y1Db1CdfQ==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.4.1.tgz", + "integrity": "sha512-7ON8M8NXh73SGZ5XvIqWHjgX2f+vvaOarNliGhjrJnv1vdjG0LVIz+ToYfPirOoBi56jxAKLfsLm40+RvxVVXA==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.3.0", - "@typescript-eslint/utils": "6.3.0", + "@typescript-eslint/typescript-estree": "6.4.1", + "@typescript-eslint/utils": "6.4.1", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -3673,9 +3728,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.3.0.tgz", - "integrity": "sha512-K6TZOvfVyc7MO9j60MkRNWyFSf86IbOatTKGrpTQnzarDZPYPVy0oe3myTMq7VjhfsUAbNUW8I5s+2lZvtx1gg==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.4.1.tgz", + "integrity": "sha512-zAAopbNuYu++ijY1GV2ylCsQsi3B8QvfPHVqhGdDcbx/NK5lkqMnCGU53amAjccSpk+LfeONxwzUhDzArSfZJg==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -3686,13 +3741,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.3.0.tgz", - "integrity": "sha512-Xh4NVDaC4eYKY4O3QGPuQNp5NxBAlEvNQYOqJquR2MePNxO11E5K3t5x4M4Mx53IZvtpW+mBxIT0s274fLUocg==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.4.1.tgz", + "integrity": "sha512-xF6Y7SatVE/OyV93h1xGgfOkHr2iXuo8ip0gbfzaKeGGuKiAnzS+HtVhSPx8Www243bwlW8IF7X0/B62SzFftg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.3.0", - "@typescript-eslint/visitor-keys": "6.3.0", + "@typescript-eslint/types": "6.4.1", + "@typescript-eslint/visitor-keys": "6.4.1", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -3713,17 +3768,17 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.3.0.tgz", - "integrity": "sha512-hLLg3BZE07XHnpzglNBG8P/IXq/ZVXraEbgY7FM0Cnc1ehM8RMdn9mat3LubJ3KBeYXXPxV1nugWbQPjGeJk6Q==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.4.1.tgz", + "integrity": "sha512-F/6r2RieNeorU0zhqZNv89s9bDZSovv3bZQpUNOmmQK1L80/cV4KEu95YUJWi75u5PhboFoKUJBnZ4FQcoqhDw==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.3.0", - "@typescript-eslint/types": "6.3.0", - "@typescript-eslint/typescript-estree": "6.3.0", + "@typescript-eslint/scope-manager": "6.4.1", + "@typescript-eslint/types": "6.4.1", + "@typescript-eslint/typescript-estree": "6.4.1", "semver": "^7.5.4" }, "engines": { @@ -3738,12 +3793,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.3.0.tgz", - "integrity": "sha512-kEhRRj7HnvaSjux1J9+7dBen15CdWmDnwrpyiHsFX6Qx2iW5LOBUgNefOFeh2PjWPlNwN8TOn6+4eBU3J/gupw==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.4.1.tgz", + "integrity": "sha512-y/TyRJsbZPkJIZQXrHfdnxVnxyKegnpEvnRGNam7s3TRR2ykGefEWOhaef00/UUN3IZxizS7BTO3svd3lCOJRQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.3.0", + "@typescript-eslint/types": "6.4.1", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -3767,12 +3822,12 @@ } }, "node_modules/@vitejs/plugin-react": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.0.3.tgz", - "integrity": "sha512-pwXDog5nwwvSIzwrvYYmA2Ljcd/ZNlcsSG2Q9CNDBwnsd55UGAyr2doXtB5j+2uymRCnCfExlznzzSFbBRcoCg==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.0.4.tgz", + "integrity": "sha512-7wU921ABnNYkETiMaZy7XqpueMnpu5VxvVps13MjmCo+utBdD79sZzrApHawHtVX66cCJQQTXFcjH0y9dSUK8g==", "dev": true, "dependencies": { - "@babel/core": "^7.22.5", + "@babel/core": "^7.22.9", "@babel/plugin-transform-react-jsx-self": "^7.22.5", "@babel/plugin-transform-react-jsx-source": "^7.22.5", "react-refresh": "^0.14.0" @@ -3785,13 +3840,13 @@ } }, "node_modules/@vitest/expect": { - "version": "0.33.0", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-0.33.0.tgz", - "integrity": "sha512-sVNf+Gla3mhTCxNJx+wJLDPp/WcstOe0Ksqz4Vec51MmgMth/ia0MGFEkIZmVGeTL5HtjYR4Wl/ZxBxBXZJTzQ==", + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-0.34.3.tgz", + "integrity": "sha512-F8MTXZUYRBVsYL1uoIft1HHWhwDbSzwAU9Zgh8S6WFC3YgVb4AnFV2GXO3P5Em8FjEYaZtTnQYoNwwBrlOMXgg==", "dev": true, "dependencies": { - "@vitest/spy": "0.33.0", - "@vitest/utils": "0.33.0", + "@vitest/spy": "0.34.3", + "@vitest/utils": "0.34.3", "chai": "^4.3.7" }, "funding": { @@ -3799,12 +3854,12 @@ } }, "node_modules/@vitest/runner": { - "version": "0.33.0", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-0.33.0.tgz", - "integrity": "sha512-UPfACnmCB6HKRHTlcgCoBh6ppl6fDn+J/xR8dTufWiKt/74Y9bHci5CKB8tESSV82zKYtkBJo9whU3mNvfaisg==", + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-0.34.3.tgz", + "integrity": "sha512-lYNq7N3vR57VMKMPLVvmJoiN4bqwzZ1euTW+XXYH5kzr3W/+xQG3b41xJn9ChJ3AhYOSoweu974S1V3qDcFESA==", "dev": true, "dependencies": { - "@vitest/utils": "0.33.0", + "@vitest/utils": "0.34.3", "p-limit": "^4.0.0", "pathe": "^1.1.1" }, @@ -3840,9 +3895,9 @@ } }, "node_modules/@vitest/snapshot": { - "version": "0.33.0", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-0.33.0.tgz", - "integrity": "sha512-tJjrl//qAHbyHajpFvr8Wsk8DIOODEebTu7pgBrP07iOepR5jYkLFiqLq2Ltxv+r0uptUb4izv1J8XBOwKkVYA==", + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-0.34.3.tgz", + "integrity": "sha512-QyPaE15DQwbnIBp/yNJ8lbvXTZxS00kRly0kfFgAD5EYmCbYcA+1EEyRalc93M0gosL/xHeg3lKAClIXYpmUiQ==", "dev": true, "dependencies": { "magic-string": "^0.30.1", @@ -3854,9 +3909,9 @@ } }, "node_modules/@vitest/spy": { - "version": "0.33.0", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-0.33.0.tgz", - "integrity": "sha512-Kv+yZ4hnH1WdiAkPUQTpRxW8kGtH8VRTnus7ZTGovFYM1ZezJpvGtb9nPIjPnptHbsyIAxYZsEpVPYgtpjGnrg==", + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-0.34.3.tgz", + "integrity": "sha512-N1V0RFQ6AI7CPgzBq9kzjRdPIgThC340DGjdKdPSE8r86aUSmeliTUgkTqLSgtEwWWsGfBQ+UetZWhK0BgJmkQ==", "dev": true, "dependencies": { "tinyspy": "^2.1.1" @@ -3866,9 +3921,9 @@ } }, "node_modules/@vitest/utils": { - "version": "0.33.0", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-0.33.0.tgz", - "integrity": "sha512-pF1w22ic965sv+EN6uoePkAOTkAPWM03Ri/jXNyMIKBb/XHLDPfhLvf/Fa9g0YECevAIz56oVYXhodLvLQ/awA==", + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-0.34.3.tgz", + "integrity": "sha512-kiSnzLG6m/tiT0XEl4U2H8JDBjFtwVlaE8I3QfGiMFR0QvnRDfYfdP3YvTBWM/6iJDAyaPY6yVQiCTUc7ZzTHA==", "dev": true, "dependencies": { "diff-sequences": "^29.4.3", @@ -3913,9 +3968,9 @@ } }, "node_modules/@wry/context": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@wry/context/-/context-0.7.0.tgz", - "integrity": "sha512-LcDAiYWRtwAoSOArfk7cuYvFXytxfVrdX7yxoUmK7pPITLk5jYh2F8knCwS7LjgYL8u1eidPlKKV6Ikqq0ODqQ==", + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@wry/context/-/context-0.7.3.tgz", + "integrity": "sha512-Nl8WTesHp89RF803Se9X3IiHjdmLBrIvPMaJkl+rKVJAYyPsz1TEUbu89943HpvujtSJgDUx9W4vZw3K1Mr3sA==", "dependencies": { "tslib": "^2.3.0" }, @@ -3924,9 +3979,9 @@ } }, "node_modules/@wry/equality": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/@wry/equality/-/equality-0.5.3.tgz", - "integrity": "sha512-avR+UXdSrsF2v8vIqIgmeTY0UR91UT+IyablCyKe/uk22uOJ8fusKZnH9JH9e1/EtLeNJBtagNmL3eJdnOV53g==", + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/@wry/equality/-/equality-0.5.6.tgz", + "integrity": "sha512-D46sfMTngaYlrH+OspKf8mIJETntFnf6Hsjb0V41jAXJ7Bx2kB8Rv8RCUujuVWYttFtHkUNp7g+FwxNQAr6mXA==", "dependencies": { "tslib": "^2.3.0" }, @@ -3935,9 +3990,9 @@ } }, "node_modules/@wry/trie": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@wry/trie/-/trie-0.3.2.tgz", - "integrity": "sha512-yRTyhWSls2OY/pYLfwff867r8ekooZ4UI+/gxot5Wj8EFwSf2rG+n+Mo/6LoLQm1TKA4GRj2+LCpbfS937dClQ==", + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@wry/trie/-/trie-0.4.3.tgz", + "integrity": "sha512-I6bHwH0fSf6RqQcnnXLJKhkSXG45MFral3GxPaY4uAl0LYDZM+YDVDAiU9bYwjTuysy1S0IeecWtmq1SZA3M1w==", "dependencies": { "tslib": "^2.3.0" }, @@ -4257,6 +4312,15 @@ "node": ">=8" } }, + "node_modules/asynciterator.prototype": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/asynciterator.prototype/-/asynciterator.prototype-1.0.0.tgz", + "integrity": "sha512-wwHYEIS0Q80f5mosx3L/dfG5t5rjEa9Ft51GTaNt862EnpyGHpgz2RkZvLPp1oF5TnAiTohkEKVEu8pQPJI7Vg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.3" + } + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -4297,9 +4361,9 @@ } }, "node_modules/axios": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz", - "integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.5.0.tgz", + "integrity": "sha512-D4DdjDo5CY50Qms0qGQTTw6Q44jl7zRwY7bthds06pUGfChBCTcQs+N743eFWGEd6pRTMd6A+I87aWyFV5wiZQ==", "dependencies": { "follow-redirects": "^1.15.0", "form-data": "^4.0.0", @@ -4399,9 +4463,9 @@ } }, "node_modules/bignumber.js": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.1.tgz", - "integrity": "sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", + "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==", "engines": { "node": "*" } @@ -5421,6 +5485,28 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-iterator-helpers": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.14.tgz", + "integrity": "sha512-JgtVnwiuoRuzLvqelrvN3Xu7H9bu2ap/kQ2CrM62iidP8SKuD99rWU3CJy++s7IVL2qb/AjXPGR/E7i9ngd/Cw==", + "dev": true, + "dependencies": { + "asynciterator.prototype": "^1.0.0", + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-set-tostringtag": "^2.0.1", + "function-bind": "^1.1.1", + "get-intrinsic": "^1.2.1", + "globalthis": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.5", + "iterator.prototype": "^1.1.0", + "safe-array-concat": "^1.0.0" + } + }, "node_modules/es-set-tostringtag": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", @@ -5540,15 +5626,15 @@ } }, "node_modules/eslint": { - "version": "8.46.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.46.0.tgz", - "integrity": "sha512-cIO74PvbW0qU8e0mIvk5IV3ToWdCq5FYG6gWPHHkx6gNdjlbAYvtfHmlCMXxjcoVaIdwy/IAt3+mDkZkfvb2Dg==", + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.48.0.tgz", + "integrity": "sha512-sb6DLeIuRXxeM1YljSe1KEx9/YYeZFQWcV8Rq9HfigmdDEugjLEVEa1ozDjL6YDjBpQHPJxJzze+alxi4T3OLg==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.1", - "@eslint/js": "^8.46.0", + "@eslint/eslintrc": "^2.1.2", + "@eslint/js": "8.48.0", "@humanwhocodes/config-array": "^0.11.10", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -5559,7 +5645,7 @@ "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.2", + "eslint-visitor-keys": "^3.4.3", "espree": "^9.6.1", "esquery": "^1.4.2", "esutils": "^2.0.2", @@ -5606,15 +5692,16 @@ } }, "node_modules/eslint-plugin-react": { - "version": "7.33.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.33.1.tgz", - "integrity": "sha512-L093k0WAMvr6VhNwReB8VgOq5s2LesZmrpPdKz/kZElQDzqS7G7+DnKoqT+w4JwuiGeAhAvHO0fvy0Eyk4ejDA==", + "version": "7.33.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.33.2.tgz", + "integrity": "sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw==", "dev": true, "dependencies": { "array-includes": "^3.1.6", "array.prototype.flatmap": "^1.3.1", "array.prototype.tosorted": "^1.1.1", "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.0.12", "estraverse": "^5.3.0", "jsx-ast-utils": "^2.4.1 || ^3.0.0", "minimatch": "^3.1.2", @@ -5680,9 +5767,9 @@ } }, "node_modules/eslint-visitor-keys": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.2.tgz", - "integrity": "sha512-8drBzUEyZ2llkpCA67iYrgEssKDUu68V8ChqqOfFupIaG/LCVPUT+CoGJpT77zJprs4T/W7p07LP7zAIMuweVw==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -5716,9 +5803,9 @@ } }, "node_modules/eslint/node_modules/globals": { - "version": "13.20.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", - "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "version": "13.21.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.21.0.tgz", + "integrity": "sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -6060,9 +6147,9 @@ } }, "node_modules/formik": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/formik/-/formik-2.4.2.tgz", - "integrity": "sha512-C6nx0hifW2uENP3M6HpPmnAE6HFWCcd8/sqBZEOHZY6lpHJ5qehsfAy43ktpFLEmkBmhiZDei726utcUB9leqg==", + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/formik/-/formik-2.4.3.tgz", + "integrity": "sha512-2Dy79Szw3zlXmZiokUdKsn+n1ow4G8hRrC/n92cOWHNTWXCRpQXlyvz6HcjW7aSQZrldytvDOavYjhfmDnUq8Q==", "funding": [ { "type": "individual", @@ -6313,9 +6400,9 @@ "dev": true }, "node_modules/graphql": { - "version": "16.7.1", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.7.1.tgz", - "integrity": "sha512-DRYR9tf+UGU0KOsMcKAlXeFfX89UiiIZ0dRU3mR0yJfu6OjZqUcp68NnFLnqQU5RexygFoDy1EW+ccOYcPfmHg==", + "version": "16.8.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.8.0.tgz", + "integrity": "sha512-0oKGaR+y3qcS5mCu1vb7KG+a89vjn06C7Ihq/dDl3jA+A8B3TKomvi3CiEcVLJQGalbu8F52LxkOym7U5sSfbg==", "engines": { "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" } @@ -6832,6 +6919,21 @@ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" }, + "node_modules/is-async-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz", + "integrity": "sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-bigint": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", @@ -6885,9 +6987,9 @@ } }, "node_modules/is-core-module": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", - "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", + "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", "dependencies": { "has": "^1.0.3" }, @@ -6919,6 +7021,18 @@ "node": ">=0.10.0" } }, + "node_modules/is-finalizationregistry": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz", + "integrity": "sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -6928,6 +7042,21 @@ "node": ">=8" } }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -6958,6 +7087,15 @@ "tslib": "^2.0.3" } }, + "node_modules/is-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", + "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-negative-zero": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", @@ -7047,6 +7185,15 @@ "node": ">=0.10.0" } }, + "node_modules/is-set": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", + "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-shared-array-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", @@ -7137,6 +7284,15 @@ "tslib": "^2.0.3" } }, + "node_modules/is-weakmap": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", + "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-weakref": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", @@ -7149,6 +7305,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-weakset": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", + "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", @@ -7189,6 +7358,18 @@ "ws": "*" } }, + "node_modules/iterator.prototype": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.1.tgz", + "integrity": "sha512-9E+nePc8C9cnQldmNl6bgpTY6zI4OPRZd97fhJ/iVZ1GifIUDVV5F6x1nEDqpe8KaMEZGT4xgrwKQDxXnjOIZQ==", + "dev": true, + "dependencies": { + "define-properties": "^1.2.0", + "get-intrinsic": "^1.2.1", + "has-symbols": "^1.0.3", + "reflect.getprototypeof": "^1.0.3" + } + }, "node_modules/jest-diff": { "version": "29.5.0", "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.5.0.tgz", @@ -7805,12 +7986,6 @@ "node": ">=12" } }, - "node_modules/magic-string/node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true - }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -7971,12 +8146,6 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, - "node_modules/natural-compare-lite": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", - "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", - "dev": true - }, "node_modules/no-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", @@ -8205,12 +8374,13 @@ } }, "node_modules/optimism": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/optimism/-/optimism-0.16.2.tgz", - "integrity": "sha512-zWNbgWj+3vLEjZNIh/okkY2EUfX+vB9TJopzIZwT1xxaMqC5hRLLraePod4c5n4He08xuXNH+zhKFFCu390wiQ==", + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/optimism/-/optimism-0.17.5.tgz", + "integrity": "sha512-TEcp8ZwK1RczmvMnvktxHSF2tKgMWjJ71xEFGX5ApLh67VsMSTy1ZUlipJw8W+KaqgOmQ+4pqwkeivY89j+4Vw==", "dependencies": { "@wry/context": "^0.7.0", - "@wry/trie": "^0.3.0" + "@wry/trie": "^0.4.3", + "tslib": "^2.3.0" } }, "node_modules/optionator": { @@ -8539,9 +8709,9 @@ } }, "node_modules/prettier": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.0.tgz", - "integrity": "sha512-zBf5eHpwHOGPC47h0zrPyNn+eAEIdEzfywMoYn2XPi0P44Zp0tSq64rq0xAREh4auw2cJZHo9QUob+NqCQky4g==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.2.tgz", + "integrity": "sha512-o2YR9qtniXvwEZlOKbveKfDQVyqxbEIWn48Z8m3ZJjBjcCmUy3xZGIv+7AkaeuaTr6yPXJjwv07ZWlsWbEy1rQ==", "dev": true, "bin": { "prettier": "bin/prettier.cjs" @@ -8843,10 +9013,30 @@ "node": ">=8.10.0" } }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.4.tgz", + "integrity": "sha512-ECkTw8TmJwW60lOTR+ZkODISW6RQ8+2CL3COqtiJKLd6MmB45hN51HprHFziKLGkAuTGQhBb91V8cy+KHlaCjw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", + "globalthis": "^1.0.3", + "which-builtin-type": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", + "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==" }, "node_modules/regexp.prototype.flags": { "version": "1.5.0", @@ -8921,11 +9111,11 @@ "peer": true }, "node_modules/resolve": { - "version": "1.22.2", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", - "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "version": "1.22.4", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.4.tgz", + "integrity": "sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==", "dependencies": { - "is-core-module": "^2.11.0", + "is-core-module": "^2.13.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -9011,9 +9201,9 @@ } }, "node_modules/rollup": { - "version": "3.26.3", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.26.3.tgz", - "integrity": "sha512-7Tin0C8l86TkpcMtXvQu6saWH93nhG3dGQ1/+l5V2TDMceTxO7kDiK6GzbfLWNNxqJXm591PcEZUozZm51ogwQ==", + "version": "3.28.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.28.1.tgz", + "integrity": "sha512-R9OMQmIHJm9znrU3m3cpE8uhN0fGdXiawME7aZIpQqvpS/85+Vt1Hq1/yVIcYfOmaQiHjvXkQAoJukvLpau6Yw==", "dev": true, "bin": { "rollup": "dist/bin/rollup" @@ -9666,9 +9856,9 @@ "dev": true }, "node_modules/tinypool": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.6.0.tgz", - "integrity": "sha512-FdswUUo5SxRizcBc6b1GSuLpLjisa8N8qMyYoP3rl+bym+QauhtJP5bvZY1ytt8krKGmMLYIRl36HBZfeAoqhQ==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.7.0.tgz", + "integrity": "sha512-zSYNUlYSMhJ6Zdou4cJwo/p7w5nmAH17GRfU/ui3ctvjXFErXXkruT4MWW6poDeXgCaIBlGLrfU6TbTXxyGMww==", "dev": true, "engines": { "node": ">=14.0.0" @@ -9984,9 +10174,9 @@ "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" }, "node_modules/typescript": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", - "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -10182,14 +10372,14 @@ } }, "node_modules/vite": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.4.tgz", - "integrity": "sha512-4mvsTxjkveWrKDJI70QmelfVqTm+ihFAb6+xf4sjEU2TmUCTlVX87tmg/QooPEMQb/lM9qGHT99ebqPziEd3wg==", + "version": "4.4.9", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.9.tgz", + "integrity": "sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==", "dev": true, "dependencies": { "esbuild": "^0.18.10", - "postcss": "^8.4.25", - "rollup": "^3.25.2" + "postcss": "^8.4.27", + "rollup": "^3.27.1" }, "bin": { "vite": "bin/vite.js" @@ -10237,9 +10427,9 @@ } }, "node_modules/vite-node": { - "version": "0.33.0", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-0.33.0.tgz", - "integrity": "sha512-19FpHYbwWWxDr73ruNahC+vtEdza52kA90Qb3La98yZ0xULqV8A5JLNPUff0f5zID4984tW7l3DH2przTJUZSw==", + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-0.34.3.tgz", + "integrity": "sha512-+0TzJf1g0tYXj6tR2vEyiA42OPq68QkRZCu/ERSo2PtsDJfBpDyEfuKbRvLmZqi/CgC7SCBtyC+WjTGNMRIaig==", "dev": true, "dependencies": { "cac": "^6.7.14", @@ -10260,9 +10450,9 @@ } }, "node_modules/vite-plugin-checker": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/vite-plugin-checker/-/vite-plugin-checker-0.6.1.tgz", - "integrity": "sha512-4fAiu3W/IwRJuJkkUZlWbLunSzsvijDf0eDN6g/MGh6BUK4SMclOTGbLJCPvdAcMOQvVmm8JyJeYLYd4//8CkA==", + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/vite-plugin-checker/-/vite-plugin-checker-0.6.2.tgz", + "integrity": "sha512-YvvvQ+IjY09BX7Ab+1pjxkELQsBd4rPhWNw8WLBeFVxu/E7O+n6VYAqNsKdK/a2luFlX/sMpoWdGFfg4HvwdJQ==", "dev": true, "dependencies": { "@babel/code-frame": "^7.12.13", @@ -10344,19 +10534,19 @@ } }, "node_modules/vitest": { - "version": "0.33.0", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.33.0.tgz", - "integrity": "sha512-1CxaugJ50xskkQ0e969R/hW47za4YXDUfWJDxip1hwbnhUjYolpfUn2AMOulqG/Dtd9WYAtkHmM/m3yKVrEejQ==", + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.34.3.tgz", + "integrity": "sha512-7+VA5Iw4S3USYk+qwPxHl8plCMhA5rtfwMjgoQXMT7rO5ldWcdsdo3U1QD289JgglGK4WeOzgoLTsGFu6VISyQ==", "dev": true, "dependencies": { "@types/chai": "^4.3.5", "@types/chai-subset": "^1.3.3", "@types/node": "*", - "@vitest/expect": "0.33.0", - "@vitest/runner": "0.33.0", - "@vitest/snapshot": "0.33.0", - "@vitest/spy": "0.33.0", - "@vitest/utils": "0.33.0", + "@vitest/expect": "0.34.3", + "@vitest/runner": "0.34.3", + "@vitest/snapshot": "0.34.3", + "@vitest/spy": "0.34.3", + "@vitest/utils": "0.34.3", "acorn": "^8.9.0", "acorn-walk": "^8.2.0", "cac": "^6.7.14", @@ -10369,9 +10559,9 @@ "std-env": "^3.3.3", "strip-literal": "^1.0.1", "tinybench": "^2.5.0", - "tinypool": "^0.6.0", + "tinypool": "^0.7.0", "vite": "^3.0.0 || ^4.0.0", - "vite-node": "0.33.0", + "vite-node": "0.34.3", "why-is-node-running": "^2.2.2" }, "bin": { @@ -10632,6 +10822,47 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/which-builtin-type": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.1.3.tgz", + "integrity": "sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw==", + "dev": true, + "dependencies": { + "function.prototype.name": "^1.1.5", + "has-tostringtag": "^1.0.0", + "is-async-function": "^2.0.0", + "is-date-object": "^1.0.5", + "is-finalizationregistry": "^1.0.2", + "is-generator-function": "^1.0.10", + "is-regex": "^1.1.4", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", + "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", + "dev": true, + "dependencies": { + "is-map": "^2.0.1", + "is-set": "^2.0.1", + "is-weakmap": "^2.0.1", + "is-weakset": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/which-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", diff --git a/package.json b/package.json index 8d18baa1..5af766de 100644 --- a/package.json +++ b/package.json @@ -3,23 +3,23 @@ "version": "0.1.0", "private": true, "dependencies": { - "@apollo/client": "3.7.17", - "@date-io/moment": "2.16.1", + "@apollo/client": "3.8.1", + "@date-io/moment": "2.17.0", "@emotion/react": "11.11.1", "@emotion/styled": "11.11.0", - "@fontsource/mulish": "5.0.5", - "@fontsource/poppins": "5.0.5", - "@mui/icons-material": "5.14.0", - "@mui/lab": "5.0.0-alpha.136", - "@mui/material": "5.14.0", + "@fontsource/mulish": "5.0.8", + "@fontsource/poppins": "5.0.8", + "@mui/icons-material": "5.14.6", + "@mui/lab": "5.0.0-alpha.141", + "@mui/material": "5.14.6", "@mui/x-date-pickers": "5.0.20", - "axios": "1.4.0", + "axios": "1.5.0", "base64url": "3.0.1", - "bignumber.js": "9.1.1", + "bignumber.js": "9.1.2", "date-fns": "2.30.0", "date-fns-tz": "2.0.0", - "formik": "2.4.2", - "graphql": "16.7.1", + "formik": "2.4.3", + "graphql": "16.8.0", "highcharts": "11.1.0", "highcharts-react-official": "3.2.0", "immer": "10.0.2", @@ -58,26 +58,26 @@ "@graphql-codegen/visitor-plugin-common": "2.13.8", "@types/chart.js": "2.9.37", "@types/classnames": "2.3.0", - "@types/jest": "29.5.3", + "@types/jest": "29.5.4", "@types/jwt-decode": "2.2.1", - "@types/lodash": "4.14.195", - "@types/react": "18.2.15", + "@types/lodash": "4.14.197", + "@types/react": "18.2.21", "@types/react-dom": "18.2.7", "@types/react-router-dom": "5.3.3", - "@typescript-eslint/eslint-plugin": "6.3.0", - "@typescript-eslint/parser": "6.3.0", + "@typescript-eslint/eslint-plugin": "6.4.1", + "@typescript-eslint/parser": "6.4.1", "@vitejs/plugin-basic-ssl": "1.0.1", - "@vitejs/plugin-react": "4.0.3", + "@vitejs/plugin-react": "4.0.4", + "eslint": "8.48.0", + "eslint-config-prettier": "9.0.0", + "eslint-plugin-react": "7.33.2", "husky": "8.0.3", - "prettier": "3.0.0", - "typescript": "5.1.6", - "vite": "4.4.4", - "vite-plugin-checker": "0.6.1", + "prettier": "3.0.2", + "typescript": "5.2.2", + "vite": "4.4.9", + "vite-plugin-checker": "0.6.2", "vite-tsconfig-paths": "4.2.0", - "vitest": "0.33.0", - "eslint": "8.46.0", - "eslint-config-prettier": "9.0.0", - "eslint-plugin-react": "7.33.1" + "vitest": "0.34.3" }, "overrides": { "semver": "7.5.4", diff --git a/src/auth/views/components/AuthContainer.tsx b/src/auth/views/components/AuthContainer.tsx index e865e50d..bf621812 100644 --- a/src/auth/views/components/AuthContainer.tsx +++ b/src/auth/views/components/AuthContainer.tsx @@ -14,7 +14,12 @@ export function AuthContainer({ children, belowCard, aboveCard }: Props) { return ( - + {aboveCard} {children} {belowCard} diff --git a/src/components/Date/DateRangePicker.tsx b/src/components/Date/DateRangePicker.tsx index 37a19a0d..cd278c69 100644 --- a/src/components/Date/DateRangePicker.tsx +++ b/src/components/Date/DateRangePicker.tsx @@ -1,7 +1,7 @@ import { Stack, TextField } from "@mui/material"; import { Dispatch } from "react"; -import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFns"; import { DatePicker, LocalizationProvider } from "@mui/x-date-pickers"; +import { AdapterMoment } from "@mui/x-date-pickers/AdapterMoment"; interface Props { from: Date; @@ -17,7 +17,7 @@ export const DateRangePicker = ({ onToChange, }: Props) => { return ( - + Date: Tue, 5 Sep 2023 09:26:25 -0400 Subject: [PATCH 3/4] chore(deps): update actions/checkout action to v4 (#879) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/deploy-to-production.yml | 2 +- .github/workflows/deploy-to-staging.yml | 2 +- .github/workflows/sanity-check.yml | 2 +- .github/workflows/semgrep.yml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 9f3a1d72..f9f23f49 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -38,7 +38,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/deploy-to-production.yml b/.github/workflows/deploy-to-production.yml index a434a89e..433ed5e3 100644 --- a/.github/workflows/deploy-to-production.yml +++ b/.github/workflows/deploy-to-production.yml @@ -12,7 +12,7 @@ jobs: url: https://ads.brave.com steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4 - name: Use Node.js uses: actions/setup-node@v3 with: diff --git a/.github/workflows/deploy-to-staging.yml b/.github/workflows/deploy-to-staging.yml index e13913cd..2af967aa 100644 --- a/.github/workflows/deploy-to-staging.yml +++ b/.github/workflows/deploy-to-staging.yml @@ -12,7 +12,7 @@ jobs: url: https://ads.bravesoftware.com steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4 - name: Use Node.js uses: actions/setup-node@v3 with: diff --git a/.github/workflows/sanity-check.yml b/.github/workflows/sanity-check.yml index 3ac332f1..c2f69967 100644 --- a/.github/workflows/sanity-check.yml +++ b/.github/workflows/sanity-check.yml @@ -12,7 +12,7 @@ jobs: check: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4 - name: Use Node.js uses: actions/setup-node@v3 with: diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml index 9afe8a34..008c81b8 100644 --- a/.github/workflows/semgrep.yml +++ b/.github/workflows/semgrep.yml @@ -19,7 +19,7 @@ jobs: steps: # Fetch project source - - uses: actions/checkout@v3 + - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4 - uses: returntocorp/semgrep-action@v1 with: publishToken: ${{ secrets.SEMGREP_APP_TOKEN }} From 25eb1dac6f4e76ea0f58602725dbe08b448b964c Mon Sep 17 00:00:00 2001 From: Ian Krieger <48930920+IanKrieger@users.noreply.github.com> Date: Tue, 5 Sep 2023 14:02:26 -0400 Subject: [PATCH 4/4] fix: correctly include createdAt (#880) * fix: correctly include createdAt * fix: update test --- src/user/hooks/useAdvertiserCreatives.ts | 1 + src/user/library/index.test.ts | 4 ++++ src/user/library/index.ts | 1 + 3 files changed, 6 insertions(+) diff --git a/src/user/hooks/useAdvertiserCreatives.ts b/src/user/hooks/useAdvertiserCreatives.ts index 451f7231..93d9c33b 100644 --- a/src/user/hooks/useAdvertiserCreatives.ts +++ b/src/user/hooks/useAdvertiserCreatives.ts @@ -12,6 +12,7 @@ export function useAdvertiserCreatives() { advertiserId: c.advertiserId, name: c.name, state: c.state, + createdAt: c.createdAt, included: false, }), ); diff --git a/src/user/library/index.test.ts b/src/user/library/index.test.ts index bd92261f..65240186 100644 --- a/src/user/library/index.test.ts +++ b/src/user/library/index.test.ts @@ -449,6 +449,7 @@ describe("edit form tests", () => { "creatives": [ { "advertiserId": "12345", + "createdAt": undefined, "id": "1234", "included": true, "name": "a creative", @@ -465,6 +466,7 @@ describe("edit form tests", () => { }, { "advertiserId": "12345", + "createdAt": undefined, "id": "1235", "included": false, "name": "a different creative", @@ -501,6 +503,7 @@ describe("edit form tests", () => { "creatives": [ { "advertiserId": "12345", + "createdAt": undefined, "id": "1234", "included": true, "name": "a creative", @@ -517,6 +520,7 @@ describe("edit form tests", () => { }, { "advertiserId": "12345", + "createdAt": undefined, "id": "1235", "included": true, "name": "a different creative", diff --git a/src/user/library/index.ts b/src/user/library/index.ts index 85be5261..00a19678 100644 --- a/src/user/library/index.ts +++ b/src/user/library/index.ts @@ -175,6 +175,7 @@ function creativeList( const c = ad.creative; return { ...validCreativeFields(c, advertiserId, included), + createdAt: c.createdAt, }; }); };