Skip to content
Open
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
4 changes: 4 additions & 0 deletions samples/aspire-shop/AspireShop.CatalogDb/CatalogDbContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ private static void DefineCatalogItem(EntityTypeBuilder<CatalogItem> builder)
builder.Property(ci => ci.PictureFileName)
.IsRequired(false);

builder.Property(ci => ci.Badge)
.IsRequired(false)
.HasMaxLength(30);

builder.Ignore(ci => ci.PictureUri);

builder.HasOne(ci => ci.CatalogBrand)
Expand Down
5 changes: 5 additions & 0 deletions samples/aspire-shop/AspireShop.CatalogDb/CatalogItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ public class CatalogItem
public required string PictureFileName { get; set; }
public string? PictureUri { get; set; }

/// <summary>
/// Optional merchandising label shown on the product (e.g. "New arrival", "Special").
/// </summary>
public string? Badge { get; set; }

public int CatalogTypeId { get; set; }
public required CatalogType CatalogType { get; set; }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,7 @@ private async Task SeedAsync(CatalogDbContext dbContext, CancellationToken cance
static List<CatalogBrand> GetPreconfiguredCatalogBrands()
{
return [
new() { Brand = "Azure" },
new() { Brand = ".NET" },
new() { Brand = "Visual Studio" },
new() { Brand = "SQL Server" },
new() { Brand = "Other" }
new() { Brand = "Aspire" }
];
}

Expand All @@ -52,33 +48,42 @@ static List<CatalogType> GetPreconfiguredCatalogTypes()
return [
new() { Type = "Mug" },
new() { Type = "T-Shirt" },
new() { Type = "Sheet" },
new() { Type = "USB Memory Stick" }
new() { Type = "Hoodie" },
new() { Type = "Jacket" },
new() { Type = "Skateboard" },
new() { Type = "Pin Badge" },
new() { Type = "Sticker" }
];
}

