Skip to main content

Copying an existing Campaign to Reorder

How can we copy an existing Campaign, modify it slightly and then order?

Copying Campaign user journey works as such:

  • User clicks "Create New From This" button (or you programmatically open the modal)
  • A modal opens:
    • If the products/contracts in the do not exist at all:
      • modal self closes and chooses "Let me choose products/contracts" option
      • user sees an alert that some products/contracts are removed
    • If the products/contracts in the basket partially exist (some may be removed):
      • modal does NOT self close and lets the user decide whether to "Copy As Is" or "Let me choose products/contracts"
      • user sees an alert that some products/contracts are removed
  • User clicks one of the following buttons:
    • "Let me choose products/contracts":
      • the modal closes
      • another modal opens with the complete order journey with the "Select Products" step set as active
    • "Copy As Is":
      • the modal closes
      • HAPI Elements automatically adds the products/contracts to user's basket
      • another modal opens with the complete order journey with "Order Review" step set as active

There are a couple of ways to initiate th copy campaign journey:

Letting the user copy a campaign by opening the "select products/contracts or copy as is" modal with copying journey and decide whether they want new products/contracts in the new campaign or existing products/contracts

This is equivalent to user clicking on "Create New From This" button on the Campaign Detail Card widget however the user journey to copy is manually initiated by you programmatically.


To initiate the journey, you can do:

// your state management variable to indicate "loading" state:
let isInitiatingCopyJourney = false

const initiateCopyCampaignJourney = async (campaignId) => {
// get the campaign details by ID
// if you already have the Campaign object then you can skip this step
const campaignToCopy = await window.hapi.campaign.api.getCampaign(campaignId)
const modifiedCampaignToCopy = {
...campaignToCopy,
// your modifications to the campaign object
// like target group, country, payment method, orderedProducts etc
}
try {
isInitiatingCopyJourney = true
const campaignProductsAndContractsBasketMeta = window.hapi.basket.utils.getBasketProductMetaFromCampaignForm(modifiedCampaignToCopy)
const productData = await window.hapi.basket.service.getProducts.run(campaignProductsAndContractsBasketMeta)
window.hapi.modal.service.openModal(window.hapi.modal.utils.modalKeys.campaignCopySelection, {
products: productData,
campaign: modifiedCampaignToCopy,
})
} catch (error) {
console.log('Campaign Copy Journey could not be started', error)
} finally {
isInitiatingCopyJourney = false
}

}

Letting the user copy a campaign by deciding on behalf of the user whether to "select products/contracts or copy as is" and user selecting products/contracts or copying as is & reviewing the order (skipping the "select products/contracts or copy as is" modal)

// your state management variable to indicate "loading" state:
let isInitiatingCopyJourney = false

const initiateCopyCampaignJourney = async (campaignId) => {
// get the campaign details by ID
// if you already have the Campaign object then you can skip this step
const campaignToCopy = await window.hapi.campaign.api.getCampaign(campaignId)
const modifiedCampaignToCopy = {
...campaignToCopy,
// your modifications to the campaign object
// like target group, country, payment method, orderedProducts etc
}
try {
isInitiatingCopyJourney = true

const withExistingProducts = true
// when true, HAPI Elements checks if products/contracts are all or partially removed:
// when all removed:
// user sees "select products" step of the order journey
// user sees an alert about removed products/contracts
// when partially removed:
// user sees "order review" step of the order journey
// user sees an alert about removed products/contracts
// in other words, even if you specify `withExistingProducts` as true
// user may be forced to select products/contracts as
// there may be no products/contracts to order

await window.hapi.campaign.service.copyCampaign.run(modifiedCampaignToCopy, withExistingProducts)
} catch (error) {
console.log('Campaign Copy Journey could not be started', error)
} finally {
isInitiatingCopyJourney = false
}

}

Programmatically copying a campaign without showing any HAPI Elements Modals to the user

There are a couple of payment methods you can use to pay for the copied campaign:

  1. ATS Payments
  2. End User Payments

If you intend to pay with payment methods other than the Wallet, then skip reading the following section about "paying with user's wallet balance".

When you intend to pay with user's wallet balance

