Browse Source

Merge pull request #205 from abpframework/gterdem/public_web_ui_fixes

UI revamp for public-web application after LaptonX
pull/207/head
Halil İbrahim Kalkan 3 years ago
committed by GitHub
parent
commit
9c0e9b1c7b
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      apps/public-web/src/EShopOnAbp.PublicWeb/Components/UserOrders/Default.cshtml
  2. 10
      apps/public-web/src/EShopOnAbp.PublicWeb/EShopOnAbpBrandingProvider.cs
  3. 95
      apps/public-web/src/EShopOnAbp.PublicWeb/Pages/Index.cshtml
  4. 63
      apps/public-web/src/EShopOnAbp.PublicWeb/Pages/ProductDetail.cshtml
  5. 16
      apps/public-web/src/EShopOnAbp.PublicWeb/Pages/_ViewStart.cshtml
  6. 70
      apps/public-web/src/EShopOnAbp.PublicWeb/Pages/index.css
  7. 16
      apps/public-web/src/EShopOnAbp.PublicWeb/Pages/index.js
  8. 100
      apps/public-web/src/EShopOnAbp.PublicWeb/Pages/productDetails.css
  9. 2
      apps/public-web/src/EShopOnAbp.PublicWeb/package.json
  10. BIN
      apps/public-web/src/EShopOnAbp.PublicWeb/wwwroot/logo.png
  11. 8
      apps/public-web/src/EShopOnAbp.PublicWeb/yarn.lock

8
apps/public-web/src/EShopOnAbp.PublicWeb/Components/UserOrders/Default.cshtml

@ -72,14 +72,14 @@
</abp-column> </abp-column>
<abp-column offset="_3" size="_2"> <abp-column offset="_3" size="_2">
<abp-row class="pb-3"> <abp-row class="pb-3">
<abp-button class="btn btn-light shadow" disabled="true" abp-tooltip-right="Not available yet"> <a class="btn btn-light" disabled="true" abp-tooltip-right="Not available yet">
@L["UserOrders:ReturnProducts"] @L["UserOrders:ReturnProducts"]
</abp-button> </a>
</abp-row> </abp-row>
<abp-row> <abp-row>
<abp-button class="btn btn-light shadow" disabled="true" abp-tooltip-right="Not available yet"> <a class="btn btn-outline-light shadow" href="/products/@item.ProductId.ToString()" abp-tooltip-right="Add review">
@L["UserOrders:AddReview"] @L["UserOrders:AddReview"]
</abp-button> </a>
</abp-row> </abp-row>
</abp-column> </abp-column>
</abp-row> </abp-row>

10
apps/public-web/src/EShopOnAbp.PublicWeb/EShopOnAbpBrandingProvider.cs

@ -0,0 +1,10 @@
using Volo.Abp.DependencyInjection;
using Volo.Abp.Ui.Branding;
namespace EShopOnAbp.PublicWeb;
[Dependency(ReplaceServices = true)]
public class EShopOnAbpBrandingProvider: DefaultBrandingProvider
{
public override string AppName => "EShopOnAbp";
}

95
apps/public-web/src/EShopOnAbp.PublicWeb/Pages/Index.cshtml