static List<CatalogItem> GetPreconfiguredItems(DbSet<CatalogBrand> catalogBrands, DbSet<CatalogType> catalogTypes)
{
var dotNet = catalogBrands.First(b => b.Brand == ".NET");
var other = catalogBrands.First(b => b.Brand == "Other");
var aspire = catalogBrands.First(b => b.Brand == "Aspire");

var mug = catalogTypes.First(c => c.Type == "Mug");
var tshirt = catalogTypes.First(c => c.Type == "T-Shirt");
var sheet = catalogTypes.First(c => c.Type == "Sheet");
var hoodie = catalogTypes.First(c => c.Type == "Hoodie");
var jacket = catalogTypes.First(c => c.Type == "Jacket");
var skateboard = catalogTypes.First(c => c.Type == "Skateboard");
var pinBadge = catalogTypes.First(c => c.Type == "Pin Badge");
var sticker = catalogTypes.First(c => c.Type == "Sticker");

return [
new() { CatalogType = tshirt, CatalogBrand = dotNet, AvailableStock = 100, Description = ".NET Bot Black Hoodie", Name = ".NET Bot Black Hoodie", Price = 19.5M, PictureFileName = "1.png" },
new() { CatalogType = mug, CatalogBrand = dotNet, AvailableStock = 100, Description = ".NET Black & White Mug", Name = ".NET Black & White Mug", Price= 8.50M, PictureFileName = "2.png" },
new() { CatalogType = tshirt, CatalogBrand = other, AvailableStock = 100, Description = "Prism White T-Shirt", Name = "Prism White T-Shirt", Price = 12, PictureFileName = "3.png" },
new() { CatalogType = tshirt, CatalogBrand = dotNet, AvailableStock = 100, Description = ".NET Foundation T-shirt", Name = ".NET Foundation T-shirt", Price = 12, PictureFileName = "4.png" },
new() { CatalogType = sheet, CatalogBrand = other, AvailableStock = 100, Description = "Roslyn Red Sheet", Name = "Roslyn Red Sheet", Price = 8.5M, PictureFileName = "5.png" },
new() { CatalogType = tshirt, CatalogBrand = dotNet, AvailableStock = 100, Description = ".NET Blue Hoodie", Name = ".NET Blue Hoodie", Price = 12, PictureFileName = "6.png" },
new() { CatalogType = tshirt, CatalogBrand = other, AvailableStock = 100, Description = "Roslyn Red T-Shirt", Name = "Roslyn Red T-Shirt", Price = 12, PictureFileName = "7.png" },
new() { CatalogType = tshirt, CatalogBrand = other, AvailableStock = 100, Description = "Kudu Purple Hoodie", Name = "Kudu Purple Hoodie", Price = 8.5M, PictureFileName = "8.png" },
new() { CatalogType = mug, CatalogBrand = other, AvailableStock = 100, Description = "Cup<T> White Mug", Name = "Cup<T> White Mug", Price = 12, PictureFileName = "9.png" },
new() { CatalogType = sheet, CatalogBrand = dotNet, AvailableStock = 100, Description = ".NET Foundation Sheet", Name = ".NET Foundation Sheet", Price = 12, PictureFileName = "10.png" },
new() { CatalogType = sheet, CatalogBrand = dotNet, AvailableStock = 100, Description = "Cup<T> Sheet", Name = "Cup<T> Sheet", Price = 8.5M, PictureFileName = "11.png" },
new() { CatalogType = tshirt, CatalogBrand = other, AvailableStock = 100, Description = "Prism White TShirt", Name = "Prism White TShirt", Price = 12, PictureFileName = "12.png" }
new() { CatalogType = hoodie, CatalogBrand = aspire, AvailableStock = 100, Name = "Aspire Charcoal Hoodie", Description = "A cozy charcoal pullover hoodie with the layered Aspire logo centered on the chest.", Price = 44.00M, PictureFileName = "1.png" },
new() { CatalogType = mug, CatalogBrand = aspire, AvailableStock = 100, Name = "Aspire Logo Mug", Description = "A glossy white ceramic mug featuring the layered Aspire logo.", Price = 12.00M, PictureFileName = "2.png" },
new() { CatalogType = tshirt, CatalogBrand = aspire, AvailableStock = 100, Name = "Aspire.dev Logo T-Shirt", Description = "A white cotton tee with a left-chest Aspire logo and the aspire.dev wordmark.", Price = 24.00M, PictureFileName = "3.png" },
new() { CatalogType = tshirt, CatalogBrand = aspire, AvailableStock = 100, Name = "Aspire OG T-Shirt", Description = "A lavender tee with an \"OG\" graphic and a mosaic band of Aspire logo tiles \u2014 a nod to the aspire.dev og:image social card.", Price = 26.00M, PictureFileName = "4.png", Badge = "og:image" },
new() { CatalogType = sticker, CatalogBrand = aspire, AvailableStock = 100, Name = "Aspire Logo Sticker", Description = "A glossy die-cut vinyl sticker of the layered Aspire logo.", Price = 3.50M, PictureFileName = "5.png" },
new() { CatalogType = hoodie, CatalogBrand = aspire, AvailableStock = 100, Name = "Aspire All-Over Print Hoodie", Description = "A sky-blue pullover hoodie with an all-over print of repeating Aspire logos.", Price = 52.00M, PictureFileName = "6.png", Badge = "New arrival" },
new() { CatalogType = tshirt, CatalogBrand = aspire, AvailableStock = 100, Name = "Aspire Red Logo T-Shirt", Description = "A bold red cotton tee with a large, centered Aspire logo.", Price = 22.00M, PictureFileName = "7.png" },
new() { CatalogType = hoodie, CatalogBrand = aspire, AvailableStock = 100, Name = "Aspire.dev Zip Hoodie", Description = "A periwinkle full-zip hoodie with a chest Aspire logo and aspire.dev down the sleeve.", Price = 49.50M, PictureFileName = "8.png" },
new() { CatalogType = mug, CatalogBrand = aspire, AvailableStock = 100, Name = "Aspire \"Say Bye to YAML\" Mug", Description = "A white ceramic mug with the Aspire logo and a playful \"Say bye to YAML\" message.", Price = 14.00M, PictureFileName = "9.png" },
new() { CatalogType = sticker, CatalogBrand = aspire, AvailableStock = 100, Name = "Aspire Logo Sticker Pack", Description = "A pack of glossy die-cut stickers featuring the layered Aspire logo.", Price = 9.00M, PictureFileName = "10.png" },
new() { CatalogType = pinBadge, CatalogBrand = aspire, AvailableStock = 100, Name = "Aspire \"No YAML\" Pin Badge", Description = "A round pin-back button badge with the Aspire logo and a \"No YAML\" callout.", Price = 5.00M, PictureFileName = "11.png" },
new() { CatalogType = tshirt, CatalogBrand = aspire, AvailableStock = 100, Name = "Aspire \"Goodbye YAML\" T-Shirt", Description = "A white tee with the Aspire logo and a \"Goodbye, YAML \u2014 Hello, Aspire\" slogan.", Price = 24.00M, PictureFileName = "12.png" },
new() { CatalogType = tshirt, CatalogBrand = aspire, AvailableStock = 100, Name = "Aspire.dev Ringer T-Shirt", Description = "A white ringer tee with lavender trim, the Aspire logo, and a \"Goodbye, YAML \u2014 Hello, aspire.dev\" slogan.", Price = 26.00M, PictureFileName = "13.png" },
new() { CatalogType = jacket, CatalogBrand = aspire, AvailableStock = 100, Name = "Aspire.dev Softshell Jacket", Description = "A tan softshell jacket with a chest Aspire logo and aspire.dev down the sleeve.", Price = 79.00M, PictureFileName = "14.png", Badge = "New arrival" },
new() { CatalogType = skateboard, CatalogBrand = aspire, AvailableStock = 100, Name = "Aspire Skateboard Deck", Description = "A white maple skateboard deck with the Aspire logo, wordmark, and aspire.dev branding.", Price = 65.00M, PictureFileName = "15.png", Badge = "Special" }
];
}

Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ protected override void Up(MigrationBuilder migrationBuilder)
Description = table.Column<string>(type: "text", nullable: true),
Price = table.Column<decimal>(type: "numeric", nullable: false),
PictureFileName = table.Column<string>(type: "text", nullable: true),
Badge = table.Column<string>(type: "character varying(30)", maxLength: 30, nullable: true),
CatalogTypeId = table.Column<int>(type: "integer", nullable: false),
CatalogBrandId = table.Column<int>(type: "integer", nullable: false),
AvailableStock = table.Column<int>(type: "integer", nullable: false),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.10")
.HasAnnotation("ProductVersion", "10.0.9")
.HasAnnotation("Relational:MaxIdentifierLength", 63);

NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
Expand Down Expand Up @@ -59,6 +59,10 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.Property<int>("AvailableStock")
.HasColumnType("integer");

b.Property<string>("Badge")
.HasMaxLength(30)
.HasColumnType("character varying(30)");

b.Property<int>("CatalogBrandId")
.HasColumnType("integer");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.9" />
<PackageReference Include="Microsoft.OpenApi" Version="2.10.0" />
<PackageReference Include="Scalar.AspNetCore" Version="2.16.11" />
</ItemGroup>

Expand Down
2 changes: 2 additions & 0 deletions samples/aspire-shop/AspireShop.CatalogService/CatalogApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ static async Task<IResult> GetCatalogItems(int? catalogBrandId, CatalogDbContext
item.Description,
item.Price,
item.AvailableStock,
item.Badge,
item.CatalogBrand is { } brand ? new CatalogBrandDto(brand.Id, brand.Brand) : null,
item.CatalogType is { } type ? new CatalogTypeDto(type.Id, type.Type) : null);
}
Expand All @@ -100,6 +101,7 @@ public record CatalogItemDto(
string? Description,
decimal Price,
int AvailableStock,
string? Badge,
CatalogBrandDto? CatalogBrand,
CatalogTypeDto? CatalogType);

Expand Down
Binary file modified samples/aspire-shop/AspireShop.CatalogService/Images/1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified samples/aspire-shop/AspireShop.CatalogService/Images/10.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified samples/aspire-shop/AspireShop.CatalogService/Images/11.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified samples/aspire-shop/AspireShop.CatalogService/Images/12.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified samples/aspire-shop/AspireShop.CatalogService/Images/13.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified samples/aspire-shop/AspireShop.CatalogService/Images/14.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified samples/aspire-shop/AspireShop.CatalogService/Images/2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified samples/aspire-shop/AspireShop.CatalogService/Images/3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified samples/aspire-shop/AspireShop.CatalogService/Images/4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified samples/aspire-shop/AspireShop.CatalogService/Images/5.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified samples/aspire-shop/AspireShop.CatalogService/Images/6.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified samples/aspire-shop/AspireShop.CatalogService/Images/7.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified samples/aspire-shop/AspireShop.CatalogService/Images/8.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified samples/aspire-shop/AspireShop.CatalogService/Images/9.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 changes: 19 additions & 4 deletions samples/aspire-shop/AspireShop.Frontend/Components/AddToCart.razor
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
@inject BasketServiceClient BasketClient
@inject NavigationManager Navigation

