fix(vps): drop Windows License toggle from estimator

Windows BYOL is free and doesn't change the price, so the toggle
was UI clutter. The Included With All Plans card already
communicates the BYOL requirement (line updated to read
"Linux included; Windows supported (bring your own license)" for
clarity), and OS template selection at checkout still lets users
pick a Windows image when they're ready to deploy.

- AddOnsPanel.vue: removed the Windows License switch
- EstimatorSection.vue: removed onWindowsChange handler
- stores/estimator.ts: removed windowsLicense state, dropped from
  share/checkout URL params and hydration
- ConfigOptionSeeder.php: dropped the legacy 'Windows License'
  option from the VPS Add-ons group (deletes existing row)
- CheckoutController.php: removed ?windows=1 query-param handling
  from buildPrefilledSelections
- VpsHosting.vue: tightened the BYOL line in the Included card

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-26 16:28:03 -04:00
parent bc5ccf1731
commit 66a65263c3
6 changed files with 8 additions and 58 deletions

View File

@@ -98,7 +98,6 @@ class CheckoutController extends Controller
*
* Recognized params:
* ?ipv4=N total IPv4 count (1-8). N>1 sets the IPv4 quantity option to (N - 1) extras.
* ?windows=1 toggles the Windows License checkbox.
* ?managed=X value slug for the VPS Managed Support radio (self|basic|pro|pilot).
* ?backup=X value slug for the Off-site Backup radio (none|lite|standard|extended|vault).
*
@@ -141,22 +140,6 @@ class CheckoutController extends Controller
}
}
// Windows License — single-value checkbox
if ($request->boolean('windows')) {
$option = $findOption('VPS Add-ons', 'Windows License');
if ($option && $option->values->isNotEmpty()) {
$value = $option->values->first();
$selections[] = [
'option_id' => $option->id,
'value_id' => $value->id,
'quantity' => null,
'text_value' => null,
'locked_price' => (float) ($value->monthly_price ?? 0),
'locked_hourly_price' => $value->hourly_price !== null ? (float) $value->hourly_price : null,
];
}
}
// Managed Support — radio
$managed = $request->query('managed');
if ($managed && $managed !== 'self') {

View File

@@ -165,11 +165,12 @@ class ConfigOptionSeeder extends Seeder
// IPv4 Addresses (quantity)
$ipv4Option = $this->seedQuantityOption($vpsAddons, 'IPv4 Addresses', 1, 8, 'addresses', 8.00, 1);
// Windows License (checkbox)
$winOption = $this->seedCheckboxOption($vpsAddons, 'Windows License', 2);
$this->seedValues($winOption, [
['label' => 'Yes (Free BYOL)', 'value' => 'yes', 'monthly' => 0, 'is_default' => false],
]);
// Drop the legacy Windows License option if it exists (BYOL is now communicated
// via marketing copy + OS template selection at checkout).
PlanConfigOption::query()
->where('group_id', $vpsAddons->id)
->where('name', 'Windows License')
->delete();
// Attach to all active VPS plans
$vpsSlugs = ['vps-1', 'vps-2', 'vps-3-custom', 'vps-4', 'vps-8', 'vps-16', 'vps-32', 'stor-500', 'stor-1tb'];

View File

@@ -7,7 +7,6 @@ import BackupTierSelector from './BackupTierSelector.vue'
interface Props {
ipv4Count: number
windowsLicense: boolean
managedTier: ManagedTier
backupTier: BackupTier
pilotAvailable: boolean
@@ -19,7 +18,6 @@ defineProps<Props>()
const emit = defineEmits<{
'update:ipv4Count': [value: number]
'update:windowsLicense': [value: boolean]
'update:managedTier': [value: ManagedTier]
'update:backupTier': [value: BackupTier]
'request-upgrade': []
@@ -44,7 +42,7 @@ const expanded = ref<boolean>(false)
<span class="text-subtitle-1 font-weight-bold">Customize add-ons</span>
<VSpacer />
<span class="text-caption text-medium-emphasis me-3 d-none d-sm-inline">
IPv4, Windows BYOL, Managed support, Backup
IPv4, Managed support, Backup
</span>
<VIcon :icon="expanded ? 'tabler-chevron-up' : 'tabler-chevron-down'" />
</button>
@@ -61,25 +59,6 @@ const expanded = ref<boolean>(false)
<VDivider class="my-5" />
<div class="d-flex align-center justify-space-between flex-wrap ga-3">
<div>
<div class="text-subtitle-2 font-weight-bold">Windows License</div>
<div class="text-caption text-medium-emphasis">
Bring your own license. Free.
</div>
</div>
<VSwitch
:model-value="windowsLicense"
color="primary"
density="compact"
hide-details
inset
@update:model-value="(v: boolean | null) => emit('update:windowsLicense', !!v)"
/>
</div>
<VDivider class="my-5" />
<ManagedSupportSelector
:model-value="managedTier"
:pilot-available="pilotAvailable"

View File

@@ -118,11 +118,6 @@ function onIpv4Change(value: number): void {
debouncePushUrl()
}
function onWindowsChange(value: boolean): void {
store.windowsLicense = value
debouncePushUrl()
}
function onManagedChange(value: 'self' | 'basic' | 'pro' | 'pilot'): void {
store.setManagedTier(value)
debouncePushUrl()
@@ -200,14 +195,12 @@ watch(
<div class="mt-5">
<AddOnsPanel
:ipv4-count="store.ipv4Count"
:windows-license="store.windowsLicense"
:managed-tier="store.managedTier"
:backup-tier="store.backupTier"
:pilot-available="store.pilotAvailable"
:cycle-suffix="cycleSuffix"
:monthly-multiplier="monthlyMultiplier"
@update:ipv4-count="onIpv4Change"
@update:windows-license="onWindowsChange"
@update:managed-tier="onManagedChange"
@update:backup-tier="onBackupChange"
@request-upgrade="onRequestUpgrade"

View File

@@ -63,7 +63,7 @@ const includedFeatures: IncludedItem[] = [
{ text: 'ZFS storage snapshots (free)' },
{ text: 'KVM virtualization' },
{ text: 'Full root access' },
{ text: 'Linux & Windows (BYOL) support' },
{ text: 'Linux included; Windows supported (bring your own license)' },
{ text: 'VirtFusion control panel' },
{ text: 'Out-of-band console / VNC access' },
{ text: 'Near-instant provisioning' },

View File

@@ -76,7 +76,6 @@ export const useEstimatorStore = defineStore('estimator', () => {
const workload = ref<string | null>(null)
const planId = ref<number | null>(null)
const ipv4Count = ref<number>(1)
const windowsLicense = ref<boolean>(false)
const managedTier = ref<ManagedTier>('self')
const backupTier = ref<BackupTier>('none')
const cycle = ref<EstimatorCycle>('monthly')
@@ -212,7 +211,6 @@ export const useEstimatorStore = defineStore('estimator', () => {
if (workload.value) params.set('w', workload.value)
if (planId.value !== null) params.set('plan', String(planId.value))
if (ipv4Count.value > 1) params.set('ipv4', String(ipv4Count.value))
if (windowsLicense.value) params.set('windows', '1')
if (managedTier.value !== 'self') params.set('managed', managedTier.value)
if (backupTier.value !== 'none') params.set('backup', backupTier.value)
if (cycle.value !== 'monthly') params.set('cycle', cycle.value)
@@ -225,7 +223,6 @@ export const useEstimatorStore = defineStore('estimator', () => {
if (planId.value === null) return null
const params = new URLSearchParams()
if (ipv4Count.value > 1) params.set('ipv4', String(ipv4Count.value))
if (windowsLicense.value) params.set('windows', '1')
if (managedTier.value !== 'self') params.set('managed', managedTier.value)
if (backupTier.value !== 'none') params.set('backup', backupTier.value)
if (cycle.value !== 'monthly') params.set('cycle', cycle.value)
@@ -252,8 +249,6 @@ export const useEstimatorStore = defineStore('estimator', () => {
if (!Number.isNaN(n) && n >= 1 && n <= 8) ipv4Count.value = n
}
if (p.get('windows') === '1' || p.get('windows') === 'true') windowsLicense.value = true
const m = p.get('managed')
if (m && ['self', 'basic', 'pro', 'pilot'].includes(m)) managedTier.value = m as ManagedTier
@@ -277,7 +272,6 @@ export const useEstimatorStore = defineStore('estimator', () => {
workload,
planId,
ipv4Count,
windowsLicense,
managedTier,
backupTier,
cycle,