-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: clone campaigns * feat: clone campaign * fix: payment type
- Loading branch information
1 parent
1f2ac78
commit 87a2a67
Showing
18 changed files
with
499 additions
and
168 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
import { | ||
Box, | ||
Button, | ||
Chip, | ||
Dialog, | ||
DialogActions, | ||
DialogContent, | ||
DialogContentText, | ||
DialogTitle, | ||
LinearProgress, | ||
} from "@mui/material"; | ||
import { | ||
CampaignFragment, | ||
useCreateCampaignMutation, | ||
} from "graphql/campaign.generated"; | ||
import { useHistory } from "react-router-dom"; | ||
import { useState } from "react"; | ||
import { AdvertiserCampaignsDocument } from "graphql/advertiser.generated"; | ||
import { createCampaignFromFragment } from "form/fragmentUtil"; | ||
import { useAdvertiser } from "auth/hooks/queries/useAdvertiser"; | ||
import ContentCopyIcon from "@mui/icons-material/ContentCopy"; | ||
|
||
interface Props { | ||
campaignFragment?: CampaignFragment | null; | ||
useChip?: boolean; | ||
} | ||
|
||
export function CloneCampaign({ campaignFragment, useChip }: Props) { | ||
const { advertiser } = useAdvertiser(); | ||
const history = useHistory(); | ||
const [open, setOpen] = useState(false); | ||
|
||
const [copyCampaign, { loading }] = useCreateCampaignMutation({ | ||
refetchQueries: [ | ||
{ | ||
query: AdvertiserCampaignsDocument, | ||
variables: { id: advertiser.id }, | ||
}, | ||
], | ||
onCompleted(data) { | ||
history.push( | ||
`/user/main/adsmanager/advanced/${data.createCampaign.id}/settings`, | ||
); | ||
}, | ||
onError() { | ||
alert(`Unable to clone campaign`); | ||
}, | ||
}); | ||
|
||
return ( | ||
<Box> | ||
{useChip ? ( | ||
<Chip | ||
color="primary" | ||
label="Clone" | ||
onClick={() => { | ||
setOpen(true); | ||
}} | ||
disabled={loading || !campaignFragment} | ||
icon={<ContentCopyIcon fontSize="small" />} | ||
/> | ||
) : ( | ||
<Button | ||
color="primary" | ||
variant="text" | ||
size="small" | ||
onClick={(e) => { | ||
e.preventDefault(); | ||
setOpen(true); | ||
}} | ||
disabled={loading || !campaignFragment} | ||
startIcon={<ContentCopyIcon />} | ||
> | ||
Clone Campaign | ||
</Button> | ||
)} | ||
<Dialog open={open} onClose={() => setOpen(false)}> | ||
<DialogTitle>{`Copy campaign: "${campaignFragment?.name}"?`}</DialogTitle> | ||
<DialogContent> | ||
<DialogContentText> | ||
Copying a campaign will take all properties including ad sets and | ||
ads, and create a new draft campaign with them. | ||
</DialogContentText> | ||
{loading && <LinearProgress />} | ||
</DialogContent> | ||
<DialogActions> | ||
<Button onClick={() => setOpen(false)} disabled={loading}> | ||
Cancel | ||
</Button> | ||
<Button | ||
disabled={loading} | ||
onClick={(e) => { | ||
e.preventDefault(); | ||
if (campaignFragment) { | ||
copyCampaign({ | ||
variables: { | ||
input: createCampaignFromFragment(campaignFragment), | ||
}, | ||
}); | ||
} else { | ||
alert("No campaign selected"); | ||
} | ||
}} | ||
> | ||
Clone | ||
</Button> | ||
</DialogActions> | ||
</Dialog> | ||
</Box> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,34 +1,41 @@ | ||
import { Button, Stack } from "@mui/material"; | ||
import { useContext } from "react"; | ||
import { DraftContext } from "state/context"; | ||
import { useHistory } from "react-router-dom"; | ||
import { useFormikContext } from "formik"; | ||
import { CampaignForm } from "user/views/adsManager/types"; | ||
import ArrowBackIcon from "@mui/icons-material/ArrowBack"; | ||
import RemoveIcon from "@mui/icons-material/Remove"; | ||
import { Link as RouterLink } from "react-router-dom"; | ||
|
||
export function ActionButtons() { | ||
const history = useHistory(); | ||
const { values } = useFormikContext<CampaignForm>(); | ||
const { setDrafts } = useContext(DraftContext); | ||
|
||
return ( | ||
<Stack mt={3} spacing={2} mr={1}> | ||
<Button size="small" onClick={() => history.push("/user/main")}> | ||
Return to dashboard | ||
</Button> | ||
{values.draftId !== undefined && ( | ||
<Button | ||
startIcon={<RemoveIcon />} | ||
component={RouterLink} | ||
size="small" | ||
color="error" | ||
sx={{ mr: 1 }} | ||
to="/user/main" | ||
onClick={() => { | ||
localStorage.removeItem(values.draftId!); | ||
setDrafts(); | ||
history.push("/user/main"); | ||
}} | ||
> | ||
Discard campaign | ||
</Button> | ||
)} | ||
<Button | ||
size="small" | ||
component={RouterLink} | ||
to="/user/main" | ||
startIcon={<ArrowBackIcon />} | ||
> | ||
Return to dashboard | ||
</Button> | ||
</Stack> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
import { CreateAdSetInput, CreateCampaignInput } from "graphql/types"; | ||
import moment from "moment"; | ||
import { CampaignFragment } from "graphql/campaign.generated"; | ||
import { AdSetFragment } from "graphql/ad-set.generated"; | ||
|
||
export function createCampaignFromFragment( | ||
data: CampaignFragment, | ||
): CreateCampaignInput { | ||
const adSets: CreateAdSetInput[] = data.adSets.map((adSet) => | ||
createAdSetFromFragment(adSet), | ||
); | ||
|
||
const two = moment().utc().add(3, "days"); | ||
return { | ||
adSets: adSets && adSets.length > 0 ? adSets : undefined, | ||
advertiserId: data.advertiser.id, | ||
budget: data.budget, | ||
currency: data.currency, | ||
dailyBudget: data.dailyBudget, | ||
dailyCap: data.dailyCap, | ||
dayPartings: (data.dayPartings ?? []).map((d) => ({ | ||
dow: d.dow, | ||
startMinute: d.startMinute, | ||
endMinute: d.endMinute, | ||
})), | ||
dayProportion: data.dayProportion, | ||
startAt: two.startOf("day").toISOString(), | ||
endAt: two.endOf("day").toISOString(), | ||
externalId: data.externalId, | ||
format: data.format, | ||
geoTargets: (data.geoTargets ?? []).map((g) => ({ | ||
code: g.code, | ||
name: g.name, | ||
})), | ||
name: `${data.name} - Copy`, | ||
pacingStrategy: data.pacingStrategy, | ||
source: data.source.toLowerCase(), | ||
state: "draft", | ||
type: data.type, | ||
paymentType: data.paymentType, | ||
}; | ||
} | ||
|
||
export function createAdSetFromFragment( | ||
data: AdSetFragment, | ||
campaignId?: string, | ||
): CreateAdSetInput { | ||
return { | ||
campaignId, | ||
ads: (data.ads ?? []) | ||
.filter((ad) => ad.state !== "deleted") | ||
.map((ad) => ({ | ||
creativeId: ad.creative.id, | ||
price: ad.price, | ||
priceType: ad.priceType, | ||
})), | ||
bannedKeywords: data.bannedKeywords, | ||
billingType: data.billingType ?? "cpm", | ||
conversions: (data.conversions ?? []).map((c) => ({ | ||
observationWindow: c.observationWindow, | ||
type: c.type, | ||
urlPattern: c.urlPattern, | ||
})), | ||
execution: data.execution, | ||
keywordSimilarity: data.keywordSimilarity, | ||
keywords: data.keywords, | ||
name: `${data.name ? data.name : data.id.split("-")[0]} - Copy`, | ||
negativeKeywords: data.negativeKeywords, | ||
oses: (data.oses ?? []).map((o) => ({ name: o.name, code: o.code })), | ||
perDay: data.perDay, | ||
segments: (data.segments ?? []).map((o) => ({ | ||
name: o.name, | ||
code: o.code, | ||
})), | ||
state: data.state, | ||
totalMax: data.totalMax, | ||
}; | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Oops, something went wrong.