diff --git a/src/WidgetWorks.Infrastructure/Seeding/DbSeeder.cs b/src/WidgetWorks.Infrastructure/Seeding/DbSeeder.cs index 2d4f022..a0c527a 100644 --- a/src/WidgetWorks.Infrastructure/Seeding/DbSeeder.cs +++ b/src/WidgetWorks.Infrastructure/Seeding/DbSeeder.cs @@ -29,11 +29,31 @@ public sealed class DbSeeder(IDbConnectionFactory factory, IPasswordHasher hashe { private static readonly (string Sku, string Name, string Description, decimal Price, int OnHand)[] DemoWidgets = [ - ("WW-001", "Standard Widget", "The dependable everyday widget.", 9.99m, 250), - ("WW-002", "Deluxe Widget", "Premium finish with a reinforced housing.", 24.99m, 120), - ("WW-003", "Mega Widget", "Oversized widget for heavy-duty jobs.", 49.99m, 60), - ("WW-004", "Mini Widget", "Compact widget for tight spaces.", 4.99m, 500), - ("WW-005", "Widget Pro Kit", "Bundle of assorted widgets and accessories.", 79.99m, 40), + ("WW-001", "Standard Widget Block", "The dependable everyday widget.", 9.99m, 250), + ("WW-002", "Standard Widget Rotary", "Dial-adjustable everyday widget.", 11.49m, 220), + ("WW-003", "Standard Widget Valve", "Inline everyday widget with twin ports.", 12.99m, 180), + ("WW-004", "Standard Widget Hub", "Four-port everyday widget.", 13.99m, 160), + ("WW-005", "Standard Widget Turbine", "Ventilated everyday widget.", 14.99m, 140), + ("WW-006", "Deluxe Widget Block", "Premium finish with a reinforced housing.", 24.99m, 120), + ("WW-007", "Deluxe Widget Rotary", "Premium dial with a gold-plated pointer.", 27.49m, 110), + ("WW-008", "Deluxe Widget Valve", "Premium inline widget, machined ports.", 29.99m, 90), + ("WW-009", "Deluxe Widget Hub", "Premium four-port widget, gold contacts.", 32.99m, 80), + ("WW-010", "Deluxe Widget Turbine", "Premium ventilated widget, balanced rotor.", 34.99m, 70), + ("WW-011", "Mega Widget Block", "Oversized widget for heavy-duty jobs.", 49.99m, 60), + ("WW-012", "Mega Widget Rotary", "Oversized dial widget for heavy-duty jobs.", 54.49m, 55), + ("WW-013", "Mega Widget Valve", "Oversized inline widget, high-flow ports.", 59.99m, 45), + ("WW-014", "Mega Widget Hub", "Oversized four-port widget for busy lines.", 64.99m, 40), + ("WW-015", "Mega Widget Turbine", "Oversized ventilated widget, high throughput.", 69.99m, 35), + ("WW-016", "Mini Widget Block", "Compact widget for tight spaces.", 4.99m, 500), + ("WW-017", "Mini Widget Rotary", "Compact dial widget for tight spaces.", 5.99m, 460), + ("WW-018", "Mini Widget Valve", "Compact inline widget, low-flow ports.", 6.49m, 420), + ("WW-019", "Mini Widget Hub", "Compact four-port widget for tight spaces.", 7.49m, 380), + ("WW-020", "Mini Widget Turbine", "Compact ventilated widget, quiet running.", 8.49m, 340), + ("WW-021", "Widget Pro Kit", "Bundle of assorted widgets and accessories.", 79.99m, 40), + ("WW-022", "Widget Starter Kit", "Open-tray bundle for a first build.", 39.99m, 90), + ("WW-023", "Widget Builder Kit", "Three-drawer cabinet of sorted widgets.", 99.99m, 30), + ("WW-024", "Widget Travel Kit", "Soft-sided bundle for work away from the bench.", 49.99m, 60), + ("WW-025", "Widget Master Kit", "Two-case bundle covering the full range.", 149.99m, 20), ]; public async Task SeedAsync(SeedOptions options, CancellationToken ct) diff --git a/tests/WidgetWorks.IntegrationTests/SeederAndMigrationTests.cs b/tests/WidgetWorks.IntegrationTests/SeederAndMigrationTests.cs index 81d5846..c22b744 100644 --- a/tests/WidgetWorks.IntegrationTests/SeederAndMigrationTests.cs +++ b/tests/WidgetWorks.IntegrationTests/SeederAndMigrationTests.cs @@ -107,13 +107,15 @@ public async Task Seeding_stocks_the_demo_catalog_and_repeats_safely() await Seeder.SeedAsync(Options(Guid.NewGuid().ToString("N")[..8]), CancellationToken.None); var standard = await widgets.GetBySkuAsync("WW-001", CancellationToken.None); - Assert.Equal("Standard Widget", standard!.Name); + Assert.Equal("Standard Widget Block", standard!.Name); Assert.Equal(9.99m, standard.Price); await Seeder.SeedAsync(Options(Guid.NewGuid().ToString("N")[..8]), CancellationToken.None); - // Still one row per SKU after a second run. - Assert.Equal(1, await widgets.CountAsync(new WidgetQuery("Standard Widget", true, 1, 50), CancellationToken.None)); + // Still one row per SKU after a second run. The search has to name a whole + // product: each department seeds five widgets, so "Standard Widget" on its own + // now matches all five of them. + Assert.Equal(1, await widgets.CountAsync(new WidgetQuery("Standard Widget Block", true, 1, 50), CancellationToken.None)); } [Fact] diff --git a/web/public/products/widget.svg b/web/public/products/widget.svg index 83aed71..dfaf001 100644 --- a/web/public/products/widget.svg +++ b/web/public/products/widget.svg @@ -1 +1 @@ - + diff --git a/web/public/products/ww-001.svg b/web/public/products/ww-001.svg index fb98253..d20f347 100644 --- a/web/public/products/ww-001.svg +++ b/web/public/products/ww-001.svg @@ -1 +1 @@ - + diff --git a/web/public/products/ww-002.svg b/web/public/products/ww-002.svg index 8e4c29d..de7640a 100644 --- a/web/public/products/ww-002.svg +++ b/web/public/products/ww-002.svg @@ -1 +1 @@ - + diff --git a/web/public/products/ww-003.svg b/web/public/products/ww-003.svg index 1fc1560..14ed72c 100644 --- a/web/public/products/ww-003.svg +++ b/web/public/products/ww-003.svg @@ -1 +1 @@ - + diff --git a/web/public/products/ww-004.svg b/web/public/products/ww-004.svg index 10693cb..880a1d9 100644 --- a/web/public/products/ww-004.svg +++ b/web/public/products/ww-004.svg @@ -1 +1 @@ - + diff --git a/web/public/products/ww-005.svg b/web/public/products/ww-005.svg index acd6690..1b87fa5 100644 --- a/web/public/products/ww-005.svg +++ b/web/public/products/ww-005.svg @@ -1 +1 @@ - + diff --git a/web/public/products/ww-006.svg b/web/public/products/ww-006.svg new file mode 100644 index 0000000..c661575 --- /dev/null +++ b/web/public/products/ww-006.svg @@ -0,0 +1 @@ + diff --git a/web/public/products/ww-007.svg b/web/public/products/ww-007.svg new file mode 100644 index 0000000..e27ef45 --- /dev/null +++ b/web/public/products/ww-007.svg @@ -0,0 +1 @@ + diff --git a/web/public/products/ww-008.svg b/web/public/products/ww-008.svg new file mode 100644 index 0000000..5b42ef6 --- /dev/null +++ b/web/public/products/ww-008.svg @@ -0,0 +1 @@ + diff --git a/web/public/products/ww-009.svg b/web/public/products/ww-009.svg new file mode 100644 index 0000000..c58cb7c --- /dev/null +++ b/web/public/products/ww-009.svg @@ -0,0 +1 @@ + diff --git a/web/public/products/ww-010.svg b/web/public/products/ww-010.svg new file mode 100644 index 0000000..0ff15d2 --- /dev/null +++ b/web/public/products/ww-010.svg @@ -0,0 +1 @@ + diff --git a/web/public/products/ww-011.svg b/web/public/products/ww-011.svg new file mode 100644 index 0000000..4ab3ade --- /dev/null +++ b/web/public/products/ww-011.svg @@ -0,0 +1 @@ + diff --git a/web/public/products/ww-012.svg b/web/public/products/ww-012.svg new file mode 100644 index 0000000..ce49a0c --- /dev/null +++ b/web/public/products/ww-012.svg @@ -0,0 +1 @@ + diff --git a/web/public/products/ww-013.svg b/web/public/products/ww-013.svg new file mode 100644 index 0000000..fdad27b --- /dev/null +++ b/web/public/products/ww-013.svg @@ -0,0 +1 @@ + diff --git a/web/public/products/ww-014.svg b/web/public/products/ww-014.svg new file mode 100644 index 0000000..bd9d188 --- /dev/null +++ b/web/public/products/ww-014.svg @@ -0,0 +1 @@ + diff --git a/web/public/products/ww-015.svg b/web/public/products/ww-015.svg new file mode 100644 index 0000000..37283b9 --- /dev/null +++ b/web/public/products/ww-015.svg @@ -0,0 +1 @@ + diff --git a/web/public/products/ww-016.svg b/web/public/products/ww-016.svg new file mode 100644 index 0000000..7f6758c --- /dev/null +++ b/web/public/products/ww-016.svg @@ -0,0 +1 @@ + diff --git a/web/public/products/ww-017.svg b/web/public/products/ww-017.svg new file mode 100644 index 0000000..078bde6 --- /dev/null +++ b/web/public/products/ww-017.svg @@ -0,0 +1 @@ + diff --git a/web/public/products/ww-018.svg b/web/public/products/ww-018.svg new file mode 100644 index 0000000..0691471 --- /dev/null +++ b/web/public/products/ww-018.svg @@ -0,0 +1 @@ + diff --git a/web/public/products/ww-019.svg b/web/public/products/ww-019.svg new file mode 100644 index 0000000..9cfc4e7 --- /dev/null +++ b/web/public/products/ww-019.svg @@ -0,0 +1 @@ + diff --git a/web/public/products/ww-020.svg b/web/public/products/ww-020.svg new file mode 100644 index 0000000..d03752b --- /dev/null +++ b/web/public/products/ww-020.svg @@ -0,0 +1 @@ + diff --git a/web/public/products/ww-021.svg b/web/public/products/ww-021.svg new file mode 100644 index 0000000..bc1f3f6 --- /dev/null +++ b/web/public/products/ww-021.svg @@ -0,0 +1 @@ + diff --git a/web/public/products/ww-022.svg b/web/public/products/ww-022.svg new file mode 100644 index 0000000..dd27f3d --- /dev/null +++ b/web/public/products/ww-022.svg @@ -0,0 +1 @@ + diff --git a/web/public/products/ww-023.svg b/web/public/products/ww-023.svg new file mode 100644 index 0000000..6e44611 --- /dev/null +++ b/web/public/products/ww-023.svg @@ -0,0 +1 @@ + diff --git a/web/public/products/ww-024.svg b/web/public/products/ww-024.svg new file mode 100644 index 0000000..5d1d4ba --- /dev/null +++ b/web/public/products/ww-024.svg @@ -0,0 +1 @@ + diff --git a/web/public/products/ww-025.svg b/web/public/products/ww-025.svg new file mode 100644 index 0000000..b5e1b45 --- /dev/null +++ b/web/public/products/ww-025.svg @@ -0,0 +1 @@ + diff --git a/web/src/lib/img.ts b/web/src/lib/img.ts index e934a79..e896ce8 100644 --- a/web/src/lib/img.ts +++ b/web/src/lib/img.ts @@ -3,12 +3,15 @@ // SKU (web/public/products) so the grid needs no external image service and // renders offline. Real product images set on the widget (admin) take // precedence; SKUs without bespoke art share a generic widget illustration. -const ILLUSTRATED_SKUS = new Set(['ww-001', 'ww-002', 'ww-003', 'ww-004', 'ww-005']) +const ILLUSTRATED = /^ww-(\d{3})$/ +const ILLUSTRATED_COUNT = 25 export function productImage(w: { sku: string; imageUrl?: string | null }): string { if (w.imageUrl && w.imageUrl.trim()) return w.imageUrl const sku = (w.sku || '').toLowerCase() - return ILLUSTRATED_SKUS.has(sku) ? `/products/${sku}.svg` : '/products/widget.svg' + const match = ILLUSTRATED.exec(sku) + const n = match ? Number(match[1]) : 0 + return n >= 1 && n <= ILLUSTRATED_COUNT ? `/products/${sku}.svg` : '/products/widget.svg' } // Deterministic, cosmetic star rating derived from the SKU. Placeholder visuals