Merge branch 'Dev' of https://github.com/dotnet/eShopOnContainers.git
This commit is contained in:
commit
e13c4f09e6
@ -53,7 +53,14 @@
|
||||
});
|
||||
});
|
||||
|
||||
services.AddCors();
|
||||
services.AddCors(options =>
|
||||
{
|
||||
options.AddPolicy("CorsPolicy",
|
||||
builder => builder.AllowAnyOrigin()
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader()
|
||||
.AllowCredentials());
|
||||
});
|
||||
|
||||
services.AddMvc(mvcoptions =>
|
||||
{
|
||||
@ -78,7 +85,7 @@
|
||||
.Wait();
|
||||
|
||||
// Use frameworks
|
||||
app.UseCors(policyBuilder => policyBuilder.AllowAnyOrigin());
|
||||
app.UseCors("CorsPolicy");
|
||||
|
||||
app.UseMvc();
|
||||
|
||||
|
@ -16,7 +16,7 @@ services:
|
||||
- catalog.data
|
||||
|
||||
catalog.data:
|
||||
image: eshop/mssql-server-private-preview
|
||||
image: microsoft/mssql-server-linux
|
||||
environment:
|
||||
- SA_PASSWORD=Pass@word
|
||||
- ACCEPT_EULA=Y
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"ConnectionString": "Server=tcp:127.0.0.1,1455;Initial Catalog=CatalogDB;User Id=sa;Password=Pass@word",
|
||||
"ConnectionString": "Server=tcp:127.0.0.1,1433;Initial Catalog=CatalogDB;User Id=sa;Password=Pass@word",
|
||||
"Logging": {
|
||||
"IncludeScopes": false,
|
||||
"LogLevel": {
|
||||
|
@ -27,7 +27,7 @@ services:
|
||||
- catalog.data
|
||||
|
||||
catalog.data:
|
||||
image: eshop/mssql-server-private-preview
|
||||
image: microsoft/mssql-server-linux
|
||||
environment:
|
||||
- SA_PASSWORD=Pass@word
|
||||
- ACCEPT_EULA=Y
|
||||
@ -55,7 +55,7 @@ services:
|
||||
- "5432:1433"
|
||||
|
||||
identity.data:
|
||||
image: eshop/mssql-server-private-preview
|
||||
image: microsoft/mssql-server-linux
|
||||
environment:
|
||||
- SA_PASSWORD=Pass@word
|
||||
- ACCEPT_EULA=Y
|
||||
|
@ -825,6 +825,7 @@ form .col-md-4 {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
|
||||
.navbar-nav {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 7.5px;
|
||||
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
After Width: | Height: | Size: 111 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
After Width: | Height: | Size: 104 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1 @@
|
||||
<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'><svg enable-background="new 0 0 32 32" height="32px" id="Слой_1" version="1.1" viewBox="0 0 32 32" width="32px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path clip-rule="evenodd" d="M21.698,15.286l-9.002-8.999 c-0.395-0.394-1.035-0.394-1.431,0c-0.395,0.394-0.395,1.034,0,1.428L19.553,16l-8.287,8.285c-0.395,0.394-0.395,1.034,0,1.429 c0.395,0.394,1.036,0.394,1.431,0l9.002-8.999C22.088,16.325,22.088,15.675,21.698,15.286z" fill="#FFFFFF" fill-rule="evenodd" id="Chevron_Right"/><g/><g/><g/><g/><g/><g/></svg>
|
After Width: | Height: | Size: 693 B |
BIN
src/Web/WebSPA/eShopOnContainers.WebSPA/Client/images/cart.png
Normal file
BIN
src/Web/WebSPA/eShopOnContainers.WebSPA/Client/images/cart.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
@ -1,14 +1,41 @@
|
||||
<!-- header component -->
|
||||
<appc-header></appc-header>
|
||||
<header class="navbar navbar-light navbar-static-top">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-sm-10">
|
||||
<a class="navbar-brand" routerLink="catalog">
|
||||
<img src="../images/brand.png" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-sm-2">
|
||||
<esh-basket-status></esh-basket-status>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="container">
|
||||
<!-- component routing placeholder -->
|
||||
<router-outlet></router-outlet>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<!-- footer component -->
|
||||
<appc-footer></appc-footer>
|
||||
<!-- component routing placeholder -->
|
||||
<router-outlet></router-outlet>
|
||||
|
||||
|
||||
<footer class="app-footer">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<br>
|
||||
<div class="app-footer-brand">
|
||||
<img src="../images/brand_dark.png" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<br>
|
||||
<br>
|
||||
<br>
|
||||
<div class="app-footer-text hidden-xs">e-ShoponContainers. All right reserved</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
|
||||
</div>
|
@ -1,2 +1,49 @@
|
||||
@import './_variables.scss';
|
||||
|
||||
@font-face {
|
||||
font-family: Montserrat;
|
||||
font-weight: 400;
|
||||
src: url("../fonts/Montserrat-Regular.eot?") format("eot"),url("../fonts/Montserrat-Regular.woff") format("woff"),url("../fonts/Montserrat-Regular.ttf") format("truetype"),url("../fonts/Montserrat-Regular.svg#Montserrat") format("svg")
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Montserrat;
|
||||
font-weight: 700;
|
||||
src: url("../fonts/Montserrat-Bold.eot?") format("eot"),url("../fonts/Montserrat-Bold.woff") format("woff"),url("../fonts/Montserrat-Bold.ttf") format("truetype"),url("../fonts/Montserrat-Bold.svg#Montserrat") format("svg")
|
||||
}
|
||||
|
||||
body {
|
||||
padding-top: 80px;
|
||||
/*padding-bottom: 20px;*/
|
||||
font-family: Montserrat,sans-serif;
|
||||
min-width:480px;
|
||||
}
|
||||
|
||||
.app {
|
||||
&-footer {
|
||||
padding-top: 40px;
|
||||
padding-bottom: 40px;
|
||||
margin-top: 40px;
|
||||
border-top: 1px solid #eee;
|
||||
background-color: black;
|
||||
width: 100%;
|
||||
|
||||
&-brand {
|
||||
margin-top: 25px;
|
||||
max-width: 231px;
|
||||
height: 52px;
|
||||
margin-left: 0px !important;
|
||||
}
|
||||
|
||||
&-text {
|
||||
text-align: right;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
color: #83d01b;
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@ -9,6 +9,7 @@ import { AppService } from './app.service';
|
||||
import { AppComponent } from './app.component';
|
||||
import { SharedModule } from './shared/shared.module';
|
||||
import { CatalogModule } from './catalog/catalog.module';
|
||||
import { BasketModule } from './basket/basket.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [AppComponent],
|
||||
@ -18,7 +19,8 @@ import { CatalogModule } from './catalog/catalog.module';
|
||||
HttpModule,
|
||||
// Only module that app module loads
|
||||
SharedModule.forRoot(),
|
||||
CatalogModule
|
||||
CatalogModule,
|
||||
BasketModule
|
||||
],
|
||||
providers: [
|
||||
AppService
|
||||
|
@ -1,15 +1,17 @@
|
||||
import { Routes, RouterModule } from '@angular/router';
|
||||
|
||||
import { BasketComponent } from './basket/basket.component';
|
||||
import { CatalogComponent } from './catalog/catalog.component';
|
||||
|
||||
export const routes: Routes = [
|
||||
{ path: '', redirectTo: 'catalog', pathMatch: 'full' }
|
||||
// Lazy async modules
|
||||
// {
|
||||
// path: 'login', loadChildren: () => new Promise(resolve => {
|
||||
// (require as any).ensure([], (require: any) => {
|
||||
// resolve(require('./+login/login.module').LoginModule);
|
||||
// });
|
||||
// })
|
||||
// }
|
||||
{ path: '', redirectTo: 'catalog', pathMatch: 'full' },
|
||||
{ path: 'basket', component: BasketComponent },
|
||||
{ path: 'catalog', component: CatalogComponent }
|
||||
//Lazy async modules (angular-loader-router) and enable a router in each module.
|
||||
//{
|
||||
// path: 'basket', loadChildren: '/basket/basket.module' });
|
||||
// })
|
||||
//}
|
||||
];
|
||||
|
||||
export const routing = RouterModule.forRoot(routes);
|
||||
|
@ -0,0 +1,8 @@
|
||||
<div class="esh-basketstatus">
|
||||
<div [routerLink]="['basket']" class="esh-basketstatus-image">
|
||||
<img src="../../../images/cart.png" />
|
||||
</div>
|
||||
<div class="esh-basketstatus-badge">
|
||||
{{badge}}
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,24 @@
|
||||
@import '../../_variables.scss';
|
||||
|
||||
.esh-basketstatus {
|
||||
|
||||
&-image {
|
||||
height: 36px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
&-badge {
|
||||
position: absolute;
|
||||
margin-top: 2px;
|
||||
margin-left: 14px;
|
||||
background-color: #83d01b;
|
||||
padding: 1px;
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
|
||||
import { BasketService } from '../basket.service';
|
||||
import { BasketWrapperService } from '../../shared/services/basket.wrapper.service';
|
||||
|
||||
@Component({
|
||||
selector: 'esh-basket-status',
|
||||
styleUrls: ['./basket-status.component.scss'],
|
||||
templateUrl: './basket-status.component.html'
|
||||
})
|
||||
export class BasketStatusComponent implements OnInit {
|
||||
subscription: Subscription;
|
||||
badge: number = 0;
|
||||
|
||||
constructor(private service: BasketService, private basketEvents: BasketWrapperService) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.subscription = this.basketEvents.addItemToBasket$.subscribe(
|
||||
item => {
|
||||
console.log(item);
|
||||
this.service.setBasket(item);
|
||||
this.badge = this.service.getBasket().length;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,63 @@
|
||||
<div class="esh-basket-header">
|
||||
<ul class="container">
|
||||
<li class="esh-basket-header-back" routerLink="/catalog">Back to list</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="container esh-basket-container">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<section>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="esh-basket-product-column">
|
||||
PRODUCT
|
||||
</th>
|
||||
<th>
|
||||
|
||||
</th>
|
||||
<th>
|
||||
BRAND
|
||||
</th>
|
||||
<th>
|
||||
PRICE
|
||||
</th>
|
||||
<th>
|
||||
QUANTITY
|
||||
</th>
|
||||
<th>
|
||||
COST
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let item of basket?.items">
|
||||
<td class="esh-basket-product-column"><img class="esh-basket-product-image" src="{{item.pictureUrl}}" /></td>
|
||||
<td class="esh-basket-product-column">{{item.productName}}</td>
|
||||
<td class="esh-basket-product-column">ROSLYN</td>
|
||||
<td class="esh-basket-product-column">$ {{item.unitPrice}}</td>
|
||||
<td class="esh-basket-product-column">
|
||||
<input type="number" style="width:100px" min="1" [(ngModel)]="item.quantity" (change)="itemQuantityChanged(item)"/>
|
||||
</td>
|
||||
<td class="esh-basket-product-column esh-basket-total-value">$ {{item.unitPrice * item.quantity}}</td>
|
||||
</tr>
|
||||
<tr class="esh-basket-totals">
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td>
|
||||
</td>
|
||||
<td>
|
||||
<div class="esh-basket-total-value">
|
||||
<div class="esh-basket-total-label"><span>TOTAL</span></div>
|
||||
<span>$ {{totalPrice}}</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,80 @@
|
||||
@import '../_variables.scss';
|
||||
|
||||
.esh-basket {
|
||||
&-header {
|
||||
background-color: #00A69C;
|
||||
height: 63px;
|
||||
|
||||
li {
|
||||
list-style: none;
|
||||
display: inline;
|
||||
opacity: 0.5;
|
||||
margin-top: 25px;
|
||||
margin-left: 10px;
|
||||
float: right;
|
||||
cursor: pointer;
|
||||
color: white;
|
||||
}
|
||||
|
||||
li a {
|
||||
color: white;
|
||||
}
|
||||
|
||||
&-back {
|
||||
float: left !important;
|
||||
margin-top: 20px !important;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
li a:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
&-container {
|
||||
min-height: 70vh;
|
||||
padding-top: 40px;
|
||||
margin-bottom: 30px;
|
||||
min-width: 992px;
|
||||
}
|
||||
|
||||
&-product-column {
|
||||
max-width: 120px;
|
||||
text-transform: uppercase;
|
||||
vertical-align: middle !important;
|
||||
}
|
||||
|
||||
&-product-image {
|
||||
max-width: 210px;
|
||||
}
|
||||
|
||||
&-total-value {
|
||||
font-size: 20px;
|
||||
color: #00a69c;
|
||||
}
|
||||
|
||||
&-total-label {
|
||||
font-size: 14px;
|
||||
color: #404040;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
&-totals {
|
||||
border-bottom:none!important;
|
||||
}
|
||||
}
|
||||
|
||||
.table td {
|
||||
border-top: solid 1px #ddd;
|
||||
}
|
||||
|
||||
.table thead th {
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -0,0 +1,36 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
import { BasketService } from './basket.service';
|
||||
import { IBasket } from '../shared/models/basket.model';
|
||||
import { IBasketItem } from '../shared/models/basketItem.model';
|
||||
|
||||
@Component({
|
||||
selector: 'esh-basket',
|
||||
styleUrls: ['./basket.component.scss'],
|
||||
templateUrl: './basket.component.html'
|
||||
})
|
||||
export class BasketComponent implements OnInit {
|
||||
basket: IBasket;
|
||||
totalPrice: number = 0;
|
||||
|
||||
constructor(private service: BasketService) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.basket = this.service.getBasket()
|
||||
this.service.basket.items.forEach(item => {
|
||||
this.totalPrice += (item.unitPrice * item.quantity)
|
||||
});
|
||||
}
|
||||
|
||||
itemQuantityChanged(item: IBasketItem) {
|
||||
this.calculateTotalPrice();
|
||||
}
|
||||
|
||||
private calculateTotalPrice() {
|
||||
this.basket = this.service.basket.items;
|
||||
this.service.basket.items.forEach(item => {
|
||||
this.totalPrice += (item.unitPrice * item.quantity)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,26 @@
|
||||
import { NgModule, ModuleWithProviders } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { BasketComponent } from './basket.component';
|
||||
import { BasketStatusComponent } from './basket-status/basket-status.component';
|
||||
//import { routing } from './basket.routes';
|
||||
import { BasketService } from './basket.service';
|
||||
|
||||
@NgModule({
|
||||
imports: [SharedModule],
|
||||
declarations: [BasketComponent, BasketStatusComponent],
|
||||
providers: [BasketService],
|
||||
exports: [BasketStatusComponent]
|
||||
})
|
||||
export class BasketModule {
|
||||
static forRoot(): ModuleWithProviders {
|
||||
return {
|
||||
ngModule: BasketModule,
|
||||
providers: [
|
||||
// Providers
|
||||
BasketService
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
//import { Routes, RouterModule } from '@angular/router';
|
||||
|
||||
//import { BasketComponent } from './basket.component';
|
||||
|
||||
//const routes: Routes = [
|
||||
// { path: '', component: BasketComponent }
|
||||
//];
|
||||
|
||||
//export const routing = RouterModule.forChild(routes);
|
@ -0,0 +1,40 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Response } from '@angular/http';
|
||||
|
||||
import { DataService } from '../shared/services/data.service';
|
||||
import { IBasket } from '../shared/models/basket.model';
|
||||
import { IBasketItem } from '../shared/models/basketItem.model';
|
||||
|
||||
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 BasketService {
|
||||
private basketUrl: string = 'http://eshopcontainers:5103';
|
||||
basket: IBasket = {
|
||||
buyerId: 'fakeIdentity',
|
||||
items: []
|
||||
};
|
||||
|
||||
constructor(private service: DataService) {
|
||||
this.basket.items = [];
|
||||
}
|
||||
|
||||
setBasket(item) {
|
||||
this.basket.items.push(item);
|
||||
this.service.post(this.basket.buyerId, this.basket.items);
|
||||
}
|
||||
|
||||
getBasket(): Observable<IBasket> {
|
||||
return this.service.get(this.basketUrl + '/' + this.basket.buyerId).map((response: Response) => {
|
||||
return response.json();
|
||||
});
|
||||
}
|
||||
|
||||
dropBasket() {
|
||||
//TODO..
|
||||
}
|
||||
}
|
@ -1,27 +1,41 @@
|
||||
<div class="catalog-banner">
|
||||
<img class="catalog-banner-image" src="../../images/main_banner.png" />
|
||||
<div class="container">
|
||||
<img src="../../images/main_banner_text.png" class="catalog-banner-text" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="catalog-filter">
|
||||
<div class="catalog-filter-container">
|
||||
<div class="container">
|
||||
<div class="catalog-banner">
|
||||
<img class="catalog-banner-image" src="../../images/main_banner.png" />
|
||||
<div class="container">
|
||||
<select (change)="onBrandFilterChanged($event, $event.target.value)">
|
||||
<option *ngFor="let brand of brands" [value]="brand.id">{{brand.brand}}</option>
|
||||
</select>
|
||||
<select (change)="onTypeFilterChanged($event, $event.target.value)">
|
||||
<option *ngFor="let type of types" [value]="type.id">{{type.type}}</option>
|
||||
</select>
|
||||
<img src="../../images/main_banner_text.png" class="catalog-banner-text" />
|
||||
</div>
|
||||
<button class="" (click)="onFilterApplied($event)">Apply</button>
|
||||
</div>
|
||||
</div>
|
||||
<esh-pager [model]="paginationInfo" (changed)="onPageChanged($event)"></esh-pager>
|
||||
<div class="catalog-content row">
|
||||
<div class="col-md-4 catalog-content-item" *ngFor="let item of catalog?.data">
|
||||
<img src="{{item.pictureUri}}"/>
|
||||
<button class="catalog-content-item-button">[ ADD TO CART ]</button>
|
||||
<div class="catalog-filter">
|
||||
<div class="catalog-filter-container">
|
||||
<div class="container">
|
||||
<div data-name="brand" class="catalog-filter-wrapper">
|
||||
<select class="catalog-filter-select" (change)="onBrandFilterChanged($event, $event.target.value)">
|
||||
<option *ngFor="let brand of brands" [value]="brand.id">{{brand.brand}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div data-name="type" class="catalog-filter-wrapper">
|
||||
<select class="catalog-filter-select" (change)="onTypeFilterChanged($event, $event.target.value)">
|
||||
<option *ngFor="let type of types" [value]="type.id">{{type.type}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<button class="catalog-filter-button" (click)="onFilterApplied($event)">
|
||||
<img src="../../images/arrow-right.svg">
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<esh-pager [model]="paginationInfo" (changed)="onPageChanged($event)"></esh-pager>
|
||||
<esh-pager [model]="paginationInfo" (changed)="onPageChanged($event)"></esh-pager>
|
||||
<div class="catalog-content row">
|
||||
<div class="col-md-4 catalog-content-item" *ngFor="let item of catalog?.data">
|
||||
<img src="{{item.pictureUri}}" />
|
||||
<button (click)="addToCart(item)" class="catalog-content-item-button">[ ADD TO CART ]</button>
|
||||
<div class="catalog-content-item-title">
|
||||
<span>{{item.name}}</span>
|
||||
</div>
|
||||
<div class="catalog-content-item-price">
|
||||
<span>{{item.price}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<esh-pager [model]="paginationInfo" (changed)="onPageChanged($event)"></esh-pager>
|
||||
</div>
|
@ -1,9 +1,13 @@
|
||||
@import '../_variables.scss';
|
||||
|
||||
.catalog{
|
||||
select::-ms-expand {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.catalog {
|
||||
&-banner {
|
||||
height: 258px;
|
||||
vertical-align:middle;
|
||||
vertical-align: middle;
|
||||
|
||||
&-image {
|
||||
width: 100%;
|
||||
@ -13,7 +17,7 @@
|
||||
}
|
||||
|
||||
&-text {
|
||||
position:relative;
|
||||
position: relative;
|
||||
top: 75px;
|
||||
}
|
||||
}
|
||||
@ -22,22 +26,70 @@
|
||||
height: 65px;
|
||||
|
||||
&-container {
|
||||
position:absolute;
|
||||
width:100%;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
background-color: $primary-colour;
|
||||
left:0;
|
||||
left: 0;
|
||||
height: 65px;
|
||||
}
|
||||
|
||||
&-button {
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
padding: 6px 20px 10px 8px;
|
||||
background-color: $primary-accent;
|
||||
color: white;
|
||||
font-size: 16px;
|
||||
margin: 10px 0;
|
||||
border: none;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
&-select {
|
||||
background-color: transparent;
|
||||
padding: 10px;
|
||||
margin: 10px;
|
||||
margin-right: 20px;
|
||||
color: #fff;
|
||||
padding-top: 20px;
|
||||
padding-bottom: 3px;
|
||||
min-width: 140px;
|
||||
border-color: #37c7ca;
|
||||
max-height: 43px;
|
||||
-webkit-appearance: none;
|
||||
|
||||
& option {
|
||||
background-color: #00a69c;
|
||||
}
|
||||
}
|
||||
|
||||
&-wrapper {
|
||||
z-index: 0;
|
||||
display: inline-block;
|
||||
margin-left: -10px;
|
||||
}
|
||||
|
||||
&-wrapper::before {
|
||||
content: attr(data-name);
|
||||
opacity: 0.5;
|
||||
z-index: 1;
|
||||
text-transform: uppercase;
|
||||
position: absolute;
|
||||
font-size: 10px;
|
||||
margin-top: 15px;
|
||||
margin-left: 21px;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
&-content{
|
||||
&-content {
|
||||
margin-top: 10px;
|
||||
|
||||
&-item {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
|
||||
&-image{
|
||||
|
||||
&-image {
|
||||
}
|
||||
|
||||
&-button {
|
||||
@ -48,7 +100,26 @@
|
||||
color: white;
|
||||
font-size: 16px;
|
||||
margin: 10px 0;
|
||||
border:none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&-title {
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
font-weight: 300;
|
||||
font-size: 16px;
|
||||
/*margin-top: 20px;*/
|
||||
}
|
||||
|
||||
&-price {
|
||||
text-align: center;
|
||||
font-weight: 900;
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
&-price::before {
|
||||
content: '$';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import { ICatalogItem } from '../shared/models/catalogItem.model';
|
||||
import { ICatalogType } from '../shared/models/catalogType.model';
|
||||
import { ICatalogBrand } from '../shared/models/catalogBrand.model';
|
||||
import { IPager } from '../shared/models/pager.model';
|
||||
import { BasketWrapperService} from '../shared/services/basket.wrapper.service';
|
||||
|
||||
@Component({
|
||||
selector: 'esh-catalog',
|
||||
@ -19,11 +20,11 @@ export class CatalogComponent implements OnInit {
|
||||
typeSelected: number;
|
||||
paginationInfo: IPager;
|
||||
|
||||
constructor(private service: CatalogService) { }
|
||||
constructor(private service: CatalogService, private basketService: BasketWrapperService) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.getBrands();
|
||||
this.getCatalog(10,0);
|
||||
this.getCatalog(10, 0);
|
||||
this.getTypes();
|
||||
}
|
||||
|
||||
@ -49,8 +50,12 @@ export class CatalogComponent implements OnInit {
|
||||
this.getCatalog(this.paginationInfo.itemsPage, value);
|
||||
}
|
||||
|
||||
addToCart(item: ICatalogItem) {
|
||||
this.basketService.addItemToBasket(item);
|
||||
}
|
||||
|
||||
getCatalog(pageSize:number, pageIndex: number, brand?: number, type?: number) {
|
||||
this.service.getCatalog(brand, type).subscribe(catalog => {
|
||||
this.service.getCatalog(pageIndex, pageSize, brand, type).subscribe(catalog => {
|
||||
this.catalog = catalog;
|
||||
console.log('catalog items retrieved: ' + catalog.count);
|
||||
|
||||
@ -58,7 +63,8 @@ export class CatalogComponent implements OnInit {
|
||||
actualPage : catalog.pageIndex,
|
||||
itemsPage : catalog.pageSize,
|
||||
totalItems : catalog.count,
|
||||
totalPages : (catalog.count / catalog.pageSize)
|
||||
totalPages: Math.ceil(catalog.count / catalog.pageSize),
|
||||
items: catalog.pageSize
|
||||
};
|
||||
|
||||
console.log(this.paginationInfo);
|
||||
|
@ -3,15 +3,13 @@ import { BrowserModule } from '@angular/platform-browser';
|
||||
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { CatalogComponent } from './catalog.component';
|
||||
import { routing } from './catalog.routes';
|
||||
//import { routing } from './catalog.routes';
|
||||
import { CatalogService } from './catalog.service';
|
||||
import { Pager } from '../shared/components/pager/pager';
|
||||
|
||||
|
||||
|
||||
@NgModule({
|
||||
imports: [routing, BrowserModule, SharedModule],
|
||||
declarations: [CatalogComponent],
|
||||
imports: [BrowserModule, SharedModule],
|
||||
declarations: [CatalogComponent],
|
||||
providers: [CatalogService]
|
||||
})
|
||||
export class CatalogModule { }
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { Routes, RouterModule } from '@angular/router';
|
||||
//import { Routes, RouterModule } from '@angular/router';
|
||||
|
||||
import { CatalogComponent } from './catalog.component';
|
||||
//import { CatalogComponent } from './catalog.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: 'catalog', component: CatalogComponent }
|
||||
];
|
||||
//const routes: Routes = [
|
||||
// { path: '', component: CatalogComponent }
|
||||
//];
|
||||
|
||||
export const routing = RouterModule.forChild(routes);
|
||||
//export const routing = RouterModule.forChild(routes);
|
||||
|
@ -1,9 +1,10 @@
|
||||
<div class="row">
|
||||
<div class="row pager">
|
||||
<div class="col-xs-4">
|
||||
<nav>
|
||||
<ul>
|
||||
<li class="page-item">
|
||||
<span class="text previous" id="Previous"
|
||||
<span class="pager-text pager-previous" id="Previous"
|
||||
[hidden]="buttonStates?.previousDisabled"
|
||||
(click)="onPreviousCliked($event)"
|
||||
aria-label="Previous">
|
||||
Previous
|
||||
@ -12,12 +13,13 @@
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
<div class="col-xs-4 u-align-center"><span>Showing {{model?.itemsPage}} of {{model?.totalItems}} products - Page {{model?.actualPage}} - {{model?.totalPages}}</span></div>
|
||||
<div class="col-xs-4 u-align-center"><span>Showing {{model?.items}} of {{model?.totalItems}} products - Page {{model?.actualPage + 1}} - {{model?.totalPages}}</span></div>
|
||||
<div class="col-xs-4">
|
||||
<nav>
|
||||
<ul>
|
||||
<li class="page-item">
|
||||
<span class="text next" id="Next"
|
||||
<span class="pager-text pager-next" id="Next"
|
||||
[hidden]="buttonStates?.nextDisabled"
|
||||
(click)="onNextClicked($event)"
|
||||
aria-label="Next">
|
||||
Next
|
||||
|
@ -0,0 +1,30 @@
|
||||
.pager {
|
||||
margin-bottom: 20px;
|
||||
margin-top: 20px;
|
||||
margin-left: 0px;
|
||||
margin-right: -30px;
|
||||
|
||||
&-previous {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&-text {
|
||||
color: #83d01b;
|
||||
}
|
||||
|
||||
&-next {
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
top: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&-isDisabled {
|
||||
cursor: not-allowed;
|
||||
opacity: .5;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import { Component, OnInit, Output, Input, EventEmitter } from '@angular/core';
|
||||
import { Component, OnInit, OnChanges, Output, Input, EventEmitter } from '@angular/core';
|
||||
|
||||
import { IPager } from '../../models/pager.model';
|
||||
|
||||
@ -7,7 +7,7 @@ import { IPager } from '../../models/pager.model';
|
||||
templateUrl: './pager.html',
|
||||
styleUrls: ['./pager.scss']
|
||||
})
|
||||
export class Pager implements OnInit {
|
||||
export class Pager implements OnInit, OnChanges {
|
||||
|
||||
@Output()
|
||||
changed: EventEmitter<number> = new EventEmitter<number>();
|
||||
@ -15,18 +15,33 @@ export class Pager implements OnInit {
|
||||
@Input()
|
||||
model: IPager;
|
||||
|
||||
buttonStates: any = {
|
||||
nextDisabled: true,
|
||||
previousDisabled: true,
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
console.log(this.model);
|
||||
}
|
||||
|
||||
ngOnChanges() {
|
||||
if (this.model) {
|
||||
this.model.items = (this.model.itemsPage > this.model.totalItems) ? this.model.totalItems : this.model.itemsPage;
|
||||
|
||||
this.buttonStates.previousDisabled = (this.model.actualPage == 0);
|
||||
this.buttonStates.nextDisabled = (this.model.actualPage + 1 >= this.model.totalPages);
|
||||
}
|
||||
}
|
||||
|
||||
onNextClicked(event: any) {
|
||||
event.preventDefault();
|
||||
console.log('Pager Next Clicked');
|
||||
console.log('Pager next clicked');
|
||||
this.changed.emit(this.model.actualPage + 1);
|
||||
}
|
||||
|
||||
onPreviousCliked(event: any) {
|
||||
event.preventDefault();
|
||||
console.log('Pager previous clicked');
|
||||
this.changed.emit(this.model.actualPage - 1);
|
||||
}
|
||||
|
||||
|
@ -1,8 +0,0 @@
|
||||
<footer class="text-muted">
|
||||
<div class="container">
|
||||
<hr>
|
||||
<p class="text-muted">
|
||||
© 2015-2016 {{'title'}} Company
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
@ -1,8 +0,0 @@
|
||||
@import '../../_variables.scss';
|
||||
|
||||
.footer {
|
||||
padding-top: 40px;
|
||||
padding-bottom: 40px;
|
||||
margin-top: 40px;
|
||||
border-top: 1px solid #eee;
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'appc-footer',
|
||||
styleUrls: ['./footer.component.scss'],
|
||||
templateUrl: './footer.component.html'
|
||||
})
|
||||
export class FooterComponent {
|
||||
constructor() { }
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
<header class="navbar navbar-light navbar-static-top">
|
||||
<div class="container">
|
||||
<nav>
|
||||
<div class="clearfix">
|
||||
<!--<button class="navbar-toggler float-xs-right hidden-sm-up collapsed" type="button" data-toggle="collapse" data-target="#bd-main-nav"
|
||||
aria-controls="bd-main-nav" aria-expanded="false" aria-label="Toggle navigation" (click)="toggleNav()">
|
||||
☰
|
||||
</button>
|
||||
<a class="navbar-brand hidden-sm-up header-brand" routerLink="home">
|
||||
Ng2fbBootstrap
|
||||
<img src="../../../images/brand.png" />
|
||||
</a>-->
|
||||
</div>
|
||||
<div class="" id="bd-main-nav" aria-expanded="false" style="height: 0px;">
|
||||
<a class="navbar-brand" routerLink="home">
|
||||
<img src="../../../images/brand.png" />
|
||||
</a>
|
||||
<!--<ul class="nav navbar-nav">
|
||||
<li class="nav-item">
|
||||
<a class="nav-item nav-link" routerLinkActive="active" routerLink="home">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-item nav-link" routerLinkActive="active" routerLink="examples">Examples</a>
|
||||
</li>
|
||||
<li>
|
||||
<ul class="nav float-xs-left float-md-right">
|
||||
|
||||
<li class="nav-item" *ngIf="authService.isLoggedIn() && authService.user()">
|
||||
<a class="nav-item nav-link" routerLinkActive="active" routerLink="profile">
|
||||
<i class="fa fa-user"></i> {{authService.user().displayName}}
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item" *ngIf="!authService.isLoggedIn()">
|
||||
<a class="nav-item nav-link" routerLinkActive="active" routerLink="register">
|
||||
<i class="fa fa-user"></i> Register
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item" *ngIf="!authService.isLoggedIn()">
|
||||
<a class="nav-item nav-link" routerLinkActive="active" routerLink="login">
|
||||
<i class="fa fa-sign-in"></i>Login
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item" *ngIf="authService.isLoggedIn() && authService.user()?.roles?.indexOf('Admin') > -1">
|
||||
<a class="nav-item nav-link" routerLinkActive="active" routerLink="admin">
|
||||
<i class="fa fa-gear"></i> Admin
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item" *ngIf="authService.isLoggedIn()">
|
||||
<a class="nav-item nav-link" (click)="authService.logout()" routerLinkActive="active" href="javascript:void(null);">
|
||||
<i class="fa fa-sign-out"></i> Logout
|
||||
</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</li>
|
||||
</ul>-->
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
@ -1,5 +0,0 @@
|
||||
@import '../../_variables.scss';
|
||||
|
||||
.header-brand {
|
||||
background-image:url('../../../images/brand.png')
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
import { Component, Inject } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'appc-header',
|
||||
styleUrls: ['./header.component.scss'],
|
||||
templateUrl: './header.component.html'
|
||||
})
|
||||
export class HeaderComponent {
|
||||
isCollapsed: boolean = true;
|
||||
constructor(private router: Router) { }
|
||||
|
||||
toggleNav() {
|
||||
this.isCollapsed = !this.isCollapsed;
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
import { IBasketItem } from './basketItem.model';
|
||||
|
||||
export interface IBasket {
|
||||
items: IBasketItem[]
|
||||
buyerId: string
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
export interface IBasketItem {
|
||||
id: string
|
||||
productId: string
|
||||
productName: string
|
||||
unitPrice: number,
|
||||
quantity: number,
|
||||
pictureUrl: string
|
||||
}
|
||||
|
@ -2,5 +2,6 @@ export interface IPager {
|
||||
itemsPage: number,
|
||||
totalItems: number,
|
||||
actualPage: number,
|
||||
totalPages: number
|
||||
totalPages: number,
|
||||
items: number,
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Subject } from 'rxjs/Subject';
|
||||
|
||||
import { ICatalogItem } from '../models/catalogItem.model';
|
||||
import { IBasketItem } from '../models/basketItem.model';
|
||||
|
||||
@Injectable()
|
||||
export class BasketWrapperService {
|
||||
|
||||
constructor() { }
|
||||
|
||||
private addItemToBasketSource = new Subject<IBasketItem>();
|
||||
addItemToBasket$ = this.addItemToBasketSource.asObservable();
|
||||
|
||||
addItemToBasket(item: ICatalogItem) {
|
||||
let basket: IBasketItem = {
|
||||
pictureUrl: item.pictureUri,
|
||||
productId: item.id,
|
||||
productName: item.name,
|
||||
quantity: 1,
|
||||
unitPrice: item.price,
|
||||
id: ''
|
||||
}
|
||||
this.addItemToBasketSource.next(basket);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -12,68 +12,66 @@ import { ErrorMessageComponent } from './forms/error-message.component';
|
||||
import { ErrorSummaryComponent } from './forms/error-summary.component';
|
||||
import { FormControlService } from './forms/form-control.service';
|
||||
|
||||
import { HeaderComponent } from './layout/header.component';
|
||||
import { FooterComponent } from './layout/footer.component';
|
||||
// Services
|
||||
import { DataService } from './services/data.service';
|
||||
import { UtilityService } from './services/utility.service';
|
||||
import { UppercasePipe } from './pipes/uppercase.pipe';
|
||||
import { BasketWrapperService} from './services/basket.wrapper.service';
|
||||
|
||||
//Components:
|
||||
import {Pager } from './components/pager/pager';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
RouterModule,
|
||||
NgbModule.forRoot(),
|
||||
// No need to export as these modules don't expose any components/directive etc'
|
||||
HttpModule,
|
||||
JsonpModule
|
||||
],
|
||||
declarations: [
|
||||
DynamicFormComponent,
|
||||
DynamicFormControlComponent,
|
||||
ErrorMessageComponent,
|
||||
ErrorSummaryComponent,
|
||||
FooterComponent,
|
||||
HeaderComponent,
|
||||
PageHeadingComponent,
|
||||
UppercasePipe,
|
||||
Pager
|
||||
],
|
||||
exports: [
|
||||
// Modules
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
RouterModule,
|
||||
NgbModule,
|
||||
// Providers, Components, directive, pipes
|
||||
DynamicFormComponent,
|
||||
DynamicFormControlComponent,
|
||||
ErrorSummaryComponent,
|
||||
ErrorMessageComponent,
|
||||
FooterComponent,
|
||||
HeaderComponent,
|
||||
PageHeadingComponent,
|
||||
UppercasePipe,
|
||||
Pager
|
||||
]
|
||||
imports: [
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
RouterModule,
|
||||
NgbModule.forRoot(),
|
||||
// No need to export as these modules don't expose any components/directive etc'
|
||||
HttpModule,
|
||||
JsonpModule
|
||||
],
|
||||
declarations: [
|
||||
DynamicFormComponent,
|
||||
DynamicFormControlComponent,
|
||||
ErrorMessageComponent,
|
||||
ErrorSummaryComponent,
|
||||
PageHeadingComponent,
|
||||
UppercasePipe,
|
||||
Pager
|
||||
],
|
||||
exports: [
|
||||
// Modules
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
RouterModule,
|
||||
NgbModule,
|
||||
// Providers, Components, directive, pipes
|
||||
DynamicFormComponent,
|
||||
DynamicFormControlComponent,
|
||||
ErrorSummaryComponent,
|
||||
ErrorMessageComponent,
|
||||
//FooterComponent,
|
||||
//HeaderComponent,
|
||||
PageHeadingComponent,
|
||||
UppercasePipe,
|
||||
Pager
|
||||
]
|
||||
|
||||
})
|
||||
export class SharedModule {
|
||||
static forRoot(): ModuleWithProviders {
|
||||
return {
|
||||
ngModule: SharedModule,
|
||||
providers: [
|
||||
// Providers
|
||||
DataService,
|
||||
FormControlService,
|
||||
UtilityService
|
||||
]
|
||||
};
|
||||
}
|
||||
static forRoot(): ModuleWithProviders {
|
||||
return {
|
||||
ngModule: SharedModule,
|
||||
providers: [
|
||||
// Providers
|
||||
DataService,
|
||||
FormControlService,
|
||||
UtilityService,
|
||||
BasketWrapperService
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -16,31 +16,36 @@ module.exports = merge({
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{ test: /\.ts$/, exclude: [/\.(spec|e2e)\.ts$/], loaders: ['awesome-typescript-loader?forkChecker=true ', 'angular2-template-loader'] },
|
||||
{
|
||||
test: /\.ts$/, exclude: [/\.(spec|e2e)\.ts$/],
|
||||
loaders: ['awesome-typescript-loader?forkChecker=true ', 'angular2-template-loader', 'angular2-router-loader']
|
||||
},
|
||||
{ test: /\.html$/, loader: "html" },
|
||||
{ test: /\.css/, loader: extractCSS.extract(['css']) },
|
||||
{ test: /\.scss$/, loaders: ['raw-loader', 'sass-loader?sourceMap'] },
|
||||
{ test: /\.json$/, loader: 'json-loader' },
|
||||
{
|
||||
test: /\.woff(\?v=\d+\.\d+\.\d+)?$/,
|
||||
loader: "url?limit=10000&mimetype=application/font-woff"
|
||||
loader: "file"
|
||||
}, {
|
||||
test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/,
|
||||
loader: "url?limit=10000&mimetype=application/font-woff"
|
||||
loader: "file"
|
||||
}, {
|
||||
test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,
|
||||
loader: "url?limit=10000&mimetype=application/octet-stream"
|
||||
loader: "file"
|
||||
}, {
|
||||
test: /\.eot(\?v=\d+\.\d+\.\d+)?$/,
|
||||
loader: "file"
|
||||
}, {
|
||||
test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
|
||||
loader: "url?limit=10000&mimetype=image/svg+xml"
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpg|gif)$/,
|
||||
test: /\.(png|jpg|gif|svg)$/,
|
||||
loader: "file"
|
||||
}
|
||||
|
||||
// {
|
||||
// test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
|
||||
//loader: "url?limit=10000&mimetype=image/svg+xml"
|
||||
//}
|
||||
]
|
||||
},
|
||||
entry: {
|
||||
|
@ -72,6 +72,7 @@
|
||||
"@types/uglify-js": "2.6.28",
|
||||
"@types/webpack": "1.12.35",
|
||||
"angular2-template-loader": "0.6.0",
|
||||
"angular2-router-loader": "0.3.4",
|
||||
"awesome-typescript-loader": "2.2.4",
|
||||
"codelyzer": "1.0.0-beta.3",
|
||||
"copy-webpack-plugin": "^4.0.0",
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 15 KiB |
Loading…
x
Reference in New Issue
Block a user