@ -4,22 +4,22 @@
@using System.Globalization @using System.Globalization
@using Microsoft.Extensions.Options @using Microsoft.Extensions.Options
@using Volo.Abp.Http.Client @using Volo.Abp.Http.Client
@using Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Rating
@model EShopOnAbp.PublicWeb.Pages.IndexModel @model EShopOnAbp.PublicWeb.Pages.IndexModel
@inject IStringLocalizer<EShopOnAbpResource> L @inject IStringLocalizer<EShopOnAbpResource> L
@inject IOptions<AbpRemoteServiceOptions> options @inject IOptions<AbpRemoteServiceOptions> options
@{ @{
const int productsColumnSize = 4; // TODO: update abp-column helper to plain html for proper row and columns css
string pageHeader = L["OurProducts"]; string pageHeader = L["OurProducts"];
} }
@section styles{ @section styles{
<abp-style src="/Pages/index.css" /> <abp-style src="/Pages/index.css"/>
} }
@section scripts { @section scripts {
<abp-script src="/client-proxies/basket-proxy.js" /> <abp-script src="/client-proxies/basket-proxy.js"/>
<abp-script src="/Pages/index.js" /> <abp-script src="/Pages/index.js"/>
} }
@if (Model.HasRemoteServiceError) @if (Model.HasRemoteServiceError)
@ -27,50 +27,57 @@
pageHeader = L["CatalogServiceNotAvailable"]; pageHeader = L["CatalogServiceNotAvailable"];
} }
<div class="row"> <div class="container">
<div class="col-lg-12"> <h3 class="pb-4 text-center">@pageHeader</h3>
<h3 class="pb-4 text-center">@pageHeader</h3> <div class="">
<div class="product-list"> <div class="product-list">
@for (int i = 0; i < Math.Ceiling(Model.Products.Count / (double)productsColumnSize); i++) @foreach (var product in Model.Products)
{ {
<abp-row> var productPrice = product.Price.ToString("C", new CultureInfo("en-US"));
@for (int j = 0; j < productsColumnSize; j++) string productImage = !product.ImageName.IsNullOrEmpty() ? $"{options.Value.RemoteServices.Default.BaseUrl}product-images/{product.ImageName}" : $"{options.Value.RemoteServices.Default.BaseUrl}product-images/@product.ImageName";
{ string buyText = L["Index:AddToBasket"];
<abp-column size-lg="_3" size-md="_4" size-sm="_6"> <div class="product-container">
@if ((i * productsColumnSize) + j < Model.Products.Count) <div class="product-list-item" data-product-id="@product.Id.ToString()">
<div class="item-link" href="/products/@product.Id.ToString()" data-product-id="@product.Id.ToString()">
<div class="product-title">
<span>
<h6>@product.Name</h6>
<span class="d-block text-muted">@product.Code</span>
</span>
</div>
@* Need read-only ratings system *@
@* <div class="product-ratings"> *@
@* @if (GlobalFeatureManager.Instance.IsEnabled<RatingsFeature>()) *@
@* { *@
@* @await Component.InvokeAsync(typeof(RatingViewComponent), new { entityType = "quote", entityId = @product.Id.ToString() }) *@
@* } *@
@* </div> *@
<div class="product-image">
<img src="@productImage" loading="lazy"/>
</div>
<div class="product-count">
<span class="d-block">@L["StockCount"]: @product.StockCount</span>
</div>
</div>
<div class="product-item-bottom">
<div class="item-added">
<div class="fa fa-check" style="color: yellowgreen; padding-right: 1em;"></div>
<span style="display: flex">Added to cart</span>
</div>
@if (product.StockCount > 0)
{ {
var product = Model.Products[(i * productsColumnSize) + j]; <abp-button class="add-basket-button" button-type="Warning" text="@buyText" data-product-id="@product.Id.ToString()"></abp-button>
var productPrice = product.Price.ToString("C", new CultureInfo("en-US"));
string productImage = !product.ImageName.IsNullOrEmpty() ? $"{options.Value.RemoteServices.Default.BaseUrl}product-images/{product.ImageName}" : $"{options.Value.RemoteServices.Default.BaseUrl}product-images/@product.ImageName";
string buyText = L["Index:AddToBasket"];
<div class="product-list-item" data-product-id="@product.Id.ToString()">
<a href="/products/@product.Id.ToString()">
<div class="col">
<div class="product-info-box">
<h6>@product.Name</h6>
<em class="d-block text-muted">@product.Code - @L["StockCount"]: @product.StockCount</em>
</div>
</div>
<img src="@productImage" />
</a>
<div class="row mt-5 ">
@if (product.StockCount > 0)
{
<abp-button class="add-basket-button" button-type="Warning" text="@buyText" data-product-id="@product.Id.ToString()"></abp-button>
}
else
{
<abp-button disabled="true" class="add-basket-button" button-type="Warning" text="@L["Index:OutOfStock"]" />
}
<div class="info-section col" style="text-align: center">
<span class="product-price">@productPrice</span>
</div>
</div>
</div>
} }
</abp-column> else
} {
</abp-row> <abp-button disabled="true" class="add-basket-button" button-type="Warning" text="@L["Index:OutOfStock"]"/>
}
<div class="info-section col">
<span class="product-price">@productPrice</span>
</div>
</div>
</div>
</div>
} }
</div> </div>
</div> </div>

63
apps/public-web/src/EShopOnAbp.PublicWeb/Pages/ProductDetail.cshtml

