From 2658576c5b3f995f5a6802e6a152542ee91d9f74355dccbeb5a29baeb2a41fc4 Mon Sep 17 00:00:00 2001 From: Andrew Date: Sun, 26 Apr 2026 18:36:57 -0400 Subject: [PATCH] feat(dedicated): filter drive bay options to chassis-compatible combos MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per-chassis page hides drive combos that require more bays than the chassis has. R440 (4-bay) no longer shows 8× / 12× options; R640 (8-bay SFF) no longer shows 16× / 24×; R640 NVMe (10-bay) hides 16× NVMe. Implementation: - routes/marketing.php /dedicated-servers/{slug}: post-load filter on configGroups. For any group whose name contains "Drive Bays", parses the leading bay-count digit from each value's slug (e.g. "8x8tb-hdd" → 8) and drops values where bay_count > chassis features.bay_count. Non-quantity values (e.g. "none") always pass through. - ConfigOptionSeeder: dropped the now-redundant "(R740xd LFF only)"/"(R740 16-bay only)" parentheticals from the labels — the filter handles compatibility, the labels stay clean. New test asserts: - R440 (4 bays) drops 8× and 12× LFF combos - R740xd LFF (12 bays) keeps all combos - R640 NVMe (10 bays) drops 16× NVMe combo 13/13 dedicated tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../database/seeders/ConfigOptionSeeder.php | 12 ++++----- website/routes/marketing.php | 25 +++++++++++++++++++ .../Marketing/DedicatedServersTest.php | 25 +++++++++++++++++++ 3 files changed, 56 insertions(+), 6 deletions(-) diff --git a/website/database/seeders/ConfigOptionSeeder.php b/website/database/seeders/ConfigOptionSeeder.php index 880f402..cea8af1 100644 --- a/website/database/seeders/ConfigOptionSeeder.php +++ b/website/database/seeders/ConfigOptionSeeder.php @@ -518,8 +518,8 @@ class ConfigOptionSeeder extends Seeder ['label' => '4× 4 TB SATA HDD', 'value' => '4x4tb-hdd', 'monthly' => 48.00], ['label' => '2× 8 TB SATA HDD', 'value' => '2x8tb-hdd', 'monthly' => 40.00], ['label' => '4× 8 TB SATA HDD', 'value' => '4x8tb-hdd', 'monthly' => 80.00], - ['label' => '8× 8 TB SATA HDD (R540 / R740xd LFF only)', 'value' => '8x8tb-hdd', 'monthly' => 160.00], - ['label' => '12× 8 TB SATA HDD (R740xd LFF only)', 'value' => '12x8tb-hdd', 'monthly' => 240.00], + ['label' => '8× 8 TB SATA HDD', 'value' => '8x8tb-hdd', 'monthly' => 160.00], + ['label' => '12× 8 TB SATA HDD', 'value' => '12x8tb-hdd', 'monthly' => 240.00], ]); $gen14LffPlanSlugs = ['r440-4lff', 'r540-8lff', 'r740xd-12lff']; @@ -546,8 +546,8 @@ class ConfigOptionSeeder extends Seeder ['label' => '2× 1.92 TB SATA SSD', 'value' => '2x1920gb-ssd', 'monthly' => 36.00], ['label' => '4× 1.92 TB SATA SSD', 'value' => '4x1920gb-ssd', 'monthly' => 72.00], ['label' => '8× 1.92 TB SATA SSD', 'value' => '8x1920gb-ssd', 'monthly' => 144.00], - ['label' => '16× 1.92 TB SATA SSD (R740 16-bay / R740xd 24-SFF only)', 'value' => '16x1920gb-ssd', 'monthly' => 288.00], - ['label' => '24× 1.92 TB SATA SSD (R740xd 24-SFF only)', 'value' => '24x1920gb-ssd', 'monthly' => 432.00], + ['label' => '16× 1.92 TB SATA SSD', 'value' => '16x1920gb-ssd', 'monthly' => 288.00], + ['label' => '24× 1.92 TB SATA SSD', 'value' => '24x1920gb-ssd', 'monthly' => 432.00], ]); $gen14SffPlanSlugs = ['r640-8sff', 'r740-16sff', 'r740xd-24sff']; @@ -573,8 +573,8 @@ class ConfigOptionSeeder extends Seeder ['label' => '4× 1 TB U.2 NVMe', 'value' => '4x1tb-nvme', 'monthly' => 88.00], ['label' => '2× 2 TB U.2 NVMe', 'value' => '2x2tb-nvme', 'monthly' => 96.00], ['label' => '4× 2 TB U.2 NVMe', 'value' => '4x2tb-nvme', 'monthly' => 192.00], - ['label' => '8× 2 TB U.2 NVMe (R640 NVMe 10-bay / R740xd NVMe 24-bay)', 'value' => '8x2tb-nvme', 'monthly' => 384.00], - ['label' => '16× 2 TB U.2 NVMe (R740xd NVMe 24-bay only)', 'value' => '16x2tb-nvme', 'monthly' => 768.00], + ['label' => '8× 2 TB U.2 NVMe', 'value' => '8x2tb-nvme', 'monthly' => 384.00], + ['label' => '16× 2 TB U.2 NVMe', 'value' => '16x2tb-nvme', 'monthly' => 768.00], ]); $gen14NvmePlanSlugs = ['r640-10nvme', 'r740xd-24nvme']; diff --git a/website/routes/marketing.php b/website/routes/marketing.php index e020149..7b6c214 100644 --- a/website/routes/marketing.php +++ b/website/routes/marketing.php @@ -114,6 +114,31 @@ Route::get('/dedicated-servers/{slug}', function (string $slug) { ->orderBy('sort_order') ->get(); + // Drive-bay combos are seeded with value slugs that start with the + // bay count (e.g. "8x8tb-hdd" → 8 bays). Filter out combos that + // don't fit this chassis so customers never see options that can't + // physically deploy on their build. + $bayCount = (int) ($plan->features['bay_count'] ?? 0); + if ($bayCount > 0) { + foreach ($configGroups as $group) { + if (! str_contains($group->name, 'Drive Bays')) { + continue; + } + foreach ($group->options as $option) { + $filtered = $option->values->filter(function ($value) use ($bayCount): bool { + if (preg_match('/^(\d+)x/', $value->value, $m)) { + return (int) $m[1] <= $bayCount; + } + + // 'none' (and any other non-quantity value) always passes. + return true; + })->values(); + + $option->setRelation('values', $filtered); + } + } + } + return Inertia::render('Marketing/DedicatedServerDetail', [ 'plan' => $plan, 'configGroups' => $configGroups, diff --git a/website/tests/Feature/Marketing/DedicatedServersTest.php b/website/tests/Feature/Marketing/DedicatedServersTest.php index 96d3139..2950b29 100644 --- a/website/tests/Feature/Marketing/DedicatedServersTest.php +++ b/website/tests/Feature/Marketing/DedicatedServersTest.php @@ -133,6 +133,31 @@ test('seeder creates the 9 dedicated 14th-gen config groups', function (): void expect($names)->toContain('Dedicated 14th Gen — NVMe Drive Bays'); }); +test('drive bay options are filtered to those that fit chassis bay count', function (): void { + // R440 has 4 LFF bays — combos requiring more bays should be hidden. + $r440Response = $this->get('/dedicated-servers/r440-4lff'); + $r440Groups = collect($r440Response->viewData('page')['props']['configGroups']); + $lff = $r440Groups->firstWhere('name', 'Dedicated 14th Gen — LFF Drive Bays'); + $values = collect($lff['options'][0]['values'])->pluck('value')->all(); + expect($values)->toContain('none', '2x4tb-hdd', '4x4tb-hdd', '2x8tb-hdd', '4x8tb-hdd'); + expect($values)->not->toContain('8x8tb-hdd', '12x8tb-hdd'); // 8 and 12 bays don't fit in 4 + + // R740xd LFF has 12 bays — should see all combos. + $r740xdResponse = $this->get('/dedicated-servers/r740xd-12lff'); + $r740xdGroups = collect($r740xdResponse->viewData('page')['props']['configGroups']); + $lff740 = $r740xdGroups->firstWhere('name', 'Dedicated 14th Gen — LFF Drive Bays'); + $values740 = collect($lff740['options'][0]['values'])->pluck('value')->all(); + expect($values740)->toContain('8x8tb-hdd', '12x8tb-hdd'); + + // R640 NVMe has 10 bays — 16× shouldn't fit but 8× should. + $r640Response = $this->get('/dedicated-servers/r640-10nvme'); + $r640Groups = collect($r640Response->viewData('page')['props']['configGroups']); + $nvme = $r640Groups->firstWhere('name', 'Dedicated 14th Gen — NVMe Drive Bays'); + $valuesNvme = collect($nvme['options'][0]['values'])->pluck('value')->all(); + expect($valuesNvme)->toContain('8x2tb-nvme'); + expect($valuesNvme)->not->toContain('16x2tb-nvme'); // 16 bays don't fit in 10 +}); + test('drive bay groups are attached to chassis by bay type', function (): void { // LFF chassis should have LFF Drive Bays attached $r440 = Plan::where('slug', 'r440-4lff')->first();