Skip to content

Commit

Permalink
feat: allow use of existing creatives
Browse files Browse the repository at this point in the history
  • Loading branch information
IanKrieger committed Aug 15, 2023
1 parent c186788 commit a2bc86e
Show file tree
Hide file tree
Showing 20 changed files with 1,013 additions and 129 deletions.
57 changes: 57 additions & 0 deletions src/components/Creatives/CreativeFields.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import {
FormControl,
Box,
FormLabel,
FormControlLabel,
Radio,
} from "@mui/material";
import { useFormikContext } from "formik";
import { CreativeInput } from "graphql/types";
import { NotificationFields } from "./NotificationFields";
import { FormikRadioGroup, FormikTextField } from "form/FormikHelpers";

interface Props {
allowTypeChange: boolean;
}

export function CreativeFields({ allowTypeChange }: Props) {
const formik = useFormikContext<CreativeInput>();
const creativeType = formik.values.type?.code;

return (
<>
<FormikTextField name="name" label="Creative Name" />

<Box>
<FormControl
component="fieldset"
margin="normal"
disabled={!allowTypeChange}
>
<FormLabel component="legend" color="secondary">
Creative Type
</FormLabel>
<FormikRadioGroup row name="type.code">
<FormControlLabel
value="notification_all_v1"
control={<Radio />}
label="Push Notification"
/>
</FormikRadioGroup>
</FormControl>
</Box>

<CreativeTypeSpecificFields creativeType={creativeType} />
</>
);
}

const CreativeTypeSpecificFields = ({
creativeType,
}: {
creativeType?: string;
}) => {
if (creativeType === "notification_all_v1") return <NotificationFields />;

return null;
};
71 changes: 71 additions & 0 deletions src/components/Creatives/CreativeList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { EnhancedTable, StandardRenderers } from "components/EnhancedTable";
import { useAdvertiserCreativesQuery } from "graphql/creative.generated";
import { uiTextForCreativeTypeCode } from "user/library";
import { CardContainer } from "components/Card/CardContainer";
import { useAdvertiser } from "auth/hooks/queries/useAdvertiser";
import { ErrorDetail } from "components/Error/ErrorDetail";
import MiniSideBar from "components/Drawer/MiniSideBar";

export function CreativeList() {
const { advertiser } = useAdvertiser();
const { data, error } = useAdvertiserCreativesQuery({
variables: {
advertiserId: advertiser.id,
},
});

if (error)
return (
<ErrorDetail error={error} additionalDetails="Unable to get creatives" />
);

return (
<MiniSideBar>
<CardContainer
header="Creatives"
sx={{
flexGrow: 1,
mr: 2,
}}
>
<EnhancedTable
rows={data?.advertiser?.creatives ?? []}
initialSortColumn={0}
initialSortDirection="desc"
initialRowsPerPage={25}
columns={[
{
title: "Created",
value: (c) => c.modifiedAt,
renderer: StandardRenderers.date,
},
{
title: "Name",
value: (c) => c.name,
},
{
title: "Type",
value: (c) => uiTextForCreativeTypeCode(c.type),
},
{
title: "Title",
value: (c) =>
c.payloadInlineContent?.title ??
c.payloadNotification?.title ??
c.payloadSearch?.title ??
c.payloadSearchHomepage?.title,
},
{
title: "Body",
value: (c) =>
c.payloadInlineContent?.ctaText ??
c.payloadNotification?.body ??
c.payloadSearch?.body ??
c.payloadSearchHomepage?.body,
},
]}
/>
</CardContainer>
</MiniSideBar>
);
}
117 changes: 117 additions & 0 deletions src/components/Creatives/NewCreative.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { Box, Snackbar } from "@mui/material";
import { Form, Formik } from "formik";
import { useHistory, useLocation, useParams } from "react-router-dom";
import { CreativeInput } from "graphql/types";
import { CreativeFields } from "./CreativeFields";
import {
AdvertiserCreativesDocument,
useCreateCreativeMutation,
} from "graphql/creative.generated";
import { useState } from "react";
import { CardContainer } from "components/Card/CardContainer";
import { ErrorDetail } from "components/Error/ErrorDetail";
import { FormikSubmitButton } from "form/FormikHelpers";
import { CreativeSchema } from "validation/CreativeSchema";

interface Params {
advertiserId: string;
}

function wait(ms: number) {
return new Promise((resolve) => window.setTimeout(resolve, ms));
}

