From cfa2e4c8d3de8c310a9d4ea4636a51d973d9c791911bec7a9045b5211a12e319 Mon Sep 17 00:00:00 2001 From: Andrew Date: Sun, 26 Apr 2026 16:05:01 -0400 Subject: [PATCH] feat(vps): add interactive estimator + refresh Included card MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements the design captured in docs/superpowers/specs/2026-04-26-vps-hosting-estimator-design.md. Estimator (after the hero on /vps-hosting): - Workload picker (9 chips) recommends a plan; "Not sure" opens a mini-quiz with a 12-app catalog and a traffic+priority follow-up. - Recommended-plan card with "or pick another" alternates dropdown. - Add-ons panel: IPv4 stepper (1-8, $8/extra), Windows BYOL toggle, 4-tier Managed Support radio (Self/Basic/Pro/Pilot @ $0/29/79/99), 5-tier Off-site Backup radio (None/Lite/Standard/Extended/Vault @ $0/5/12/25/59). - Pilot tier gated to VPS-8+ via plan.features.tier; auto-fallback to Pro on plan downgrade with snackbar warning. - Billing cycle toggle (Monthly / Quarterly / Annual) reuses per-cycle prices already on plan_prices and plan_config_values. - Sticky footer with live total, "Order this configuration" (deep-links to /checkout/{plan} with all params), and "Copy share link" (history.replaceState debounced 300ms). - Plans-table rows get an "Estimate →" link that pre-fills the estimator with that plan and scrolls up. Backend: - PlanSeeder: each VPS plan gets features.tier (1-32) for gating. - ConfigOptionSeeder: scope existing Server Management group to dedicated only; add VPS Managed Support and Off-site Backup groups with full per-cycle prices. - routes/marketing.php /vps-hosting: pass addOns + workloadMap + appExamples Inertia props. - CheckoutController::show: build prefilledSelections from ?ipv4&windows&managed&backup query params; Vue page hydrates configSelections from this prop. Included With All Plans: rewritten to 13 accurate items with per-line wording (10 Gbps fair-use uplink, ZFS snapshots free, KVM virtualization, rDNS/PTR control, OOB console/VNC, 99.9% SLA, etc.) plus a "Coming soon" badge for DDoS protection. Tests: 10 Pest feature tests in tests/Feature/Marketing/ VpsHostingEstimatorTest.php cover the page props, both new seeded groups, plan-tier metadata, Server Management dedicated scope, configGroups attachment, and checkout query-param pre-fill round-trip. All 10 pass. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../Account/CheckoutController.php | 108 +++++++ .../Http/Middleware/HandleInertiaRequests.php | 1 + website/app/Providers/AppServiceProvider.php | 2 +- website/config/app.php | 18 ++ .../database/seeders/ConfigOptionSeeder.php | 66 +++- website/database/seeders/PlanSeeder.php | 9 + .../resources/ts/Components/AppTopNavbar.vue | 4 - .../Marketing/Estimator/AddOnsPanel.vue | 135 ++++++++ .../Estimator/BackupTierSelector.vue | 106 ++++++ .../Estimator/BillingCycleToggle.vue | 90 ++++++ .../Marketing/Estimator/EstimatorFooter.vue | 115 +++++++ .../Marketing/Estimator/EstimatorSection.vue | 251 +++++++++++++++ .../Marketing/Estimator/IPv4Stepper.vue | 98 ++++++ .../Estimator/ManagedSupportSelector.vue | 157 +++++++++ .../Marketing/Estimator/MiniQuizDialog.vue | 214 ++++++++++++ .../Estimator/RecommendedPlanCard.vue | 149 +++++++++ .../Marketing/Estimator/WorkloadPicker.vue | 92 ++++++ .../resources/ts/Layouts/AccountLayout.vue | 3 +- website/resources/ts/Layouts/AdminLayout.vue | 3 +- website/resources/ts/Layouts/AuthLayout.vue | 3 +- .../resources/ts/Layouts/MarketingLayout.vue | 6 +- website/resources/ts/Pages/Cart/Index.vue | 7 +- website/resources/ts/Pages/Checkout/Show.vue | 25 +- .../ts/Pages/Marketing/AcceptableUse.vue | 2 +- .../ts/Pages/Marketing/DedicatedServers.vue | 3 +- website/resources/ts/Pages/Marketing/Home.vue | 3 +- .../resources/ts/Pages/Marketing/Pricing.vue | 3 +- .../ts/Pages/Marketing/PrivacyPolicy.vue | 2 +- website/resources/ts/Pages/Marketing/Sla.vue | 2 +- .../ts/Pages/Marketing/TermsOfService.vue | 2 +- .../ts/Pages/Marketing/VpsHosting.vue | 128 ++++++-- .../ts/Pages/Marketing/WebHosting.vue | 3 +- website/resources/ts/Pages/Services/Show.vue | 6 +- website/resources/ts/plugins/vuetify/index.ts | 13 +- website/resources/ts/stores/estimator.ts | 304 ++++++++++++++++++ website/resources/ts/utils/resolvers.ts | 23 +- website/resources/views/app.blade.php | 2 +- website/routes/marketing.php | 41 +++ .../Marketing/VpsHostingEstimatorTest.php | 156 +++++++++ 39 files changed, 2258 insertions(+), 97 deletions(-) create mode 100644 website/resources/ts/Components/Marketing/Estimator/AddOnsPanel.vue create mode 100644 website/resources/ts/Components/Marketing/Estimator/BackupTierSelector.vue create mode 100644 website/resources/ts/Components/Marketing/Estimator/BillingCycleToggle.vue create mode 100644 website/resources/ts/Components/Marketing/Estimator/EstimatorFooter.vue create mode 100644 website/resources/ts/Components/Marketing/Estimator/EstimatorSection.vue create mode 100644 website/resources/ts/Components/Marketing/Estimator/IPv4Stepper.vue create mode 100644 website/resources/ts/Components/Marketing/Estimator/ManagedSupportSelector.vue create mode 100644 website/resources/ts/Components/Marketing/Estimator/MiniQuizDialog.vue create mode 100644 website/resources/ts/Components/Marketing/Estimator/RecommendedPlanCard.vue create mode 100644 website/resources/ts/Components/Marketing/Estimator/WorkloadPicker.vue create mode 100644 website/resources/ts/stores/estimator.ts create mode 100644 website/tests/Feature/Marketing/VpsHostingEstimatorTest.php diff --git a/website/app/Http/Controllers/Account/CheckoutController.php b/website/app/Http/Controllers/Account/CheckoutController.php index c4dd8d9..fc6a33f 100644 --- a/website/app/Http/Controllers/Account/CheckoutController.php +++ b/website/app/Http/Controllers/Account/CheckoutController.php @@ -79,6 +79,8 @@ class CheckoutController extends Controller ->orderBy('sort_order') ->get(); + $prefilledSelections = $this->buildPrefilledSelections($configGroups, request()); + return Inertia::render('Checkout/Show', [ 'plan' => $plan->load('prices'), 'configGroups' => $configGroups, @@ -87,9 +89,115 @@ class CheckoutController extends Controller 'stripeKey' => config('cashier.key'), 'osTemplates' => $osTemplates, 'osTemplateGroups' => $osTemplateGroups, + 'prefilledSelections' => $prefilledSelections, ]); } + /** + * Build pre-filled config selections from query params for estimator deep-links. + * + * 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). + * + * @param \Illuminate\Database\Eloquent\Collection $configGroups + * @return array + */ + private function buildPrefilledSelections($configGroups, Request $request): array + { + $selections = []; + + $findOption = function (string $groupName, string $optionName) use ($configGroups): ?PlanConfigOption { + foreach ($configGroups as $group) { + if ($group->name !== $groupName) { + continue; + } + foreach ($group->options as $option) { + if ($option->name === $optionName) { + return $option; + } + } + } + + return null; + }; + + // IPv4 — total count, first is included free, charge starts at extra #1 + $ipv4Total = (int) $request->query('ipv4', 1); + if ($ipv4Total > 1) { + $option = $findOption('VPS Add-ons', 'IPv4 Addresses'); + if ($option) { + $extras = max(0, $ipv4Total - 1); + $selections[] = [ + 'option_id' => $option->id, + 'value_id' => null, + 'quantity' => $extras, + 'text_value' => null, + 'locked_price' => (float) ($option->monthly_price ?? 0) * $extras, + 'locked_hourly_price' => $option->hourly_price !== null ? (float) $option->hourly_price * $extras : null, + ]; + } + } + + // 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') { + $option = $findOption('VPS Managed Support', 'Managed Support'); + if ($option) { + $value = $option->values->firstWhere('value', $managed); + if ($value) { + $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, + ]; + } + } + } + + // Off-site Backup — radio + $backup = $request->query('backup'); + if ($backup && $backup !== 'none') { + $option = $findOption('Off-site Backup', 'Backup Tier'); + if ($option) { + $value = $option->values->firstWhere('value', $backup); + if ($value) { + $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, + ]; + } + } + } + + return $selections; + } + public function showCustom(string $serviceType): Response { $plan = Plan::where('slug', "{$serviceType}-custom")->firstOrFail(); diff --git a/website/app/Http/Middleware/HandleInertiaRequests.php b/website/app/Http/Middleware/HandleInertiaRequests.php index fea525d..3f6febc 100644 --- a/website/app/Http/Middleware/HandleInertiaRequests.php +++ b/website/app/Http/Middleware/HandleInertiaRequests.php @@ -27,6 +27,7 @@ class HandleInertiaRequests extends Middleware 'account' => config('app.domains.account'), 'admin' => config('app.domains.admin'), ], + 'panels' => fn () => config('app.panels'), ]); } } diff --git a/website/app/Providers/AppServiceProvider.php b/website/app/Providers/AppServiceProvider.php index c738f9d..31e51ca 100644 --- a/website/app/Providers/AppServiceProvider.php +++ b/website/app/Providers/AppServiceProvider.php @@ -32,7 +32,7 @@ class AppServiceProvider extends ServiceProvider public function boot(): void { - if ($this->app->environment('production', 'local')) { + if ($this->app->environment('production')) { URL::forceScheme('https'); } diff --git a/website/config/app.php b/website/config/app.php index e4af86a..4332306 100644 --- a/website/config/app.php +++ b/website/config/app.php @@ -139,6 +139,24 @@ return [ 'admin' => env('DOMAIN_ADMIN', 'admin.ezscale.dev'), ], + /* + |-------------------------------------------------------------------------- + | Provisioning Panel URLs + |-------------------------------------------------------------------------- + | + | Base URLs for the customer-facing control panels of each provisioning + | platform. The frontend builds deep links by appending /server/{id}. + | Override per-environment via .env so dev/staging point at the right host. + | + */ + + 'panels' => [ + 'virtfusion' => env('PANEL_URL_VIRTFUSION', 'https://panel.ezscale.cloud'), + 'synergycp' => env('PANEL_URL_SYNERGYCP', 'https://dedicated.ezscale.cloud'), + 'enhance' => env('PANEL_URL_ENHANCE', 'https://hosting.ezscale.cloud'), + 'pterodactyl' => env('PANEL_URL_PTERODACTYL', 'https://game.ezscale.cloud'), + ], + /* |-------------------------------------------------------------------------- | Screenshot Authentication diff --git a/website/database/seeders/ConfigOptionSeeder.php b/website/database/seeders/ConfigOptionSeeder.php index 0f0dfa5..9fb42f2 100644 --- a/website/database/seeders/ConfigOptionSeeder.php +++ b/website/database/seeders/ConfigOptionSeeder.php @@ -70,13 +70,13 @@ class ConfigOptionSeeder extends Seeder private function seedPresetGroups(): void { - // ─── Server Management (all dedicated + VPS plans) ────────────── + // ─── Server Management (dedicated only) ───────────────────────── $serverMgmt = PlanConfigGroup::updateOrCreate( ['name' => 'Server Management'], [ 'description' => 'Add managed support to your server.', 'mode' => 'preset', - 'service_type' => null, + 'service_type' => 'dedicated', 'is_active' => true, 'sort_order' => 10, ], @@ -89,12 +89,66 @@ class ConfigOptionSeeder extends Seeder ['label' => 'Fully Managed', 'value' => 'fully_managed', 'monthly' => 60.00], ]); - // Attach to all active dedicated AND vps plans - $dedicatedAndVpsPlans = Plan::query() - ->whereIn('service_type', ['dedicated', 'vps']) + // Attach to dedicated plans only (VPS plans use the dedicated VPS Managed Support group below) + $dedicatedPlanIds = Plan::query() + ->where('service_type', 'dedicated') ->whereIn('status', ['active', 'internal']) ->pluck('id'); - $serverMgmt->plans()->syncWithoutDetaching($dedicatedAndVpsPlans); + $serverMgmt->plans()->sync($dedicatedPlanIds); + + // ─── VPS Managed Support ──────────────────────────────────────── + // 4-tier radio: Self ($0) / Basic ($29) / Pro ($79) / Pilot ($99, VPS-8+). + // Pilot tier gating (min plan tier 8) is enforced in the frontend. + $vpsManaged = PlanConfigGroup::updateOrCreate( + ['name' => 'VPS Managed Support'], + [ + 'description' => 'Choose how hands-on you want our team to be with your VPS.', + 'mode' => 'preset', + 'service_type' => 'vps', + 'is_active' => true, + 'sort_order' => 10, + ], + ); + + $vpsManagedOption = $this->seedRadioOption($vpsManaged, 'Managed Support', false, 1); + $this->seedValues($vpsManagedOption, [ + ['label' => 'Self-Managed', 'value' => 'self', 'monthly' => 0, 'is_default' => true], + ['label' => 'Managed Basic', 'value' => 'basic', 'monthly' => 29.00], + ['label' => 'Managed Pro', 'value' => 'pro', 'monthly' => 79.00], + ['label' => 'Pilot', 'value' => 'pilot', 'monthly' => 99.00], + ]); + + $vpsPlanIds = Plan::query() + ->where('service_type', 'vps') + ->whereIn('status', ['active', 'internal']) + ->pluck('id'); + $vpsManaged->plans()->syncWithoutDetaching($vpsPlanIds); + + // ─── Off-site Backup (VPS) ────────────────────────────────────── + // 5-tier radio: None / Lite (7d, 200GB, $5) / Standard (30d, 500GB, $12) + // / Extended (90d, 1TB, $25) / Vault (1yr, 2TB, $59). + // StorJ-backed, encrypted at rest. Restores included (no egress charge). + $vpsBackup = PlanConfigGroup::updateOrCreate( + ['name' => 'Off-site Backup'], + [ + 'description' => 'Encrypted off-site backups stored on StorJ. Daily snapshots, free restores.', + 'mode' => 'preset', + 'service_type' => 'vps', + 'is_active' => true, + 'sort_order' => 12, + ], + ); + + $vpsBackupOption = $this->seedRadioOption($vpsBackup, 'Backup Tier', false, 1); + $this->seedValues($vpsBackupOption, [ + ['label' => 'None', 'value' => 'none', 'monthly' => 0, 'is_default' => true], + ['label' => 'Lite (7-day, 200 GB)', 'value' => 'lite', 'monthly' => 5.00], + ['label' => 'Standard (30-day, 500 GB)', 'value' => 'standard', 'monthly' => 12.00], + ['label' => 'Extended (90-day, 1 TB)', 'value' => 'extended', 'monthly' => 25.00], + ['label' => 'Vault (1-year, 2 TB)', 'value' => 'vault', 'monthly' => 59.00], + ]); + + $vpsBackup->plans()->syncWithoutDetaching($vpsPlanIds); // ─── VPS Add-ons ──────────────────────────────────────────────── $vpsAddons = PlanConfigGroup::updateOrCreate( diff --git a/website/database/seeders/PlanSeeder.php b/website/database/seeders/PlanSeeder.php index 9c52f44..20566e0 100644 --- a/website/database/seeders/PlanSeeder.php +++ b/website/database/seeders/PlanSeeder.php @@ -47,6 +47,7 @@ class PlanSeeder extends Seeder 'price' => 5.00, 'billing_cycle' => 'monthly', 'features' => [ + 'tier' => 1, 'cpu' => '1 vCPU', 'ram' => '1 GB', 'storage' => '25 GB SSD', @@ -67,6 +68,7 @@ class PlanSeeder extends Seeder 'price' => 8.00, 'billing_cycle' => 'monthly', 'features' => [ + 'tier' => 2, 'cpu' => '1 vCPU', 'ram' => '2 GB', 'storage' => '50 GB SSD', @@ -87,6 +89,7 @@ class PlanSeeder extends Seeder 'price' => 0.00, 'billing_cycle' => 'monthly', 'features' => [ + 'tier' => 3, 'cpu' => '2 vCPU', 'ram' => '3 GB', 'storage' => '60 GB SSD', @@ -108,6 +111,7 @@ class PlanSeeder extends Seeder 'price' => 15.00, 'billing_cycle' => 'monthly', 'features' => [ + 'tier' => 4, 'cpu' => '2 vCPU', 'ram' => '4 GB', 'storage' => '80 GB SSD', @@ -128,6 +132,7 @@ class PlanSeeder extends Seeder 'price' => 30.00, 'billing_cycle' => 'monthly', 'features' => [ + 'tier' => 8, 'cpu' => '4 vCPU', 'ram' => '8 GB', 'storage' => '160 GB SSD', @@ -148,6 +153,7 @@ class PlanSeeder extends Seeder 'price' => 55.00, 'billing_cycle' => 'monthly', 'features' => [ + 'tier' => 16, 'cpu' => '6 vCPU', 'ram' => '16 GB', 'storage' => '320 GB SSD', @@ -168,6 +174,7 @@ class PlanSeeder extends Seeder 'price' => 99.00, 'billing_cycle' => 'monthly', 'features' => [ + 'tier' => 32, 'cpu' => '8 vCPU', 'ram' => '32 GB', 'storage' => '640 GB SSD', @@ -188,6 +195,7 @@ class PlanSeeder extends Seeder 'price' => 18.00, 'billing_cycle' => 'monthly', 'features' => [ + 'tier' => 4, 'cpu' => '2 vCPU', 'ram' => '2 GB', 'storage' => '500 GB SSD', @@ -208,6 +216,7 @@ class PlanSeeder extends Seeder 'price' => 28.00, 'billing_cycle' => 'monthly', 'features' => [ + 'tier' => 4, 'cpu' => '2 vCPU', 'ram' => '4 GB', 'storage' => '1 TB SSD', diff --git a/website/resources/ts/Components/AppTopNavbar.vue b/website/resources/ts/Components/AppTopNavbar.vue index 355ae43..a1a1cc7 100644 --- a/website/resources/ts/Components/AppTopNavbar.vue +++ b/website/resources/ts/Components/AppTopNavbar.vue @@ -1,6 +1,4 @@ + + + + diff --git a/website/resources/ts/Components/Marketing/Estimator/BackupTierSelector.vue b/website/resources/ts/Components/Marketing/Estimator/BackupTierSelector.vue new file mode 100644 index 0000000..9115f5a --- /dev/null +++ b/website/resources/ts/Components/Marketing/Estimator/BackupTierSelector.vue @@ -0,0 +1,106 @@ + + + + + diff --git a/website/resources/ts/Components/Marketing/Estimator/BillingCycleToggle.vue b/website/resources/ts/Components/Marketing/Estimator/BillingCycleToggle.vue new file mode 100644 index 0000000..eb44afd --- /dev/null +++ b/website/resources/ts/Components/Marketing/Estimator/BillingCycleToggle.vue @@ -0,0 +1,90 @@ + + + + + diff --git a/website/resources/ts/Components/Marketing/Estimator/EstimatorFooter.vue b/website/resources/ts/Components/Marketing/Estimator/EstimatorFooter.vue new file mode 100644 index 0000000..f1c1d9d --- /dev/null +++ b/website/resources/ts/Components/Marketing/Estimator/EstimatorFooter.vue @@ -0,0 +1,115 @@ + + + + + diff --git a/website/resources/ts/Components/Marketing/Estimator/EstimatorSection.vue b/website/resources/ts/Components/Marketing/Estimator/EstimatorSection.vue new file mode 100644 index 0000000..4e93806 --- /dev/null +++ b/website/resources/ts/Components/Marketing/Estimator/EstimatorSection.vue @@ -0,0 +1,251 @@ + + + + + diff --git a/website/resources/ts/Components/Marketing/Estimator/IPv4Stepper.vue b/website/resources/ts/Components/Marketing/Estimator/IPv4Stepper.vue new file mode 100644 index 0000000..fc07f2d --- /dev/null +++ b/website/resources/ts/Components/Marketing/Estimator/IPv4Stepper.vue @@ -0,0 +1,98 @@ + + + + + diff --git a/website/resources/ts/Components/Marketing/Estimator/ManagedSupportSelector.vue b/website/resources/ts/Components/Marketing/Estimator/ManagedSupportSelector.vue new file mode 100644 index 0000000..cfe23ce --- /dev/null +++ b/website/resources/ts/Components/Marketing/Estimator/ManagedSupportSelector.vue @@ -0,0 +1,157 @@ + + + + + diff --git a/website/resources/ts/Components/Marketing/Estimator/MiniQuizDialog.vue b/website/resources/ts/Components/Marketing/Estimator/MiniQuizDialog.vue new file mode 100644 index 0000000..dfa5aea --- /dev/null +++ b/website/resources/ts/Components/Marketing/Estimator/MiniQuizDialog.vue @@ -0,0 +1,214 @@ + + + + + diff --git a/website/resources/ts/Components/Marketing/Estimator/RecommendedPlanCard.vue b/website/resources/ts/Components/Marketing/Estimator/RecommendedPlanCard.vue new file mode 100644 index 0000000..e5f1c49 --- /dev/null +++ b/website/resources/ts/Components/Marketing/Estimator/RecommendedPlanCard.vue @@ -0,0 +1,149 @@ + + + + + diff --git a/website/resources/ts/Components/Marketing/Estimator/WorkloadPicker.vue b/website/resources/ts/Components/Marketing/Estimator/WorkloadPicker.vue new file mode 100644 index 0000000..fea2641 --- /dev/null +++ b/website/resources/ts/Components/Marketing/Estimator/WorkloadPicker.vue @@ -0,0 +1,92 @@ + + + + + diff --git a/website/resources/ts/Layouts/AccountLayout.vue b/website/resources/ts/Layouts/AccountLayout.vue index d91c9fa..7517b9b 100644 --- a/website/resources/ts/Layouts/AccountLayout.vue +++ b/website/resources/ts/Layouts/AccountLayout.vue @@ -8,6 +8,7 @@ import CommandPalette from '@/Components/CommandPalette.vue' import NotificationPanel from '@/Components/NotificationPanel.vue' import ToastStack from '@/Components/ToastStack.vue' import { useToastStore } from '@/stores/toast' +import { crossDomainUrl } from '@/utils/resolvers' interface AuthUser { name: string @@ -25,7 +26,7 @@ const page = usePage() const props = computed(() => page.props as unknown as PageProps) const user = computed(() => props.value.auth?.user) const isImpersonating = computed(() => props.value.impersonating) -const adminUrl = computed(() => `https://${props.value.domains?.admin}`) +const adminUrl = computed(() => crossDomainUrl(props.value.domains?.admin)) const sidebarCollapsed = ref(false) const mobileOpen = ref(false) diff --git a/website/resources/ts/Layouts/AdminLayout.vue b/website/resources/ts/Layouts/AdminLayout.vue index 95f4a8e..a041aad 100644 --- a/website/resources/ts/Layouts/AdminLayout.vue +++ b/website/resources/ts/Layouts/AdminLayout.vue @@ -8,6 +8,7 @@ import CommandPalette from '@/Components/CommandPalette.vue' import NotificationPanel from '@/Components/NotificationPanel.vue' import ToastStack from '@/Components/ToastStack.vue' import { useToastStore } from '@/stores/toast' +import { crossDomainUrl } from '@/utils/resolvers' interface AuthUser { name: string @@ -23,7 +24,7 @@ interface PageProps { const page = usePage() const props = computed(() => page.props as unknown as PageProps) const user = computed(() => props.value.auth?.user) -const accountUrl = computed(() => `https://${props.value.domains?.account}`) +const accountUrl = computed(() => crossDomainUrl(props.value.domains?.account)) const sidebarCollapsed = ref(false) const mobileOpen = ref(false) diff --git a/website/resources/ts/Layouts/AuthLayout.vue b/website/resources/ts/Layouts/AuthLayout.vue index e88a7ca..6c49d2b 100644 --- a/website/resources/ts/Layouts/AuthLayout.vue +++ b/website/resources/ts/Layouts/AuthLayout.vue @@ -1,6 +1,7 @@