This is considered bad practice when the "wallet" payment method is used as you will be spending their wallet balance on behalf of them without asking for their permission and will cause confusion on user's end as they will wonder why their balance has been deducted. To overcome this bad practice, the following flow need to be added by you in your own application:

  • you have a button in your UI to "Copy campaign"
  • when user clicks, you open a popup (or some other way, like showing a page, totally up to you):
    • clearly show them:
      • what is the total price of the campaign
      • show products that will be ordered as part of the campaign
      • show what you have modified (if any) when copying the campaign (like Country of the Vacancy changed, salary changed etc.)
      • explain them wallet balance will be deducted and ask for their "yes" or "no"
    • user selects "yes" to order:
      • you call HAPI Elements JS API and initiate ordering of the copied campaign
      • you show an alert when campaign was copied successfully
      • HAPI Elements will automatically update wallet balance
      • HAPI Elements will automatically refetch campaigns list
    • user selects "no" to order:
      • you close your popup (or send user back to the page they came from)
      • you DO NOT call HAPI Elements JS API to initiate ordering of the copied campaign
note

Even if you select "wallet", "purchase order" or "direct charge" while copying the campaign as part of paymentMethod of CampaignCreateForm type, if the campaign only has "contracts", since there is nothing to pay for and credits will be deducted from user's contract, the paymentMethod will automatically be set by HAPI Elements to be ATS Managed. You can read more about this in Wallet Balance Payment Method and Direct Charge Payment Method and Purchase Order Payment Method.

Here are some variables and functions that you can use while constructing your own copy campaign journey:

// Getting campaign details by given ID
const campaignId = "campaign-id-here"
const campaignToCopy = await window.hapi.campaign.api.getCampaign(campaignId)
const modifiedCampaignToCopy = {
...campaignToCopy,
// your modifications to the campaign object
// like target group, country, payment method, orderedProducts etc
}
// Getting products/contracts basket meta of a given campaign
const campaignProductsAndContractsBasketMeta = window
.hapi
.basket
.utils
.getBasketProductMetaFromCampaignForm(modifiedCampaignToCopy)

// Getting actual product/contract data by given product/contract basket meta
const arrayOfProductsAndContracts = await window
.hapi
.basket
.service
.getProducts
.run(campaignProductsAndContractsBasketMeta)

// Calculating total price of given products/contracts
const totalPriceInUSD = window.hapi.basket.utils.getProductsTotal(
arrayOfProductsAndContracts,
window.hapi.product.utils.currencyKeys.USD
)

// Getting basket products
const basketProducts = window.hapi.basket.state.products.value

// Some utilities for products/contracts:
const productOrContract = arrayOfProductsAndContracts[0];
// Getting ID of a product/contract
window.hapi.basket.utils.getBasketProductId(productOrContract)
// Getting Title of a product/contract
window.hapi.basket.utils.getBasketProductTitle(productOrContract)
// Checking if a productOrContract is a product
window.hapi.basket.utils.getIsBasketProductProduct(productOrContract)
// Checking if a productOrContract is a contract
window.hapi.basket.utils.getIsBasketProductContract(productOrContract)
// Getting price of product/contract
window.hapi.basket.utils.getBasketProductPrice(productOrContract)
// Getting board type of product/contract
window.hapi.basket.utils.getBasketProductBoardType(productOrContract)
// Getting time to process of product/contract
window.hapi.basket.utils.getBasketProductTimeToProcess(productOrContract)
// Getting duration of product/contract
window.hapi.basket.utils.getBasketProductDuration(productOrContract)
// Checking if productOrContract is already in basket
window.hapi.basket.utils.getIsProductOrContractInBasket(productOrContract)
// A user can only order contracts of the same group in a single order.
// You can use this to find conflicting contracts
const contract = arrayOfProductsAndContracts.find(window.hapi.basket.utils.getIsBasketProductContract)
window.hapi.basket.utils.getConflictingContractsWithDifferentGroupsInBasket(contract, basketProducts)

// Updating wallet (and the balance)
window.hapi.wallet.service.getWallet.run()

// Refreshing campaigns list
window.hapi.campaign.service.getCampaigns.run()

// Getting a blank campaign form
const blankCampaignForm = window.hapi.campaign.utils.getInitialCampaignForm()
When you intend to pay with purchase order or ATS managed payment methods
const campaignId = "campaign-id-here"
const campaignToCopy = await window.hapi.campaign.api.getCampaign(campaignId)
const modifiedCampaignToCopy = {
...campaignToCopy,
paymentMethod: window.hapi.orderJourney.utils.paymentMethodKeys.purchaseOrder // or .atsManaged
// your modifications to the campaign object
// like target group, country, payment method, orderedProducts etc
}

Ordering the copied campaign via the Service function

With this approach, HAPI Elements handles the orchestration of the ordering flow meaning that it has some opinions. If you are showing campaign and order related Elements widgets somewhere in your UI, the user will see the UI change because of these opinions. If you don't show any UI to the end user or you don't mind the UI changing behind the scenes, this is the recommended way to order a copied campaign. If not, you can use Ordering the copied campaign via the API function to order but it is a bit more complex and will require you to manage the flow.


The opinions of the service function are as follows:

  • it sets window.hapi.campaign.state.campaignIsCreating to true
  • it automatically prepares the order request body through window.hapi.campaign.state.campaignForm
  • it orders the campaign through the API:
    • if order is successful
      • it resets the campaignForm to the default blank form
      • it clears the basket
      • it shows a success alert
      • it sets the window.hapi.orderJourney.state.stepActiveIndex to the index of order-confirmation step
      • it closes "campaign copy journey" modal
      • it gets the wallet so balance updates
      • it refreshes campaigns list
      • it returns the created campaign
    • if order errors
      • it shows an error message "something went wrong"
      • it sets the window.hapi.orderJourney.state.stepActiveIndex to the index of order-review step
      • it sets window.hapi.orderJourney.state.orderErrors with error?.response?.data || null
      • it throws the error so error bubbles up
    • in either case (in the finally block of try catch)
      - it sets `window.hapi.campaign.state.campaignIsCreating` to false

To show the errors you can use Order Errors widget.

// your state management variable to indicate "loading" state:
let isCopyingCampaign = false

const campaignId = "campaign-id-here"
const campaignToCopy = await window.hapi.campaign.api.getCampaign(campaignId)
const modifiedCampaignToCopy = {
...campaignToCopy,
// your modifications to the campaign object
// like target group, country, payment method, orderedProducts etc
}
const campaignProductsAndContractsBasketMeta = window.hapi.basket.utils.getBasketProductMetaFromCampaignForm(modifiedCampaignToCopy)
try {
isCopyingCampaign = true
// copy the campaign's products to basket
window.hapi.basket.state.productsMeta = campaignProductsAndContractsBasketMeta
// set the modified campaign as the current `campaignForm`
window.hapi.campaign.state.campaignForm = modifiedCampaignToCopy
// `orderCampaign` service function will automatically handle conversion of
// `campaignForm` to the API request structure and do necessary actions
// mentioned above such as showing alerts, setting active step etc.
const orderedCampaign = await window.hapi.campaign.service.orderCampaign()
} catch (error) {
// HAPI elements will automatically show an alert
// and set `orderErrors` with the error received
// you should be showing "he-orderjourney-ordererrors" widget
// in your UI so errors are displayed
} finally {
isCopyingCampaign = false
}

Ordering the copied campaign via the API function

With this approach, the management of copying is up to you. You need to handle refreshing of the wallet as well as the errors.

To handle the errors you can use Order Errors widget and the orderErrors object in orderJourney state submodule. A complete example is shown below:

// your state management variable to indicate "loading" state:
let isCopyingCampaign = false

const campaignId = "campaign-id-here"
const campaignToCopy = await window.hapi.campaign.api.getCampaign(campaignId)
const modifiedCampaignToCopy = {
...campaignToCopy,
// your modifications to the campaign object
// like target group, country, payment method, orderedProducts etc
}
const orderRequestBody = window.hapi.campaign.utils.getCopyCampaignRequestBody(modifiedCampaignToCopy)
try {
isCopyingCampaign = true
const orderedCampaign = await window.hapi.campaign.api.orderCampaign(orderRequestBody)
// refetch wallet so balance updates
await window.hapi.wallet.service.getWallet.run()
// clear the basket
window.hapi.basket.service.clear()
// refresh campaigns list
window.hapi.campaign.service.getCampaigns.run()
// show your own alert for success
// myOwnAlert.show()
} catch (error) {
// this will ensure that Order Errors widget displays the errors
window.hapi.orderJourney.state.orderErrors =
error?.response?.data || null
// show your own alert for error
// myOwnAlert.show()
} finally {
isCopyingCampaign = false
}