<EditForm Model="this" FormName="@($"addtocart-{Item.Id}")" OnSubmit="HandleAddToCart" data-enhance>
<EditForm Model="this" FormName="@FormName" OnSubmit="HandleAddToCart" data-enhance data-preserve-scroll>
<input type="hidden" name="itemid" value="@Item.Id" />
<button type="submit" class="add-button" aria-label="@($"Add {Item.Name} to cart")" title="@($"Add {Item.Name} to cart")">
<button type="submit" class="@ButtonClass" aria-label="@($"Add {Item.Name} to cart")" title="@($"Add {Item.Name} to cart")">
<span class="add-icon" aria-hidden="true">@Icons.Plus</span>
<span class="add-text">Add to cart</span>
</button>
Expand All @@ -14,14 +14,29 @@
[EditorRequired]
public required CatalogItem Item { get; set; }

// Discriminator so the same item can render more than one add-to-cart form on a single page
// (e.g. the product card and the quick-look modal) without two enhanced forms sharing a name.
[Parameter]
public string? Context { get; set; }

[Parameter]
public string ButtonClass { get; set; } = "add-button";

[SupplyParameterFromForm]
public int ItemId { get; set; }

private string FormName => string.IsNullOrEmpty(Context)
? $"addtocart-{Item.Id}"
: $"addtocart-{Context}-{Item.Id}";

private async Task HandleAddToCart()
{
await BasketClient.AddToCartAsync("user", ItemId);

// Preserve query string
Navigation.NavigateTo($"/{new Uri(Navigation.Uri).Query}");
// Re-request the current page (preserving the pagination query) so a freshly initialized
// component tree reflects the updated basket — e.g. the cart count in the top bar. The
// form is marked data-preserve-scroll so site.js keeps the scroll position across this
// enhanced refresh, avoiding a jarring jump to the top.
Navigation.Refresh();
}
}
12 changes: 12 additions & 0 deletions samples/aspire-shop/AspireShop.Frontend/Components/App.razor
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,17 @@
<meta name="color-scheme" content="light dark" />
<meta name="theme-color" content="#f3ede3" media="(prefers-color-scheme: light)" />
<meta name="theme-color" content="#14100c" media="(prefers-color-scheme: dark)" />
<script>
// Apply the saved theme before first paint to avoid a flash of the wrong color scheme.
(function () {
try {
var t = localStorage.getItem('aspireshop-theme');
if (t === 'light' || t === 'dark') {
document.documentElement.setAttribute('data-theme', t);
}
} catch (e) { /* localStorage unavailable */ }
})();
</script>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link rel="stylesheet"
Expand All @@ -21,6 +32,7 @@
<body>
<Routes />
<script src="_framework/blazor.web.js"></script>
<script src="site.js"></script>
</body>

