Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .ai/skills/theme-creation.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@ Do not use this skill for changes to `Grand.Web/Views` themselves — that is `.
16. Add `Views/{ThemeName}/_ViewStart.cshtml`.
17. Copy **only** the views the theme actually changes. Everything else resolves through the fallback locations. Each copied view is a file that must be re-reconciled on every upgrade.
18. A copied view keeps the same `@model`, the same route names, and the same storefront data attributes — `data-cart-action`, product IDs, quick-view URLs, wishlist, compare, add-to-cart, image `alt` / `title` / `loading`.
19. Keep widget zones (`@await Component.InvokeAsync("Widget", new { widgetZone = "..." })`) present in copied views. Dropping a zone silently disables every installed widget on that page.
19. Keep widget zones (`<vc:widget widget-zone="..." />` or `@await Component.InvokeAsync("Widget", new { widgetZone = "..." })`) present in copied views. Dropping a zone silently disables every installed widget on that page.
A `<vc:...>` tag only works for a view component in an assembly the theme registers with `@addTagHelper`, which here is `Grand.Web.Common` (`<vc:widget>`). A theme cannot reference `Grand.Web` (`GRAND001`), so every other `<vc:...>` copied from a `Grand.Web` view, such as `<vc:related-products>`, renders as an empty unknown element with no build error. Rewrite those as `@await Component.InvokeAsync("RelatedProducts", new { productId = Model.Id })`.
20. A theme must not change view models, controller actions, or route definitions. If a theme needs different data, the change belongs in `Grand.Web`, not the theme.

#### Assets
Expand Down
14 changes: 14 additions & 0 deletions src/Core/Grand.Infrastructure/Caching/Constants/CommonCacheKey.cs
Original file line number Diff line number Diff line change
Expand Up @@ -217,4 +217,18 @@ public static partial class CacheKey
public static string PAGES_PATTERN_KEY => "Grand.pages.";

#endregion

#region Widgets

/// <summary>
/// Key for widget info
/// </summary>
/// <remarks>
/// {0} : current store ID
/// {1} : customer groups
/// {2} : widget zone
/// </remarks>
public static string WIDGET_MODEL_KEY => "Grand.pres.widget-{0}-{1}-{2}";

#endregion
}
Original file line number Diff line number Diff line change
Expand Up @@ -236,10 +236,10 @@
</gn-tabs>
</div>
</div>
<vc:products-also-purchased product-id="@Model.Id" product-thumb-picture-size="null"></vc:products-also-purchased>
<vc:related-products product-id="@Model.Id" product-thumb-picture-size="null"></vc:related-products>
<vc:similar-products product-id="@Model.Id" product-thumb-picture-size="null"></vc:similar-products>
<vc:recommended-products product-id="@Model.Id" product-thumb-picture-size="null"></vc:recommended-products>
@await Component.InvokeAsync("ProductsAlsoPurchased", new { productId = Model.Id })
@await Component.InvokeAsync("RelatedProducts", new { productId = Model.Id })
@await Component.InvokeAsync("SimilarProducts", new { productId = Model.Id })
@await Component.InvokeAsync("RecommendedProducts", new { productId = Model.Id })
@if (Model.RenderCaptcha)
{
<partial name="Partials/Captcha"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@
using Grand.Infrastructure.Caching;
using Grand.Infrastructure.Configuration;
using Grand.Mediator;
using Grand.Web.Components;
using Grand.Web.Common.Components;
using Grand.Web.Models.Cms;
using Microsoft.AspNetCore.Mvc.ViewComponents;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;

namespace Grand.Web.Tests.Components;
namespace Grand.Web.Common.Tests.Components;

[TestClass]
public class WidgetViewComponentTests
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@
using Grand.Domain.Customers;
using Grand.Infrastructure;
using Grand.Infrastructure.Caching;
using Grand.Web.Common.Components;
using Grand.Web.Events.Cache;
using Grand.Infrastructure.Caching.Constants;
using Grand.Web.Models.Cms;
using Microsoft.AspNetCore.Mvc;

namespace Grand.Web.Components;
namespace Grand.Web.Common.Components;

public class WidgetViewComponent : BaseViewComponent
{
Expand All @@ -24,7 +23,7 @@ public WidgetViewComponent(IContextAccessor contextAccessor, ICacheBase cacheBas

public async Task<IViewComponentResult> InvokeAsync(string widgetZone, object additionalData = null)
{
var cacheKey = string.Format(CacheKeyConst.WIDGET_MODEL_KEY, _contextAccessor.StoreContext.CurrentStore.Id,
var cacheKey = string.Format(CacheKey.WIDGET_MODEL_KEY, _contextAccessor.StoreContext.CurrentStore.Id,
string.Join(",", _contextAccessor.WorkContext.CurrentCustomer.GetCustomerGroupIds()),
widgetZone);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
using Grand.Infrastructure.Models;

//the namespace stays from Grand.Web.Models: storefront and theme _ViewImports import it
namespace Grand.Web.Models.Cms;

public class WidgetModel : BaseModel
{
public string WidgetZone { get; set; }
public string ViewComponentName { get; set; }
public object AdditionalData { get; set; }
}
}
10 changes: 0 additions & 10 deletions src/Web/Grand.Web/Events/Cache/CacheKeyConst.cs
Original file line number Diff line number Diff line change
Expand Up @@ -288,16 +288,6 @@ public static class CacheKeyConst
/// </remarks>
public const string SITEMAP_PAGE_MODEL_KEY = "Grand.pres.sitemap.page-{0}-{1}-{2}";

/// <summary>
/// Key for widget info
/// </summary>
/// <remarks>
/// {0} : current store ID
/// {1} : customer groups
/// {2} : widget zone
/// </remarks>
public const string WIDGET_MODEL_KEY = "Grand.pres.widget-{0}-{1}-{2}";

/// <summary>
/// Key for caching
/// </summary>
Expand Down
Loading