Browse Source

Created marketing campaigns view details

pull/223/head
Ramón Tomás 7 years ago
parent
commit
9fee40e358
20 changed files with 381 additions and 98 deletions
  1. +5
    -2
      src/Services/Identity/Identity.API/Configuration/Config.cs
  2. +24
    -0
      src/Services/Marketing/Marketing.API/ViewModel/PaginatedItemsViewModel.cs
  3. +3
    -1
      src/Web/WebSPA/Client/modules/app.module.ts
  4. +5
    -1
      src/Web/WebSPA/Client/modules/app.routes.ts
  5. +0
    -1
      src/Web/WebSPA/Client/modules/campaign/campaign.module.ts
  6. +0
    -89
      src/Web/WebSPA/Client/modules/campaign/campaign.service.ts
  7. +17
    -0
      src/Web/WebSPA/Client/modules/campaigns/campaigns-detail/campaigns-detail.component.html
  8. +57
    -0
      src/Web/WebSPA/Client/modules/campaigns/campaigns-detail/campaigns-detail.component.scss
  9. +30
    -0
      src/Web/WebSPA/Client/modules/campaigns/campaigns-detail/campaigns-detail.component.ts
  10. +30
    -0
      src/Web/WebSPA/Client/modules/campaigns/campaigns.component.html
  11. +65
    -0
      src/Web/WebSPA/Client/modules/campaigns/campaigns.component.scss
  12. +49
    -0
      src/Web/WebSPA/Client/modules/campaigns/campaigns.component.ts
  13. +15
    -0
      src/Web/WebSPA/Client/modules/campaigns/campaigns.module.ts
  14. +52
    -0
      src/Web/WebSPA/Client/modules/campaigns/campaigns.service.ts
  15. +7
    -0
      src/Web/WebSPA/Client/modules/shared/components/identity/identity.html
  16. +1
    -1
      src/Web/WebSPA/Client/modules/shared/components/identity/identity.scss
  17. +9
    -0
      src/Web/WebSPA/Client/modules/shared/models/campaign.model.ts
  18. +8
    -0
      src/Web/WebSPA/Client/modules/shared/models/campaignItem.model.ts
  19. +1
    -1
      src/Web/WebSPA/Client/modules/shared/services/security.service.ts
  20. +3
    -2
      test/Services/FunctionalTests/Services/Marketing/MarketingScenarios.cs

+ 5
- 2
src/Services/Identity/Identity.API/Configuration/Config.cs View File

@ -50,7 +50,9 @@ namespace Identity.API.Configuration
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"orders",
"basket"
"basket",
"locations",
"marketing"
}
},
new Client
@ -74,7 +76,8 @@ namespace Identity.API.Configuration
IdentityServerConstants.StandardScopes.OfflineAccess,
"orders",
"basket",
"locations"
"locations",
"marketing"
},
//Allow requesting refresh tokens for long lived API access
AllowOfflineAccess = true,


+ 24
- 0
src/Services/Marketing/Marketing.API/ViewModel/PaginatedItemsViewModel.cs View File

@ -0,0 +1,24 @@
namespace Microsoft.eShopOnContainers.Services.Marketing.API.ViewModel
{
using System.Collections.Generic;
public class PaginatedItemsViewModel<TEntity> where TEntity : class
{
public int PageIndex { get; private set; }
public int PageSize { get; private set; }
public long Count { get; private set; }
public IEnumerable<TEntity> Data { get; private set; }
public PaginatedItemsViewModel(int pageIndex, int pageSize, long count, IEnumerable<TEntity> data)
{
this.PageIndex = pageIndex;
this.PageSize = pageSize;
this.Count = count;
this.Data = data;
}
}
}

+ 3
- 1
src/Web/WebSPA/Client/modules/app.module.ts View File

