Skip to content

Ib challenge Add Discount page using superpowers #697

Closed
IevaBendikiene wants to merge 14 commits into
grandnode:developfrom
IevaBendikiene:IB_challenge-_AI_Champion
Closed

Ib challenge Add Discount page using superpowers #697
IevaBendikiene wants to merge 14 commits into
grandnode:developfrom
IevaBendikiene:IB_challenge-_AI_Champion

Conversation

@IevaBendikiene
Copy link
Copy Markdown

Resolves #issueNumber
Type: feature|bugfix|

Issue

Description of the issue this PR is solving, why it's happening, and how to reproduce it.

Solution

Summarize your solution to the problem. Please include short description.

Breaking changes

If you have a breaking changes, list them here, otherwise list none.

Testing

  1. List the steps needed for testing your PR.
  2. Assume that everyone already know how to run the GrandNode, and do the basic configuration.
  3. Be detailed enough that someone can work through it easily.

IevaBendikiene and others added 14 commits May 20, 2026 23:35
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…rSettings

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Change sidebar link text to "My Discounts" and icon to currency-dollar
  in both Grand.Web and Theme.Modern navigation templates
- Replace raw localization keys with hardcoded text in CustomerDiscounts
  view (title "My discounts", empty state "No discounts at the moment")
- Add grandnode-startup.sh with start/restart/stop commands; restart
  builds from source and runs on port 80 for local development

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
BootstrapVue's bundled icon set does not include currency-dollar,
causing it to render as blank. Use an inline SVG path instead.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
[CustomerGroupAuthorize(SystemCustomerGroupNames.Registered)]
public virtual async Task<IActionResult> CustomerDiscounts()
{
var storeId = _contextAccessor.StoreContext.CurrentStore.Id;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CustomerDiscount action should not be available when HideDiscounts is set to true. Please add the appropriate conditions.

Comment thread grandnode-startup.sh
@@ -0,0 +1,173 @@
#!/usr/bin/env bash
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please delete this file.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new “My Discounts” area under the customer account section, with navigation support and an admin setting to hide/show the tab. It also introduces a root-level dev helper script for starting GrandNode2 via Docker or running from source.

Changes:

  • Added a new customer account page (CustomerDiscounts) and route (account/discounts) to display discounts.
  • Extended customer navigation + settings to support a “Discounts” tab with a “Hide discounts” toggle in Admin.
  • Added grandnode-startup.sh to start/restart/stop a local dev environment with Docker/.NET.

Reviewed changes

Copilot reviewed 12 out of 13 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
src/Web/Grand.Web/Views/Shared/Components/CustomerNavigation/Default.cshtml Adds “My Discounts” link to the customer navigation menu.
src/Plugins/Theme.Modern/Views/Modern/Shared/Components/CustomerNavigation/Default.cshtml Mirrors the new “My Discounts” navigation entry for the Modern theme.
src/Web/Grand.Web/Views/Discount/CustomerDiscounts.cshtml New account page view rendering a discounts table / empty state.
src/Web/Grand.Web/Controllers/DiscountController.cs New controller action to load discounts and return the view.
src/Web/Grand.Web/Endpoints/EndpointProvider.cs Registers the new CustomerDiscounts route.
src/Web/Grand.Web/Models/Customer/CustomerNavigationModel.cs Adds HideDiscounts flag + new AccountNavigationEnum.Discounts value.
src/Web/Grand.Web/Models/Catalog/CustomerDiscountModel.cs Introduces a view model for displaying discount info on the new page.
src/Web/Grand.Web/Features/Handlers/Customers/GetNavigationHandler.cs Populates HideDiscounts based on CustomerSettings.HideDiscountsTab.
src/Core/Grand.Domain/Customers/CustomerSettings.cs Adds HideDiscountsTab setting to the domain model.
src/Web/Grand.Web.AdminShared/Models/Settings/CustomerSettingsModel.cs Adds HideDiscountsTab to the admin settings model.
src/Web/Grand.Web.Admin/Areas/Admin/Views/Setting/Partials/Customer.TabCustomerSettings.cshtml Adds a checkbox in Admin settings UI for hiding the Discounts tab.
grandnode-startup.sh Adds a dev helper script to start Docker services and/or run from source.
Comments suppressed due to low confidence (1)

src/Web/Grand.Web/Views/Discount/CustomerDiscounts.cshtml:71

  • The empty-state message "No discounts at the moment" is hardcoded and not localized. Please switch it to a Loc[...] resource so it can be translated like the rest of the account UI.
    else
    {
        <p class="text-muted">No discounts at the moment</p>
    }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

{
<li class="customer-discounts">
<a href="@Url.RouteUrl("CustomerDiscounts")" class="@if (Model.SelectedTab == AccountNavigationEnum.Discounts) { <text>active</text> }else { <text>inactive</text> }">
<svg viewBox="0 0 16 16" width="1em" height="1em" focusable="false" role="img" xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="b-icon bi"><path d="M4 10.781c.148 1.667 1.513 2.85 3.591 3.003V15h1.043v-1.216c2.27-.179 3.678-1.438 3.678-3.3 0-1.59-.947-2.51-2.956-3.028l-.722-.187V3.467c1.122.11 1.879.714 2.07 1.616h1.47c-.166-1.6-1.54-2.748-3.54-2.875V1H7.591v1.233c-1.939.23-3.27 1.472-3.27 3.156 0 1.454.966 2.483 2.661 2.917l.61.162v4.031c-1.149-.17-1.94-.8-2.131-1.718H4zm3.391-3.836c-1.043-.263-1.6-.825-1.6-1.616 0-.944.704-1.641 1.8-1.828v3.495l-.2-.051zm1.591 1.872c1.287.323 1.852.859 1.852 1.769 0 1.097-.826 1.828-2.2 1.939V8.73l.348.086z"/></svg> <span>My Discounts</span>
{
<li class="customer-discounts">
<a href="@Url.RouteUrl("CustomerDiscounts")" class="@if (Model.SelectedTab == AccountNavigationEnum.Discounts) { <text>active</text> }else { <text>inactive</text> }">
<svg viewBox="0 0 16 16" width="1em" height="1em" focusable="false" role="img" xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="b-icon bi"><path d="M4 10.781c.148 1.667 1.513 2.85 3.591 3.003V15h1.043v-1.216c2.27-.179 3.678-1.438 3.678-3.3 0-1.59-.947-2.51-2.956-3.028l-.722-.187V3.467c1.122.11 1.879.714 2.07 1.616h1.47c-.166-1.6-1.54-2.748-3.54-2.875V1H7.591v1.233c-1.939.23-3.27 1.472-3.27 3.156 0 1.454.966 2.483 2.661 2.917l.61.162v4.031c-1.149-.17-1.94-.8-2.131-1.718H4zm3.391-3.836c-1.043-.263-1.6-.825-1.6-1.616 0-.944.704-1.641 1.8-1.828v3.495l-.2-.051zm1.591 1.872c1.287.323 1.852.859 1.852 1.769 0 1.097-.826 1.828-2.2 1.939V8.73l.348.086z"/></svg> <span>My Discounts</span>
Comment on lines +13 to +15
<section class="page account-page customer-discounts-page pl-lg-3 pt-lg-0 pt-3">
<h1 class="h2 generalTitle">My discounts</h1>
@if (Model.Any())
Comment on lines +31 to +38
// GetActiveDiscountsByContext requires a non-null DiscountType; use GetDiscountsQuery to get all types
var allDiscounts = await _discountService.GetDiscountsQuery(discountType: null, storeId: storeId);

var now = DateTime.UtcNow;
var model = allDiscounts
.Where(d => d.IsEnabled
&& (d.StartDateUtc == null || d.StartDateUtc <= now)
&& (d.EndDateUtc == null || d.EndDateUtc >= now))
Comment on lines +26 to +33
[HttpGet]
[CustomerGroupAuthorize(SystemCustomerGroupNames.Registered)]
public virtual async Task<IActionResult> CustomerDiscounts()
{
var storeId = _contextAccessor.StoreContext.CurrentStore.Id;
// GetActiveDiscountsByContext requires a non-null DiscountType; use GetDiscountsQuery to get all types
var allDiscounts = await _discountService.GetDiscountsQuery(discountType: null, storeId: storeId);

Comment on lines 174 to +179
[GrandResourceDisplayName("Admin.Settings.Customer.HideCoursesTab")]
public bool HideCoursesTab { get; set; }

[GrandResourceDisplayName("Admin.Settings.Customer.HideDiscountsTab")]
public bool HideDiscountsTab { get; set; }

Comment on lines +129 to +139
<div class="form-group">
<div class="col-6 col-md-6 col-sm-6">
<admin-label asp-for="CustomerSettings.HideDiscountsTab" class="control-label"/>
</div>
<div class="col-6 col-md-6 col-sm-6">
<label class="mt-checkbox mt-checkbox-outline control control-checkbox">
<admin-input asp-for="CustomerSettings.HideDiscountsTab"/>
<div class="control__indicator"></div>
</label>
<span asp-validation-for="CustomerSettings.HideDiscountsTab"></span>
</div>
Comment thread grandnode-startup.sh
Comment on lines +54 to +63
warn "Starting GrandNode2 container..."
if docker ps -a --format '{{.Names}}' | grep -q '^grandnode2$'; then
docker start grandnode2 > /dev/null
log "GrandNode2 container started (existing)"
else
docker run -d -p 80:8080 --name grandnode2 --link mongodb:mongo \
-v grandnode_images:/app/wwwroot/assets/images \
-v grandnode_appdata:/app/App_Data \
grandnode/grandnode2 > /dev/null
log "GrandNode2 container created and started"
Comment thread grandnode-startup.sh
Comment on lines +25 to +63
if docker ps -a --format '{{.Names}}' | grep -q '^mongodb$'; then
docker start mongodb > /dev/null
log "MongoDB container started (existing)"
else
docker run -d -p 127.0.0.1:27017:27017 --name mongodb mongo > /dev/null
log "MongoDB container created and started"
fi
}

stop_dev_process() {
if [ -f "$PID_FILE" ]; then
local pid
pid=$(cat "$PID_FILE")
if kill -0 "$pid" 2>/dev/null; then
warn "Stopping local dev server (pid $pid)..."
kill "$pid" 2>/dev/null || true
sleep 2
kill -9 "$pid" 2>/dev/null || true
log "Dev server stopped"
fi
rm -f "$PID_FILE"
fi
}

# ── commands ─────────────────────────────────────────────────────────────────

cmd_start() {
start_mongodb

warn "Starting GrandNode2 container..."
if docker ps -a --format '{{.Names}}' | grep -q '^grandnode2$'; then
docker start grandnode2 > /dev/null
log "GrandNode2 container started (existing)"
else
docker run -d -p 80:8080 --name grandnode2 --link mongodb:mongo \
-v grandnode_images:/app/wwwroot/assets/images \
-v grandnode_appdata:/app/App_Data \
grandnode/grandnode2 > /dev/null
log "GrandNode2 container created and started"
Comment thread grandnode-startup.sh
Comment on lines +1 to +15
#!/usr/bin/env bash
set -euo pipefail

GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
NC='\033[0m'

log() { echo -e "${GREEN}[OK]${NC} $1"; }
warn() { echo -e "${YELLOW}[..] $1${NC}"; }
fail() { echo -e "${RED}[FAIL]${NC} $1"; exit 1; }

COMMAND="${1:-start}"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PID_FILE="$SCRIPT_DIR/.grandnode-dev.pid"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants