diff --git a/CHANGELOG.md b/CHANGELOG.md index 9cc3d5078..e05ccbd61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [1.4.0] - 2019-11-22 +### Added +- Conversions definition +- Target URL placeholders +- Pops ad units configuration +- Site domain requirement +### Changed +- Hid Requires section from site filtering + ## [1.2.5] - 2019-10-01 ### Changed - Upgrade @angular/cli to version 1.7.0 @@ -133,7 +142,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Advertiser features (Campaigns & Ads) - Publisher features (Sites & AdUnits) -[Unreleased]: https://github.com/adshares/adpanel/compare/v1.2.5...develop +[Unreleased]: https://github.com/adshares/adpanel/compare/v1.4.0...develop +[1.4.0]: https://github.com/adshares/adpanel/compare/v1.2.5...v1.4.0 [1.2.5]: https://github.com/adshares/adpanel/compare/v1.2.4...v1.2.5 [1.2.4]: https://github.com/adshares/adpanel/compare/v1.2.1...v1.2.4 [1.2.1]: https://github.com/adshares/adpanel/compare/v0.11.0...v1.2.1 diff --git a/src/app/admin/dashboard/dashboard.component.ts b/src/app/admin/dashboard/dashboard.component.ts index 55f825c21..55513e0a7 100644 --- a/src/app/admin/dashboard/dashboard.component.ts +++ b/src/app/admin/dashboard/dashboard.component.ts @@ -42,7 +42,7 @@ export class DashboardComponent extends HandleSubscription implements OnInit { link: '/admin/dashboard/privacy', values: [ {name: 'Privacy', icon: 'assets/images/preferences.svg'}, - {name: 'Terms and condition', icon: 'assets/images/preferences.svg'}, + {name: 'Terms and conditions', icon: 'assets/images/preferences.svg'}, ], }, { diff --git a/src/app/admin/finances/finances-settings.component.html b/src/app/admin/finances/finances-settings.component.html index a30d35eb4..478675459 100644 --- a/src/app/admin/finances/finances-settings.component.html +++ b/src/app/admin/finances/finances-settings.component.html @@ -65,7 +65,7 @@

Walle

Cold wallet settings

- Enable Cold Wallet + Enable cold wallet

License

@@ -25,12 +25,12 @@

License

diff --git a/src/app/admin/privacy-and-terms-settings/privacy-and-terms-settings.component.html b/src/app/admin/privacy-and-terms-settings/privacy-and-terms-settings.component.html index f3b4f89bc..0cdb4a43d 100644 --- a/src/app/admin/privacy-and-terms-settings/privacy-and-terms-settings.component.html +++ b/src/app/admin/privacy-and-terms-settings/privacy-and-terms-settings.component.html @@ -1,6 +1,6 @@
-

Privacy Settings

+

Privacy

Priva -

Terms Settings

+

Terms and conditions

save_alt - Download Advertisers Report + Download advertisers' report save_alt - Download Publishers Report + Download publishers' report

