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: * Recognized params:
* ?ipv4=N total IPv4 count (1-8). N>1 sets the IPv4 quantity option to (N - 1) extras. * ?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). * ?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). * ?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 Support — radio
$managed = $request->query('managed'); $managed = $request->query('managed');
if ($managed && $managed !== 'self') { if ($managed && $managed !== 'self') {

View File

@@ -165,11 +165,12 @@ class ConfigOptionSeeder extends Seeder
// IPv4 Addresses (quantity) // IPv4 Addresses (quantity)
$ipv4Option = $this->seedQuantityOption($vpsAddons, 'IPv4 Addresses', 1, 8, 'addresses', 8.00, 1); $ipv4Option = $this->seedQuantityOption($vpsAddons, 'IPv4 Addresses', 1, 8, 'addresses', 8.00, 1);
// Windows License (checkbox) // Drop the legacy Windows License option if it exists (BYOL is now communicated
$winOption = $this->seedCheckboxOption($vpsAddons, 'Windows License', 2); // via marketing copy + OS template selection at checkout).
$this->seedValues($winOption, [ PlanConfigOption::query()
['label' => 'Yes (Free BYOL)', 'value' => 'yes', 'monthly' => 0, 'is_default' => false], ->where('group_id', $vpsAddons->id)
]); ->where('name', 'Windows License')
->delete();
// Attach to all active VPS plans // 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']; $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 { interface Props {
ipv4Count: number ipv4Count: number
windowsLicense: boolean
managedTier: ManagedTier managedTier: ManagedTier
backupTier: BackupTier backupTier: BackupTier
pilotAvailable: boolean pilotAvailable: boolean
@@ -19,7 +18,6 @@ defineProps<Props>()
const emit = defineEmits<{ const emit = defineEmits<{
'update:ipv4Count': [value: number] 'update:ipv4Count': [value: number]
'update:windowsLicense': [value: boolean]
'update:managedTier': [value: ManagedTier] 'update:managedTier': [value: ManagedTier]
'update:backupTier': [value: BackupTier] 'update:backupTier': [value: BackupTier]
'request-upgrade': [] 'request-upgrade': []
@@ -44,7 +42,7 @@ const expanded = ref<boolean>(false)
<span class="text-subtitle-1 font-weight-bold">Customize add-ons</span> <span class="text-subtitle-1 font-weight-bold">Customize add-ons</span>
<VSpacer /> <VSpacer />
<span class="text-caption text-medium-emphasis me-3 d-none d-sm-inline"> <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> </span>
<VIcon :icon="expanded ? 'tabler-chevron-up' : 'tabler-chevron-down'" /> <VIcon :icon="expanded ? 'tabler-chevron-up' : 'tabler-chevron-down'" />
</button> </button>
@@ -61,25 +59,6 @@ const expanded = ref<boolean>(false)
<VDivider class="my-5" /> <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 <ManagedSupportSelector
:model-value="managedTier" :model-value="managedTier"
:pilot-available="pilotAvailable" :pilot-available="pilotAvailable"

View File

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

View File

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

View File

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