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>
279 lines
10 KiB
Vue
279 lines
10 KiB
Vue
<script lang="ts" setup>
|
|
import { Head, usePage } from '@inertiajs/vue3'
|
|
import { computed } from 'vue'
|
|
import MarketingLayout from '@/Layouts/MarketingLayout.vue'
|
|
import SectionHeader from '@/Components/Marketing/SectionHeader.vue'
|
|
import HeroSection from '@/Components/Marketing/HeroSection.vue'
|
|
import VpsHero from '@/Components/Marketing/VpsHero.vue'
|
|
import EstimatorSection from '@/Components/Marketing/Estimator/EstimatorSection.vue'
|
|
import { useEstimatorStore, type EstimatorPlan, type EstimatorAddOnGroup, type WorkloadEntry, type AppExample } from '@/stores/estimator'
|
|
import { crossDomainUrl } from '@/utils/resolvers'
|
|
|
|
defineOptions({ layout: MarketingLayout })
|
|
|
|
interface PageProps {
|
|
plans: EstimatorPlan[]
|
|
addOns: EstimatorAddOnGroup[]
|
|
workloadMap: Record<string, WorkloadEntry>
|
|
appExamples: AppExample[]
|
|
domains: { marketing: string; account: string; admin: string }
|
|
}
|
|
|
|
interface Feature {
|
|
icon: string
|
|
title: string
|
|
description: string
|
|
}
|
|
|
|
interface IncludedItem {
|
|
text: string
|
|
comingSoon?: boolean
|
|
}
|
|
|
|
const page = usePage()
|
|
const props = computed(() => page.props as unknown as PageProps)
|
|
const accountUrl = computed<string>(() => crossDomainUrl(props.value.domains?.account))
|
|
const plans = computed<EstimatorPlan[]>(() => props.value.plans || [])
|
|
const addOns = computed<EstimatorAddOnGroup[]>(() => props.value.addOns || [])
|
|
const workloadMap = computed<Record<string, WorkloadEntry>>(() => props.value.workloadMap || {})
|
|
const appExamples = computed<AppExample[]>(() => props.value.appExamples || [])
|
|
|
|
const estimator = useEstimatorStore()
|
|
|
|
const startingPrice = computed<string>(() => {
|
|
if (plans.value.length === 0) return '5.00'
|
|
const lowest = Math.min(...plans.value.map(p => parseFloat(p.price)))
|
|
return lowest % 1 === 0 ? lowest.toString() : lowest.toFixed(2)
|
|
})
|
|
|
|
const features: Feature[] = [
|
|
{ icon: 'tabler-database', title: 'RAID 10 SSD Storage', description: 'Redundant SSD arrays for fast read/write speeds and data protection.' },
|
|
{ icon: 'tabler-server', title: 'KVM Virtualization', description: 'Full hardware virtualization for predictable, dedicated performance.' },
|
|
{ icon: 'tabler-rocket', title: 'Near-Instant Provisioning', description: 'Your VPS is deployed seconds after ordering.' },
|
|
{ icon: 'tabler-refresh', title: 'Free ZFS Snapshots', description: 'Built-in snapshots for quick rollbacks. Off-site backup add-ons available.' },
|
|
{ icon: 'tabler-terminal', title: 'Full Root Access', description: 'Complete control over your server environment.' },
|
|
{ icon: 'tabler-server', title: 'VirtFusion Panel', description: 'Out-of-band console + VNC access included.' },
|
|
]
|
|
|
|
const includedFeatures: IncludedItem[] = [
|
|
{ text: '1 free IPv4 + 1 /64 IPv6 block' },
|
|
{ text: 'IPv4 rDNS / PTR control' },
|
|
{ text: '10 Gbps shared uplink (fair-use per AUP)' },
|
|
{ text: 'RAID 10 SSD storage' },
|
|
{ text: 'ZFS storage snapshots (free)' },
|
|
{ text: 'KVM virtualization' },
|
|
{ text: 'Full root access' },
|
|
{ text: 'Linux included; Windows supported (bring your own license)' },
|
|
{ text: 'VirtFusion control panel' },
|
|
{ text: 'Out-of-band console / VNC access' },
|
|
{ text: 'Near-instant provisioning' },
|
|
{ text: '99.9% uptime SLA' },
|
|
{ text: '14-day money-back guarantee' },
|
|
{ text: 'DDoS protection', comingSoon: true },
|
|
]
|
|
|
|
// Keys from features JSON that should not be shown as table columns
|
|
const internalKeys = new Set([
|
|
'control_panel',
|
|
'os',
|
|
'ipv4',
|
|
'ipv6',
|
|
'tier',
|
|
])
|
|
|
|
function getFeature(plan: EstimatorPlan, key: string): string {
|
|
return String(plan.features?.[key] ?? '-')
|
|
}
|
|
|
|
function formatPrice(plan: EstimatorPlan): string {
|
|
const price = parseFloat(plan.price) || 0
|
|
return price % 1 === 0 ? `$${price}` : `$${price.toFixed(2)}`
|
|
}
|
|
|
|
function prefillEstimator(planId: number): void {
|
|
estimator.setPlan(planId)
|
|
if (typeof window !== 'undefined') {
|
|
const url = estimator.shareUrl
|
|
window.history.replaceState({}, '', url)
|
|
// Smooth-scroll up to the estimator
|
|
const el = document.querySelector('.estimator-section')
|
|
if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<Head title="VPS Hosting | EZSCALE" />
|
|
<div>
|
|
<!-- Hero -->
|
|
<HeroSection>
|
|
<template #content>
|
|
<VChip color="primary" variant="tonal" class="mb-4">VPS Hosting</VChip>
|
|
<h1 class="text-h3 font-weight-bold mb-3">
|
|
Virtual Private <span class="hero-gradient-text">Servers</span>
|
|
</h1>
|
|
<p class="text-h6 text-medium-emphasis font-weight-regular mb-4" style="max-width: 600px;">
|
|
High-performance VPS hosting with RAID 10 SSD storage, dedicated resources, and full root access from our Atlanta, GA datacenter.
|
|
</p>
|
|
<p class="text-body-1 text-medium-emphasis mb-8">
|
|
Starting at just <span class="text-primary font-weight-bold">${{ startingPrice }}/mo</span>
|
|
</p>
|
|
<a :href="plans.length ? accountUrl + '/checkout/' + plans[0].id : '/pricing'" class="text-decoration-none">
|
|
<VBtn color="primary" size="large" rounded="lg">
|
|
Get Started
|
|
<VIcon icon="tabler-arrow-right" end />
|
|
</VBtn>
|
|
</a>
|
|
</template>
|
|
|
|
<template #visual>
|
|
<VpsHero />
|
|
</template>
|
|
</HeroSection>
|
|
|
|
<!-- Estimator -->
|
|
<EstimatorSection
|
|
:plans="plans"
|
|
:add-ons="addOns"
|
|
:workload-map="workloadMap"
|
|
:app-examples="appExamples"
|
|
:account-url="accountUrl"
|
|
/>
|
|
|
|
<!-- Features -->
|
|
<VContainer class="marketing-section">
|
|
<SectionHeader
|
|
label="Features"
|
|
title="Why Choose EZSCALE VPS?"
|
|
highlight-word="EZSCALE"
|
|
subtitle="Enterprise-grade infrastructure with the simplicity you deserve."
|
|
/>
|
|
<VRow>
|
|
<VCol v-for="(feature, index) in features" :key="feature.title" cols="12" sm="6" md="4">
|
|
<div :class="['d-flex ga-3 mb-4 feature-card-hover pa-3 rounded-lg', `fade-in-up stagger-${index + 1}`]">
|
|
<VAvatar color="primary" variant="tonal" size="44">
|
|
<VIcon :icon="feature.icon" size="22" />
|
|
</VAvatar>
|
|
<div>
|
|
<h3 class="text-subtitle-1 font-weight-bold">{{ feature.title }}</h3>
|
|
<p class="text-body-2 text-medium-emphasis mb-0">{{ feature.description }}</p>
|
|
</div>
|
|
</div>
|
|
</VCol>
|
|
</VRow>
|
|
</VContainer>
|
|
|
|
<!-- Plans Table -->
|
|
<div class="section-alt-bg marketing-section">
|
|
<VContainer>
|
|
<SectionHeader
|
|
label="Pricing"
|
|
title="VPS Plans"
|
|
highlight-word="Plans"
|
|
subtitle="All plans hosted in our Atlanta, GA datacenter. DDoS protection, full root access, and VirtFusion panel included."
|
|
/>
|
|
|
|
<VCard class="feature-card-hover">
|
|
<div style="overflow-x: auto; -webkit-overflow-scrolling: touch;">
|
|
<VTable>
|
|
<thead>
|
|
<tr>
|
|
<th>Plan</th>
|
|
<th>CPU</th>
|
|
<th>RAM</th>
|
|
<th>Storage</th>
|
|
<th>Bandwidth</th>
|
|
<th>Price</th>
|
|
<th />
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="plan in plans" :key="plan.id">
|
|
<td class="font-weight-bold">{{ plan.name }}</td>
|
|
<td>{{ getFeature(plan, 'cpu') }}</td>
|
|
<td>{{ getFeature(plan, 'ram') }}</td>
|
|
<td>{{ getFeature(plan, 'storage') }}</td>
|
|
<td>{{ getFeature(plan, 'bandwidth') }}</td>
|
|
<td class="text-primary font-weight-bold">{{ formatPrice(plan) }}/mo</td>
|
|
<td>
|
|
<div class="d-flex align-center ga-2">
|
|
<a :href="accountUrl + '/checkout/' + plan.id" class="text-decoration-none">
|
|
<VBtn color="primary" size="small" variant="tonal">Order Now</VBtn>
|
|
</a>
|
|
<VBtn
|
|
size="small"
|
|
variant="text"
|
|
color="primary"
|
|
class="text-caption"
|
|
@click="prefillEstimator(plan.id)"
|
|
>
|
|
Estimate →
|
|
</VBtn>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</VTable>
|
|
</div>
|
|
</VCard>
|
|
|
|
<!-- Included with all plans -->
|
|
<VCard class="mt-8 pa-6 feature-card-hover">
|
|
<h3 class="text-h5 font-weight-bold mb-4 text-center">Included With All Plans</h3>
|
|
<VRow>
|
|
<VCol
|
|
v-for="item in includedFeatures"
|
|
:key="item.text"
|
|
cols="12"
|
|
sm="6"
|
|
md="4"
|
|
>
|
|
<div class="d-flex align-center ga-2 mb-2">
|
|
<VIcon
|
|
:icon="item.comingSoon ? 'tabler-clock' : 'tabler-circle-check'"
|
|
:color="item.comingSoon ? 'warning' : 'success'"
|
|
size="20"
|
|
/>
|
|
<span class="text-body-1">{{ item.text }}</span>
|
|
<VChip
|
|
v-if="item.comingSoon"
|
|
size="x-small"
|
|
color="warning"
|
|
variant="tonal"
|
|
density="compact"
|
|
class="ms-1"
|
|
>
|
|
Coming soon
|
|
</VChip>
|
|
</div>
|
|
</VCol>
|
|
</VRow>
|
|
</VCard>
|
|
</VContainer>
|
|
</div>
|
|
|
|
<!-- CTA -->
|
|
<div class="marketing-section" style="background: linear-gradient(135deg, rgb(var(--v-theme-primary), 0.12), rgb(var(--v-theme-surface)));">
|
|
<VContainer class="text-center">
|
|
<h2 class="text-h4 font-weight-bold mb-3">Ready to Get Started?</h2>
|
|
<p class="text-body-1 text-medium-emphasis mb-6">
|
|
Deploy your VPS in seconds with full root access, DDoS protection, and expert support.
|
|
</p>
|
|
<div class="d-flex ga-3 justify-center flex-wrap">
|
|
<a :href="plans.length ? accountUrl + '/checkout/' + plans[0].id : '/pricing'" class="text-decoration-none">
|
|
<VBtn color="primary" size="large" rounded="lg">
|
|
Get Started
|
|
<VIcon icon="tabler-arrow-right" end />
|
|
</VBtn>
|
|
</a>
|
|
<a href="/contact" class="text-decoration-none">
|
|
<VBtn color="primary" variant="outlined" size="large" rounded="lg">
|
|
Contact Sales
|
|
</VBtn>
|
|
</a>
|
|
</div>
|
|
</VContainer>
|
|
</div>
|
|
</div>
|
|
</template>
|