diff --git a/src/app/advertiser/advertiser-routing.module.ts b/src/app/advertiser/advertiser-routing.module.ts index b4cc184b7..e58896083 100644 --- a/src/app/advertiser/advertiser-routing.module.ts +++ b/src/app/advertiser/advertiser-routing.module.ts @@ -4,6 +4,7 @@ import { RouterModule, Routes } from '@angular/router'; import { AdvertiserComponent } from './advertiser.component'; import { EditCampaignComponent } from './edit-campaign/edit-campaign.component'; import { EditCampaignBasicInformationComponent } from './edit-campaign/edit-campaign-basic-info/edit-campaign-basic-information.component'; +import { EditCampaignConversionComponent } from './edit-campaign/edit-campaign-conversion/edit-campaign-conversion.component'; import { EditCampaignAdditionalTargetingComponent } from './edit-campaign/edit-campaign-additional-targeting/edit-campaign-additional-targeting.component'; import { EditCampaignCreateAdsComponent } from './edit-campaign/edit-campaign-create-ads/edit-campaign-create-ads.component'; import { EditCampaignSummaryComponent } from './edit-campaign/edit-campaign-summary/edit-campaign-summary.component'; @@ -64,6 +65,10 @@ const advertiserRoutes: Routes = [ path: 'basic-information', component: EditCampaignBasicInformationComponent, }, + { + path: 'conversion', + component: EditCampaignConversionComponent, + }, { path: 'additional-targeting', component: EditCampaignAdditionalTargetingComponent, diff --git a/src/app/advertiser/advertiser.module.ts b/src/app/advertiser/advertiser.module.ts index 6553d6bc3..46d57f5cd 100644 --- a/src/app/advertiser/advertiser.module.ts +++ b/src/app/advertiser/advertiser.module.ts @@ -6,13 +6,15 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatDatepickerModule } from '@angular/material/datepicker'; import { MatMomentDateModule } from '@angular/material-moment-adapter'; -import { MatInputModule } from '@angular/material'; +import { MatCheckboxModule, MatInputModule } from '@angular/material'; import { MatSelectModule } from '@angular/material/select'; import { MatExpansionModule } from '@angular/material/expansion'; import { MatMenuModule } from '@angular/material/menu'; +import { MatTabsModule } from '@angular/material/tabs'; import { FileUploadModule } from 'ng2-file-upload'; -import { OwlDateTimeModule, OwlNativeDateTimeModule, OWL_DATE_TIME_FORMATS } from 'ng-pick-datetime'; +import { OWL_DATE_TIME_FORMATS, OwlDateTimeModule, OwlNativeDateTimeModule } from 'ng-pick-datetime'; import { OwlMomentDateTimeModule } from 'ng-pick-datetime-moment'; +import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; import { AppCommonModule } from 'common/common.module'; import { AdvertiserComponent } from './advertiser.component'; import { AdvertiserRoutingModule } from './advertiser-routing.module'; @@ -20,6 +22,7 @@ import { EditCampaignComponent } from './edit-campaign/edit-campaign.component'; import { CampaignListComponent } from './campaign-list/campaign-list.component'; import { CampaignListItemComponent } from './campaign-list/campaign-list-item/campaign-list-item.component'; import { EditCampaignBasicInformationComponent } from './edit-campaign/edit-campaign-basic-info/edit-campaign-basic-information.component'; +import { EditCampaignConversionComponent } from './edit-campaign/edit-campaign-conversion/edit-campaign-conversion.component'; import { EditCampaignAdditionalTargetingComponent } from './edit-campaign/edit-campaign-additional-targeting/edit-campaign-additional-targeting.component'; import { EditCampaignCreateAdsComponent } from './edit-campaign/edit-campaign-create-ads/edit-campaign-create-ads.component'; import { EditCampaignSummaryComponent } from './edit-campaign/edit-campaign-summary/edit-campaign-summary.component'; @@ -28,32 +31,32 @@ import { CampaignDetailsComponent } from './campaign-details/campaign-details.co import { AdListComponent } from './campaign-details/ad-list/ad-list.component'; import { AdListItemComponent } from './campaign-details/ad-list/ad-list-item/ad-list-item.component'; -import { CampaignBudgetPerDayPipe } from "common/pipes/campaign-budget-per-day.pipe"; +import { CampaignBudgetPerDayPipe } from 'common/pipes/campaign-budget-per-day.pipe'; import { AdvertiserGuard } from './advertiser-guard.service'; import { CampaignResolver } from './resolvers/campaign.resolver'; import { TargetingCriteriaResolver } from './resolvers/targeting-criteria.resolver'; -import { - DATE_AND_TIME_PICKER_FORMATS -} from "common/utilities/consts"; - +import { DATE_AND_TIME_PICKER_FORMATS } from 'common/utilities/consts'; const matModules = [ + MatCheckboxModule, MatExpansionModule, MatFormFieldModule, MatMomentDateModule, MatDatepickerModule, MatInputModule, MatMenuModule, - MatSelectModule + MatSelectModule, + MatTabsModule, ]; const editCampaignComponents = [ EditCampaignComponent, EditCampaignBasicInformationComponent, + EditCampaignConversionComponent, EditCampaignAdditionalTargetingComponent, EditCampaignCreateAdsComponent, - EditCampaignSummaryComponent + EditCampaignSummaryComponent, ]; const advertiserComponents = [ @@ -63,7 +66,7 @@ const advertiserComponents = [ DashboardComponent, CampaignDetailsComponent, AdListComponent, - AdListItemComponent + AdListItemComponent, ]; @NgModule({ @@ -72,25 +75,26 @@ const advertiserComponents = [ HttpModule, AppCommonModule, AdvertiserRoutingModule, + FontAwesomeModule, FormsModule, ReactiveFormsModule, FileUploadModule, OwlDateTimeModule, OwlNativeDateTimeModule, OwlMomentDateTimeModule, - ...matModules + ...matModules, ], providers: [ AdvertiserGuard, CampaignResolver, TargetingCriteriaResolver, - {provide: OWL_DATE_TIME_FORMATS, useValue: DATE_AND_TIME_PICKER_FORMATS} + {provide: OWL_DATE_TIME_FORMATS, useValue: DATE_AND_TIME_PICKER_FORMATS}, ], declarations: [ CampaignBudgetPerDayPipe, ...advertiserComponents, - ...editCampaignComponents + ...editCampaignComponents, ] }) export class AdvertiserModule { diff --git a/src/app/advertiser/advertiser.service.ts b/src/app/advertiser/advertiser.service.ts index a90e7bf0c..7137f78ec 100644 --- a/src/app/advertiser/advertiser.service.ts +++ b/src/app/advertiser/advertiser.service.ts @@ -3,7 +3,7 @@ import { Injectable } from '@angular/core'; import { Observable } from 'rxjs/Observable'; import { Store } from "@ngrx/store"; import { environment } from 'environments/environment'; -import { Campaign, CampaignTotalsResponse } from 'models/campaign.model'; +import { Campaign, CampaignConversion, CampaignTotalsResponse } from 'models/campaign.model'; import { TargetingOption } from 'models/targeting-option.model'; import { parseTargetingForBackend } from 'common/components/targeting/targeting.helpers'; import { NavigationStart, Router } from "@angular/router"; @@ -59,7 +59,6 @@ export class AdvertiserService { const targetingObject = parseTargetingForBackend(campaign.targetingArray); Object.assign(campaign, {targeting: targetingObject}); } - return this.http.patch(`${environment.apiUrl}/campaigns/${campaign.id}`, {campaign}); } diff --git a/src/app/advertiser/campaign-details/campaign-details.component.html b/src/app/advertiser/campaign-details/campaign-details.component.html index 85cc9b9a0..80387a793 100644 --- a/src/app/advertiser/campaign-details/campaign-details.component.html +++ b/src/app/advertiser/campaign-details/campaign-details.component.html @@ -12,7 +12,7 @@ class=" dwmth-copy dwmth-copy--small"> - My Campaigns + My campaigns

- Delete Campaign + Delete campaign
@@ -188,7 +188,7 @@ class=" dwmth-copy--small label"> - Max Cpm + Max CPM

- Date of Start + dwmth-copy--small + label" + > + Start date

- Date of End + End date

- Edit Basic Info + Edit basic info
+ +
+
+
+ New feature! +
+ + +
+
+
+
+ Name +
+
+ Type +
+
+ Cost +
+
+ Occurrences +
+
+ +
+
+ {{ conversion.name }} +
+
+ {{ conversion.eventType }} +
+
+ {{ conversion.cost | formatMoney:2 }} +
+
+ {{ conversion.occurrences }} +
+
+
+
+
@@ -307,7 +400,7 @@ dwmth-icon--prepend" src="assets/images/edit-blue.svg" alt=""> - {{(!targeting.requires.length && !targeting.excludes.length) ? 'Add Targeting' : 'Edit Targeting'}} + {{(!targeting.requires.length && !targeting.excludes.length) ? 'Add targeting' : 'Edit targeting'}}

Requires:

+ dwmth-copy--semi">Targeting:

Excludes:

+ dwmth-copy--semi">Exclusions:
- +
- List of Ads + List of ads

Add new icon - {{campaign.ads.length ? 'Create new Ad' : 'Add first Ad' }} + {{campaign.ads.length ? 'Add new banner ad' : 'Upload your first ad'}} diff --git a/src/app/advertiser/campaign-list/campaign-list.component.html b/src/app/advertiser/campaign-list/campaign-list.component.html index 4899d8fb8..b151a3730 100644 --- a/src/app/advertiser/campaign-list/campaign-list.component.html +++ b/src/app/advertiser/campaign-list/campaign-list.component.html @@ -13,7 +13,7 @@ class="posters-list__totals-row"> - Total - All Campaigns + Total - All campaigns - My Campaigns + My campaigns

- 1. Requires + Targeting @@ -31,8 +31,9 @@

- In this window, you can select the keywords that will help you - choose the sites that are related to your campaign and meet your requirements. + If you want to apply specific targeting to your campaign (e.g. language, country, device etc.), please select options below. + In case you want to target specific website, make sure that it is one of Adshares publishers. + Please note that your campaign reach will be limited by the options you select.