@ -11,6 +11,7 @@ import { SharedModule } from './shared/shared.module';
import { CatalogModule } from './catalog/catalog.module';
import { OrdersModule } from './orders/orders.module';
import { BasketModule } from './basket/basket.module';
import { CampaignsModule } from './campaigns/campaigns.module';
@NgModule({
declarations: [AppComponent],
@ -22,7 +23,8 @@ import { BasketModule } from './basket/basket.module';
SharedModule.forRoot(),
CatalogModule,
OrdersModule,
BasketModule
BasketModule,
CampaignsModule
],
providers: [
AppService


+ 5
- 1
src/Web/WebSPA/Client/modules/app.routes.ts View File

@ -5,6 +5,8 @@ import { CatalogComponent } from './catalog/catalog.component';
import { OrdersComponent } from './orders/orders.component';
import { OrdersDetailComponent } from './orders/orders-detail/orders-detail.component';
import { OrdersNewComponent } from './orders/orders-new/orders-new.component';
import { CampaignsComponent } from './campaigns/campaigns.component';
import { CampaignsDetailComponent } from './campaigns/campaigns-detail/campaigns-detail.component';
export const routes: Routes = [
{ path: '', redirectTo: 'catalog', pathMatch: 'full' },
@ -12,7 +14,9 @@ export const routes: Routes = [
{ path: 'catalog', component: CatalogComponent },
{ path: 'orders', component: OrdersComponent },
{ path: 'orders/:id', component: OrdersDetailComponent },
{ path: 'order', component: OrdersNewComponent }
{ path: 'order', component: OrdersNewComponent },
{ path: 'campaigns', component: CampaignsComponent },
{ path: 'campaigns/:id', component: CampaignsDetailComponent }
];
export const routing = RouterModule.forRoot(routes);

+ 0
- 1
src/Web/WebSPA/Client/modules/campaign/campaign.module.ts View File

@ -1 +0,0 @@


+ 0
- 89
src/Web/WebSPA/Client/modules/campaign/campaign.service.ts View File

@ -1,89 +0,0 @@
import { Injectable } from '@angular/core';
import { Response } from '@angular/http';
import { DataService } from '../shared/services/data.service';
import { IOrder } from '../shared/models/order.model';
import { IOrderItem } from '../shared/models/orderItem.model';
import { IOrderDetail } from "../shared/models/order-detail.model";
import { SecurityService } from '../shared/services/security.service';
import { ConfigurationService } from '../shared/services/configuration.service';
import 'rxjs/Rx';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/throw';
import { Observer } from 'rxjs/Observer';
import 'rxjs/add/operator/map';
@Injectable()
export class CampaignService {
private marketingUrl: string = '';
constructor(private service: DataService, private identityService: SecurityService, private configurationService: ConfigurationService) {
if (this.configurationService.isReady)
this.marketingUrl = this.configurationService.serverSettings.marketingUrl;
else
this.configurationService.settingsLoaded$.subscribe(x => this.marketingUrl = this.configurationService.serverSettings.marketingUrl);
}
getOrders(): Observable<IOrder[]> {
let url = this.marketingUrl + '/api/v1/campaigns/';
return this.service.get(url).map((response: Response) => {
return response.json();
});
}
getOrder(id: number): Observable<IOrderDetail> {
let url = this.marketingUrl + '/api/v1/campaigns/' + id;
return this.service.get(url).map((response: Response) => {
return response.json();
});
}
mapOrderAndIdentityInfoNewOrder(): IOrder {
let order = <IOrder>{};
let basket = this.basketService.basket;
let identityInfo = this.identityService.UserData;
console.log(basket);
console.log(identityInfo);
// Identity data mapping:
order.street = identityInfo.address_street;
order.city = identityInfo.address_city;
order.country = identityInfo.address_country;
order.state = identityInfo.address_state;
order.zipcode = identityInfo.address_zip_code;
order.cardexpiration = identityInfo.card_expiration;
order.cardnumber = identityInfo.card_number;
order.cardsecuritynumber = identityInfo.card_security_number;
order.cardtypeid = identityInfo.card_type;
order.cardholdername = identityInfo.card_holder;
order.total = 0;
order.expiration = identityInfo.card_expiration;
// basket data mapping:
order.orderItems = new Array<IOrderItem>();
basket.items.forEach(x => {
let item: IOrderItem = <IOrderItem>{};
item.pictureurl = x.pictureUrl;
item.productId = +x.productId;
item.productname = x.productName;
item.unitprice = x.unitPrice;
item.units = x.quantity;
order.total += (item.unitprice * item.units);
order.orderItems.push(item);
});
order.buyer = basket.buyerId;
return order;
}
}

+ 17
- 0
src/Web/WebSPA/Client/modules/campaigns/campaigns-detail/campaigns-detail.component.html View File

@ -0,0 +1,17 @@
<esh-header url="/campaigns">Back to campaigns</esh-header>
<div class="container">
<div class="esh-campaign_detail">
<div class="card esh-campaigns-items">
<img class="card-img-top" src="{{campaign.pictureUri}}" alt="{{campaign.name}}">
<div class="card-block">
<h4 class="card-title">{{campaign.name}}</h4>
<p class="card-text">{{campaign.description}}</p>
<p class="card-text">
<small class="text-muted">
From {{campaign.from | date}} Until {{campaign.to | date}}
</small>
</p>
</div>
</div>
</div>
</div>

+ 57
- 0
src/Web/WebSPA/Client/modules/campaigns/campaigns-detail/campaigns-detail.component.scss View File

@ -0,0 +1,57 @@
@import '../../variables';
.esh-campaign_detail {
min-height: 80vh;
margin-top: 1rem;
&-section {
padding: 1rem 0;
&--right {
text-align: right;
}
}
&-titles {
padding-bottom: 1rem;
padding-top: 2rem;
}
&-title {
text-transform: uppercase;
}
&-items {
&--border {
border-bottom: $border-light solid $color-foreground-bright;
padding: .5rem 0;
&:last-of-type {
border-color: transparent;
}
}
}
$item-height: 8rem;
&-item {
font-size: $font-size-m;
font-weight: $font-weight-semilight;
&--middle {
line-height: $item-height;
@media screen and (max-width: $media-screen-s) {
line-height: $font-size-m;
}
}
&--mark {
color: $color-secondary;
}
}
&-image {
height: $item-height;
}
}

+ 30
- 0
src/Web/WebSPA/Client/modules/campaigns/campaigns-detail/campaigns-detail.component.ts View File

@ -0,0 +1,30 @@
import { Component, OnInit } from '@angular/core';
import { CampaignsService } from '../campaigns.service';
import { ICampaignItem } from '../../shared/models/campaignItem.model';
import { ActivatedRoute } from '@angular/router';
@Component({
selector: 'esh-campaigns_detail',
styleUrls: ['./campaigns-detail.component.scss'],
templateUrl: './campaigns-detail.component.html'
})
export class CampaignsDetailComponent implements OnInit {
public campaign: ICampaignItem = <ICampaignItem>{};
constructor(private service: CampaignsService, private route: ActivatedRoute) { }
ngOnInit() {
this.route.params.subscribe(params => {
let id = +params['id']; // (+) converts string 'id' to a number
this.getCampaign(id);
});
}
getCampaign(id: number) {
this.service.getCampaign(id).subscribe(campaign => {
this.campaign = campaign;
console.log('campaign retrieved: ' + campaign.id);
console.log(this.campaign);
});
}
}

+ 30
- 0
src/Web/WebSPA/Client/modules/campaigns/campaigns.component.html View File

@ -0,0 +1,30 @@
<esh-header url="/catalog">Back to catalog</esh-header>
<div class="container">
<div *ngIf="campaigns?.data?.length > 0">
<esh-pager [model]="paginationInfo" (changed)="onPageChanged($event)"></esh-pager>
<div class="card-group esh-campaign-items row">
<div class="esh-campaign-item col-md-4"
*ngFor="let item of campaigns.data">
<div class="card-block">
<h4 class="card-title esh-campaigns-name">{{item.name}}</h4>
<img class="card-img-top esh-campaigns-thumbnail" src="{{item.pictureUri}}" alt="{{item.name}}">
<input [ngClass]="{'esh-campaign-button': true}" type="submit" value="More details" routerLink="/campaigns/{{item.id}}">
</div>
<div class="card-footer">
<small class="text-muted">
From {{item.from | date }} To {{item.to | date }}
</small>
</div>
</div>
</div>
<esh-pager [model]="paginationInfo" (changed)="onPageChanged($event)"></esh-pager>
</div>
<div *ngIf="campaigns?.data?.length == 0">
<span>THERE ARE NO RESULTS THAT MATCH YOUR SEARCH</span>
</div>
</div>

+ 65
- 0
src/Web/WebSPA/Client/modules/campaigns/campaigns.component.scss View File

@ -0,0 +1,65 @@
@import '../variables';
.esh-campaign {
$banner-height: 260px;
&-title {
position: relative;
top: $banner-height / 3.5;
}
&-items {
margin-top: 1rem;
}
&-item {
margin-bottom: 1.5rem;
text-align: center;
width: 33%;
display: inline-block;
float: none !important;
@media screen and (max-width: $media-screen-m) {
width: 50%;
}
@media screen and (max-width: $media-screen-s) {
width: 100%;
}
}
&-thumbnail {
max-width: 370px;
width: 100%;
}
&-button {
background-color: $color-secondary;
border: 0;
color: $color-foreground-brighter;
cursor: pointer;
font-size: $font-size-m;
height: 3rem;
margin-top: 1rem;
transition: all $animation-speed-default;
width: 80%;
&.is-disabled {
opacity: .5;
pointer-events: none;
}
&:hover {
background-color: $color-secondary-darker;
transition: all $animation-speed-default;
}
}
&-name {
font-size: $font-size-m;
font-weight: $font-weight-semilight;
margin-top: .5rem;
text-align: center;
text-transform: uppercase;
}
}

+ 49
- 0
src/Web/WebSPA/Client/modules/campaigns/campaigns.component.ts View File

@ -0,0 +1,49 @@
import { Component, OnInit } from '@angular/core';
import { CampaignsService } from './campaigns.service';
import { ICampaign } from '../shared/models/campaign.model';
import { IPager } from '../shared/models/pager.model';
import { ConfigurationService } from '../shared/services/configuration.service';
@Component({
selector: 'esh-campaigns',
styleUrls: ['./campaigns.component.scss'],
templateUrl: './campaigns.component.html'
})
export class CampaignsComponent implements OnInit {
private interval = null;
paginationInfo: IPager;
campaigns: ICampaign;
constructor(private service: CampaignsService, private configurationService: ConfigurationService) { }
ngOnInit() {
if (this.configurationService.isReady) {
this.getCampaigns(9, 0)
} else {
this.configurationService.settingsLoaded$.subscribe(x => {
this.getCampaigns(9, 0);
});
}
}
onPageChanged(value: any) {
console.log('campaigns pager event fired' + value);
//event.preventDefault();
this.paginationInfo.actualPage = value;
this.getCampaigns(this.paginationInfo.itemsPage, value);
}
getCampaigns(pageSize: number, pageIndex: number) {
this.service.getCampaigns(pageIndex, pageSize).subscribe(campaigns => {
this.campaigns = campaigns;
this.paginationInfo = {
actualPage : campaigns.pageIndex,
itemsPage : campaigns.pageSize,
totalItems : campaigns.count,
totalPages: Math.ceil(campaigns.count / campaigns.pageSize),
items: campaigns.pageSize
};
});
}
}

+ 15
- 0
src/Web/WebSPA/Client/modules/campaigns/campaigns.module.ts View File

@ -0,0 +1,15 @@
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { SharedModule } from '../shared/shared.module';
import { CampaignsComponent } from './campaigns.component';
import { CampaignsDetailComponent } from './campaigns-detail/campaigns-detail.component';
import { CampaignsService } from './campaigns.service';
import { Header } from '../shared/components/header/header';
@NgModule({
imports: [BrowserModule, SharedModule],
declarations: [CampaignsComponent, CampaignsDetailComponent],
providers: [CampaignsService]
})
export class CampaignsModule { }

+ 52
- 0
src/Web/WebSPA/Client/modules/campaigns/campaigns.service.ts View File

@ -0,0 +1,52 @@
import { Injectable } from '@angular/core';
import { Response } from '@angular/http';
import { DataService } from '../shared/services/data.service';
import { ICampaign } from '../shared/models/campaign.model';
import { ICampaignItem } from '../shared/models/campaignItem.model';
import { SecurityService } from '../shared/services/security.service';
import { ConfigurationService } from '../shared/services/configuration.service';
import 'rxjs/Rx';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/throw';
import { Observer } from 'rxjs/Observer';
import 'rxjs/add/operator/map';
@Injectable()
export class CampaignsService {
private marketingUrl: string = '';
private buyerId: string = '';
constructor(private service: DataService, private identityService: SecurityService, private configurationService: ConfigurationService) {
if (this.identityService.IsAuthorized) {
if (this.identityService.UserData) {
this.buyerId = this.identityService.UserData.sub;
}
}
if (this.configurationService.isReady)
this.marketingUrl = this.configurationService.serverSettings.marketingUrl;
else
this.configurationService.settingsLoaded$.subscribe(x => this.marketingUrl = this.configurationService.serverSettings.marketingUrl);
}
getCampaigns(pageIndex: number, pageSize: number): Observable<ICampaign> {
let url = this.marketingUrl + '/api/v1/campaigns/user/' + this.buyerId;
url = url + '?pageIndex=' + pageIndex + '&pageSize=' + pageSize;
return this.service.get(url).map((response: Response) => {
return response.json();
});
}
getCampaign(id: number): Observable<ICampaignItem> {
let url = this.marketingUrl + '/api/v1/campaigns/' + id;
return this.service.get(url).map((response: Response) => {
return response.json();
});
}
}

+ 7
- 0
src/Web/WebSPA/Client/modules/shared/components/identity/identity.html View File

@ -25,6 +25,13 @@
<img class="esh-identity-image" src="assets/images/my_orders.png">
</div>
<div class="esh-identity-item"
[routerLink]="['campaigns']">
<div class="esh-identity-name esh-identity-name--upper">My campaigns</div>
<img class="esh-identity-image" src="assets/images/my_orders.png">
</div>
<div class="esh-identity-item"
(click)="logoutClicked($event)">


+ 1
- 1
src/Web/WebSPA/Client/modules/shared/components/identity/identity.scss View File

@ -1,7 +1,7 @@
@import '../../../variables';
.esh-identity {
line-height: 3rem;
line-height: 2.1rem;
position: relative;
text-align: right;


+ 9
- 0
src/Web/WebSPA/Client/modules/shared/models/campaign.model.ts View File

@ -0,0 +1,9 @@
import {ICampaignItem} from './campaignItem.model';
export interface ICampaign {
data: ICampaignItem[];
pageIndex: number;
pageSize: number;
count: number;
}

+ 8
- 0
src/Web/WebSPA/Client/modules/shared/models/campaignItem.model.ts View File

@ -0,0 +1,8 @@
export interface ICampaignItem {
id: number;
name: string;
description: string;
from: Date;
to: Date;
pictureUri: string;
}

+ 1
- 1
src/Web/WebSPA/Client/modules/shared/services/security.service.ts View File

@ -82,7 +82,7 @@ export class SecurityService {
let client_id = 'js';
let redirect_uri = location.origin + '/';
let response_type = 'id_token token';
let scope = 'openid profile orders basket';
let scope = 'openid profile orders basket marketing locations';
let nonce = 'N' + Math.random() + '' + Date.now();
let state = Date.now() + '' + Math.random();


+ 3
- 2
test/Services/FunctionalTests/Services/Marketing/MarketingScenarios.cs View File

@ -11,6 +11,7 @@
using Xunit;
using System.Collections.Generic;
using Microsoft.eShopOnContainers.Services.Marketing.API.Dto;
using Microsoft.eShopOnContainers.Services.Catalog.API.ViewModel;
public class MarketingScenarios : MarketingScenariosBase
{
@ -49,9 +50,9 @@
.GetAsync(CampaignScenariosBase.Get.UserCampaignsByUserId(userId));
responseBody = await UserLocationCampaignResponse.Content.ReadAsStringAsync();
var userLocationCampaigns = JsonConvert.DeserializeObject<List<CampaignDTO>>(responseBody);
var userLocationCampaigns = JsonConvert.DeserializeObject<PaginatedItemsViewModel<CampaignDTO>>(responseBody);
Assert.True(userLocationCampaigns.Count > 0);
Assert.True(userLocationCampaigns.Data != null);
}
}
}


Loading…
Cancel
Save