@ -1,60 +1,68 @@
@page "/products/{id:guid}" @page "/products/{id:guid}"
@using EShopOnAbp.Localization @using EShopOnAbp.Localization
@using EShopOnAbp.PublicWeb.Components.Basket
@using Microsoft.Extensions.Localization @using Microsoft.Extensions.Localization
@using System.Globalization @using System.Globalization
@using Microsoft.Extensions.Options @using Microsoft.Extensions.Options
@using Volo.Abp.GlobalFeatures @using Volo.Abp.GlobalFeatures
@using Volo.Abp.Http.Client @using Volo.Abp.Http.Client
@using Volo.Abp.Users
@using Volo.CmsKit.GlobalFeatures @using Volo.CmsKit.GlobalFeatures
@using Volo.CmsKit.Public.Comments
@using Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Commenting @using Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Commenting
@using Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Rating @using Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Rating
@inject IStringLocalizer<EShopOnAbpResource> L @inject IStringLocalizer<EShopOnAbpResource> L
@inject IOptions<AbpRemoteServiceOptions> options @inject IOptions<AbpRemoteServiceOptions> options
@inject ICurrentUser CurrentUser
@model EShopOnAbp.PublicWeb.Pages.ProductDetailModel @model EShopOnAbp.PublicWeb.Pages.ProductDetailModel
@section styles{ @section styles{
<abp-style src="/Pages/index.css" /> <abp-style src="/Pages/productDetails.css"/>
} }
@section scripts { @section scripts {
<abp-script src="/client-proxies/basket-proxy.js" /> <abp-script src="/client-proxies/basket-proxy.js"/>
<abp-script src="/Pages/index.js" /> <abp-script src="/Pages/index.js"/>
<abp-script src="/Pages/productDetail.js" /> <abp-script src="/Pages/productDetail.js"/>
} }
<div class="container"> <div class="container">
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-lg-6 col-md-6 col-sm-6 "> <div class="col-lg-6 col-md-6 col-sm-6 ">
<div class="product-list"> <div class="product-container">
@{ @{
var product = Model.Product; var product = Model.Product;
var productPrice = product.Price.ToString("C", new CultureInfo("en-US")); var productPrice = product.Price.ToString("C", new CultureInfo("en-US"));
string productImage = !product.ImageName.IsNullOrEmpty() ? $"{options.Value.RemoteServices.Default.BaseUrl}product-images/{product.ImageName}" : $"{options.Value.RemoteServices.Default.BaseUrl}product-images/@product.ImageName"; string productImage = !product.ImageName.IsNullOrEmpty() ? $"{options.Value.RemoteServices.Default.BaseUrl}product-images/{product.ImageName}" : $"{options.Value.RemoteServices.Default.BaseUrl}product-images/@product.ImageName";
string buyText = L["Index:AddToBasket"]; string buyText = L["Index:AddToBasket"];
<div class="product-list-item" data-product-id="@product.Id.ToString()" style="cursor:auto"> <div class="product-list-item" data-product-id="@product.Id.ToString()">
<div class="col"> <a href="/products/@product.Id.ToString()">
<div class="product-info-box"> <div class="product-title">
<h6>@product.Name</h6> <span>
<em class="d-block text-muted">@product.Code - @L["StockCount"]: @product.StockCount</em> <h6>@product.Name</h6>
<span class="d-block text-muted">@product.Code</span>
</span>
</div>
<div class="product-image">
<img src="@productImage" loading="lazy"/>
</div>
<div class="product-count">
<span class="d-block">@L["StockCount"]: @product.StockCount</span>
</div>
</a>
<div class="product-item-bottom">
<div class="item-added">
<div class="fa fa-check" style="color: yellowgreen; padding-right: 1em;"></div>
<span style="display: flex">Added to cart</span>
</div> </div>
</div>
<img src="@productImage" />
<div class="row mt-5 ">
@if (product.StockCount > 0) @if (product.StockCount > 0)
{ {
<abp-button class="add-basket-button" button-type="Warning" text="@buyText" data-product-id="@product.Id.ToString()"></abp-button> <abp-button class="add-basket-button" button-type="Warning" text="@buyText" data-product-id="@product.Id.ToString()"></abp-button>
} }
else else
{ {
<abp-button disabled="true" class="add-basket-button" button-type="Warning" text="@L["Index:OutOfStock"]" /> <abp-button disabled="true" class="add-basket-button" button-type="Warning" text="@L["Index:OutOfStock"]"/>
} }
<div class="info-section col" style="text-align: center"> <div class="info-section col">
<span class="product-price">@productPrice</span> <span class="product-price">@productPrice</span>
</div> </div>
</div> </div>
@ -66,13 +74,14 @@
<abp-column size-md="_6"> <abp-column size-md="_6">
@if (GlobalFeatureManager.Instance.IsEnabled<RatingsFeature>() && Model.IsPurschased) @if (GlobalFeatureManager.Instance.IsEnabled<RatingsFeature>() && Model.IsPurschased)
{ {
@await Component.InvokeAsync(typeof(RatingViewComponent), new {entityType = "quote", entityId = @product.Id.ToString()}) @await Component.InvokeAsync(typeof(RatingViewComponent), new { entityType = "quote", entityId = @product.Id.ToString() })
} }
</abp-column> </abp-column>
<abp-column size-md="_12"> <abp-column size-md="_12">
@if (GlobalFeatureManager.Instance.IsEnabled<CommentsFeature>()) @* Need read-only commenting system *@
@if (GlobalFeatureManager.Instance.IsEnabled<CommentsFeature>() && Model.IsPurschased)
{ {
@await Component.InvokeAsync(typeof(CommentingViewComponent), new {entityType = "quote", entityId = @product.Id.ToString()}) @await Component.InvokeAsync(typeof(CommentingViewComponent), new { entityType = "quote", entityId = @product.Id.ToString() })
} }
</abp-column> </abp-column>
</abp-row> </abp-row>

16
apps/public-web/src/EShopOnAbp.PublicWeb/Pages/_ViewStart.cshtml

@ -2,4 +2,18 @@
@inject IThemeManager ThemeManager @inject IThemeManager ThemeManager
@{ @{
Layout = ThemeManager.CurrentTheme.GetPublicLayout(); Layout = ThemeManager.CurrentTheme.GetPublicLayout();
} }
<div class="logo" >
<a href="/" >
<img src="/logo.png" style="height: 2.2rem" />
</a>
</div>
<style>
.logo {
position: sticky;
top: 0;
z-index: 101;
padding-bottom: 1em;
margin-top: -90px;
}
</style>

70
apps/public-web/src/EShopOnAbp.PublicWeb/Pages/index.css

@ -5,20 +5,67 @@
a, a:hover, a:visited, a:active { a, a:hover, a:visited, a:active {
color: inherit; color: inherit;
text-decoration: none; text-decoration: none;
padding-bottom: 1em;
} }
.product-list-item { .product-list {
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 1em 2em; /* row-gap column gap */
}
.product-container {
text-align: center;
background-color: #fff; background-color: #fff;
padding: 2em; padding: 2em 2em 1em 2em;
border: 1px solid #eee; border: 1px solid #eee;
border-radius: 3px; border-radius: 3px;
box-shadow: 0 0 10px rgba(0, 0, 0, .05); box-shadow: 0 0 10px rgba(0, 0, 0, .05);
margin-bottom: 2em; margin-bottom: 2em;
transition: all .2s; transition: all .2s;
display: flex;
flex: 0 0 20%;
flex-direction: column;
}
.product-list-item {
display: flex;
flex-direction: column;
}
.product-ratings {
display: flex;
justify-content: center;
}
.text-muted.ms-1.text-decoration-none {
display: none;
}
.rating-undo-link.text-decoration-none {
display: none;
} }
.product-list-item:hover { .product-title {
box-shadow: 0 0 20px rgba(0, 0, 0, .1); min-height: 70px;
padding-bottom: 1em;
}
.product-item-bottom {
display: flex;
flex-direction: column;
min-height: 70px;
}
.product-count {
display: flex;
justify-content: center;
padding: 1em 0 1em 0em;
}
.product-container:hover {
box-shadow: 1px 1px 20px rgba(0, 0, 0, .2);
cursor: pointer; cursor: pointer;
} }
@ -30,10 +77,6 @@ a, a:hover, a:visited, a:active {
opacity: .8 opacity: .8
} }
.product-info-box {
min-height: 80px;
}
.add-basket-button { .add-basket-button {
display: none; display: none;
font-weight: 500; font-weight: 500;
@ -41,6 +84,11 @@ a, a:hover, a:visited, a:active {
.product-list-item:hover .add-basket-button { .product-list-item:hover .add-basket-button {
display: inline-block; display: inline-block;
width: -webkit-fill-available;
}
.info-section {
text-align: center;
} }
.product-list-item:hover .info-section { .product-list-item:hover .info-section {
@ -51,4 +99,10 @@ a, a:hover, a:visited, a:active {
font-weight: bolder; font-weight: bolder;
font-size: larger; font-size: larger;
line-height: 40px; line-height: 40px;
}
.item-added {
display: none;
justify-content: center;
align-items: baseline;
} }

16
apps/public-web/src/EShopOnAbp.PublicWeb/Pages/index.js

@ -17,8 +17,22 @@
.data('abp-widget-manager') .data('abp-widget-manager')
.refresh(); .refresh();
abp.notify.success("Added product to your basket.", "Successfully added"); abp.notify.success("Added product to your basket.", "Successfully added"); //TODO: localize the msg
var $itemAdded = $this.parent(".product-item-bottom").find(".item-added");
$($itemAdded).css({display: "flex"});
$this.css({opacity: 0.5});
$this.animate({opacity: 1}, 2000);
}).catch(err => {
abp.notify.error(err.message, "Failed while adding item"); //TODO: localize the msg
}); });
}); });
}); });
$(function () {
$(".item-link").click(function () {
var $this = $(this);
var productId = $this.attr('data-product-id');
location.href += "products/" + productId;
});
})
})(); })();