export const NewCreative: React.FC = () => {
const params = useParams<Params>();
const history = useHistory();
const location = useLocation<CreativeInput>();

const defaultValue: CreativeInput & { targetUrlValid: boolean } = {
advertiserId: params.advertiserId,
state: "active",
name: "",
type: {
code: "",
name: "",
},
targetUrlValid: false,
payloadNotification: {
body: "",
targetUrl: "",
title: "",
},
startAt: null,
endAt: null,
};

const initialValue = location.state ?? defaultValue;

const [createCreativeMutation, { error }] = useCreateCreativeMutation({
refetchQueries: [
{
query: AdvertiserCreativesDocument,
variables: { advertiserId: params.advertiserId },
},
],
});

const [id, setId] = useState("");

const doSubmit = async (values: CreativeInput) => {
const input: CreativeInput = {
advertiserId: values.advertiserId,
name: values.name,
payloadNotification: values.payloadNotification,
startAt: values.startAt,
endAt: values.endAt,
state: values.state,
type: values.type,
};

const response = await createCreativeMutation({
variables: { input },
});
const id = response.data?.createCreative.id;
if (id) {
setId(id);
await wait(2000);
history.replace(id);
}
};

return (
<Box>
<CardContainer header="New creative">
<Formik
initialValues={initialValue}
onSubmit={doSubmit}
validationSchema={CreativeSchema}
>
<Form>
<CreativeFields allowTypeChange={true} />

<ErrorDetail
error={error}
additionalDetails="Unable to create creative"
/>

<Box
mt={1}
display="flex"
justifyContent="flex-end"
alignItems="baseline"
>
<FormikSubmitButton isCreate={true} allowNavigation={!!id} />
</Box>
</Form>
</Formik>

<Snackbar
message={`Creative ${id} successfully created`}
open={!!id}
autoHideDuration={5000}
/>
</CardContainer>
</Box>
);
};
26 changes: 26 additions & 0 deletions src/components/Creatives/NotificationFields.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { FormikTextField } from "form/FormikHelpers";
import { UrlResolver } from "components/Url/UrlResolver";

export function NotificationFields() {
return (
<>
<FormikTextField
name="payloadNotification.title"
label="Title"
maxLengthInstantFeedback={30}
/>
<FormikTextField
name="payloadNotification.body"
label="Body"
maxLengthInstantFeedback={60}
/>

<UrlResolver
name="payloadNotification.targetUrl"
validator="targetUrlValid"
label="Ad Target URL"
helperText="Example - https://brave.com/brave-rewards/"
/>
</>
);
}
3 changes: 1 addition & 2 deletions src/components/Drawer/MiniSideBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ export default function MiniSideBar({ children }: PropsWithChildren) {
/>
),
},
// Possible future enhancements, not visible to user but help keep spacing
{
label: "Creatives",
href: "/user/main/creatives",
Expand All @@ -53,8 +52,8 @@ export default function MiniSideBar({ children }: PropsWithChildren) {
sx={{ color: "text.secondary" }}
/>
),
disabled: true,
},
// Possible future enhancements, not visible to user but help keep spacing
{
label: "Assets",
href: "/user/main/assets",
Expand Down
39 changes: 1 addition & 38 deletions src/components/EnhancedTable/renderers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,8 @@ import {
useUpdateCampaignMutation,
} from "graphql/campaign.generated";
import { AdvertiserCampaignsDocument } from "graphql/advertiser.generated";
import {
useUpdateAdMutation,
useUpdateAdSetMutation,
} from "graphql/ad-set.generated";
import { useUpdateAdSetMutation } from "graphql/ad-set.generated";
import { OnOff } from "../Switch/OnOff";
import { AdDetails } from "user/ads/AdList";
import { displayFromCampaignState } from "util/displayState";
import { AdSetDetails } from "user/adSet/AdSetList";

Expand Down Expand Up @@ -163,36 +159,3 @@ export function adSetOnOffState(c: AdSetDetails): ReactNode {
/>
);
}

export function adOnOffState(c: AdDetails): ReactNode {
const [updateAd, { loading }] = useUpdateAdMutation({
refetchQueries: [
{
query: AdvertiserCampaignsDocument,
variables: { id: c.campaignId },
},
],
});

return (
<OnOff
onChange={(s) => {
{
updateAd({
variables: {
updateAdInput: {
id: c.id,
state: s,
},
},
});
}
}}
loading={loading}
state={c.state}
end={c.campaignEnd}
source={c.campaignSource}
type="Ad"
/>
);
}
Loading

0 comments on commit a2bc86e

Please sign in to comment.