@@ -81,7 +82,7 @@ dwmth-heading--secondary" > - 2. Excludes + Exclusions @@ -90,8 +91,7 @@

- In this window, you can select the keywords that will help you - exclude the sites that don't match your targeting. + If you want your ads not to be displayed on specific domains, devices, in certain countries or by a specific group of users, please select options below.

@@ -157,7 +157,7 @@ save-as-draft" data-test="advertiser-edit-campaign-save-as-draft" > - Save as Draft + Save as draft
@@ -29,7 +29,7 @@ for="campaignName" class="dwmth-form-label" > - Campaign Name + Campaign name
+ + + + + Placeholders + + + +
+ List of available placeholders to use in target URL: +
+ +
+
+ {{placeholder.id}} + {{placeholder.description}} +
+
+ +
+ Place selected placeholders in the target URL. + Any of these placeholders will be replaced with the proper value. + For example {{ PLACEHOLDERS[0].id }} will be replaced with the {{ PLACEHOLDERS[0].description }}. +
+
+
+
@@ -211,7 +248,7 @@ for="campaign-budget-per-day" class="dwmth-form-label" > - Budget ({{currencyCode}} / day) + Budget ({{currencyCode}}/day) - Budget ({{currencyCode}} / hour) + Budget ({{currencyCode}}/hour) - + -
- - - Optional - -
+ @@ -415,12 +435,12 @@ (click)="onStepBack()" [disabled]="campaignBasicInformationSubmitted || changesSaved" data-test="advertiser-navigate-to-dashboard"> - {{ createCampaignMode ? 'Back to Dashboard' : 'Back' }} + {{ createCampaignMode ? 'Back to dashboard' : 'Back' }} +
+ +
+ + + + +
+
Delete
+
Generate link
+
+ +
+ +
+
+ + + Cannot be empty. + +
+
+ + + {{ type }} + + + + Cannot be empty. + +
+
+ + + Must be greater than zero. + +
+
+
+ +
+
+ +
+
+ +
+ + + +
+ In this mode each conversion has a unique HTTP request which must be created dynamically. + The advertiser has more control over the conversion settings compared to the basic mode. + It is possible to set different amount for each conversion. +
+ +
+ Secret phrase is a character string necessary to sign the transferred data. + If you would like to learn how to use the secret phrase please + + refer to the instruction + + . +
+ Secret phrase: + {{campaign.secret}} + + file_copy + +
+
+ + +
+ +
+ +
+ + + + + + +
Delete
+
Generate link
+
+ +
+
+
+
+ + + Cannot be empty. + +
+
+ + + {{ type }} + + + + Cannot be empty. + +
+
+
+ + + +
+ + Value is required. + +
+
+ +
+
+ + +
+ +
+ + +
+
+ +
+
+ +
+
+
+
+
+ +
+ +
+

+ Click conversion +