</html>
9 changes: 9 additions & 0 deletions samples/aspire-shop/AspireShop.Frontend/Components/Icons.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,15 @@ public static class Icons
public static readonly MarkupString GitHub = Svg(
"M208.31,75.68A59.78,59.78,0,0,0,202.93,28,8,8,0,0,0,196,24a59.75,59.75,0,0,0-48,24H124A59.75,59.75,0,0,0,76,24a8,8,0,0,0-6.93,4,59.78,59.78,0,0,0-5.38,47.68A58.14,58.14,0,0,0,56,104v8a56.06,56.06,0,0,0,48.44,55.47A39.8,39.8,0,0,0,96,192v8H72a24,24,0,0,1-24-24A40,40,0,0,0,8,136a8,8,0,0,0,0,16,24,24,0,0,1,24,24,40,40,0,0,0,40,40H96v16a8,8,0,0,0,16,0V192a24,24,0,0,1,48,0v40a8,8,0,0,0,16,0V192a39.8,39.8,0,0,0-8.44-24.53A56.06,56.06,0,0,0,216,112v-8A58.14,58.14,0,0,0,208.31,75.68ZM200,112a40,40,0,0,1-40,40H112a40,40,0,0,1-40-40v-8a41.74,41.74,0,0,1,6.9-22.48A8,8,0,0,0,80,73.83a43.81,43.81,0,0,1,.79-33.58,43.88,43.88,0,0,1,32.32,20.06A8,8,0,0,0,119.82,64h32.35a8,8,0,0,0,6.74-3.69,43.87,43.87,0,0,1,32.32-20.06A43.81,43.81,0,0,1,192,73.83a8.09,8.09,0,0,0,1,7.65A41.72,41.72,0,0,1,200,104Z");

public static readonly MarkupString Close = Svg(
"M205.66,194.34a8,8,0,0,1-11.32,11.32L128,139.31,61.66,205.66a8,8,0,0,1-11.32-11.32L116.69,128,50.34,61.66A8,8,0,0,1,61.66,50.34L128,116.69l66.34-66.35a8,8,0,0,1,11.32,11.32L139.31,128Z");

public static readonly MarkupString Sun = Svg(
"M120,40V16a8,8,0,0,1,16,0V40a8,8,0,0,1-16,0Zm72,88a64,64,0,1,1-64-64A64.07,64.07,0,0,1,192,128Zm-16,0a48,48,0,1,0-48,48A48.05,48.05,0,0,0,176,128ZM58.34,69.66A8,8,0,0,0,69.66,58.34l-16-16A8,8,0,0,0,42.34,53.66Zm0,116.68-16,16a8,8,0,0,0,11.32,11.32l16-16a8,8,0,0,0-11.32-11.32ZM192,72a8,8,0,0,0,5.66-2.34l16-16a8,8,0,0,0-11.32-11.32l-16,16A8,8,0,0,0,192,72Zm5.66,114.34a8,8,0,0,0-11.32,11.32l16,16a8,8,0,0,0,11.32-11.32ZM48,128a8,8,0,0,0-8-8H16a8,8,0,0,0,0,16H40A8,8,0,0,0,48,128Zm80,80a8,8,0,0,0-8,8v24a8,8,0,0,0,16,0V216A8,8,0,0,0,128,208Zm112-88H216a8,8,0,0,0,0,16h24a8,8,0,0,0,0-16Z");

public static readonly MarkupString Moon = Svg(
"M233.54,142.23a8,8,0,0,0-8-2,88.08,88.08,0,0,1-109.8-109.8,8,8,0,0,0-10-10,104.84,104.84,0,0,0-52.91,37A104,104,0,0,0,136,224a103.09,103.09,0,0,0,62.52-20.88,104.84,104.84,0,0,0,37-52.91A8,8,0,0,0,233.54,142.23ZM188.9,190.34A88,88,0,0,1,65.66,67.11a89,89,0,0,1,31.4-26A106,106,0,0,0,96,56,104.11,104.11,0,0,0,200,160a106,106,0,0,0,14.92-1.06A89,89,0,0,1,188.9,190.34Z");

private static MarkupString Svg(string path) => new(
$"<svg class=\"ph\" viewBox=\"0 0 256 256\" fill=\"currentColor\" aria-hidden=\"true\" focusable=\"false\"><path d=\"{path}\"/></svg>");
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
&copy; @DateTime.Today.Year · MIT Licensed
</a>
<span class="footer-sep" aria-hidden="true"></span>
<span class="footer-meta">@Message</span>
<a href="https://aspire.dev" rel="noopener noreferrer" target="_blank" class="footer-link">
@Message
</a>
<span class="footer-sep" aria-hidden="true"></span>
<a href="https://github.com/dotnet/aspire-samples/tree/main/samples/aspire-shop"
rel="noopener noreferrer" target="_blank" class="footer-link">
Expand Down
Loading
Loading