Add all files

This commit is contained in:
root
2025-11-30 11:57:22 +01:00
parent 2ee51cac4f
commit 044271db3d
1476 changed files with 142544 additions and 0 deletions

View File

@@ -0,0 +1,45 @@
<?php
namespace App\Livewire\Services;
use App\Livewire\Component;
use App\Models\Service;
use App\Models\ServiceCancellation;
use Livewire\Attributes\Validate;
class Cancel extends Component
{
public Service $service;
#[Validate('required|in:end_of_period,immediate')]
public $type = 'end_of_period';
#[Validate('required')]
public $reason = '';
public function cancelService()
{
$this->authorize('view', $this->service);
$this->validate();
// Event hook will handle the cancellation (if its immediate or end of period)
ServiceCancellation::create([
'service_id' => $this->service->id,
'type' => $this->type,
'reason' => $this->reason,
]);
$this->notify(__('services.cancellation_requested'), 'success');
$this->redirect(route('services.show', $this->service), true);
}
public function render()
{
return view('services.cancel')->layoutData([
'title' => __('services.cancellation', ['service' => $this->service->product->name]),
'sidebar' => true,
]);
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace App\Livewire\Services;
use App\Livewire\Component;
use Illuminate\Support\Facades\Auth;
use Livewire\WithPagination;
class Index extends Component
{
use WithPagination;
public $status = null;
public function render()
{
$query = Auth::user()->services()
->with([
'product.category',
'product.server',
'plan',
'proxmoxServer',
])
->orderBy('created_at', 'desc');
if ($this->status) {
$query->where('status', $this->status);
}
return view('services.index', [
'services' => $query->paginate(config('settings.pagination')),
])->layoutData([
'title' => 'Services',
'sidebar' => true,
]);
}
}

View File

@@ -0,0 +1,117 @@
<?php
namespace App\Livewire\Services;
use App\Helpers\ExtensionHelper;
use App\Livewire\Component;
use App\Models\Service;
use Paymenter\Extensions\Servers\Proxmox\Models\Server as ProxmoxServer;
use Livewire\Attributes\Locked;
use Livewire\Attributes\Url;
class Show extends Component
{
public Service $service;
public ?ProxmoxServer $proxmoxServer = null;
#[Locked]
public $buttons = [];
#[Locked]
public $views = [];
#[Url('tab', except: false), Locked]
public $currentView;
#[Url('cancel', except: false)]
public bool $showCancel = false;
public function mount()
{
// Attach Proxmox server (if any) so we can show hostname on the service page
$this->proxmoxServer = ProxmoxServer::where('service_id', $this->service->id)->first();
// Only fetch the actions if the service is active
if ($this->service->status == Service::STATUS_ACTIVE) {
$actions = [];
try {
$actions = ExtensionHelper::getActions($this->service);
} catch (\Exception $e) {
}
// separate the actions into buttons and views
foreach ($actions as $action) {
if ($action['type'] == 'button') {
$this->buttons[] = $action;
} elseif ($action['type'] == 'view') {
$this->views[] = $action;
}
}
$this->currentView = $this->currentView ?? ($this->views[0]['name'] ?? null);
}
}
public function changeView($view)
{
if (!$view) {
return;
}
if ($this->currentView === $view || !in_array($view, array_column($this->views, 'name'))) {
return $this->skipRender();
}
$this->currentView = $view;
}
public function updatedShowCancel($value)
{
if (!$this->service->cancellable) {
$this->notify('This service cannot be cancelled', 'error');
$this->showCancel = false;
return;
}
}
public function goto($function)
{
// Check if function is allowed
if (!in_array($function, array_column($this->buttons, 'function'))) {
$this->notify('This action is not allowed', 'error');
return;
}
$result = ExtensionHelper::callService($this->service, $function);
// If its a response, return it
if (!is_string($result)) {
return $result;
}
$this->redirect($result);
}
public function render()
{
$view = null;
$previousView = $this->currentView;
if ($this->currentView) {
try {
// Search array for the current view
$currentViewObj = $this->views[array_search($this->currentView, array_column($this->views, 'name'))] ?? null;
if (!$currentViewObj) {
throw new \Exception('View not found');
}
$view = ExtensionHelper::getView($this->service, $currentViewObj);
} catch (\Exception $e) {
if ($previousView !== $this->views[0]['name'] ?? null) {
$this->notify('Got an error while trying to load the view', 'error');
}
$this->currentView = $this->views[0]['name'] ?? null;
}
}
return view('services.show', ['extensionView' => $view])->layoutData([
'title' => 'Services',
'sidebar' => true,
]);
}
}

View File

@@ -0,0 +1,263 @@
<?php
namespace App\Livewire\Services;
use App\Classes\Price;
use App\Events\Invoice\Created as InvoiceCreated;
use App\Jobs\Server\UpgradeJob;
use App\Livewire\Component;
use App\Models\Invoice;
use App\Models\Product;
use App\Models\Service;
use App\Models\ServiceUpgrade;
use Carbon\Carbon;
use Illuminate\Support\Facades\Auth;
use Livewire\Attributes\Computed;
class Upgrade extends Component
{
public Service $service;
public $upgrade;
public Product $upgradeProduct;
public int $step = 1;
public $configOptions = [];
public function mount()
{
$this->authorize('view', $this->service);
if (!$this->service->upgradable) {
$this->notify('This service is not upgradable.', 'error');
return $this->redirect(route('services.show', $this->service), true);
}
$this->upgradeProduct = $this->service->product;
$this->upgrade = $this->service->product->id;
$this->totalToday();
// We only have upgrabble config options if the product has any
if ($this->service->productUpgrades()->count() === 0) {
$this->nextStep();
}
}
#[Computed]
public function totalToday()
{
$upgrade = new ServiceUpgrade([
'service' => $this->service,
'product' => $this->upgradeProduct,
]);
$total = $upgrade->calculateProratedAmount(
$this->service->product,
$this->upgradeProduct
)->price;
// Calculate prices for config options
foreach ($this->configOptions as $optionId => $value) {
$option = $this->upgradeProduct->upgradableConfigOptions->where('id', $optionId)->first();
if (!$option || !$option->children->contains('id', $value)) {
continue;
}
$oldPrice = $this->service->configs->where('config_option_id', $optionId)->first();
$ctotal = $upgrade->calculateProratedAmount(
$oldPrice ? $oldPrice->configValue : null,
$option->children->find($value)
);
$total += $ctotal->price;
}
return new Price([
'price' => $total,
'currency' => $this->service->currency,
]);
}
// When upgrade changes, update the upgradeProduct
public function updatedUpgrade($upgrade)
{
// Check if the upgrade is valid
if (!$this->service->productUpgrades()->contains($upgrade) && $upgrade != $this->service->product_id) {
$this->notify('Invalid upgrade.', 'error');
return;
}
$this->upgradeProduct = Product::findOrFail($upgrade);
}
public function nextStep()
{
$currentConfigOptions = $this->service->configs->pluck('config_value_id', 'config_option_id')->toArray();
$this->configOptions = $this->upgradeProduct->upgradableConfigOptions->mapWithKeys(function ($option) use ($currentConfigOptions) {
return [
$option->id => $currentConfigOptions[$option->id]
?? $this->configOptions[$option->id]
?? $option->children->first()->id,
];
})->toArray();
$this->step++;
}
public function rules()
{
$rules = [
'upgradeProduct.id' => [
'required',
function ($attribute, $value, $fail) {
$plan = $this->upgradeProduct->availablePlans()
->where('billing_period', $this->service->plan->billing_period)
->where('billing_unit', $this->service->plan->billing_unit)
->first();
if (!$plan) {
$fail(__('Invalid upgrade.'));
}
},
],
];
foreach ($this->upgradeProduct->upgradableConfigOptions as $option) {
if (in_array($option->type, ['text', 'number'])) {
$rules["configOptions.{$option->id}"] = ['required'];
} elseif ($option->type === 'checkbox') {
} else {
$rules["configOptions.{$option->id}"] = ['required', 'exists:config_options,id'];
}
}
return $rules;
}
public function doUpgrade()
{
if (!$this->service->upgradable) {
$this->notify('This service is not upgradable.', 'error');
return $this->redirect(route('services.show', $this->service), true);
}
$this->validate();
$upgradePlan = $this->upgradeProduct->availablePlans()->where('billing_period', $this->service->plan->billing_period)->where('billing_unit', $this->service->plan->billing_unit)->first();
// The old config options must be in upgradableConfigOptions
$serviceConfigOptions = $this->service->configs->pluck('config_value_id', 'config_option_id')->toArray();
$configOptions = collect($serviceConfigOptions)->filter(function ($value, $key) use ($serviceConfigOptions) {
return isset($serviceConfigOptions[$key]) && $this->upgradeProduct->upgradableConfigOptions->contains('id', $key);
})->toArray();
// If the product did not change and the config options are the same, present the user with a message
if ($this->upgradeProduct->id === $this->service->product_id && $this->configOptions == $configOptions) {
$this->notify('You have not changed anything. Please select a different product or change the configuration options.', 'error');
return;
}
$upgrade = new ServiceUpgrade([
'service_id' => $this->service->id,
'product_id' => $this->upgradeProduct->id,
'plan_id' => $upgradePlan->id,
]);
$upgrade->save();
if ($this->configOptions) {
foreach ($this->configOptions as $optionId => $value) {
$upgrade->configs()->create([
'config_option_id' => $optionId,
'config_value_id' => $value,
]);
}
}
$price = $upgrade->calculatePrice();
if ($price->price <= 0) {
$upgrade->status = ServiceUpgrade::STATUS_COMPLETED;
$upgrade->save();
$upgrade->service()->update([
'plan_id' => $upgrade->plan_id,
'product_id' => $upgrade->product_id,
]);
// Update the service configs
foreach ($upgrade->configs as $config) {
$upgrade->service->configs()->updateOrCreate(
['config_option_id' => $config->config_option_id],
['config_value_id' => $config->config_value_id]
);
}
$this->service->refresh();
$this->service->recalculatePrice();
if ($this->service->product->server) {
// If the service has a server, dispatch the upgrade job
UpgradeJob::dispatch($this->service);
}
// Check if user has credits in this currency
/** @var \App\Models\User */
$user = Auth::user();
$credit = $user->credits()->where('currency_code', $price->currency->code)->first();
if ($credit) {
// Increment the credits, `abs()` ensures the amount to add is positive
$credit->increment('amount', abs($price->price));
} else {
$user->credits()->create([
'currency_code' => $price->currency->code,
'amount' => $price->price,
]);
}
if ($price->price < 0) {
$this->notify('The upgrade has been completed. We\'ve added the remaining amount to your account balance.', 'success');
} else {
$this->notify('The upgrade has been completed.', 'success');
}
return $this->redirect(route('services.show', $this->service), true);
}
$invoice = new Invoice([
'currency_code' => $this->service->currency_code,
'status' => Invoice::STATUS_PENDING,
'due_at' => Carbon::now()->addDays(7),
'user_id' => $this->service->user_id,
]);
$invoice->save();
$upgrade->invoice_id = $invoice->id;
$upgrade->save();
$invoice->items()->create([
'description' => 'Upgrade ' . $this->service->product->name . ' to ' . $this->upgradeProduct->name,
'price' => $price->price,
'quantity' => 1,
'reference_id' => $upgrade->id,
'reference_type' => ServiceUpgrade::class,
]);
event(new InvoiceCreated($invoice));
$this->notify('The upgrade has been added to your cart. Please complete the payment to proceed.', 'success');
return $this->redirect(route('invoices.show', $invoice));
}
public function render()
{
return view('services.upgrade')->layoutData([
'title' => 'Upgrade Service',
'sidebar' => true,
]);
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace App\Livewire\Services;
use App\Livewire\Component;
use Illuminate\Support\Facades\Auth;
use Livewire\WithPagination;
class Widget extends Component
{
use WithPagination;
public $status = null;
public $limit = null;
public function render()
{
$query = Auth::user()->services()->orderByDesc('created_at');
if ($this->status) {
$query->where('status', $this->status);
} else {
$query->where('status', '!=', 'cancelled');
}
$services = $this->limit
? $query->take($this->limit)->get()
: $query->paginate(config('settings.pagination'));
return view('services.widget', [
'services' => $services,
])->layoutData([
'title' => 'Services',
]);
}
}