From bda12f4fcdd078eb266c479d28fa3c9b796792d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Podkalicki?= <38037054+PawelPodkalicki@users.noreply.github.com> Date: Mon, 7 Oct 2019 17:30:38 +0200 Subject: [PATCH 01/17] Update texts (#701) * Update texts * Update texts * Update texts' case --- .../admin/dashboard/dashboard.component.ts | 2 +- .../finances/finances-settings.component.html | 2 +- .../license/license.component.html | 8 ++--- .../privacy-and-terms-settings.component.html | 4 +-- .../user-reports/user-reports.component.html | 4 +-- .../campaign-details.component.html | 27 +++++++------- .../campaign-list.component.html | 2 +- .../dashboard/dashboard.component.html | 4 +-- ...mpaign-additional-targeting.component.html | 16 ++++----- ...-campaign-basic-information.component.html | 25 ++++++------- .../edit-campaign-create-ads.component.html | 35 ++++++++++++------- .../edit-campaign-summary.component.html | 18 +++++----- .../forgotten-password.component.html | 4 +-- src/app/auth/login/login.component.html | 2 +- src/app/auth/register/register.component.html | 4 +-- .../reset-password.component.html | 2 +- .../chart-filter/chart-filter.component.html | 4 +-- .../edit-asset-navigation.component.ts | 6 ++-- .../change-address-dialog.component.html | 6 ++-- .../leave-edit-process-dialog.component.html | 2 +- .../withdraw-funds-dialog.component.html | 2 +- .../dashboard/dashboard.component.html | 4 +-- .../site-code-dialog.component.html | 4 +-- ...t-site-additional-targeting.component.html | 2 +- ...edit-site-basic-information.component.html | 2 +- .../edit-site-create-ad-units.component.html | 2 +- .../edit-site-navigation.component.ts | 4 +-- .../edit-site-summary.component.html | 12 +++---- .../ad-units/ad-units.component.html | 2 +- .../site-details/site-details.component.html | 6 ++-- .../site-list/site-list.component.html | 2 +- .../preferences/preferences.component.html | 14 ++------ 32 files changed, 116 insertions(+), 117 deletions(-) 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/campaign-details/campaign-details.component.html b/src/app/advertiser/campaign-details/campaign-details.component.html index 85cc9b9a0..682d50594 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
@@ -371,7 +372,7 @@ dwmth-copy dwmth-copy--semi" > - List of Ads + List of ads

Add new icon - {{campaign.ads.length ? 'Create new Ad' : 'Add first Ad' }} + {{campaign.ads.length ? 'Create new 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 - Budget ({{currencyCode}} / day) + Budget ({{currencyCode}}/day) - Budget ({{currencyCode}} / hour) + Budget ({{currencyCode}}/hour) - + - Optional + (optional)
- {{ createCampaignMode ? 'Back to Dashboard' : 'Back' }} + {{ createCampaignMode ? 'Back to dashboard' : 'Back' }} @@ -453,12 +462,12 @@ > Add new icon - Create new Ad + Add new banner ad @@ -491,7 +500,7 @@ save-as-draft" data-test="advertiser-edit-campaign-save-as-draft" > - Save as Draft + Save as draft 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 - Confirm Password + Confirm password - Sort by Date Range + Sort by date range diff --git a/src/app/publisher/site-details/site-details.component.html b/src/app/publisher/site-details/site-details.component.html index e37a719ff..0ff08caab 100644 --- a/src/app/publisher/site-details/site-details.component.html +++ b/src/app/publisher/site-details/site-details.component.html @@ -19,7 +19,7 @@ dwmth-copy dwmth-copy--small" > - My Sites + My sites

- Edit Basic Info + Edit basic info
@@ -219,7 +219,7 @@ src="assets/images/edit-blue.svg" alt="" > - {{(!filtering.requires.length && !filtering.excludes.length) ? 'Add Filtering' : 'Edit Filtering'}} + {{(!filtering.requires.length && !filtering.excludes.length) ? 'Add filtering' : 'Edit filtering'}}
- Total - All Sites + Total - All sites diff --git a/src/app/settings/general-settings/preferences/preferences.component.html b/src/app/settings/general-settings/preferences/preferences.component.html index 69aae0499..bced847d4 100644 --- a/src/app/settings/general-settings/preferences/preferences.component.html +++ b/src/app/settings/general-settings/preferences/preferences.component.html @@ -37,7 +37,7 @@ for="email" class="dwmth-form-label" > - Email Address + Email address -
- Confirm Password + Confirm password
- -
From c0392b113796cb0a523384146a617c070b7a6086 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Podkalicki?= <38037054+PawelPodkalicki@users.noreply.github.com> Date: Mon, 7 Oct 2019 17:31:07 +0200 Subject: [PATCH 02/17] Fix menu deposit button on Windows (#700) --- src/app/common/components/header/header.component.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/common/components/header/header.component.scss b/src/app/common/components/header/header.component.scss index 9513c739b..e1dc3fc1b 100644 --- a/src/app/common/components/header/header.component.scss +++ b/src/app/common/components/header/header.component.scss @@ -95,7 +95,7 @@ background: pal(white); z-index: 999; width: 100%; - min-width: 170px; + min-width: 180px; opacity: 0; visibility: hidden; transition: all 0.2s ease; From 47946b2d49947e1661cd79bed2d6127f8578b93c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Podkalicki?= <38037054+PawelPodkalicki@users.noreply.github.com> Date: Mon, 7 Oct 2019 17:33:54 +0200 Subject: [PATCH 03/17] Grey out inactive account type (#702) --- src/app/common/components/header/header.component.html | 2 ++ src/app/common/components/header/header.component.scss | 7 +++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/app/common/components/header/header.component.html b/src/app/common/components/header/header.component.html index 27852a2d2..9f1b3d7ad 100644 --- a/src/app/common/components/header/header.component.html +++ b/src/app/common/components/header/header.component.html @@ -46,6 +46,7 @@ dwmth-copy--uppercase dwmth-copy--semi dwmth-copy--lineheight-reset" + [ngClass]="{'active-type': activeUserType === userRolesEnum.ADVERTISER}" (click)="setActiveUserType(userRolesEnum.ADVERTISER)" > Date: Mon, 7 Oct 2019 17:35:54 +0200 Subject: [PATCH 04/17] Hide Requires section from site filtering (#699) --- CHANGELOG.md | 2 ++ ...edit-site-additional-targeting.component.html | 7 +++++-- .../edit-site-additional-targeting.component.ts | 16 ++++++---------- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9cc3d5078..14fad98d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Changed +- Hid Requires section from site filtering ## [1.2.5] - 2019-10-01 ### Changed diff --git a/src/app/publisher/edit-site/edit-site-additional-targeting/edit-site-additional-targeting.component.html b/src/app/publisher/edit-site/edit-site-additional-targeting/edit-site-additional-targeting.component.html index 1256d7c2e..9bc578c29 100644 --- a/src/app/publisher/edit-site/edit-site-additional-targeting/edit-site-additional-targeting.component.html +++ b/src/app/publisher/edit-site/edit-site-additional-targeting/edit-site-additional-targeting.component.html @@ -8,11 +8,13 @@ class="targeting-accordion" data-test="publisher-edit-site-additional-targeting-accordion" > -
+
replace rest of targeting variables with filtering ones @Component({ @@ -38,7 +33,7 @@ export class EditSiteAdditionalTargetingComponent extends HandleSubscription imp excludedItems: TargetingOptionValue[] = []; createSiteMode: boolean; changesSaved: boolean = false; - filtering; + showRequiresSection: boolean = false; constructor( private route: ActivatedRoute, @@ -136,6 +131,7 @@ export class EditSiteAdditionalTargetingComponent extends HandleSubscription imp } const filtering = lastEditedSite.filteringArray; this.addedItems = [...filtering.requires]; + this.showRequiresSection = this.addedItems.length > 0; this.excludedItems = [...filtering.excludes]; }); this.subscriptions.push(lastSiteSubscription); From 8c112feb95b17ce1e7ae20a55cecdbb094ceaa52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Podkalicki?= <38037054+PawelPodkalicki@users.noreply.github.com> Date: Fri, 25 Oct 2019 09:43:35 +0200 Subject: [PATCH 05/17] Update texts (#703) --- .../edit-site-summary/edit-site-summary.component.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/publisher/edit-site/edit-site-summary/edit-site-summary.component.html b/src/app/publisher/edit-site/edit-site-summary/edit-site-summary.component.html index 82f0f5d0e..f45ac54fc 100644 --- a/src/app/publisher/edit-site/edit-site-summary/edit-site-summary.component.html +++ b/src/app/publisher/edit-site/edit-site-summary/edit-site-summary.component.html @@ -21,10 +21,10 @@ dwmth-copy--medium" > Please check if all settings are correct and publish the site. - Next you need to get your code. + Next, you need to get your code. Once you click the "Add site" button you will be redirected to the main dashboard. Please click the site you have just added, then the "Get code" button, copy the code for each of the ad units and place it on your website. - If you have any doubts please + If you have any issues or questions please Date: Wed, 30 Oct 2019 14:24:10 +0100 Subject: [PATCH 06/17] Add conversion definition (#708) * Add conversion definition * Add backend model * add tabs * Add conversions to campaign initial object * Remove redundant code * add type select * fix validating disabled fields * remove isConversionActive * remove table elements * styling * fix deleting * hide header if list is empty * conversion id->uuid * fix value validating - required in basic mode * add secret * Improved conversions layout, information dialog + additional ux fixes * Saving conversions without navigating back to details. Disabling save button when there are no changes and other minor ux improvements * Fix navigation issues * Add helper tooltip-icons, enable copy link and secret to clipboard, ui improvements * add isValueMutable and isRepeatable columns for advanced conversions * Update texts * fix * Enable adjusting app-label tooltip position, remove unused animations * Value input validation for advanced forms * Transform value input values * Transform value input values - added conditions when no value is allowed * Transform value input values - enable decimal values * Tooltip for name label * Show defined conversions * Move conversion secret to campaign * Fix value conversion for mutable value * Move conversion click to campaign * Fix conversion ads to clicks conversion * revert environment.ts * Change field budgetType to limitType * Rename campaign conversions field to conversionDefinitions * Revert "Rename campaign conversions field to conversionDefinitions" This reverts commit 7e86d3ba * Remove unused service call getConversions * Change texts * Update conversion budget description --- .../advertiser/advertiser-routing.module.ts | 5 + src/app/advertiser/advertiser.module.ts | 13 +- src/app/advertiser/advertiser.service.ts | 3 +- .../campaign-details.component.html | 84 ++- .../edit-campaign-conversion.component.html | 573 ++++++++++++++++++ .../edit-campaign-conversion.component.scss | 105 ++++ .../edit-campaign-conversion.component.ts | 325 ++++++++++ src/app/app.module.ts | 2 + src/app/common/common.module.ts | 7 +- .../labelWithTooltip.component.html | 11 + .../labelWithTooltip.component.scss | 7 + .../labelWithTooltip.component.ts | 16 + .../information-dialog.component.html | 34 ++ .../information-dialog.component.scss | 43 ++ .../information-dialog.component.ts | 41 ++ src/app/models/campaign.model.ts | 43 +- src/app/models/initial-state/campaign.ts | 15 +- .../site-details/site-details.component.html | 4 +- .../store/advertiser/advertiser.actions.ts | 9 + .../store/advertiser/advertiser.effects.ts | 23 +- src/app/store/common/common.effects.ts | 2 + src/styles/_flexbox.scss | 4 + 22 files changed, 1350 insertions(+), 19 deletions(-) create mode 100644 src/app/advertiser/edit-campaign/edit-campaign-conversion/edit-campaign-conversion.component.html create mode 100644 src/app/advertiser/edit-campaign/edit-campaign-conversion/edit-campaign-conversion.component.scss create mode 100644 src/app/advertiser/edit-campaign/edit-campaign-conversion/edit-campaign-conversion.component.ts create mode 100644 src/app/common/components/labelWithTooltip/labelWithTooltip.component.html create mode 100644 src/app/common/components/labelWithTooltip/labelWithTooltip.component.scss create mode 100644 src/app/common/components/labelWithTooltip/labelWithTooltip.component.ts create mode 100644 src/app/common/dialog/information-dialog/information-dialog.component.html create mode 100644 src/app/common/dialog/information-dialog/information-dialog.component.scss create mode 100644 src/app/common/dialog/information-dialog/information-dialog.component.ts 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..1de998096 100644 --- a/src/app/advertiser/advertiser.module.ts +++ b/src/app/advertiser/advertiser.module.ts @@ -1,15 +1,16 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { HttpModule } from '@angular/http'; -import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +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 { OwlMomentDateTimeModule } from 'ng-pick-datetime-moment'; @@ -20,6 +21,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'; @@ -33,24 +35,27 @@ 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 +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 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 682d50594..b603c02e2 100644 --- a/src/app/advertiser/campaign-details/campaign-details.component.html +++ b/src/app/advertiser/campaign-details/campaign-details.component.html @@ -294,6 +294,82 @@
+ +
+
+ +
+
+
+
+ Name +
+
+ Type +
+
+ Cost +
+
+ Occurrences +
+
+ +
+
+ {{ conversion.name }} +
+
+ {{ conversion.eventType }} +
+
+ {{ conversion.cost | formatMoney:2 }} +
+
+ {{ conversion.occurrences }} +
+
+
+
+
@@ -308,7 +384,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:
- +
+
+

+ Conversions +

+

+ Conversions are users' actions which meet business expectations, e. g. making a purchase or registering on the website. + If advertisers are satisfied they can pay back. On this website you can define your conversions and the amount you are willing to spend on them. +

+
+ +
+

+ Conversions +

+ + + +
+ In this mode each conversion has a constant cost. + All conversion expenses are limited by the banner ad campaign budget. + It is possible to execute one conversion type per impression. +
+
+ +
+ +
+ + + + +
+
Delete
+
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 also has more control over the conversion settings. + Each conversion may have a different cost. + The single conversion cost is optional and can be defined while the campaign is created. +
+ +
+ Secret is a string of characters used to sign the transferred data. +
+ Secret: + {{campaign.secret}} + + file_copy + +
+
+ + +
+ +
+ +
+ + + + + + +
Delete
+
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 to basic or advanced link click events won’t be registered, unless a conversion takes place. + Click link icon to obtain 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..7c21abe5f --- /dev/null +++ b/src/app/advertiser/edit-campaign/edit-campaign-conversion/edit-campaign-conversion.component.scss @@ -0,0 +1,105 @@ +@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 0; + } + + &__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..2623eedf2 --- /dev/null +++ b/src/app/advertiser/edit-campaign/edit-campaign-conversion/edit-campaign-conversion.component.ts @@ -0,0 +1,325 @@ +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 { InformationDialogComponent } from 'common/dialog/information-dialog/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'; + +@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 { + 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: 0, label: 'Default'}, + {value: 1, label: 'Basic link'}, + {value: 2, 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 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) { + let message = 'The link above is a conversion address, that must be used in order to execute a conversion. ' + + 'Please, place it on your site (e.g. as a src attribute of an img element). ' + + 'Before you proceed further, please read the instruction'; + message += isAdvanced ? ' and modify the link according to the guidelines:' : ':'; + + this.dialog.open(InformationDialogComponent, { + data: { + title: 'Conversion link', + message: message, + link: link, + href: 'https://github.com/adshares/adserver/wiki/Conversions', + secret: this.campaign.secret, + } + }); + } + + 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/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/common/common.module.ts b/src/app/common/common.module.ts index 4f66422ff..090bc973e 100644 --- a/src/app/common/common.module.ts +++ b/src/app/common/common.module.ts @@ -66,6 +66,8 @@ import { SuccessSnackbarComponent } from "common/dialog/success-snackbar/success import { InputComponent } from "common/components/input/input.component"; import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; import { TargetingCustomOptionInputComponent } from "common/components/targeting/targeting-custom-option-input/targeting-custom-option-input.component"; +import { InformationDialogComponent } from "common/dialog/information-dialog/information-dialog.component"; +import { LabelWithTooltipComponent } from "common/components/labelWithTooltip/labelWithTooltip.component"; const matModules = [ MatDialogModule, @@ -95,6 +97,7 @@ const dialogs = [ ChangeAutomaticWithdrawDialogComponent, UserConfirmResponseDialogComponent, WarningDialogComponent, + InformationDialogComponent ]; const appComponents = [ @@ -121,7 +124,8 @@ const appComponents = [ BannerPreviewComponent, SettingsMenuItemComponent, SuccessSnackbarComponent, - InputComponent + InputComponent, + LabelWithTooltipComponent, ]; @NgModule({ @@ -150,6 +154,7 @@ const appComponents = [ ], exports: [ ...appComponents, + BrowserAnimationsModule, MatTooltipModule, MatSpinner, MatDialogContent, diff --git a/src/app/common/components/labelWithTooltip/labelWithTooltip.component.html b/src/app/common/components/labelWithTooltip/labelWithTooltip.component.html new file mode 100644 index 000000000..6d22ce507 --- /dev/null +++ b/src/app/common/components/labelWithTooltip/labelWithTooltip.component.html @@ -0,0 +1,11 @@ + + + help + + diff --git a/src/app/common/components/labelWithTooltip/labelWithTooltip.component.scss b/src/app/common/components/labelWithTooltip/labelWithTooltip.component.scss new file mode 100644 index 000000000..de8861870 --- /dev/null +++ b/src/app/common/components/labelWithTooltip/labelWithTooltip.component.scss @@ -0,0 +1,7 @@ +@import "../../../../styles/colors"; +.labelWithTooltip__icon { + margin-left: 10px; + margin-right: 0; + font-size: 20px; + color: pal(blue, base); +} diff --git a/src/app/common/components/labelWithTooltip/labelWithTooltip.component.ts b/src/app/common/components/labelWithTooltip/labelWithTooltip.component.ts new file mode 100644 index 000000000..516488a01 --- /dev/null +++ b/src/app/common/components/labelWithTooltip/labelWithTooltip.component.ts @@ -0,0 +1,16 @@ +import { Component, Input, Output } from '@angular/core'; +import { Subject } from "rxjs"; +import { faQuestionCircle } from "@fortawesome/free-solid-svg-icons"; + +@Component({ + selector: 'app-label', + templateUrl: './labelWithTooltip.component.html', + styleUrls: ['./labelWithTooltip.component.scss'], + host: {'class': 'app-label'}, +}) +export class LabelWithTooltipComponent { + @Input() label: string; + @Input() forId: string; + @Input() tooltip: string; + @Input() tooltipPosition: string; +} diff --git a/src/app/common/dialog/information-dialog/information-dialog.component.html b/src/app/common/dialog/information-dialog/information-dialog.component.html new file mode 100644 index 000000000..d78441c0d --- /dev/null +++ b/src/app/common/dialog/information-dialog/information-dialog.component.html @@ -0,0 +1,34 @@ + +
+

+ {{ title }} +

+
+ Link: + {{link}} + + file_copy + +
+

+ {{ message }} + +
+
{{ href }} +

+ diff --git a/src/app/common/dialog/information-dialog/information-dialog.component.scss b/src/app/common/dialog/information-dialog/information-dialog.component.scss new file mode 100644 index 000000000..6f8b7479e --- /dev/null +++ b/src/app/common/dialog/information-dialog/information-dialog.component.scss @@ -0,0 +1,43 @@ +@import "./../../../../styles"; + +.information-dialog { + max-width: 800px; + + &__box { + @include copy; + display: flex; + justify-content: center; + margin: 0 auto 8px; + } + + &__info { + margin-left: 8px; + margin-right: 8px; + white-space: nowrap; + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; + } + + &__link { + @include copy($color: pal(blue, base)); + display: block; + margin-top: 8px; + + &:hover, + &:focus { + color: darken(pal(blue, base), 15%); + } + } + + &__copy-icon { + cursor: pointer; + color: pal(gray, mid-dark); + outline: none; + + &:hover, + &:focus { + color: darken(pal(gray, mid-dark), 10%); + } + } +} diff --git a/src/app/common/dialog/information-dialog/information-dialog.component.ts b/src/app/common/dialog/information-dialog/information-dialog.component.ts new file mode 100644 index 000000000..4cfa3a256 --- /dev/null +++ b/src/app/common/dialog/information-dialog/information-dialog.component.ts @@ -0,0 +1,41 @@ +import { Component, Inject } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { AppState } from 'models/app-state.model'; +import { ShowSuccessSnackbar } from 'store/common/common.actions'; +import { Store } from '@ngrx/store'; + +@Component({ + selector: 'app-information-dialog', + templateUrl: './information-dialog.component.html', + styleUrls: ['./information-dialog.component.scss'], +}) +export class InformationDialogComponent { + title: string = ''; + message: string = ''; + link: string = ''; + href: string = ''; + + constructor( + public dialogRef: MatDialogRef, + private store: Store, + @Inject(MAT_DIALOG_DATA) public data: any, + ) { + } + + ngOnInit() { + this.message = (this.data && this.data.message) ? this.data.message : ''; + this.title = (this.data && this.data.title) ? this.data.title : ''; + this.link = (this.data && this.data.link) ? this.data.link : ''; + this.href = (this.data && this.data.href) ? this.data.href : ''; + } + + 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/models/campaign.model.ts b/src/app/models/campaign.model.ts index a1736e7b0..b14fd1809 100644 --- a/src/app/models/campaign.model.ts +++ b/src/app/models/campaign.model.ts @@ -16,7 +16,10 @@ interface Campaign { averageCpc?: number; averageCpm?: number; cost?: number; - conversions?: number; + conversions?: CampaignConversion[]; + secret: string; + conversionClick: number; + conversionClickLink?: string; classificationStatus: number; classificationTags?: string; } @@ -43,6 +46,34 @@ interface CampaignBasicInformation { dateEnd?: string; } +interface CampaignConversion { + uuid?: string; + name: string; + limitType: string; + eventType: string; + type: string; + value?: number; + limit?: number; + link?: string; + isValueMutable?: number; + isRepeatable?: number; + cost: number; + occurrences: number; +} + +interface CampaignConversionItem { + uuid?: string; + name: string; + eventType: string; + isAdvanced: boolean; + isInBudget: boolean; + value?: string; + limit?: string; + link?: string; + isValueMutable?: boolean; + isRepeatable?: boolean; +} + interface Ad { id: number; status: number; @@ -67,4 +98,12 @@ interface CampaignTotalsResponse { } -export { Campaign, CampaignBasicInformation, Ad, CampaignTotals, CampaignTotalsResponse }; +export { + Campaign, + CampaignBasicInformation, + CampaignConversion, + CampaignConversionItem, + Ad, + CampaignTotals, + CampaignTotalsResponse +}; diff --git a/src/app/models/initial-state/campaign.ts b/src/app/models/initial-state/campaign.ts index 23724b630..ee923d60b 100644 --- a/src/app/models/initial-state/campaign.ts +++ b/src/app/models/initial-state/campaign.ts @@ -1,4 +1,4 @@ -import {Campaign, CampaignTotals} from 'models/campaign.model'; +import { Campaign, CampaignConversionItem, CampaignTotals } from 'models/campaign.model'; import {campaignStatusesEnum} from 'models/enum/campaign.enum'; import {classificationStatusesEnum} from 'models/enum/classification.enum'; import * as moment from 'moment'; @@ -35,5 +35,18 @@ export const campaignInitialState: Campaign = { ads: [], id: 0, + conversions: [], + secret: '', + conversionClick: 0, classificationStatus: classificationStatusesEnum.DISABLED, }; + +export const campaignConversionItemInitialState: CampaignConversionItem = { + name: '', + eventType: '', + isAdvanced: false, + isInBudget: true, + value: null, + limit: null, +}; + diff --git a/src/app/publisher/site-details/site-details.component.html b/src/app/publisher/site-details/site-details.component.html index 0ff08caab..41a0dd1af 100644 --- a/src/app/publisher/site-details/site-details.component.html +++ b/src/app/publisher/site-details/site-details.component.html @@ -237,7 +237,7 @@ >

Requires:

+ dwmth-copy--semi">Targeting:

Excludes:

+ dwmth-copy--semi">Exclusions:
{ - return Observable.of(new UpdateCampaignFailure(err) + return Observable.of(new UpdateCampaignFailure(`An error occurred. Error code: ${err.status}`) + ) + }) + ); + + @Effect() + saveConversion = this.actions$ + .ofType(SAVE_CONVERSION) + .map(toPayload) + .switchMap((payload) => this.service.updateCampaign(payload) + .switchMap(() => { + return [ + new UpdateCampaignSuccess(payload), + new ShowSuccessSnackbar(SAVE_SUCCESS), + ]; + }) + .catch((err) => { + return Observable.of(new UpdateCampaignFailure(`An error occurred. Error code: ${err.status}`) ) }) ); diff --git a/src/app/store/common/common.effects.ts b/src/app/store/common/common.effects.ts index 1ceb5e39d..bb1a4e777 100644 --- a/src/app/store/common/common.effects.ts +++ b/src/app/store/common/common.effects.ts @@ -11,6 +11,7 @@ import { import { UPDATE_CAMPAIGN_STATUS_FAILURE, DELETE_CAMPAIGN_FAILURE, + UPDATE_CAMPAIGN_FAILURE, } from "store/advertiser/advertiser.actions"; import { ADD_SITE_TO_SITES_FAILURE, @@ -46,6 +47,7 @@ export class CommonEffects { .ofType( SHOW_DIALOG_ON_ERROR, UPDATE_CAMPAIGN_STATUS_FAILURE, + UPDATE_CAMPAIGN_FAILURE, DELETE_CAMPAIGN_FAILURE, ADD_SITE_TO_SITES_FAILURE, SET_ADMIN_SETTINGS_FAILURE, diff --git a/src/styles/_flexbox.scss b/src/styles/_flexbox.scss index 5e96befc1..b30ac7396 100644 --- a/src/styles/_flexbox.scss +++ b/src/styles/_flexbox.scss @@ -118,6 +118,10 @@ $column_sizes: 12; align-items: center; } +.align-self-center { + align-self: center; +} + .align-end { align-items: flex-end; } From 6d22974331f0293c219385975489b9d2b361b36a Mon Sep 17 00:00:00 2001 From: Maciej Pilarczyk Date: Wed, 30 Oct 2019 14:25:20 +0100 Subject: [PATCH 07/17] Update CHANGELOG.md --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 14fad98d9..c1bda581c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] + +## [1.3.0] - 2019-10-30 +### Added +- Add conversion definition ### Changed - Hid Requires section from site filtering From de9ac45491be113c2b45b8b13539483d89dce489 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Podkalicki?= <38037054+PawelPodkalicki@users.noreply.github.com> Date: Thu, 7 Nov 2019 14:21:09 +0100 Subject: [PATCH 08/17] Update texts (#710) * Update texts * Change conversion dialog * Update texts * Add new feature label * Fix vulnerabilities for target blank --- src/app/advertiser/advertiser.module.ts | 23 +++--- .../campaign-details.component.html | 58 ++++++++++---- .../campaign-details.component.scss | 7 ++ .../campaign-details.component.ts | 2 + ...-campaign-basic-information.component.html | 20 +---- .../edit-campaign-conversion.component.html | 77 +++++++++++-------- .../edit-campaign-conversion.component.ts | 27 ++++--- .../edit-campaign-summary.component.html | 32 ++++++++ .../edit-campaign-summary.component.scss | 4 + src/app/auth/register/register.component.html | 2 + src/app/common/common.module.ts | 43 +++++------ .../add-funds-dialog.component.html | 2 +- ...on-link-information-dialog.component.html} | 30 ++++++-- ...on-link-information-dialog.component.scss} | 0 ...sion-link-information-dialog.component.ts} | 20 ++--- .../withdraw-funds-dialog.component.html | 3 +- src/app/models/enum/campaign.enum.ts | 6 ++ src/app/models/initial-state/campaign.ts | 4 +- .../edit-site-summary.component.html | 5 +- .../billing-history-withdrawal.component.html | 1 + .../user-wallet/user-wallet.component.html | 1 + 21 files changed, 227 insertions(+), 140 deletions(-) rename src/app/common/dialog/information-dialog/{information-dialog.component.html => conversion-link-information-dialog.component.html} (50%) rename src/app/common/dialog/information-dialog/{information-dialog.component.scss => conversion-link-information-dialog.component.scss} (100%) rename src/app/common/dialog/information-dialog/{information-dialog.component.ts => conversion-link-information-dialog.component.ts} (59%) diff --git a/src/app/advertiser/advertiser.module.ts b/src/app/advertiser/advertiser.module.ts index 1de998096..46d57f5cd 100644 --- a/src/app/advertiser/advertiser.module.ts +++ b/src/app/advertiser/advertiser.module.ts @@ -1,7 +1,7 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { HttpModule } from '@angular/http'; -import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatDatepickerModule } from '@angular/material/datepicker'; @@ -12,8 +12,9 @@ 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'; @@ -30,14 +31,11 @@ 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 = [ @@ -58,7 +56,7 @@ const editCampaignComponents = [ EditCampaignConversionComponent, EditCampaignAdditionalTargetingComponent, EditCampaignCreateAdsComponent, - EditCampaignSummaryComponent + EditCampaignSummaryComponent, ]; const advertiserComponents = [ @@ -68,7 +66,7 @@ const advertiserComponents = [ DashboardComponent, CampaignDetailsComponent, AdListComponent, - AdListItemComponent + AdListItemComponent, ]; @NgModule({ @@ -77,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/campaign-details/campaign-details.component.html b/src/app/advertiser/campaign-details/campaign-details.component.html index b603c02e2..89af13528 100644 --- a/src/app/advertiser/campaign-details/campaign-details.component.html +++ b/src/app/advertiser/campaign-details/campaign-details.component.html @@ -300,21 +300,49 @@ dwmth-box--high" >
-
+ + + Edit conversions +
- {{campaign.ads.length ? 'Create new ad' : 'Upload your first ad'}} + {{campaign.ads.length ? 'Add new banner ad' : 'Upload your first ad'}}
diff --git a/src/app/advertiser/campaign-details/campaign-details.component.scss b/src/app/advertiser/campaign-details/campaign-details.component.scss index bfe5bc735..387d9f4aa 100644 --- a/src/app/advertiser/campaign-details/campaign-details.component.scss +++ b/src/app/advertiser/campaign-details/campaign-details.component.scss @@ -81,3 +81,10 @@ margin-left: 10px; margin-top: 2px; } + +.new-feature { + .dwmth-icon { + margin-bottom: 4px; + margin-left: 4px; + } +} diff --git a/src/app/advertiser/campaign-details/campaign-details.component.ts b/src/app/advertiser/campaign-details/campaign-details.component.ts index 22f6aacbf..a82953687 100644 --- a/src/app/advertiser/campaign-details/campaign-details.component.ts +++ b/src/app/advertiser/campaign-details/campaign-details.component.ts @@ -1,6 +1,7 @@ import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { Store } from '@ngrx/store'; +import { faExclamation } from '@fortawesome/free-solid-svg-icons'; import * as moment from 'moment'; import { Campaign } from 'models/campaign.model'; import { AppState } from 'models/app-state.model'; @@ -39,6 +40,7 @@ export class CampaignDetailsComponent extends HandleSubscription implements OnIn targetingOptions: AssetTargeting; currentChartFilterSettings: ChartFilterSettings; currentCampaignStatus: string; + faExclamation = faExclamation; constructor( private route: ActivatedRoute, diff --git a/src/app/advertiser/edit-campaign/edit-campaign-basic-info/edit-campaign-basic-information.component.html b/src/app/advertiser/edit-campaign/edit-campaign-basic-info/edit-campaign-basic-information.component.html index e0f700f8c..bc0a3ab95 100644 --- a/src/app/advertiser/edit-campaign/edit-campaign-basic-info/edit-campaign-basic-information.component.html +++ b/src/app/advertiser/edit-campaign/edit-campaign-basic-info/edit-campaign-basic-information.component.html @@ -359,23 +359,9 @@ col-xs-5 col-xxl-3" > -
- - - (optional) - -
+ diff --git a/src/app/advertiser/edit-campaign/edit-campaign-conversion/edit-campaign-conversion.component.html b/src/app/advertiser/edit-campaign/edit-campaign-conversion/edit-campaign-conversion.component.html index 8e29ea0c8..3f5861a9b 100644 --- a/src/app/advertiser/edit-campaign/edit-campaign-conversion/edit-campaign-conversion.component.html +++ b/src/app/advertiser/edit-campaign/edit-campaign-conversion/edit-campaign-conversion.component.html @@ -16,8 +16,8 @@ dwmth-copy dwmth-copy--medium" > - Conversions are users' actions which meet business expectations, e. g. making a purchase or registering on the website. - If advertisers are satisfied they can pay back. On this website you can define your conversions and the amount you are willing to spend on them. + Here you can define conversions and the amount you are willing to spend on a single conversion. + This dashboard lets you set specific actions you want to pay for – e.g. registrations or sales.

@@ -43,8 +43,8 @@ dwmth-copy description-type" > - In this mode each conversion has a constant cost. - All conversion expenses are limited by the banner ad campaign budget. + In this mode each conversion has a constant value. + All conversion expenses are limited by the total campaign budget. It is possible to execute one conversion type per impression.
@@ -74,7 +74,7 @@ forId="name" label="Name" tooltipPosition="after" - tooltip="Enter the conversion name here" + tooltip="Enter the conversion name here – e.g. sales, registration" >
-
Delete
-
Link
+
Delete
+
Generate link
@@ -178,6 +178,7 @@ col-xs-1 row cell + justify-center align-center" >
- Secret is a string of characters used to sign the transferred data. + 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: + Secret phrase: {{campaign.secret}} -
Delete
-
Link
+
Delete
+
Generate link
@@ -420,7 +430,7 @@
@@ -531,7 +544,7 @@ *ngIf="campaign.conversionClickLink" class="campaign-edit-conversion__btn" title="Get Link" - (click)="openDialog(campaign.conversionClickLink)" + (click)="openDialog(campaign.conversionClickLink, isConversionClickAdvanced)" > link 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 index 2623eedf2..6eccb5121 100644 --- 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 @@ -18,10 +18,12 @@ import { 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 { InformationDialogComponent } from 'common/dialog/information-dialog/information-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', @@ -29,6 +31,7 @@ import { adsToClicks, formatMoney } from 'common/utilities/helpers'; 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'; @@ -57,9 +60,9 @@ export class EditCampaignConversionComponent extends HandleSubscription implemen ]; readonly clickConversionTypes = [ - {value: 0, label: 'Default'}, - {value: 1, label: 'Basic link'}, - {value: 2, label: 'Advanced link'}, + {value: campaignConversionClick.NONE, label: 'Default'}, + {value: campaignConversionClick.BASIC, label: 'Basic link'}, + {value: campaignConversionClick.ADVANCED, label: 'Advanced link'}, ]; conversionItemForms: FormGroup[] = []; @@ -138,6 +141,10 @@ export class EditCampaignConversionComponent extends HandleSubscription implemen }); } + get isConversionClickAdvanced(): boolean { + return this.campaign.conversionClick === campaignConversionClick.ADVANCED; + } + get isFormValid(): boolean { this.validateAdvancedValueControl(); return this.conversionItemForms.every(item => item.valid); @@ -292,18 +299,10 @@ export class EditCampaignConversionComponent extends HandleSubscription implemen } openDialog(link: string, isAdvanced: boolean = true) { - let message = 'The link above is a conversion address, that must be used in order to execute a conversion. ' + - 'Please, place it on your site (e.g. as a src attribute of an img element). ' + - 'Before you proceed further, please read the instruction'; - message += isAdvanced ? ' and modify the link according to the guidelines:' : ':'; - - this.dialog.open(InformationDialogComponent, { + this.dialog.open(ConversionLinkInformationDialogComponent, { data: { - title: 'Conversion link', - message: message, + isAdvanced: isAdvanced, link: link, - href: 'https://github.com/adshares/adserver/wiki/Conversions', - secret: this.campaign.secret, } }); } diff --git a/src/app/advertiser/edit-campaign/edit-campaign-summary/edit-campaign-summary.component.html b/src/app/advertiser/edit-campaign/edit-campaign-summary/edit-campaign-summary.component.html index b38b74001..4c6cd619f 100644 --- a/src/app/advertiser/edit-campaign/edit-campaign-summary/edit-campaign-summary.component.html +++ b/src/app/advertiser/edit-campaign/edit-campaign-summary/edit-campaign-summary.component.html @@ -391,6 +391,38 @@
+
+
+

+ Please note that it is possible to base your campaign on conversions - i.e. set specific actions you want to pay for – e.g. registrations or sales. + In case you want to use this feature, please go to your campaign settings and select “Edit conversions”. + Click + + here + + to read more about conversion handling. +

+
+
+
@@ -158,6 +159,7 @@ diff --git a/src/app/common/common.module.ts b/src/app/common/common.module.ts index 090bc973e..cf47bdaa4 100644 --- a/src/app/common/common.module.ts +++ b/src/app/common/common.module.ts @@ -1,26 +1,23 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; -import { - FormsModule, - ReactiveFormsModule -} from '@angular/forms'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { MatSelectModule } from '@angular/material/select'; import { MatChipsModule } from '@angular/material/chips'; import { + MatButtonModule, + MatCheckboxModule, MatDialogContent, MatDialogModule, + MatIconModule, MatInputModule, + MatMenuModule, MatSlideToggle, - MatSpinner, - MatCheckboxModule, MatSnackBarModule, - MatTooltipModule, - MatIconModule, - MatButtonModule, + MatSpinner, MatToolbarModule, - MatMenuModule, + MatTooltipModule, } from '@angular/material'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { MatDatepickerModule } from '@angular/material/datepicker'; @@ -44,11 +41,7 @@ import { PushNotificationComponent } from './components/push-notifications/push- import { ChartComponent } from './components/chart/chart.component'; import { ChartFilterComponent } from './components/chart-filter/chart-filter.component'; import { ChartFilterByTypeComponent } from './components/chart-filter-by-type/chart-filter-by-type.component'; -import { - AdsharesTokenPipe, - CalculateInCurrency, - ClickToADSPipe, -} from './pipes/adshares-token.pipe'; +import { AdsharesTokenPipe, CalculateInCurrency, ClickToADSPipe } from './pipes/adshares-token.pipe'; import { TrustHtmlPipe, TrustUrlPipe } from './pipes/trust.pipe'; import { MomentDatePipe } from './pipes/moment-date.pipe'; import { EmailNotActivatedBarComponent } from 'app/auth/email/not-activated-bar.component'; @@ -61,13 +54,13 @@ import { AssetHelpersService } from './asset-helpers.service'; import { PushNotificationsService } from './components/push-notifications/push-notifications.service'; import { WarningDialogComponent } from 'common/dialog/warning-dialog/warning-dialog.component'; import { BannerPreviewComponent } from 'common/components/banner-preview/banner-preview.component'; -import { SettingsMenuItemComponent } from "common/components/settings-menu-item/settings-menu-item.component"; -import { SuccessSnackbarComponent } from "common/dialog/success-snackbar/success-snackbar.component"; -import { InputComponent } from "common/components/input/input.component"; +import { SettingsMenuItemComponent } from 'common/components/settings-menu-item/settings-menu-item.component'; +import { SuccessSnackbarComponent } from 'common/dialog/success-snackbar/success-snackbar.component'; +import { InputComponent } from 'common/components/input/input.component'; import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; -import { TargetingCustomOptionInputComponent } from "common/components/targeting/targeting-custom-option-input/targeting-custom-option-input.component"; -import { InformationDialogComponent } from "common/dialog/information-dialog/information-dialog.component"; -import { LabelWithTooltipComponent } from "common/components/labelWithTooltip/labelWithTooltip.component"; +import { TargetingCustomOptionInputComponent } from 'common/components/targeting/targeting-custom-option-input/targeting-custom-option-input.component'; +import { ConversionLinkInformationDialogComponent } from 'common/dialog/information-dialog/conversion-link-information-dialog.component'; +import { LabelWithTooltipComponent } from 'common/components/labelWithTooltip/labelWithTooltip.component'; const matModules = [ MatDialogModule, @@ -97,7 +90,7 @@ const dialogs = [ ChangeAutomaticWithdrawDialogComponent, UserConfirmResponseDialogComponent, WarningDialogComponent, - InformationDialogComponent + ConversionLinkInformationDialogComponent, ]; const appComponents = [ @@ -144,13 +137,13 @@ const appComponents = [ ...appComponents, ], entryComponents: [ - ...dialogs + ...dialogs, ], providers: [ ChartService, CommonService, AssetHelpersService, - PushNotificationsService + PushNotificationsService, ], exports: [ ...appComponents, @@ -159,7 +152,7 @@ const appComponents = [ MatSpinner, MatDialogContent, MatSlideToggle, - MatIconModule + MatIconModule, ] }) diff --git a/src/app/common/dialog/add-funds-dialog/add-funds-dialog.component.html b/src/app/common/dialog/add-funds-dialog/add-funds-dialog.component.html index c5b3d4d77..afe5ba389 100644 --- a/src/app/common/dialog/add-funds-dialog/add-funds-dialog.component.html +++ b/src/app/common/dialog/add-funds-dialog/add-funds-dialog.component.html @@ -57,7 +57,7 @@ add-funds-dialog__hint" > Please transfer your ADS coins to this address. - Remember to add a message, otherwise your funds will not be deposited. + Remember to add a message (aka Payment ID or Memo), otherwise your funds will not be deposited.