100
apps/public-web/src/EShopOnAbp.PublicWeb/Pages/productDetails.css

@ -0,0 +1,100 @@
body {
background-color: #f9f9f9;
}
a, a:hover, a:visited, a:active {
color: inherit;
text-decoration: none;
padding-bottom: 1em;
}
.product-list {
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 1em 2em; /* row-gap column gap */
}
.product-container {
text-align: center;
background-color: #fff;
padding: 2em 2em 1em 2em;
border: 1px solid #eee;
border-radius: 3px;
box-shadow: 0 0 10px rgba(0, 0, 0, .05);
margin-bottom: 2em;
transition: all .2s;
display: flex;
flex: 0 0 20%;
flex-direction: column;
}
.product-list-item {
display: flex;
flex-direction: column;
}
.product-ratings {
display: flex;
justify-content: center;
}
.product-title {
min-height: 70px;
padding-bottom: 1em;
}
.product-item-bottom {
display: flex;
flex-direction: column;
min-height: 70px;
}
.product-count {
display: flex;
justify-content: center;
padding: 1em 0 1em 0em;
}
.product-container:hover {
box-shadow: 1px 1px 20px rgba(0, 0, 0, .2);
cursor: pointer;
}
.product-list-item img {
width: 100%;
}
.product-list-item .text-muted {
opacity: .8
}
.add-basket-button {
display: none;
font-weight: 500;
}
.product-list-item:hover .add-basket-button {
display: inline-block;
width: -webkit-fill-available;
}
.info-section {
text-align: center;
}
.product-list-item:hover .info-section {
display: none;
}
.product-price {
font-weight: bolder;
font-size: larger;
line-height: 40px;
}
.item-added {
display: none;
justify-content: center;
align-items: baseline;
}

