2026-02-26 17:22:03 +00:00
|
|
|
|
<div wire:init="loadRefundEligibility">
|
2023-08-24 14:14:09 +00:00
|
|
|
|
@if (subscriptionProvider() === 'stripe')
|
2026-02-26 17:22:03 +00:00
|
|
|
|
{{-- Plan Overview --}}
|
2026-03-03 11:28:16 +00:00
|
|
|
|
<section x-data="{
|
|
|
|
|
|
qty: {{ $quantity }},
|
|
|
|
|
|
get current() { return $wire.server_limits; },
|
|
|
|
|
|
activeServers: {{ currentTeam()->servers->count() }},
|
|
|
|
|
|
preview: @js($pricePreview),
|
|
|
|
|
|
loading: false,
|
|
|
|
|
|
showModal: false,
|
|
|
|
|
|
async fetchPreview() {
|
|
|
|
|
|
if (this.qty < 2 || this.qty > 100 || this.qty === this.current) { return; }
|
|
|
|
|
|
this.loading = true;
|
|
|
|
|
|
this.preview = null;
|
|
|
|
|
|
await $wire.loadPricePreview(this.qty);
|
|
|
|
|
|
this.preview = $wire.pricePreview;
|
|
|
|
|
|
this.loading = false;
|
|
|
|
|
|
},
|
|
|
|
|
|
fmt(cents) {
|
|
|
|
|
|
if (!this.preview) return '';
|
|
|
|
|
|
const c = this.preview.currency;
|
|
|
|
|
|
return c === 'USD' ? '$' + (cents / 100).toFixed(2) : (cents / 100).toFixed(2) + ' ' + c;
|
|
|
|
|
|
},
|
|
|
|
|
|
get isReduction() { return this.qty < this.activeServers; },
|
|
|
|
|
|
get hasChanged() { return this.qty !== this.current; },
|
|
|
|
|
|
get hasPreview() { return this.preview !== null; },
|
|
|
|
|
|
openAdjust() {
|
|
|
|
|
|
this.showModal = true;
|
|
|
|
|
|
},
|
|
|
|
|
|
closeAdjust() {
|
|
|
|
|
|
this.showModal = false;
|
|
|
|
|
|
this.qty = this.current;
|
|
|
|
|
|
this.preview = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}" @success.window="preview = null; showModal = false; qty = $wire.server_limits"
|
|
|
|
|
|
@keydown.escape.window="if (showModal) { closeAdjust(); }" class="-mt-2">
|
2026-02-26 17:22:03 +00:00
|
|
|
|
<h3 class="pb-2">Plan Overview</h3>
|
2026-03-18 14:11:19 +00:00
|
|
|
|
<div class="space-y-2">
|
|
|
|
|
|
<div class="text-sm">
|
|
|
|
|
|
<span class="text-neutral-500">Plan:</span>
|
|
|
|
|
|
<span class="dark:text-warning font-medium">
|
2026-02-26 17:22:03 +00:00
|
|
|
|
@if (data_get(currentTeam(), 'subscription')->type() == 'dynamic')
|
|
|
|
|
|
Pay-as-you-go
|
|
|
|
|
|
@else
|
|
|
|
|
|
{{ data_get(currentTeam(), 'subscription')->type() }}
|
|
|
|
|
|
@endif
|
2026-03-18 14:11:19 +00:00
|
|
|
|
</span>
|
|
|
|
|
|
<span class="text-neutral-500">· {{ $billingInterval === 'yearly' ? 'Yearly' : 'Monthly' }}</span>
|
|
|
|
|
|
<span class="text-neutral-500">·</span>
|
|
|
|
|
|
@if (currentTeam()->subscription->stripe_cancel_at_period_end)
|
|
|
|
|
|
<span class="text-red-500 font-medium">Cancelling at end of period</span>
|
|
|
|
|
|
@else
|
|
|
|
|
|
<span class="text-green-500 font-medium">Active</span>
|
|
|
|
|
|
@endif
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="text-sm flex items-center gap-2 flex-wrap">
|
|
|
|
|
|
<span>
|
|
|
|
|
|
<span class="text-neutral-500">Active servers:</span>
|
|
|
|
|
|
<span class="font-medium {{ currentTeam()->serverOverflow() ? 'text-red-500' : 'dark:text-white' }}">{{ currentTeam()->servers->count() }}</span>
|
|
|
|
|
|
<span class="text-neutral-500">/</span>
|
|
|
|
|
|
<span class="font-medium dark:text-white" x-text="current"></span>
|
|
|
|
|
|
<span class="text-neutral-500">paid</span>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<x-forms.button isHighlighted @click="openAdjust()">Adjust</x-forms.button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="text-sm text-neutral-500">
|
|
|
|
|
|
@if ($refundCheckLoading)
|
|
|
|
|
|
<x-loading text="Loading..." />
|
|
|
|
|
|
@elseif ($nextBillingDate)
|
2026-02-26 17:22:03 +00:00
|
|
|
|
@if (currentTeam()->subscription->stripe_cancel_at_period_end)
|
2026-03-18 14:11:19 +00:00
|
|
|
|
Cancels on <span class="dark:text-white font-medium">{{ $nextBillingDate }}</span>
|
2026-02-26 17:22:03 +00:00
|
|
|
|
@else
|
2026-03-18 14:11:19 +00:00
|
|
|
|
Next billing <span class="dark:text-white font-medium">{{ $nextBillingDate }}</span>
|
2026-02-26 17:22:03 +00:00
|
|
|
|
@endif
|
2026-03-18 14:11:19 +00:00
|
|
|
|
@endif
|
2026-02-26 17:22:03 +00:00
|
|
|
|
</div>
|
2024-05-02 10:27:04 +00:00
|
|
|
|
</div>
|
2026-02-26 17:22:03 +00:00
|
|
|
|
|
2024-02-25 21:08:44 +00:00
|
|
|
|
@if (currentTeam()->serverOverflow())
|
2026-02-26 17:22:03 +00:00
|
|
|
|
<x-callout type="danger" title="Server limit exceeded" class="mt-4">
|
|
|
|
|
|
You must delete {{ currentTeam()->servers->count() - $server_limits }} servers or upgrade your
|
|
|
|
|
|
subscription. Excess servers will be deactivated.
|
2025-09-25 09:14:56 +00:00
|
|
|
|
</x-callout>
|
2024-02-25 21:08:44 +00:00
|
|
|
|
@endif
|
2026-02-26 17:22:03 +00:00
|
|
|
|
|
2026-03-03 11:28:16 +00:00
|
|
|
|
{{-- Adjust Server Limit Modal --}}
|
|
|
|
|
|
<template x-teleport="body">
|
|
|
|
|
|
<div x-show="showModal"
|
|
|
|
|
|
class="fixed top-0 left-0 z-99 flex items-center justify-center w-screen h-screen p-4" x-cloak>
|
|
|
|
|
|
<div x-show="showModal" class="absolute inset-0 w-full h-full bg-black/20 backdrop-blur-xs"
|
|
|
|
|
|
@click="closeAdjust()">
|
2026-02-26 17:22:03 +00:00
|
|
|
|
</div>
|
2026-03-03 11:28:16 +00:00
|
|
|
|
<div x-show="showModal" x-trap.inert.noscroll="showModal"
|
|
|
|
|
|
x-transition:enter="ease-out duration-100"
|
|
|
|
|
|
x-transition:enter-start="opacity-0 -translate-y-2 sm:scale-95"
|
|
|
|
|
|
x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100"
|
|
|
|
|
|
x-transition:leave="ease-in duration-100"
|
|
|
|
|
|
x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100"
|
|
|
|
|
|
x-transition:leave-end="opacity-0 -translate-y-2 sm:scale-95"
|
|
|
|
|
|
class="relative w-full border rounded-sm min-w-full lg:min-w-[36rem] max-w-[48rem] max-h-[calc(100vh-2rem)] bg-neutral-100 border-neutral-400 dark:bg-base dark:border-coolgray-300 flex flex-col">
|
|
|
|
|
|
<div class="flex justify-between items-center py-6 px-7 shrink-0">
|
2026-03-18 14:11:19 +00:00
|
|
|
|
<h3 class="text-2xl font-bold">Adjust Server Limit</h3>
|
2026-03-03 11:28:16 +00:00
|
|
|
|
<button @click="closeAdjust()"
|
2026-03-18 14:11:19 +00:00
|
|
|
|
class="flex justify-center items-center w-8 h-8 rounded-full dark:text-white hover:bg-coolgray-300">
|
2026-03-03 11:28:16 +00:00
|
|
|
|
<svg class="w-6 h-6" xmlns="http://www.w3.org/2000/svg" fill="none"
|
|
|
|
|
|
viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
|
|
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="relative w-auto overflow-y-auto px-7 pb-6 space-y-4"
|
|
|
|
|
|
style="-webkit-overflow-scrolling: touch;">
|
|
|
|
|
|
{{-- Server count input --}}
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<label class="text-xs font-bold text-neutral-500 uppercase tracking-wide">Paid Servers</label>
|
|
|
|
|
|
<div class="flex items-center gap-3 pt-1">
|
|
|
|
|
|
<input type="number" min="{{ $minServerLimit }}" max="{{ $maxServerLimit }}" step="1"
|
|
|
|
|
|
x-model.number="qty"
|
|
|
|
|
|
@input="preview = null"
|
|
|
|
|
|
@change="qty = Math.min({{ $maxServerLimit }}, Math.max({{ $minServerLimit }}, qty || {{ $minServerLimit }}))"
|
|
|
|
|
|
class="w-20 px-2 py-1 text-xl font-bold text-center rounded border dark:bg-coolgray-200 dark:border-coolgray-400 border-neutral-200 dark:text-white">
|
|
|
|
|
|
<x-forms.button
|
|
|
|
|
|
isHighlighted
|
|
|
|
|
|
x-bind:disabled="!hasChanged || loading"
|
|
|
|
|
|
@click="fetchPreview()">
|
|
|
|
|
|
Calculate Price
|
|
|
|
|
|
</x-forms.button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-02-26 17:22:03 +00:00
|
|
|
|
|
2026-03-03 11:28:16 +00:00
|
|
|
|
{{-- Loading --}}
|
|
|
|
|
|
<div x-show="loading" x-cloak>
|
|
|
|
|
|
<x-loading text="Loading price preview..." />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{{-- Price Preview --}}
|
|
|
|
|
|
<div class="space-y-4" x-show="!loading && hasPreview" x-cloak>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<div class="text-xs font-bold text-neutral-500 uppercase tracking-wide pb-1.5">Due now</div>
|
|
|
|
|
|
<div class="flex justify-between gap-6 text-sm font-bold">
|
|
|
|
|
|
<span class="dark:text-white">Prorated charge</span>
|
2026-03-10 16:14:08 +00:00
|
|
|
|
<span class="dark:text-warning" x-text="fmt(preview?.due_now)"></span>
|
2026-03-03 11:28:16 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
<p class="text-xs text-neutral-500 pt-1">Charged immediately to your payment method.</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div>
|
2026-03-18 14:11:19 +00:00
|
|
|
|
<div class="text-xs font-bold text-neutral-500 uppercase tracking-wide pb-1.5">
|
|
|
|
|
|
Next billing cycle
|
|
|
|
|
|
@if ($nextBillingDate)
|
|
|
|
|
|
<span class="normal-case font-normal">· {{ $nextBillingDate }}</span>
|
|
|
|
|
|
@endif
|
|
|
|
|
|
</div>
|
2026-03-03 11:28:16 +00:00
|
|
|
|
<div class="space-y-1.5">
|
|
|
|
|
|
<div class="flex justify-between gap-6 text-sm">
|
2026-03-10 16:14:08 +00:00
|
|
|
|
<span class="text-neutral-500" x-text="preview?.quantity + ' servers × ' + fmt(preview?.unit_price)"></span>
|
|
|
|
|
|
<span class="dark:text-white" x-text="fmt(preview?.recurring_subtotal)"></span>
|
2026-03-03 11:28:16 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
<div class="flex justify-between gap-6 text-sm" x-show="preview?.tax_description" x-cloak>
|
|
|
|
|
|
<span class="text-neutral-500" x-text="preview?.tax_description"></span>
|
|
|
|
|
|
<span class="dark:text-white" x-text="fmt(preview?.recurring_tax)"></span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="flex justify-between gap-6 text-sm font-bold pt-1.5 border-t dark:border-coolgray-400 border-neutral-200">
|
2026-03-18 14:11:19 +00:00
|
|
|
|
<span class="dark:text-white">Total / {{ $billingInterval === 'yearly' ? 'year' : 'month' }}</span>
|
2026-03-10 16:14:08 +00:00
|
|
|
|
<span class="dark:text-white" x-text="fmt(preview?.recurring_total)"></span>
|
2026-03-03 11:28:16 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{{-- Update Button with Confirmation --}}
|
|
|
|
|
|
<x-modal-confirmation
|
|
|
|
|
|
title="Confirm Server Limit Update"
|
|
|
|
|
|
buttonTitle="Update Server Limit"
|
|
|
|
|
|
submitAction="updateQuantity"
|
|
|
|
|
|
:confirmWithText="false"
|
|
|
|
|
|
:confirmWithPassword="false"
|
|
|
|
|
|
:actions="[
|
|
|
|
|
|
'Your server limit will be updated immediately.',
|
|
|
|
|
|
'The prorated amount will be invoiced and charged now.',
|
|
|
|
|
|
]"
|
|
|
|
|
|
warningMessage="This will update your subscription and charge the prorated amount to your payment method."
|
|
|
|
|
|
step2ButtonText="Confirm & Pay">
|
|
|
|
|
|
<x-slot:content>
|
2026-03-18 14:11:19 +00:00
|
|
|
|
<x-forms.button class="w-full" @click="$wire.set('quantity', qty)">
|
2026-03-03 11:28:16 +00:00
|
|
|
|
Update Server Limit
|
|
|
|
|
|
</x-forms.button>
|
|
|
|
|
|
</x-slot:content>
|
|
|
|
|
|
</x-modal-confirmation>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{{-- Reduction Warning --}}
|
|
|
|
|
|
<div x-show="isReduction" x-cloak>
|
|
|
|
|
|
<x-callout type="danger" title="Warning">
|
|
|
|
|
|
Reducing below your active server count will deactivate excess servers.
|
|
|
|
|
|
</x-callout>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-02-26 17:22:03 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-03-03 11:28:16 +00:00
|
|
|
|
</template>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
2026-03-18 14:11:19 +00:00
|
|
|
|
{{-- Manage Subscription --}}
|
2026-03-03 11:28:16 +00:00
|
|
|
|
<section>
|
|
|
|
|
|
<h3 class="pb-2">Manage Subscription</h3>
|
|
|
|
|
|
<div class="flex flex-wrap items-center gap-2">
|
|
|
|
|
|
<x-forms.button class="gap-2" wire:click='stripeCustomerPortal'>
|
|
|
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4" fill="none" viewBox="0 0 24 24"
|
|
|
|
|
|
stroke-width="1.5" stroke="currentColor">
|
|
|
|
|
|
<path stroke-linecap="round" stroke-linejoin="round"
|
|
|
|
|
|
d="M2.25 8.25h19.5M2.25 9h19.5m-16.5 5.25h6m-6 2.25h3m-3.75 3h15a2.25 2.25 0 0 0 2.25-2.25V6.75A2.25 2.25 0 0 0 19.5 4.5h-15a2.25 2.25 0 0 0-2.25 2.25v10.5A2.25 2.25 0 0 0 4.5 19.5Z" />
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
Manage Billing on Stripe
|
|
|
|
|
|
</x-forms.button>
|
2026-03-18 14:11:19 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</section>
|
2026-03-03 11:28:16 +00:00
|
|
|
|
|
2026-03-18 14:11:19 +00:00
|
|
|
|
{{-- Cancel Subscription --}}
|
|
|
|
|
|
<section>
|
|
|
|
|
|
<h3 class="pb-2">Cancel Subscription</h3>
|
|
|
|
|
|
<div class="flex flex-wrap items-center gap-2">
|
2026-03-03 11:28:16 +00:00
|
|
|
|
@if (currentTeam()->subscription->stripe_cancel_at_period_end)
|
|
|
|
|
|
<x-forms.button wire:click="resumeSubscription">Resume Subscription</x-forms.button>
|
|
|
|
|
|
@else
|
|
|
|
|
|
<x-modal-confirmation title="Cancel at End of Billing Period?"
|
|
|
|
|
|
buttonTitle="Cancel at Period End" submitAction="cancelAtPeriodEnd"
|
|
|
|
|
|
:actions="[
|
|
|
|
|
|
'Your subscription will remain active until the end of the current billing period.',
|
|
|
|
|
|
'No further charges will be made after the current period.',
|
|
|
|
|
|
'You can resubscribe at any time.',
|
|
|
|
|
|
]" confirmationText="{{ currentTeam()->name }}"
|
|
|
|
|
|
confirmationLabel="Enter your team name to confirm"
|
|
|
|
|
|
shortConfirmationLabel="Team Name" step2ButtonText="Confirm Cancellation" />
|
|
|
|
|
|
<x-modal-confirmation title="Cancel Immediately?" buttonTitle="Cancel Immediately"
|
|
|
|
|
|
isErrorButton submitAction="cancelImmediately"
|
|
|
|
|
|
:actions="[
|
|
|
|
|
|
'Your subscription will be cancelled immediately.',
|
|
|
|
|
|
'All servers will be deactivated.',
|
|
|
|
|
|
'No refund will be issued for the remaining period.',
|
|
|
|
|
|
]" confirmationText="{{ currentTeam()->name }}"
|
|
|
|
|
|
confirmationLabel="Enter your team name to confirm"
|
|
|
|
|
|
shortConfirmationLabel="Team Name" step2ButtonText="Permanently Cancel" />
|
|
|
|
|
|
@endif
|
2026-03-18 14:11:19 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
@if (currentTeam()->subscription->stripe_cancel_at_period_end)
|
|
|
|
|
|
<p class="mt-2 text-sm text-neutral-500">Your subscription is set to cancel at the end of the billing period.</p>
|
|
|
|
|
|
@endif
|
|
|
|
|
|
</section>
|
2026-03-03 11:28:16 +00:00
|
|
|
|
|
2026-03-18 14:11:19 +00:00
|
|
|
|
{{-- Refund --}}
|
|
|
|
|
|
<section>
|
|
|
|
|
|
<h3 class="pb-2">Refund</h3>
|
|
|
|
|
|
<div class="flex flex-wrap items-center gap-2">
|
2026-03-03 11:28:16 +00:00
|
|
|
|
@if ($refundCheckLoading)
|
2026-03-18 14:11:19 +00:00
|
|
|
|
<x-forms.button disabled>Request Full Refund</x-forms.button>
|
2026-03-03 11:28:16 +00:00
|
|
|
|
@elseif ($isRefundEligible && !currentTeam()->subscription->stripe_cancel_at_period_end)
|
|
|
|
|
|
<x-modal-confirmation title="Request Full Refund?" buttonTitle="Request Full Refund"
|
|
|
|
|
|
isErrorButton submitAction="refundSubscription"
|
|
|
|
|
|
:actions="[
|
|
|
|
|
|
'Your latest payment will be fully refunded.',
|
|
|
|
|
|
'Your subscription will be cancelled immediately.',
|
|
|
|
|
|
'All servers will be deactivated.',
|
|
|
|
|
|
]" confirmationText="{{ currentTeam()->name }}"
|
|
|
|
|
|
confirmationLabel="Enter your team name to confirm" shortConfirmationLabel="Team Name"
|
|
|
|
|
|
step2ButtonText="Confirm Refund & Cancel" />
|
2026-03-18 14:11:19 +00:00
|
|
|
|
@else
|
|
|
|
|
|
<x-forms.button disabled>Request Full Refund</x-forms.button>
|
2026-03-03 11:28:16 +00:00
|
|
|
|
@endif
|
|
|
|
|
|
</div>
|
2026-03-18 14:11:19 +00:00
|
|
|
|
<p class="mt-2 text-sm text-neutral-500">
|
|
|
|
|
|
@if ($refundCheckLoading)
|
|
|
|
|
|
Checking refund eligibility...
|
|
|
|
|
|
@elseif ($isRefundEligible && !currentTeam()->subscription->stripe_cancel_at_period_end)
|
|
|
|
|
|
Eligible for a full refund — <strong class="dark:text-warning">{{ $refundDaysRemaining }}</strong> days remaining.
|
|
|
|
|
|
@elseif ($refundAlreadyUsed)
|
|
|
|
|
|
Refund already processed. Each team is eligible for one refund only.
|
|
|
|
|
|
@else
|
|
|
|
|
|
Not eligible for a refund.
|
|
|
|
|
|
@endif
|
|
|
|
|
|
</p>
|
2026-03-03 11:28:16 +00:00
|
|
|
|
</section>
|
2026-02-26 17:22:03 +00:00
|
|
|
|
|
|
|
|
|
|
<div class="text-sm text-neutral-500">
|
|
|
|
|
|
Need help? <a class="underline dark:text-white" href="{{ config('constants.urls.contact') }}"
|
|
|
|
|
|
target="_blank">Contact us.</a>
|
2023-08-30 16:23:55 +00:00
|
|
|
|
</div>
|
2023-08-17 13:19:37 +00:00
|
|
|
|
@endif
|
|
|
|
|
|
</div>
|