+
+ Click conversion type defines how clicks are registered. + By default all click events are registered when user clicks the ad banner. + If you change the setting to basic or advanced, link click events won’t be registered unless a predefined action takes place (e.g. unless a user scrolls down on advertiser’s landing page).
+ In case you change the click conversion type, please make sure to SAVE CHANGES before you proceed, otherwise your changes will not be effective. + Click the link icon to get more information. +
+ +
+ + Select click conversion type: + + + {{ clickConversionType.label }} + + + +
+ +
+
+
+ +
+ + + +
+
diff --git a/src/app/advertiser/edit-campaign/edit-campaign-conversion/edit-campaign-conversion.component.scss b/src/app/advertiser/edit-campaign/edit-campaign-conversion/edit-campaign-conversion.component.scss new file mode 100644 index 000000000..fb5a4e2cd --- /dev/null +++ b/src/app/advertiser/edit-campaign/edit-campaign-conversion/edit-campaign-conversion.component.scss @@ -0,0 +1,111 @@ +@import '../../../../styles.scss'; + +@keyframes shake { + from { + transform: rotate(25deg); + } + + to { + transform: rotate(-25deg); + } +} + +.campaign-edit-conversion { + max-width: 1300px; + margin: 0 auto; + + @include media(xxl-up) { + max-width: 1300px; + } + + @include media(xxxl-up) { + max-width: 2000px; + } + + .dwmth-box { + margin: 10px 0; + } + + .dwmth-form-input { + overflow: hidden; + } + + &__btn { + border: none; + background: transparent; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + font-size: 26px; + color: pal(blue, base); + padding: 8px 30px; + margin: -8px -30px; + + & .material-icons { + margin: 0; + font-size: inherit; + } + + &-add { + margin-left: 24px; + } + + &:focus, + &:hover { + color: darken(pal(blue, base), 10%); + } + } + + .description-type { + margin: 18px auto; + max-width: 850px; + text-align: center; + + i { + vertical-align: middle; + } + } + + &__list { + display: flex; + flex-direction: column; + } + + &__select { + height: 46px; + } + + .cell { + max-height: 46px; + height: 46px; + flex-wrap: nowrap; + } + + &__value-input { + max-width: 150px; + margin-left: 10px; + } + + .input-disabled { + pointer-events: none; + } + + &__copy-icon { + cursor: pointer; + color: pal(gray, mid-dark); + outline: none; + + &:hover, + &:focus { + color: darken(pal(gray, mid-dark), 10%); + } + } +} + +.shake { + &:focus, + &:hover { + animation: shake 0.25s linear infinite alternate; + } +} diff --git a/src/app/advertiser/edit-campaign/edit-campaign-conversion/edit-campaign-conversion.component.ts b/src/app/advertiser/edit-campaign/edit-campaign-conversion/edit-campaign-conversion.component.ts new file mode 100644 index 000000000..6eccb5121 --- /dev/null +++ b/src/app/advertiser/edit-campaign/edit-campaign-conversion/edit-campaign-conversion.component.ts @@ -0,0 +1,324 @@ +import { Component, OnInit } from '@angular/core'; +import { FormControl, FormGroup, Validators } from '@angular/forms'; +import { MatDialog } from '@angular/material'; +import { ActivatedRoute, Router } from '@angular/router'; +import { Actions } from '@ngrx/effects'; +import { Store } from '@ngrx/store'; + +import { AppState } from 'models/app-state.model'; +import { Campaign, CampaignConversion, CampaignConversionItem } from 'models/campaign.model'; +import { campaignConversionItemInitialState } from 'models/initial-state/campaign'; +import { + ClearLastEditedCampaign, + SaveConversion, + UPDATE_CAMPAIGN_FAILURE, + UPDATE_CAMPAIGN_SUCCESS +} from 'store/advertiser/advertiser.actions'; + +import { AdvertiserService } from 'advertiser/advertiser.service'; +import { HandleSubscription } from 'common/handle-subscription'; +import { ConfirmResponseDialogComponent } from 'common/dialog/confirm-response-dialog/confirm-response-dialog.component'; +import { ConversionLinkInformationDialogComponent } from 'common/dialog/information-dialog/conversion-link-information-dialog.component'; +import { ShowDialogOnError, ShowSuccessSnackbar } from 'store/common/common.actions'; +import { ClickToADSPipe } from 'common/pipes/adshares-token.pipe'; +import { adsToClicks, formatMoney } from 'common/utilities/helpers'; +import { environment } from 'environments/environment'; +import { campaignConversionClick } from 'models/enum/campaign.enum'; + +@Component({ + selector: 'app-edit-campaign-conversion', + templateUrl: './edit-campaign-conversion.component.html', + styleUrls: ['./edit-campaign-conversion.component.scss'] +}) +export class EditCampaignConversionComponent extends HandleSubscription implements OnInit { + currencyCode: string = environment.currencyCode; + readonly TYPE_ADVANCED: string = 'advanced'; + readonly TYPE_BASIC: string = 'basic'; + + private readonly CONVERSION_COUNT_MAXIMAL: number = 5; + private readonly BUDGET_TYPE_IN: string = 'in_budget'; + private readonly BUDGET_TYPE_OUT: string = 'out_of_budget'; + + readonly availableEventTypes = [ + 'Add payment info', + 'Add to cart', + 'Add to wishlist', + 'Complete registration', + 'Contact', + 'Customize Product', + 'Donate', + 'Find Location', + 'Initiate checkout', + 'Lead', + 'Purchase', + 'Schedule', + 'Search', + 'Start trial', + 'Submit application', + 'Subscribe', + 'View content', + ]; + + readonly clickConversionTypes = [ + {value: campaignConversionClick.NONE, label: 'Default'}, + {value: campaignConversionClick.BASIC, label: 'Basic link'}, + {value: campaignConversionClick.ADVANCED, label: 'Advanced link'}, + ]; + + conversionItemForms: FormGroup[] = []; + campaign: Campaign; + + validateForm: boolean = false; + submitted: boolean = false; + + action$: Actions; + + constructor( + private router: Router, + private route: ActivatedRoute, + private store: Store, + private advertiserService: AdvertiserService, + private dialog: MatDialog, + action$: Actions, + private clickToADSPipe: ClickToADSPipe + ) { + super(); + this.action$ = action$; + } + + ngOnInit() { + this.getFormDataFromStore(); + } + + get conversionItemFormsAdvanced(): FormGroup[] { + return this.conversionItemForms.filter(form => form.get('isAdvanced').value); + } + + get conversionItemFormsBasic(): FormGroup[] { + return this.conversionItemForms.filter(form => !form.get('isAdvanced').value); + } + + updateCampaignConversion(): void { + this.submitted = true; + this.validateForm = true; + + if (!this.isFormValid) { + this.submitted = false; + return; + } + + this.validateForm = false; + this.campaign = { + ...this.campaign, + conversions: this.conversionsToSave, + }; + this.store.dispatch(new SaveConversion(this.campaign)); + + this.action$ + .ofType( + UPDATE_CAMPAIGN_SUCCESS, + UPDATE_CAMPAIGN_FAILURE + ) + .first() + .subscribe((action) => { + this.submitted = false; + if (action.type === UPDATE_CAMPAIGN_SUCCESS) { + this.conversionItemForms.forEach(item => item.markAsPristine()); + this.conversionItemForms.forEach(item => item.markAsUntouched()); + this.advertiserService.getCampaign(this.campaign.id) + .first() + .subscribe( + (data) => { + this.campaign = data.campaign; + this.conversionItemForms = []; + this.adjustConversionData(this.campaign.conversions) + }, + (err) => { + this.store.dispatch(new ShowDialogOnError(err.code)) + } + ) + } + }); + } + + get isConversionClickAdvanced(): boolean { + return this.campaign.conversionClick === campaignConversionClick.ADVANCED; + } + + get isFormValid(): boolean { + this.validateAdvancedValueControl(); + return this.conversionItemForms.every(item => item.valid); + } + + validateAdvancedValueControl() { + const elementsWithError = this.conversionItemForms + .filter(el => el.controls.isAdvanced.value && !!el.controls.isValueMutable.value === false && !el.controls.value.value); + const validElements = this.conversionItemForms + .filter(el => el.controls.isAdvanced.value && !!el.controls.isValueMutable.value === true && el.controls.value.status === 'INVALID'); + + if (elementsWithError) { + elementsWithError.forEach(el => { + el.controls.value.setErrors({'required': true}) + }) + } + + if (validElements) { + validElements.forEach(el => { + el.controls.value.setErrors(null) + }) + } + } + + generateFormConversionItem(item: CampaignConversionItem): FormGroup { + const itemUuid = item.uuid; + const itemIsAdvanced = item.isAdvanced; + const isItemFromBackend = itemUuid != null; + + const valueValidators = [Validators.min(0)]; + if (!itemIsAdvanced) { + valueValidators.push(Validators.required); + } + + return new FormGroup({ + uuid: new FormControl(itemUuid), + name: new FormControl({value: item.name, disabled: isItemFromBackend}, Validators.required), + type: new FormControl({value: item.eventType, disabled: isItemFromBackend}, Validators.required), + isAdvanced: new FormControl({value: itemIsAdvanced, disabled: isItemFromBackend}), + isInBudget: new FormControl({value: item.isInBudget, disabled: isItemFromBackend || !itemIsAdvanced}), + isValueMutable: new FormControl({ + value: item.isValueMutable || 0, + disabled: isItemFromBackend || !itemIsAdvanced + }), + isRepeatable: new FormControl({value: item.isRepeatable || 0, disabled: isItemFromBackend || !itemIsAdvanced}), + value: new FormControl({value: item.value, disabled: isItemFromBackend}, valueValidators), + limit: new FormControl({value: item.limit, disabled: isItemFromBackend}, Validators.min(0)), + link: new FormControl(item.link), + }); + } + + get conversionsToSave(): CampaignConversion[] { + return this.conversionItemForms.map((form) => { + const value = form.get('value').value; + const limit = form.get('limit').value; + + return { + uuid: form.get('uuid').value, + name: form.get('name').value, + limitType: form.get('isInBudget').value ? this.BUDGET_TYPE_IN : this.BUDGET_TYPE_OUT, + eventType: form.get('type').value, + type: form.get('isAdvanced').value ? this.TYPE_ADVANCED : this.TYPE_BASIC, + value: value !== null ? adsToClicks(parseFloat(value)) : null, + limit: limit !== null ? adsToClicks(parseFloat(limit)) : null, + isValueMutable: form.get('isValueMutable').value, + isRepeatable: form.get('isRepeatable').value, + }; + }); + } + + adjustConversionData(conversions) { + conversions.forEach(conversion => { + const item = { + uuid: conversion.uuid, + name: conversion.name, + eventType: conversion.eventType, + isAdvanced: conversion.type === this.TYPE_ADVANCED, + isInBudget: conversion.limitType !== this.BUDGET_TYPE_OUT, + isValueMutable: conversion.isValueMutable, + isRepeatable: conversion.isRepeatable, + value: conversion.value !== null ? formatMoney(conversion.value) : null, + limit: conversion.limit !== null ? formatMoney(conversion.limit) : null, + link: conversion.link, + }; + this.addConversion(item); + }); + } + + + getFormDataFromStore(): void { + let subscription = this.store.select('state', 'advertiser', 'lastEditedCampaign') + .first() + .subscribe((lastEditedCampaign: Campaign) => { + this.campaign = lastEditedCampaign; + this.adjustConversionData(this.campaign.conversions); + }, () => { + }); + + this.subscriptions.push(subscription); + } + + addConversionEmpty(type: string): void { + const item = { + ...campaignConversionItemInitialState, + isAdvanced: this.TYPE_ADVANCED === type, + }; + + this.addConversion(item); + } + + addConversion(item: CampaignConversionItem): void { + if (this.conversionItemForms.length >= this.CONVERSION_COUNT_MAXIMAL) { + this.dialog.open(ConfirmResponseDialogComponent, { + data: { + title: 'Maximum conversion count reached', + message: `You are not able to add more than ${this.CONVERSION_COUNT_MAXIMAL} conversions.`, + } + }); + + return; + } + this.conversionItemForms.push(this.generateFormConversionItem(item)); + } + + deleteConversion(isAdvancedList: boolean, subListIndex: number): void { + let index = this.getIndexOnMainList(isAdvancedList, subListIndex); + + this.conversionItemForms.splice(index, 1); + } + + private getIndexOnMainList(isAdvancedList: boolean, listIndex: number) { + let mainListIndex = 0; + let listIndexTemporary = -1; + + for (; mainListIndex < this.conversionItemForms.length; mainListIndex++) { + if (this.conversionItemForms[mainListIndex].get('isAdvanced').value === isAdvancedList) { + listIndexTemporary++; + } + + if (listIndex === listIndexTemporary) { + break; + } + } + + return mainListIndex; + } + + openDialogForForm(form: FormGroup) { + const isAdvanced = form.get('isAdvanced').value; + const link = form.get('link').value; + this.openDialog(link, isAdvanced); + } + + openDialog(link: string, isAdvanced: boolean = true) { + this.dialog.open(ConversionLinkInformationDialogComponent, { + data: { + isAdvanced: isAdvanced, + link: link, + } + }); + } + + onStepBack(): void { + this.store.dispatch(new ClearLastEditedCampaign()); + this.router.navigate(['/advertiser', 'campaign', this.campaign.id]); + } + + copyToClipboard(content: string) { + document.addEventListener('copy', (e: ClipboardEvent) => { + e.clipboardData.setData('text/plain', (content)); + e.preventDefault(); + document.removeEventListener('copy', null); + }); + document.execCommand('copy'); + this.store.dispatch(new ShowSuccessSnackbar('Copied!')) + } +} diff --git a/src/app/advertiser/edit-campaign/edit-campaign-create-ads/edit-campaign-create-ads.component.html b/src/app/advertiser/edit-campaign/edit-campaign-create-ads/edit-campaign-create-ads.component.html index dbf2cf37d..353cc26ba 100644 --- a/src/app/advertiser/edit-campaign/edit-campaign-create-ads/edit-campaign-create-ads.component.html +++ b/src/app/advertiser/edit-campaign/edit-campaign-create-ads/edit-campaign-create-ads.component.html @@ -13,14 +13,14 @@ dwmth-heading dwmth-heading--primary" > - Create Ads + Upload ads