2
apps/public-web/src/EShopOnAbp.PublicWeb/package.json

@ -3,7 +3,7 @@
"name": "EShopOnAbpPublicWeb", "name": "EShopOnAbpPublicWeb",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@abp/aspnetcore.mvc.ui.theme.basic": "~7.0.0", "@abp/aspnetcore.mvc.ui.theme.leptonxlite": "~2.0.0-rc.4",
"@abp/signalr": "~7.0.0", "@abp/signalr": "~7.0.0",
"@abp/cms-kit": "~7.0.0" "@abp/cms-kit": "~7.0.0"
} }

BIN
apps/public-web/src/EShopOnAbp.PublicWeb/wwwroot/logo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

8
apps/public-web/src/EShopOnAbp.PublicWeb/yarn.lock

@ -2,10 +2,10 @@
# yarn lockfile v1 # yarn lockfile v1
"@abp/aspnetcore.mvc.ui.theme.basic@~7.0.0": "@abp/aspnetcore.mvc.ui.theme.leptonxlite@~2.0.0-rc.4":
version "7.0.0" version "2.0.5"
resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-7.0.0.tgz#b61845e6c2d7486575ab0fd177e7c87005f51d54" resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.leptonxlite/-/aspnetcore.mvc.ui.theme.leptonxlite-2.0.5.tgz#5b4a59beba91cdd11da77a2bac78e533212b7f0b"
integrity sha512-T4rhCG+O3agwD0pt7m/977ID8Hm0ME9zlqyHIQWiIIaRbicmgblr3OY5szv0C5ttCTUsjzeYlxtcv7I+7SXcaA== integrity sha512-jO02MB/cm6+iUv5AORSahGOOeAEALccs6qsYoGCAK/1k8Ej2p51B/7UZZ3qvdQJ8zGPeFwyEXMQoq4vkg4c2gQ==
dependencies: dependencies:
"@abp/aspnetcore.mvc.ui.theme.shared" "~7.0.0" "@abp/aspnetcore.mvc.ui.theme.shared" "~7.0.0"

Loading…
Cancel
Save