Merge display marketing campaigns feature for SPA app
This commit is contained in:
commit
e1a007edfa
@ -61,6 +61,7 @@ services:
|
|||||||
- MongoConnectionString=mongodb://nosql.data
|
- MongoConnectionString=mongodb://nosql.data
|
||||||
- MongoDatabase=MarketingDb
|
- MongoDatabase=MarketingDb
|
||||||
- EventBusConnection=rabbitmq
|
- EventBusConnection=rabbitmq
|
||||||
|
- ExternalCatalogBaseUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5110 #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105.
|
||||||
- identityUrl=http://identity.api #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105.
|
- identityUrl=http://identity.api #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105.
|
||||||
ports:
|
ports:
|
||||||
- "5110:80"
|
- "5110:80"
|
||||||
@ -73,6 +74,7 @@ services:
|
|||||||
- OrderingUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5102
|
- OrderingUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5102
|
||||||
- IdentityUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5105 #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105.
|
- IdentityUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5105 #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105.
|
||||||
- BasketUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5103
|
- BasketUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5103
|
||||||
|
- MarketingUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5110
|
||||||
- CatalogUrlHC=http://catalog.api/hc
|
- CatalogUrlHC=http://catalog.api/hc
|
||||||
- OrderingUrlHC=http://ordering.api/hc
|
- OrderingUrlHC=http://ordering.api/hc
|
||||||
- IdentityUrlHC=http://identity.api/hc #Local: Use ${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}, if using external IP or DNS name from browser.
|
- IdentityUrlHC=http://identity.api/hc #Local: Use ${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}, if using external IP or DNS name from browser.
|
||||||
@ -87,7 +89,8 @@ services:
|
|||||||
- CatalogUrl=http://catalog.api
|
- CatalogUrl=http://catalog.api
|
||||||
- OrderingUrl=http://ordering.api
|
- OrderingUrl=http://ordering.api
|
||||||
- BasketUrl=http://basket.api
|
- BasketUrl=http://basket.api
|
||||||
- IdentityUrl=http://10.0.75.1:5105 #Local: Use 10.0.75.1 in a "Docker for Windows" environment, if using "localhost" from browser.
|
- IdentityUrl=http://10.0.75.1:5105
|
||||||
|
- MarketingUrl=http://marketing.api #Local: Use 10.0.75.1 in a "Docker for Windows" environment, if using "localhost" from browser.
|
||||||
#Remote: Use ${ESHOP_EXTERNAL_DNS_NAME_OR_IP} if using external IP or DNS name from browser.
|
#Remote: Use ${ESHOP_EXTERNAL_DNS_NAME_OR_IP} if using external IP or DNS name from browser.
|
||||||
ports:
|
ports:
|
||||||
- "5100:80"
|
- "5100:80"
|
||||||
|
@ -76,6 +76,7 @@ services:
|
|||||||
- ordering.api
|
- ordering.api
|
||||||
- identity.api
|
- identity.api
|
||||||
- basket.api
|
- basket.api
|
||||||
|
- marketing.api
|
||||||
|
|
||||||
sql.data:
|
sql.data:
|
||||||
image: microsoft/mssql-server-linux
|
image: microsoft/mssql-server-linux
|
||||||
@ -108,7 +109,7 @@ services:
|
|||||||
- rabbitmq
|
- rabbitmq
|
||||||
|
|
||||||
locations.api:
|
locations.api:
|
||||||
image: locations.api
|
image: eshop/locations.api
|
||||||
build:
|
build:
|
||||||
context: ./src/Services/Location/Locations.API
|
context: ./src/Services/Location/Locations.API
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
|
@ -50,7 +50,9 @@ namespace Identity.API.Configuration
|
|||||||
IdentityServerConstants.StandardScopes.OpenId,
|
IdentityServerConstants.StandardScopes.OpenId,
|
||||||
IdentityServerConstants.StandardScopes.Profile,
|
IdentityServerConstants.StandardScopes.Profile,
|
||||||
"orders",
|
"orders",
|
||||||
"basket"
|
"basket",
|
||||||
|
"locations",
|
||||||
|
"marketing"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
new Client
|
new Client
|
||||||
@ -74,7 +76,8 @@ namespace Identity.API.Configuration
|
|||||||
IdentityServerConstants.StandardScopes.OfflineAccess,
|
IdentityServerConstants.StandardScopes.OfflineAccess,
|
||||||
"orders",
|
"orders",
|
||||||
"basket",
|
"basket",
|
||||||
"locations"
|
"locations",
|
||||||
|
"marketing"
|
||||||
},
|
},
|
||||||
//Allow requesting refresh tokens for long lived API access
|
//Allow requesting refresh tokens for long lived API access
|
||||||
AllowOfflineAccess = true,
|
AllowOfflineAccess = true,
|
||||||
@ -108,7 +111,8 @@ namespace Identity.API.Configuration
|
|||||||
IdentityServerConstants.StandardScopes.OfflineAccess,
|
IdentityServerConstants.StandardScopes.OfflineAccess,
|
||||||
"orders",
|
"orders",
|
||||||
"basket",
|
"basket",
|
||||||
"locations"
|
"locations",
|
||||||
|
"marketing"
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,29 +1,34 @@
|
|||||||
namespace Microsoft.eShopOnContainers.Services.Marketing.API.Controllers
|
namespace Microsoft.eShopOnContainers.Services.Marketing.API.Controllers
|
||||||
{
|
{
|
||||||
using Infrastructure.Repositories;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.eShopOnContainers.Services.Marketing.API.Infrastructure;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.eShopOnContainers.Services.Marketing.API.Model;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Microsoft.eShopOnContainers.Services.Marketing.API.Dto;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Microsoft.AspNetCore.Authorization;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Infrastructure.Repositories;
|
||||||
|
using AspNetCore.Mvc;
|
||||||
|
using Infrastructure;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Model;
|
||||||
|
using EntityFrameworkCore;
|
||||||
|
using Dto;
|
||||||
|
using AspNetCore.Authorization;
|
||||||
|
using Extensions.Options;
|
||||||
|
using Microsoft.eShopOnContainers.Services.Marketing.API.ViewModel;
|
||||||
|
|
||||||
[Route("api/v1/[controller]")]
|
[Route("api/v1/[controller]")]
|
||||||
[Authorize]
|
[Authorize]
|
||||||
public class CampaignsController : Controller
|
public class CampaignsController : Controller
|
||||||
{
|
{
|
||||||
private readonly MarketingContext _context;
|
private readonly MarketingContext _context;
|
||||||
|
private readonly MarketingSettings _settings;
|
||||||
private readonly IMarketingDataRepository _marketingDataRepository;
|
private readonly IMarketingDataRepository _marketingDataRepository;
|
||||||
|
|
||||||
public CampaignsController(MarketingContext context,
|
public CampaignsController(MarketingContext context,
|
||||||
IMarketingDataRepository marketingDataRepository)
|
IMarketingDataRepository marketingDataRepository,
|
||||||
|
IOptionsSnapshot<MarketingSettings> settings)
|
||||||
{
|
{
|
||||||
_context = context;
|
_context = context;
|
||||||
_marketingDataRepository = marketingDataRepository;
|
_marketingDataRepository = marketingDataRepository;
|
||||||
|
_settings = settings.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
@ -88,10 +93,11 @@
|
|||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
campaignToUpdate.Name = campaignDto.Name;
|
||||||
campaignToUpdate.Description = campaignDto.Description;
|
campaignToUpdate.Description = campaignDto.Description;
|
||||||
campaignToUpdate.From = campaignDto.From;
|
campaignToUpdate.From = campaignDto.From;
|
||||||
campaignToUpdate.To = campaignDto.To;
|
campaignToUpdate.To = campaignDto.To;
|
||||||
campaignToUpdate.Url = campaignDto.Url;
|
campaignToUpdate.PictureUri = campaignDto.PictureUri;
|
||||||
|
|
||||||
await _context.SaveChangesAsync();
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
@ -119,24 +125,21 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("user/{userId:guid}")]
|
[HttpGet("user/{userId:guid}")]
|
||||||
public async Task<IActionResult> GetCampaignsByUserId(Guid userId)
|
public async Task<IActionResult> GetCampaignsByUserId(Guid userId, int pageSize = 10, int pageIndex = 0)
|
||||||
{
|
{
|
||||||
var marketingData = await _marketingDataRepository.GetAsync(userId.ToString());
|
var marketingData = await _marketingDataRepository.GetAsync(userId.ToString());
|
||||||
|
|
||||||
if (marketingData is null)
|
|
||||||
{
|
|
||||||
return NotFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
var campaignDtoList = new List<CampaignDTO>();
|
var campaignDtoList = new List<CampaignDTO>();
|
||||||
|
|
||||||
//Get User Location Campaign
|
if (marketingData != null)
|
||||||
foreach(var userLocation in marketingData.Locations)
|
|
||||||
{
|
{
|
||||||
|
var locationIdCandidateList = marketingData.Locations.Select(x => x.LocationId);
|
||||||
var userCampaignList = await _context.Rules
|
var userCampaignList = await _context.Rules
|
||||||
.OfType<UserLocationRule>()
|
.OfType<UserLocationRule>()
|
||||||
.Include(c => c.Campaign)
|
.Include(c => c.Campaign)
|
||||||
.Where(c => c.LocationId == userLocation.LocationId)
|
.Where(c => c.Campaign.From <= DateTime.Now
|
||||||
|
&& c.Campaign.To >= DateTime.Now
|
||||||
|
&& locationIdCandidateList.Contains(c.LocationId))
|
||||||
.Select(c => c.Campaign)
|
.Select(c => c.Campaign)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
@ -145,9 +148,18 @@
|
|||||||
var userCampaignDtoList = MapCampaignModelListToDtoList(userCampaignList);
|
var userCampaignDtoList = MapCampaignModelListToDtoList(userCampaignList);
|
||||||
campaignDtoList.AddRange(userCampaignDtoList);
|
campaignDtoList.AddRange(userCampaignDtoList);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(campaignDtoList);
|
var totalItems = campaignDtoList.Count();
|
||||||
|
campaignDtoList = campaignDtoList
|
||||||
|
.Skip(pageSize * pageIndex)
|
||||||
|
.Take(pageSize).ToList();
|
||||||
|
|
||||||
|
var model = new PaginatedItemsViewModel<CampaignDTO>(
|
||||||
|
pageIndex, pageSize, totalItems, campaignDtoList);
|
||||||
|
|
||||||
|
return Ok(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -166,10 +178,11 @@
|
|||||||
return new CampaignDTO
|
return new CampaignDTO
|
||||||
{
|
{
|
||||||
Id = campaign.Id,
|
Id = campaign.Id,
|
||||||
|
Name = campaign.Name,
|
||||||
Description = campaign.Description,
|
Description = campaign.Description,
|
||||||
From = campaign.From,
|
From = campaign.From,
|
||||||
To = campaign.To,
|
To = campaign.To,
|
||||||
Url = campaign.Url,
|
PictureUri = GetUriPlaceholder(campaign.PictureUri)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -178,11 +191,21 @@
|
|||||||
return new Campaign
|
return new Campaign
|
||||||
{
|
{
|
||||||
Id = campaignDto.Id,
|
Id = campaignDto.Id,
|
||||||
|
Name = campaignDto.Name,
|
||||||
Description = campaignDto.Description,
|
Description = campaignDto.Description,
|
||||||
From = campaignDto.From,
|
From = campaignDto.From,
|
||||||
To = campaignDto.To,
|
To = campaignDto.To,
|
||||||
Url = campaignDto.Url
|
PictureUri = campaignDto.PictureUri
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string GetUriPlaceholder(string campaignUri)
|
||||||
|
{
|
||||||
|
var baseUri = _settings.ExternalCatalogBaseUrl;
|
||||||
|
|
||||||
|
campaignUri = campaignUri.Replace("http://externalcatalogbaseurltobereplaced", baseUri);
|
||||||
|
|
||||||
|
return campaignUri;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
namespace Microsoft.eShopOnContainers.Services.Marketing.API.Controllers
|
||||||
|
{
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
|
||||||
|
public class PicController : Controller
|
||||||
|
{
|
||||||
|
private readonly IHostingEnvironment _env;
|
||||||
|
public PicController(IHostingEnvironment env)
|
||||||
|
{
|
||||||
|
_env = env;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
[Route("api/v1/campaigns/{campaignId:int}/pic")]
|
||||||
|
public IActionResult GetImage(int campaignId)
|
||||||
|
{
|
||||||
|
var webRoot = _env.WebRootPath;
|
||||||
|
var path = Path.Combine(webRoot, campaignId + ".png");
|
||||||
|
|
||||||
|
var buffer = System.IO.File.ReadAllBytes(path);
|
||||||
|
|
||||||
|
return File(buffer, "image/png");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -6,12 +6,14 @@
|
|||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
public string Description { get; set; }
|
public string Description { get; set; }
|
||||||
|
|
||||||
public DateTime From { get; set; }
|
public DateTime From { get; set; }
|
||||||
|
|
||||||
public DateTime To { get; set; }
|
public DateTime To { get; set; }
|
||||||
|
|
||||||
public string Url { get; set; }
|
public string PictureUri { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -31,8 +31,8 @@
|
|||||||
.ForSqlServerUseSequenceHiLo("campaign_hilo")
|
.ForSqlServerUseSequenceHiLo("campaign_hilo")
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
builder.Property(m => m.Description)
|
builder.Property(m => m.Name)
|
||||||
.HasColumnName("Description")
|
.HasColumnName("Name")
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
builder.Property(m => m.From)
|
builder.Property(m => m.From)
|
||||||
@ -47,6 +47,10 @@
|
|||||||
.HasColumnName("Description")
|
.HasColumnName("Description")
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
|
builder.Property(m => m.PictureUri)
|
||||||
|
.HasColumnName("PictureUri")
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
builder.HasMany(m => m.Rules)
|
builder.HasMany(m => m.Rules)
|
||||||
.WithOne(r => r.Campaign)
|
.WithOne(r => r.Campaign)
|
||||||
.HasForeignKey(r => r.CampaignId)
|
.HasForeignKey(r => r.CampaignId)
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
public class MarketingContextSeed
|
public static class MarketingContextSeed
|
||||||
{
|
{
|
||||||
public static async Task SeedAsync(IApplicationBuilder applicationBuilder, ILoggerFactory loggerFactory, int? retry = 0)
|
public static async Task SeedAsync(IApplicationBuilder applicationBuilder, ILoggerFactory loggerFactory, int? retry = 0)
|
||||||
{
|
{
|
||||||
@ -33,31 +33,33 @@
|
|||||||
{
|
{
|
||||||
new Campaign
|
new Campaign
|
||||||
{
|
{
|
||||||
Description = "Campaign1",
|
Name = ".NET Bot Black Hoodie 50% OFF",
|
||||||
|
Description = "Campaign Description 1",
|
||||||
From = DateTime.Now,
|
From = DateTime.Now,
|
||||||
To = DateTime.Now.AddDays(7),
|
To = DateTime.Now.AddDays(7),
|
||||||
Url = "http://CampaignUrl.test/12f09ed3cef54187123f500ad",
|
PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/campaigns/1/pic",
|
||||||
Rules = new List<Rule>
|
Rules = new List<Rule>
|
||||||
{
|
{
|
||||||
new UserLocationRule
|
new UserLocationRule
|
||||||
{
|
{
|
||||||
Description = "UserLocationRule1",
|
Description = "Campaign is only for United States users.",
|
||||||
LocationId = 1
|
LocationId = 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
new Campaign
|
new Campaign
|
||||||
{
|
{
|
||||||
Description = "Campaign2",
|
Name = "Roslyn Red T-Shirt 3x2",
|
||||||
From = DateTime.Now.AddDays(7),
|
Description = "Campaign Description 2",
|
||||||
|
From = DateTime.Now.AddDays(-7),
|
||||||
To = DateTime.Now.AddDays(14),
|
To = DateTime.Now.AddDays(14),
|
||||||
Url = "http://CampaignUrl.test/02a59eda65f241871239000ff",
|
PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/campaigns/2/pic",
|
||||||
Rules = new List<Rule>
|
Rules = new List<Rule>
|
||||||
{
|
{
|
||||||
new UserLocationRule
|
new UserLocationRule
|
||||||
{
|
{
|
||||||
Description = "UserLocationRule2",
|
Description = "Campaign is only for Seattle users.",
|
||||||
LocationId = 6
|
LocationId = 3
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,8 +8,8 @@ using Microsoft.eShopOnContainers.Services.Marketing.API.Infrastructure;
|
|||||||
namespace Microsoft.eShopOnContainers.Services.Marketing.API.Infrastructure.MarketingMigrations
|
namespace Microsoft.eShopOnContainers.Services.Marketing.API.Infrastructure.MarketingMigrations
|
||||||
{
|
{
|
||||||
[DbContext(typeof(MarketingContext))]
|
[DbContext(typeof(MarketingContext))]
|
||||||
[Migration("20170609104915_Initial")]
|
[Migration("20170615163431_Init")]
|
||||||
partial class Initial
|
partial class Init
|
||||||
{
|
{
|
||||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
{
|
{
|
||||||
@ -33,11 +33,17 @@ namespace Microsoft.eShopOnContainers.Services.Marketing.API.Infrastructure.Mark
|
|||||||
b.Property<DateTime>("From")
|
b.Property<DateTime>("From")
|
||||||
.HasColumnName("From");
|
.HasColumnName("From");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnName("Name");
|
||||||
|
|
||||||
|
b.Property<string>("PictureUri")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnName("PictureUri");
|
||||||
|
|
||||||
b.Property<DateTime>("To")
|
b.Property<DateTime>("To")
|
||||||
.HasColumnName("To");
|
.HasColumnName("To");
|
||||||
|
|
||||||
b.Property<string>("Url");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
b.ToTable("Campaign");
|
b.ToTable("Campaign");
|
@ -4,7 +4,7 @@ using Microsoft.EntityFrameworkCore.Migrations;
|
|||||||
|
|
||||||
namespace Microsoft.eShopOnContainers.Services.Marketing.API.Infrastructure.MarketingMigrations
|
namespace Microsoft.eShopOnContainers.Services.Marketing.API.Infrastructure.MarketingMigrations
|
||||||
{
|
{
|
||||||
public partial class Initial : Migration
|
public partial class Init : Migration
|
||||||
{
|
{
|
||||||
protected override void Up(MigrationBuilder migrationBuilder)
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
{
|
{
|
||||||
@ -23,8 +23,9 @@ namespace Microsoft.eShopOnContainers.Services.Marketing.API.Infrastructure.Mark
|
|||||||
Id = table.Column<int>(nullable: false),
|
Id = table.Column<int>(nullable: false),
|
||||||
Description = table.Column<string>(nullable: false),
|
Description = table.Column<string>(nullable: false),
|
||||||
From = table.Column<DateTime>(nullable: false),
|
From = table.Column<DateTime>(nullable: false),
|
||||||
To = table.Column<DateTime>(nullable: false),
|
Name = table.Column<string>(nullable: false),
|
||||||
Url = table.Column<string>(nullable: true)
|
PictureUri = table.Column<string>(nullable: false),
|
||||||
|
To = table.Column<DateTime>(nullable: false)
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
@ -32,11 +32,17 @@ namespace Microsoft.eShopOnContainers.Services.Marketing.API.Infrastructure.Mark
|
|||||||
b.Property<DateTime>("From")
|
b.Property<DateTime>("From")
|
||||||
.HasColumnName("From");
|
.HasColumnName("From");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnName("Name");
|
||||||
|
|
||||||
|
b.Property<string>("PictureUri")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnName("PictureUri");
|
||||||
|
|
||||||
b.Property<DateTime>("To")
|
b.Property<DateTime>("To")
|
||||||
.HasColumnName("To");
|
.HasColumnName("To");
|
||||||
|
|
||||||
b.Property<string>("Url");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
b.ToTable("Campaign");
|
b.ToTable("Campaign");
|
||||||
|
@ -12,6 +12,10 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="Infrastructure\MarketingMigrations\" />
|
<Folder Include="Infrastructure\MarketingMigrations\" />
|
||||||
|
<Content Include="Pics\**\*;">
|
||||||
|
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||||
|
</Content>
|
||||||
|
<Folder Include="Infrastructure\MarketingMigrations\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="4.1.0" />
|
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="4.1.0" />
|
||||||
@ -53,4 +57,13 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\EventBusRabbitMQ\EventBusRabbitMQ.csproj" />
|
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\EventBusRabbitMQ\EventBusRabbitMQ.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Update="Dockerfile">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="Pics\*">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -5,5 +5,6 @@
|
|||||||
public string ConnectionString { get; set; }
|
public string ConnectionString { get; set; }
|
||||||
public string MongoConnectionString { get; set; }
|
public string MongoConnectionString { get; set; }
|
||||||
public string MongoDatabase { get; set; }
|
public string MongoDatabase { get; set; }
|
||||||
|
public string ExternalCatalogBaseUrl { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,13 +7,15 @@
|
|||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
public string Description { get; set; }
|
public string Description { get; set; }
|
||||||
|
|
||||||
public DateTime From { get; set; }
|
public DateTime From { get; set; }
|
||||||
|
|
||||||
public DateTime To { get; set; }
|
public DateTime To { get; set; }
|
||||||
|
|
||||||
public string Url { get; set; }
|
public string PictureUri { get; set; }
|
||||||
|
|
||||||
public List<Rule> Rules { get; set; }
|
public List<Rule> Rules { get; set; }
|
||||||
|
|
||||||
|
BIN
src/Services/Marketing/Marketing.API/Pics/1.png
Normal file
BIN
src/Services/Marketing/Marketing.API/Pics/1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 136 KiB |
BIN
src/Services/Marketing/Marketing.API/Pics/2.png
Normal file
BIN
src/Services/Marketing/Marketing.API/Pics/2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 166 KiB |
@ -12,6 +12,7 @@
|
|||||||
.UseKestrel()
|
.UseKestrel()
|
||||||
.UseContentRoot(Directory.GetCurrentDirectory())
|
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||||
.UseStartup<Startup>()
|
.UseStartup<Startup>()
|
||||||
|
.UseWebRoot("Pics")
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
host.Run();
|
host.Run();
|
||||||
|
@ -20,6 +20,9 @@
|
|||||||
using Infrastructure.Repositories;
|
using Infrastructure.Repositories;
|
||||||
using Autofac;
|
using Autofac;
|
||||||
using Autofac.Extensions.DependencyInjection;
|
using Autofac.Extensions.DependencyInjection;
|
||||||
|
using Polly;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Data.SqlClient;
|
||||||
|
|
||||||
public class Startup
|
public class Startup
|
||||||
{
|
{
|
||||||
@ -133,10 +136,12 @@
|
|||||||
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
|
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
|
||||||
});
|
});
|
||||||
|
|
||||||
ConfigureEventBus(app);
|
var context = (MarketingContext)app
|
||||||
|
.ApplicationServices.GetService(typeof(MarketingContext));
|
||||||
|
|
||||||
MarketingContextSeed.SeedAsync(app, loggerFactory)
|
WaitForSqlAvailabilityAsync(context, loggerFactory, app).Wait();
|
||||||
.Wait();
|
|
||||||
|
ConfigureEventBus(app);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void ConfigureAuth(IApplicationBuilder app)
|
protected virtual void ConfigureAuth(IApplicationBuilder app)
|
||||||
@ -166,5 +171,28 @@
|
|||||||
eventBus.Subscribe<UserLocationUpdatedIntegrationEvent,
|
eventBus.Subscribe<UserLocationUpdatedIntegrationEvent,
|
||||||
IIntegrationEventHandler<UserLocationUpdatedIntegrationEvent>>();
|
IIntegrationEventHandler<UserLocationUpdatedIntegrationEvent>>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task WaitForSqlAvailabilityAsync(MarketingContext ctx, ILoggerFactory loggerFactory, IApplicationBuilder app, int retries = 0)
|
||||||
|
{
|
||||||
|
var logger = loggerFactory.CreateLogger(nameof(Startup));
|
||||||
|
var policy = CreatePolicy(retries, logger, nameof(WaitForSqlAvailabilityAsync));
|
||||||
|
await policy.ExecuteAsync(async () =>
|
||||||
|
{
|
||||||
|
await MarketingContextSeed.SeedAsync(app, loggerFactory);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private Policy CreatePolicy(int retries, ILogger logger, string prefix)
|
||||||
|
{
|
||||||
|
return Policy.Handle<SqlException>().
|
||||||
|
WaitAndRetryAsync(
|
||||||
|
retryCount: retries,
|
||||||
|
sleepDurationProvider: retry => TimeSpan.FromSeconds(5),
|
||||||
|
onRetry: (exception, timeSpan, retry, ctx) =>
|
||||||
|
{
|
||||||
|
logger.LogTrace($"[{prefix}] Exception {exception.GetType().Name} with message ${exception.Message} detected on attempt {retry} of {retries}");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,23 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -8,5 +8,6 @@
|
|||||||
"ConnectionString": "127.0.0.1",
|
"ConnectionString": "127.0.0.1",
|
||||||
"MongoConnectionString": "mongodb://nosql.data",
|
"MongoConnectionString": "mongodb://nosql.data",
|
||||||
"MongoDatabase": "MarketingDb",
|
"MongoDatabase": "MarketingDb",
|
||||||
"IdentityUrl": "http://localhost:5105"
|
"IdentityUrl": "http://localhost:5105",
|
||||||
|
"ExternalCatalogBaseUrl": "http://localhost:5110"
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ namespace Microsoft.eShopOnContainers.WebMVC
|
|||||||
public string CatalogUrl { get; set; }
|
public string CatalogUrl { get; set; }
|
||||||
public string OrderingUrl { get; set; }
|
public string OrderingUrl { get; set; }
|
||||||
public string BasketUrl { get; set; }
|
public string BasketUrl { get; set; }
|
||||||
|
public string MarketingUrl { get; set; }
|
||||||
public Logging Logging { get; set; }
|
public Logging Logging { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
64
src/Web/WebMVC/Controllers/CampaignsController.cs
Normal file
64
src/Web/WebMVC/Controllers/CampaignsController.cs
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
namespace Microsoft.eShopOnContainers.WebMVC.Controllers
|
||||||
|
{
|
||||||
|
using AspNetCore.Authorization;
|
||||||
|
using AspNetCore.Mvc;
|
||||||
|
using Services;
|
||||||
|
using ViewModels;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System;
|
||||||
|
using ViewModels.Pagination;
|
||||||
|
using global::WebMVC.ViewModels;
|
||||||
|
|
||||||
|
[Authorize]
|
||||||
|
public class CampaignsController : Controller
|
||||||
|
{
|
||||||
|
private readonly ICampaignService _campaignService;
|
||||||
|
|
||||||
|
public CampaignsController(ICampaignService campaignService) =>
|
||||||
|
_campaignService = campaignService;
|
||||||
|
|
||||||
|
public async Task<IActionResult> Index(int page = 0, int pageSize = 10)
|
||||||
|
{
|
||||||
|
var campaignList = await _campaignService.GetCampaigns(pageSize, page);
|
||||||
|
|
||||||
|
var vm = new CampaignViewModel()
|
||||||
|
{
|
||||||
|
CampaignItems = campaignList.Data,
|
||||||
|
PaginationInfo = new PaginationInfo()
|
||||||
|
{
|
||||||
|
ActualPage = page,
|
||||||
|
ItemsPerPage = pageSize,
|
||||||
|
TotalItems = campaignList.Count,
|
||||||
|
TotalPages = (int)Math.Ceiling(((decimal)campaignList.Count / pageSize))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
vm.PaginationInfo.Next = (vm.PaginationInfo.ActualPage == vm.PaginationInfo.TotalPages - 1) ? "is-disabled" : "";
|
||||||
|
vm.PaginationInfo.Previous = (vm.PaginationInfo.ActualPage == 0) ? "is-disabled" : "";
|
||||||
|
|
||||||
|
return View(vm);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IActionResult> Details(int id)
|
||||||
|
{
|
||||||
|
var campaignDto = await _campaignService.GetCampaignById(id);
|
||||||
|
|
||||||
|
if (campaignDto is null)
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
var campaign = new CampaignItem
|
||||||
|
{
|
||||||
|
Id = campaignDto.Id,
|
||||||
|
Name = campaignDto.Name,
|
||||||
|
Description = campaignDto.Description,
|
||||||
|
From = campaignDto.From,
|
||||||
|
To = campaignDto.To,
|
||||||
|
PictureUri = campaignDto.PictureUri
|
||||||
|
};
|
||||||
|
|
||||||
|
return View(campaign);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,6 @@
|
|||||||
namespace WebMVC.Infrastructure
|
using System;
|
||||||
|
|
||||||
|
namespace WebMVC.Infrastructure
|
||||||
{
|
{
|
||||||
public static class API
|
public static class API
|
||||||
{
|
{
|
||||||
@ -79,5 +81,18 @@
|
|||||||
return $"{baseUri}catalogTypes";
|
return $"{baseUri}catalogTypes";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class Marketing
|
||||||
|
{
|
||||||
|
public static string GetAllCampaigns(string baseUri, string userId, int take, int page)
|
||||||
|
{
|
||||||
|
return $"{baseUri}user/{userId}?pageSize={take}&pageIndex={page}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetAllCampaignById(string baseUri, int id)
|
||||||
|
{
|
||||||
|
return $"{baseUri}{id}";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
70
src/Web/WebMVC/Services/CampaignService.cs
Normal file
70
src/Web/WebMVC/Services/CampaignService.cs
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
namespace Microsoft.eShopOnContainers.WebMVC.Services
|
||||||
|
{
|
||||||
|
using global::WebMVC.Infrastructure;
|
||||||
|
using AspNetCore.Authentication;
|
||||||
|
using AspNetCore.Http;
|
||||||
|
using BuildingBlocks.Resilience.Http;
|
||||||
|
using ViewModels;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
public class CampaignService : ICampaignService
|
||||||
|
{
|
||||||
|
private readonly IOptionsSnapshot<AppSettings> _settings;
|
||||||
|
private readonly IHttpClient _apiClient;
|
||||||
|
private readonly ILogger<CampaignService> _logger;
|
||||||
|
private readonly string _remoteServiceBaseUrl;
|
||||||
|
private readonly IHttpContextAccessor _httpContextAccesor;
|
||||||
|
|
||||||
|
public CampaignService(IOptionsSnapshot<AppSettings> settings, IHttpClient httpClient,
|
||||||
|
ILogger<CampaignService> logger, IHttpContextAccessor httpContextAccesor)
|
||||||
|
{
|
||||||
|
_settings = settings;
|
||||||
|
_apiClient = httpClient;
|
||||||
|
_logger = logger;
|
||||||
|
|
||||||
|
_remoteServiceBaseUrl = $"{_settings.Value.MarketingUrl}/api/v1/campaigns/";
|
||||||
|
_httpContextAccesor = httpContextAccesor ?? throw new ArgumentNullException(nameof(httpContextAccesor));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Campaign> GetCampaigns(int pageSize, int pageIndex)
|
||||||
|
{
|
||||||
|
var userId = GetUserIdentity();
|
||||||
|
var allCampaignItemsUri = API.Marketing.GetAllCampaigns(_remoteServiceBaseUrl,
|
||||||
|
userId, pageSize, pageIndex);
|
||||||
|
|
||||||
|
var authorizationToken = await GetUserTokenAsync();
|
||||||
|
var dataString = await _apiClient.GetStringAsync(allCampaignItemsUri, authorizationToken);
|
||||||
|
|
||||||
|
var response = JsonConvert.DeserializeObject<Campaign>(dataString);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<CampaignItem> GetCampaignById(int id)
|
||||||
|
{
|
||||||
|
var campaignByIdItemUri = API.Marketing.GetAllCampaignById(_remoteServiceBaseUrl, id);
|
||||||
|
|
||||||
|
var authorizationToken = await GetUserTokenAsync();
|
||||||
|
var dataString = await _apiClient.GetStringAsync(campaignByIdItemUri, authorizationToken);
|
||||||
|
|
||||||
|
var response = JsonConvert.DeserializeObject<CampaignItem>(dataString);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetUserIdentity()
|
||||||
|
{
|
||||||
|
return _httpContextAccesor.HttpContext.User.FindFirst("sub").Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<string> GetUserTokenAsync()
|
||||||
|
{
|
||||||
|
var context = _httpContextAccesor.HttpContext;
|
||||||
|
return await context.Authentication.GetTokenAsync("access_token");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
13
src/Web/WebMVC/Services/ICampaignService.cs
Normal file
13
src/Web/WebMVC/Services/ICampaignService.cs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
namespace Microsoft.eShopOnContainers.WebMVC.Services
|
||||||
|
{
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using ViewModels;
|
||||||
|
|
||||||
|
public interface ICampaignService
|
||||||
|
{
|
||||||
|
Task<Campaign> GetCampaigns(int pageSize, int pageIndex);
|
||||||
|
|
||||||
|
Task<CampaignItem> GetCampaignById(int id);
|
||||||
|
}
|
||||||
|
}
|
@ -70,6 +70,7 @@ namespace Microsoft.eShopOnContainers.WebMVC
|
|||||||
services.AddTransient<ICatalogService, CatalogService>();
|
services.AddTransient<ICatalogService, CatalogService>();
|
||||||
services.AddTransient<IOrderingService, OrderingService>();
|
services.AddTransient<IOrderingService, OrderingService>();
|
||||||
services.AddTransient<IBasketService, BasketService>();
|
services.AddTransient<IBasketService, BasketService>();
|
||||||
|
services.AddTransient<ICampaignService, CampaignService>();
|
||||||
services.AddTransient<IIdentityParser<ApplicationUser>, IdentityParser>();
|
services.AddTransient<IIdentityParser<ApplicationUser>, IdentityParser>();
|
||||||
|
|
||||||
if (Configuration.GetValue<string>("UseResilientHttp") == bool.TrueString)
|
if (Configuration.GetValue<string>("UseResilientHttp") == bool.TrueString)
|
||||||
@ -125,7 +126,7 @@ namespace Microsoft.eShopOnContainers.WebMVC
|
|||||||
SaveTokens = true,
|
SaveTokens = true,
|
||||||
GetClaimsFromUserInfoEndpoint = true,
|
GetClaimsFromUserInfoEndpoint = true,
|
||||||
RequireHttpsMetadata = false,
|
RequireHttpsMetadata = false,
|
||||||
Scope = { "openid", "profile", "orders", "basket" }
|
Scope = { "openid", "profile", "orders", "basket", "marketing" }
|
||||||
};
|
};
|
||||||
|
|
||||||
//Wait untill identity service is ready on compose.
|
//Wait untill identity service is ready on compose.
|
||||||
|
12
src/Web/WebMVC/ViewModels/Campaign.cs
Normal file
12
src/Web/WebMVC/ViewModels/Campaign.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
namespace Microsoft.eShopOnContainers.WebMVC.ViewModels
|
||||||
|
{
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
public class Campaign
|
||||||
|
{
|
||||||
|
public int PageIndex { get; set; }
|
||||||
|
public int PageSize { get; set; }
|
||||||
|
public int Count { get; set; }
|
||||||
|
public List<CampaignItem> Data { get; set; }
|
||||||
|
}
|
||||||
|
}
|
19
src/Web/WebMVC/ViewModels/CampaignItem.cs
Normal file
19
src/Web/WebMVC/ViewModels/CampaignItem.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
namespace Microsoft.eShopOnContainers.WebMVC.ViewModels
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
|
||||||
|
public class CampaignItem
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public string Description { get; set; }
|
||||||
|
|
||||||
|
public DateTime From { get; set; }
|
||||||
|
|
||||||
|
public DateTime To { get; set; }
|
||||||
|
|
||||||
|
public string PictureUri { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
namespace WebMVC.ViewModels
|
||||||
|
{
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.eShopOnContainers.WebMVC.ViewModels;
|
||||||
|
using Microsoft.eShopOnContainers.WebMVC.ViewModels.Pagination;
|
||||||
|
|
||||||
|
public class CampaignViewModel
|
||||||
|
{
|
||||||
|
public IEnumerable<CampaignItem> CampaignItems { get; set; }
|
||||||
|
public PaginationInfo PaginationInfo { get; set; }
|
||||||
|
}
|
||||||
|
}
|
28
src/Web/WebMVC/Views/Campaigns/Details.cshtml
Normal file
28
src/Web/WebMVC/Views/Campaigns/Details.cshtml
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
@{
|
||||||
|
ViewData["Title"] = "Campaign details";
|
||||||
|
@model CampaignItem
|
||||||
|
}
|
||||||
|
<section class="esh-campaigns-hero">
|
||||||
|
<div class="container">
|
||||||
|
<img class="esh-campaigns-title" src="~/images/main_banner_text.png" />
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
@Html.Partial("_Header", new List<Header>() {
|
||||||
|
new Header() { Controller = "Catalog", Text = "Back to catalog" },
|
||||||
|
new Header() { Controller = "Campaigns", Text = "Back to Campaigns" } })
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="card esh-campaigns-items">
|
||||||
|
<img class="card-img-top" src="@Model.PictureUri" alt="Card image cap">
|
||||||
|
<div class="card-block">
|
||||||
|
<h4 class="card-title">@Model.Name</h4>
|
||||||
|
<p class="card-text">@Model.Description</p>
|
||||||
|
<p class="card-text">
|
||||||
|
<small class="text-muted">
|
||||||
|
From @Model.From.ToString("MMMM dd, yyyy") until @Model.To.ToString("MMMM dd, yyyy")
|
||||||
|
</small>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
37
src/Web/WebMVC/Views/Campaigns/Index.cshtml
Normal file
37
src/Web/WebMVC/Views/Campaigns/Index.cshtml
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
@{
|
||||||
|
ViewData["Title"] = "Campaigns";
|
||||||
|
@model WebMVC.ViewModels.CampaignViewModel
|
||||||
|
}
|
||||||
|
|
||||||
|
<section class="esh-campaigns-hero">
|
||||||
|
<div class="container">
|
||||||
|
<img class="esh-campaigns-title" src="~/images/main_banner_text.png" />
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
@Html.Partial("_Header", new List<Header>() {
|
||||||
|
new Header() { Controller = "Catalog", Text = "Back to catalog" } })
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
@if (Model.CampaignItems != null && Model.CampaignItems.Any())
|
||||||
|
{
|
||||||
|
@Html.Partial("_pagination", Model.PaginationInfo)
|
||||||
|
|
||||||
|
<div class="card-group esh-campaigns-items row">
|
||||||
|
@foreach (var catalogItem in Model.CampaignItems)
|
||||||
|
{
|
||||||
|
<div class="esh-campaigns-item col-md-4">
|
||||||
|
@Html.Partial("_campaign", catalogItem)
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@Html.Partial("_pagination", Model.PaginationInfo)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="esh-campaigns-items row">
|
||||||
|
THERE ARE NO CAMPAIGNS
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
17
src/Web/WebMVC/Views/Campaigns/_campaign.cshtml
Normal file
17
src/Web/WebMVC/Views/Campaigns/_campaign.cshtml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
@model CampaignItem
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<form asp-controller="Campaigns" asp-action="Details" asp-route-id="@Model.Id">
|
||||||
|
<div class="card-block">
|
||||||
|
<h4 class="card-title esh-campaigns-name">@Model.Name</h4>
|
||||||
|
<img class="card-img-top esh-campaigns-thumbnail" src="@Model.PictureUri" alt="@Model.Name">
|
||||||
|
<input class="esh-campaigns-button" type="submit" value="More details">
|
||||||
|
</div>
|
||||||
|
<div class="card-footer">
|
||||||
|
|
||||||
|
<small class="text-muted">
|
||||||
|
From @Model.From.ToString("MMMM dd, yyyy") until @Model.To.ToString("MMMM dd, yyyy")
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</form>
|
32
src/Web/WebMVC/Views/Campaigns/_pagination.cshtml
Normal file
32
src/Web/WebMVC/Views/Campaigns/_pagination.cshtml
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
@model Microsoft.eShopOnContainers.WebMVC.ViewModels.Pagination.PaginationInfo
|
||||||
|
|
||||||
|
<div class="esh-pager">
|
||||||
|
<div class="container">
|
||||||
|
<article class="esh-pager-wrapper row">
|
||||||
|
<nav>
|
||||||
|
<a class="esh-pager-item esh-pager-item--navigable @Model.Previous"
|
||||||
|
id="Previous"
|
||||||
|
asp-controller="Campaigns"
|
||||||
|
asp-action="Index"
|
||||||
|
asp-route-page="@(Model.ActualPage -1)"
|
||||||
|
aria-label="Previous">
|
||||||
|
Previous
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<span class="esh-pager-item">
|
||||||
|
Showing @Model.ItemsPerPage of @Model.TotalItems products - Page @(Model.ActualPage + 1) - @Model.TotalPages
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<a class="esh-pager-item esh-pager-item--navigable @Model.Next"
|
||||||
|
id="Next"
|
||||||
|
asp-controller="Campaigns"
|
||||||
|
asp-action="Index"
|
||||||
|
asp-route-page="@(Model.ActualPage + 1)"
|
||||||
|
aria-label="Next">
|
||||||
|
Next
|
||||||
|
</a>
|
||||||
|
</nav>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
@ -13,6 +13,7 @@
|
|||||||
<link rel="stylesheet" href="~/css/shared/components/identity/identity.css" />
|
<link rel="stylesheet" href="~/css/shared/components/identity/identity.css" />
|
||||||
<link rel="stylesheet" href="~/css/shared/components/pager/pager.css" />
|
<link rel="stylesheet" href="~/css/shared/components/pager/pager.css" />
|
||||||
<link rel="stylesheet" href="~/css/basket/basket.component.css" />
|
<link rel="stylesheet" href="~/css/basket/basket.component.css" />
|
||||||
|
<link rel="stylesheet" href="~/css/campaigns/campaigns.component.css" />
|
||||||
<link rel="stylesheet" href="~/css/basket/basket-status/basket-status.component.css" />
|
<link rel="stylesheet" href="~/css/basket/basket-status/basket-status.component.css" />
|
||||||
<link rel="stylesheet" href="~/css/catalog/catalog.component.css" />
|
<link rel="stylesheet" href="~/css/catalog/catalog.component.css" />
|
||||||
<link rel="stylesheet" href="~/css/orders/orders.component.css" />
|
<link rel="stylesheet" href="~/css/orders/orders.component.css" />
|
||||||
|
@ -26,6 +26,14 @@
|
|||||||
<img class="esh-identity-image" src="~/images/my_orders.png">
|
<img class="esh-identity-image" src="~/images/my_orders.png">
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
<a class="esh-identity-item"
|
||||||
|
asp-controller="Campaigns"
|
||||||
|
asp-action="Index">
|
||||||
|
|
||||||
|
<div class="esh-identity-name esh-identity-name--upper">Campaigns</div>
|
||||||
|
<img class="esh-identity-image" src="~/images/my_orders.png">
|
||||||
|
</a>
|
||||||
|
|
||||||
<a class="esh-identity-item"
|
<a class="esh-identity-item"
|
||||||
href="javascript:document.getElementById('logoutForm').submit()">
|
href="javascript:document.getElementById('logoutForm').submit()">
|
||||||
|
|
||||||
|
@ -9,6 +9,18 @@
|
|||||||
<DockerComposeProjectPath>..\..\..\docker-compose.dcproj</DockerComposeProjectPath>
|
<DockerComposeProjectPath>..\..\..\docker-compose.dcproj</DockerComposeProjectPath>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Remove="wwwroot/css\campaigns\catalog.component.css" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Include="wwwroot\css\campaigns\campaigns.component.css" />
|
||||||
|
<Content Include="wwwroot\css\campaigns\orders.component.css" />
|
||||||
|
<Content Include="wwwroot\css\catalog\orders.component.css">
|
||||||
|
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||||
|
</Content>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<!--<ItemGroup>
|
<!--<ItemGroup>
|
||||||
<Compile Remove="wwwroot\lib\bootstrap\**" />
|
<Compile Remove="wwwroot\lib\bootstrap\**" />
|
||||||
<Content Remove="wwwroot\lib\bootstrap\**" />
|
<Content Remove="wwwroot\lib\bootstrap\**" />
|
||||||
@ -70,4 +82,8 @@
|
|||||||
<Folder Include="wwwroot\lib\" />
|
<Folder Include="wwwroot\lib\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="ViewModels\CampaignItem.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
"CatalogUrl": "http://localhost:5101",
|
"CatalogUrl": "http://localhost:5101",
|
||||||
"OrderingUrl": "http://localhost:5102",
|
"OrderingUrl": "http://localhost:5102",
|
||||||
"BasketUrl": "http://localhost:5103",
|
"BasketUrl": "http://localhost:5103",
|
||||||
|
"MarketingUrl": "http://localhost:5110",
|
||||||
"IdentityUrl": "http://localhost:5105",
|
"IdentityUrl": "http://localhost:5105",
|
||||||
"CallBackUrl": "http://localhost:5100/",
|
"CallBackUrl": "http://localhost:5100/",
|
||||||
"IsClusterEnv": "False",
|
"IsClusterEnv": "False",
|
||||||
|
98
src/Web/WebMVC/wwwroot/css/campaigns/campaigns.component.css
Normal file
98
src/Web/WebMVC/wwwroot/css/campaigns/campaigns.component.css
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
.esh-campaigns-hero {
|
||||||
|
background-image: url("../../images/main_banner.png");
|
||||||
|
background-size: cover;
|
||||||
|
height: 260px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.esh-campaigns-title {
|
||||||
|
position: relative;
|
||||||
|
top: 74.28571px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.esh-campaigns-label::before {
|
||||||
|
color: rgba(255, 255, 255, 0.5);
|
||||||
|
content: attr(data-title);
|
||||||
|
font-size: 0.65rem;
|
||||||
|
margin-top: 0.65rem;
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
position: absolute;
|
||||||
|
text-transform: uppercase;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.esh-campaigns-label::after {
|
||||||
|
background-image: url("../../images/arrow-down.png");
|
||||||
|
height: 7px;
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
right: 1.5rem;
|
||||||
|
top: 2.5rem;
|
||||||
|
width: 10px;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.esh-campaigns-items {
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.esh-campaigns-item {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
width: 33%;
|
||||||
|
display: inline-block;
|
||||||
|
float: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 1024px) {
|
||||||
|
.esh-campaigns-item {
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 768px) {
|
||||||
|
.esh-campaigns-item {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.esh-campaigns-thumbnail {
|
||||||
|
max-width: 370px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.esh-campaigns-button {
|
||||||
|
background-color: #83D01B;
|
||||||
|
border: none;
|
||||||
|
color: #FFFFFF;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1rem;
|
||||||
|
height: 3rem;
|
||||||
|
margin-top: 1rem;
|
||||||
|
transition: all 0.35s;
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
.esh-campaigns-button.is-disabled {
|
||||||
|
opacity: .5;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.esh-campaigns-button:hover {
|
||||||
|
background-color: #4a760f;
|
||||||
|
transition: all 0.35s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.esh-campaigns-name {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 300;
|
||||||
|
margin-top: .5rem;
|
||||||
|
text-align: center;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.esh-campaigns-description {
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 300;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
@ -29,7 +29,7 @@
|
|||||||
|
|
||||||
.esh-identity-drop {
|
.esh-identity-drop {
|
||||||
background: #FFFFFF;
|
background: #FFFFFF;
|
||||||
height: 0;
|
height: 0rem;
|
||||||
min-width: 14rem;
|
min-width: 14rem;
|
||||||
right: 0;
|
right: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@ -41,7 +41,7 @@
|
|||||||
|
|
||||||
.esh-identity:hover .esh-identity-drop {
|
.esh-identity:hover .esh-identity-drop {
|
||||||
border: 1px solid #EEEEEE;
|
border: 1px solid #EEEEEE;
|
||||||
height: 7rem;
|
height: 9.5rem;
|
||||||
transition: height 0.35s;
|
transition: height 0.35s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,5 +12,6 @@ namespace eShopOnContainers.WebSPA
|
|||||||
public string OrderingUrl { get; set; }
|
public string OrderingUrl { get; set; }
|
||||||
public string IdentityUrl { get; set; }
|
public string IdentityUrl { get; set; }
|
||||||
public string BasketUrl { get; set; }
|
public string BasketUrl { get; set; }
|
||||||
|
public string MarketingUrl { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ import { SharedModule } from './shared/shared.module';
|
|||||||
import { CatalogModule } from './catalog/catalog.module';
|
import { CatalogModule } from './catalog/catalog.module';
|
||||||
import { OrdersModule } from './orders/orders.module';
|
import { OrdersModule } from './orders/orders.module';
|
||||||
import { BasketModule } from './basket/basket.module';
|
import { BasketModule } from './basket/basket.module';
|
||||||
|
import { CampaignsModule } from './campaigns/campaigns.module';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [AppComponent],
|
declarations: [AppComponent],
|
||||||
@ -22,7 +23,8 @@ import { BasketModule } from './basket/basket.module';
|
|||||||
SharedModule.forRoot(),
|
SharedModule.forRoot(),
|
||||||
CatalogModule,
|
CatalogModule,
|
||||||
OrdersModule,
|
OrdersModule,
|
||||||
BasketModule
|
BasketModule,
|
||||||
|
CampaignsModule
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
AppService
|
AppService
|
||||||
|
@ -5,6 +5,8 @@ import { CatalogComponent } from './catalog/catalog.component';
|
|||||||
import { OrdersComponent } from './orders/orders.component';
|
import { OrdersComponent } from './orders/orders.component';
|
||||||
import { OrdersDetailComponent } from './orders/orders-detail/orders-detail.component';
|
import { OrdersDetailComponent } from './orders/orders-detail/orders-detail.component';
|
||||||
import { OrdersNewComponent } from './orders/orders-new/orders-new.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 = [
|
export const routes: Routes = [
|
||||||
{ path: '', redirectTo: 'catalog', pathMatch: 'full' },
|
{ path: '', redirectTo: 'catalog', pathMatch: 'full' },
|
||||||
@ -12,7 +14,9 @@ export const routes: Routes = [
|
|||||||
{ path: 'catalog', component: CatalogComponent },
|
{ path: 'catalog', component: CatalogComponent },
|
||||||
{ path: 'orders', component: OrdersComponent },
|
{ path: 'orders', component: OrdersComponent },
|
||||||
{ path: 'orders/:id', component: OrdersDetailComponent },
|
{ 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);
|
export const routing = RouterModule.forRoot(routes);
|
||||||
|
@ -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>
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -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-campaign-name">{{item.name}}</h4>
|
||||||
|
<img class="card-img-top esh-campaign-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>
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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
src/Web/WebSPA/Client/modules/campaigns/campaigns.module.ts
Normal file
15
src/Web/WebSPA/Client/modules/campaigns/campaigns.module.ts
Normal 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
src/Web/WebSPA/Client/modules/campaigns/campaigns.service.ts
Normal file
52
src/Web/WebSPA/Client/modules/campaigns/campaigns.service.ts
Normal 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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -25,6 +25,13 @@
|
|||||||
<img class="esh-identity-image" src="assets/images/my_orders.png">
|
<img class="esh-identity-image" src="assets/images/my_orders.png">
|
||||||
</div>
|
</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"
|
<div class="esh-identity-item"
|
||||||
(click)="logoutClicked($event)">
|
(click)="logoutClicked($event)">
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
@import '../../../variables';
|
@import '../../../variables';
|
||||||
|
|
||||||
.esh-identity {
|
.esh-identity {
|
||||||
line-height: 3rem;
|
line-height: 2.1rem;
|
||||||
position: relative;
|
position: relative;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
|
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
import {ICampaignItem} from './campaignItem.model';
|
||||||
|
|
||||||
|
export interface ICampaign {
|
||||||
|
data: ICampaignItem[];
|
||||||
|
pageIndex: number;
|
||||||
|
pageSize: number;
|
||||||
|
count: number;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
|||||||
|
export interface ICampaignItem {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
from: Date;
|
||||||
|
to: Date;
|
||||||
|
pictureUri: string;
|
||||||
|
}
|
@ -2,5 +2,6 @@ export interface IConfiguration {
|
|||||||
catalogUrl: string,
|
catalogUrl: string,
|
||||||
orderingUrl: string,
|
orderingUrl: string,
|
||||||
identityUrl: string,
|
identityUrl: string,
|
||||||
basketUrl: string
|
basketUrl: string,
|
||||||
|
marketingUrl: string
|
||||||
}
|
}
|
@ -32,6 +32,7 @@ export class ConfigurationService {
|
|||||||
this.storageService.store('catalogUrl', this.serverSettings.catalogUrl);
|
this.storageService.store('catalogUrl', this.serverSettings.catalogUrl);
|
||||||
this.storageService.store('identityUrl', this.serverSettings.identityUrl);
|
this.storageService.store('identityUrl', this.serverSettings.identityUrl);
|
||||||
this.storageService.store('orderingUrl', this.serverSettings.orderingUrl);
|
this.storageService.store('orderingUrl', this.serverSettings.orderingUrl);
|
||||||
|
this.storageService.store('marketingUrl', this.serverSettings.marketingUrl);
|
||||||
this.isReady = true;
|
this.isReady = true;
|
||||||
this.settingsLoadedSource.next();
|
this.settingsLoadedSource.next();
|
||||||
});
|
});
|
||||||
|
@ -82,7 +82,7 @@ export class SecurityService {
|
|||||||
let client_id = 'js';
|
let client_id = 'js';
|
||||||
let redirect_uri = location.origin + '/';
|
let redirect_uri = location.origin + '/';
|
||||||
let response_type = 'id_token token';
|
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 nonce = 'N' + Math.random() + '' + Date.now();
|
||||||
let state = Date.now() + '' + Math.random();
|
let state = Date.now() + '' + Math.random();
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
"OrderingUrl": "http://localhost:5102",
|
"OrderingUrl": "http://localhost:5102",
|
||||||
"BasketUrl": "http://localhost:5103",
|
"BasketUrl": "http://localhost:5103",
|
||||||
"IdentityUrl": "http://localhost:5105",
|
"IdentityUrl": "http://localhost:5105",
|
||||||
|
"MarketingUrl": "http://localhost:5110",
|
||||||
"CallBackUrl": "http://localhost:5104/",
|
"CallBackUrl": "http://localhost:5104/",
|
||||||
"IsClusterEnv": "False",
|
"IsClusterEnv": "False",
|
||||||
"Logging": {
|
"Logging": {
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
using Xunit;
|
using Xunit;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Microsoft.eShopOnContainers.Services.Marketing.API.Dto;
|
using Microsoft.eShopOnContainers.Services.Marketing.API.Dto;
|
||||||
|
using Microsoft.eShopOnContainers.Services.Catalog.API.ViewModel;
|
||||||
|
|
||||||
public class MarketingScenarios : MarketingScenariosBase
|
public class MarketingScenarios : MarketingScenariosBase
|
||||||
{
|
{
|
||||||
@ -49,9 +50,9 @@
|
|||||||
.GetAsync(CampaignScenariosBase.Get.UserCampaignsByUserId(userId));
|
.GetAsync(CampaignScenariosBase.Get.UserCampaignsByUserId(userId));
|
||||||
|
|
||||||
responseBody = await UserLocationCampaignResponse.Content.ReadAsStringAsync();
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -117,10 +117,11 @@
|
|||||||
{
|
{
|
||||||
return new CampaignDTO()
|
return new CampaignDTO()
|
||||||
{
|
{
|
||||||
|
Name = "FakeCampaignName",
|
||||||
Description = "FakeCampaignDescription",
|
Description = "FakeCampaignDescription",
|
||||||
From = DateTime.Now,
|
From = DateTime.Now,
|
||||||
To = DateTime.Now.AddDays(7),
|
To = DateTime.Now.AddDays(7),
|
||||||
Url = "http://CampaignUrl.test/fdaf91ad0cef5419719f50198",
|
PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/campaigns/0/pic"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user