- Below you can create list of advertisements displayed during this campaign. + Please select the ad type and upload your banner ads.

@@ -88,7 +88,7 @@ for="name" class="dwmth-form-label" > - Short Headline + Short headline
- + - {{adSizes[adForm.get('size').value]}} + {{ adForm.get('size').value }}
@@ -254,10 +254,19 @@ (change)="uploadBanner($event)" data-test="advertiser-edit-campaign-create-ads-form-image-upload">

-

- We support - {{adForm.get('type').value === 0 ? 'JPG, PNG AND GIF' : 'ZIP' }} - Make sure that your files is no more than 500 kb. +

+ We support JPG, PNG and GIF files. + Please make sure that the file you are uploading is not larger than 500 KB. +

+

+ Please upload your HTML banner ad as a zip archive. + Please make sure that the file is not larger than 500 KB.

@@ -333,7 +342,7 @@
- {{ adSizes[adForm.get('size').value] }} + {{ adForm.get('size').value }} - {{ adSizes[adForm.get('size').value] }} + {{ adForm.get('size').value }} - @@ -399,8 +408,8 @@ diff --git a/src/app/advertiser/edit-campaign/edit-campaign-summary/edit-campaign-summary.component.scss b/src/app/advertiser/edit-campaign/edit-campaign-summary/edit-campaign-summary.component.scss index 257f705e4..dedc7a61b 100644 --- a/src/app/advertiser/edit-campaign/edit-campaign-summary/edit-campaign-summary.component.scss +++ b/src/app/advertiser/edit-campaign/edit-campaign-summary/edit-campaign-summary.component.scss @@ -98,6 +98,10 @@ margin-bottom: 25px; } } + + &__note-info { + padding: 12px 30px 12px; + } } .campaign-poster { diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 3acbe89ce..999db0e66 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -46,6 +46,7 @@ import { MomentDateAdapter } from '@angular/material-moment-adapter'; import { DATE_PICKER_FORMATS } from "common/utilities/consts"; import { ImpersonationService } from "./impersonation/impersonation.service"; import { ImpersonationModule } from "./impersonation/impersonation.module"; +import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; const appModules = [ AppCommonModule, @@ -65,6 +66,7 @@ const appModules = [ imports: [ ApiModule, BrowserModule, + BrowserAnimationsModule, HttpClientModule, AppRoutingModule, ImpersonationModule, diff --git a/src/app/auth/forgotten-password/forgotten-password.component.html b/src/app/auth/forgotten-password/forgotten-password.component.html index 06ecdd9c8..8e6e3b0c7 100644 --- a/src/app/auth/forgotten-password/forgotten-password.component.html +++ b/src/app/auth/forgotten-password/forgotten-password.component.html @@ -29,7 +29,7 @@ for="email" class="dwmth-form-label" > - Email Address + Email address - Reset Password + Reset password diff --git a/src/app/auth/login/login.component.html b/src/app/auth/login/login.component.html index c20ca691b..c6a2f4efb 100644 --- a/src/app/auth/login/login.component.html +++ b/src/app/auth/login/login.component.html @@ -36,7 +36,7 @@ for="email" class="dwmth-form-label" > - Email Address + Email address - Email Address + Email address - Confirm Password + Confirm password @@ -158,6 +159,7 @@ diff --git a/src/app/auth/reset-password/reset-password.component.html b/src/app/auth/reset-password/reset-password.component.html index 068cb9867..cbbf15f44 100644 --- a/src/app/auth/reset-password/reset-password.component.html +++ b/src/app/auth/reset-password/reset-password.component.html @@ -62,7 +62,7 @@ for="confirmPassword" class="dwmth-form-label" > - Confirm Password + Confirm password this.banner).url; - const bannerSizeArray = adSizesEnum[(this.banner).size].split('x'); + const bannerSizeArray = (this.banner).creativeSize.split('x'); this.bannerChosenSize = { width: bannerSizeArray[0], height: bannerSizeArray[1] diff --git a/src/app/common/components/chart-filter/chart-filter.component.html b/src/app/common/components/chart-filter/chart-filter.component.html index c2b0427ba..e53dbcee5 100644 --- a/src/app/common/components/chart-filter/chart-filter.component.html +++ b/src/app/common/components/chart-filter/chart-filter.component.html @@ -30,7 +30,7 @@ dwmth-copy hidden-md" > - Sort by Date Range + Sort by date range