Commit 5bea1a96 authored by Emiliano Balbuena's avatar Emiliano Balbuena

(feat): Show warning when campaign cannot be fulfilled

1 merge request!387WIP: Boost Campaigns (&24)
Pipeline #71568899 passed with stages
in 28 minutes and 7 seconds
......@@ -344,6 +344,18 @@ m-app {
font-size: 16px;
font-weight: bold;
padding: 4px;
.m-tooltip i.material-icons {
vertical-align: middle;
font-size: 1em;
margin-left: 8px;
position: relative;
top: -1px;
@include m-theme(){
color: themed($m-red);
}
}
}
}
......
import { Injectable } from '@angular/core';
import { Campaign, CampaignDeliveryStatus, CampaignPayment, CampaignType } from './campaigns.type';
import { Campaign, CampaignDeliveryStatus, CampaignPayment, CampaignPreview, CampaignType } from './campaigns.type';
import { Client } from '../../../services/api/client';
import isInt from '../../../helpers/is-int';
import getGuidFromUrn from '../../../helpers/get-guid-from-urn';
......@@ -84,6 +84,20 @@ export class CampaignsService {
!!campaign.type;
}
async preview(campaign: Campaign): Promise<CampaignPreview | false> {
if (
!campaign.type ||
!campaign.entity_urns ||
!campaign.entity_urns.length ||
!campaign.budget ||
!campaign.budget_type
) {
return false;
}
return (await this.client.post(`api/v2/boost/campaigns/preview`, campaign) as any).preview;
}
async prepare(campaign: Campaign): Promise<{ checksum: string, guid: string }> {
if (campaign.entity_urns.length !== 1) {
throw new Error('Campaigns without a single entity are unsupported');
......
......@@ -40,3 +40,7 @@ export type CampaignPayment = {
txHash: string,
amount: number,
};
export type CampaignPreview = {
cannot_fulfill_daily?: boolean,
};
......@@ -24,7 +24,13 @@
<m-tooltip icon="help" i18n>TBD</m-tooltip>
</label>
<input id="boost-creator__name" type="text" name="name" [(ngModel)]="campaign.name">
<input
id="boost-creator__name"
type="text"
name="name"
[ngModel]="campaign.name"
(ngModelChange)="campaign.name = $event; triggerPreview()"
>
</div>
<div class="m-form--field">
......@@ -41,7 +47,7 @@
[class.m-btn--readonly]="isEditing"
[for]="'type__' + type.id"
[tabindex]="!type.disabled ? 0 : -1"
(keydown.space)="campaign.type = type.id"
(keydown.space)="campaign.type = type.id; triggerPreview()"
>
<input
type="radio"
......@@ -49,7 +55,7 @@
[value]="type.id"
[checked]="type.id === campaign.type"
[disabled]="isEditing || type.disabled"
(change)="campaign.type = type.id"
(change)="campaign.type = type.id; triggerPreview()"
name="type"
>
......@@ -84,7 +90,8 @@
<m-boost-campaigns-creator--content-selector
*ngIf="campaign.type === 'newsfeed' || campaign.type === 'content'"
[type]="campaign.type"
[(content)]="campaign.entity_urns"
[content]="campaign.entity_urns"
(contentChange)="campaign.entity_urns = $event; triggerPreview()"
[readonly]="isEditing"
></m-boost-campaigns-creator--content-selector>
</div>
......@@ -109,8 +116,8 @@
buttonClass="m-boost-campaigns-creator--hashtag-selector-button"
[useContent]="true"
[tags]="campaign.hashtags"
(tagsAdded)="onTagsAdded($event)"
(tagsRemoved)="onTagsRemoved($event)"
(tagsAdded)="onTagsAdded($event); triggerPreview()"
(tagsRemoved)="onTagsRemoved($event); triggerPreview()"
>
<i class="material-icons">check</i>
</m-hashtags-selector>
......@@ -124,7 +131,12 @@
<m-tooltip icon="help" i18n>TBD</m-tooltip>
</label>
<div class="m-date-selector--input" mdl-datetime-picker [date]="campaign.start" (dateChange)="onStartDateChange($event)">
<div
class="m-date-selector--input"
mdl-datetime-picker
[date]="campaign.start"
(dateChange)="onStartDateChange($event); triggerPreview()"
>
<input id="boost-creator__start" type="text" placeholder="Now" i18n [value]="campaign.start | date:'MMM dd'" readonly>
<i class="material-icons">keyboard_arrow_down</i>
</div>
......@@ -136,7 +148,11 @@
<m-tooltip icon="help" i18n>TBD</m-tooltip>
</label>
<div class="m-date-selector--input" mdl-datetime-picker [date]="campaign.end" (dateChange)="onEndDateChange($event)">
<div
class="m-date-selector--input"
mdl-datetime-picker [date]="campaign.end"
(dateChange)="onEndDateChange($event); triggerPreview()"
>
<input id="boost-creator__end" type="text" placeholder="End of times" i18n [value]="campaign.end | date:'MMM dd'" readonly>
<i class="material-icons">keyboard_arrow_down</i>
</div>
......@@ -155,7 +171,7 @@
type="number"
name="budget"
[ngModel]="campaign.budget"
(ngModelChange)="campaign.budget = $event; calcImpressions()"
(ngModelChange)="campaign.budget = $event; triggerPreview()"
>
<span>TOKENS</span>
......@@ -169,7 +185,14 @@
</label>
<div class="m-form--read-only-value">
<span>{{campaign.impressions | number}}</span>
<span>
{{campaign.impressions | number}}
<m-tooltip icon="warning" *ngIf="preview?.cannot_fulfill_daily" i18n>
There might be issues trying to fulfill the impressions per day
goal for this campaign.
</m-tooltip>
</span>
</div>
</div>
......
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit } from "@angular/core";
import { ActivatedRoute, Router } from '@angular/router';
import { Campaign, CampaignDeliveryStatus, CampaignPayment } from "../campaigns.type";
import { Campaign, CampaignDeliveryStatus, CampaignPayment, CampaignPreview } from "../campaigns.type";
import { CampaignsService } from '../campaigns.service';
import { Subscription } from 'rxjs';
import { Subject, Subscription } from 'rxjs';
import { Tag } from '../../../hashtags/types/tag';
import { CampaignPaymentsService } from '../campaign-payments.service';
import { debounceTime } from 'rxjs/operators';
@Component({
providers: [CampaignsService, CampaignPaymentsService],
......@@ -24,8 +25,14 @@ export class BoostCampaignsCreatorComponent implements OnInit, OnDestroy {
currentError: string = '';
preview: CampaignPreview = {};
protected route$: Subscription;
protected previewSubject: Subject<Campaign> = new Subject();
protected preview$: Subscription;
constructor(
protected service: CampaignsService,
protected payments: CampaignPaymentsService,
......@@ -48,10 +55,18 @@ export class BoostCampaignsCreatorComponent implements OnInit, OnDestroy {
this.load();
}
});
this.preview$ = this.previewSubject
.pipe(debounceTime(600))
.subscribe(async (campaign: Campaign) => {
this.preview = (await this.service.preview(campaign)) || {};
this.detectChanges();
});
}
ngOnDestroy() {
this.route$.unsubscribe();
this.preview$.unsubscribe();
}
createFrom({ type, from }) {
......@@ -156,6 +171,11 @@ export class BoostCampaignsCreatorComponent implements OnInit, OnDestroy {
return this.service.getTypes();
}
triggerPreview() {
this.calcImpressions();
this.previewSubject.next(this.campaign);
}
async submit() {
if (!this.canSubmit() || this.inProgress) {
return;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment