From e5f3d19c5d1bbfe9e8bf53dc8b88652ccc9e15c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Thu, 8 Feb 2024 19:55:29 +0100 Subject: [PATCH 001/188] Add v3 method to SubscriptionPurchaseController --- .../ClientPortal/SubscriptionPurchaseController.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/Http/Controllers/ClientPortal/SubscriptionPurchaseController.php b/app/Http/Controllers/ClientPortal/SubscriptionPurchaseController.php index 62d776869f37..7e8a1784135d 100644 --- a/app/Http/Controllers/ClientPortal/SubscriptionPurchaseController.php +++ b/app/Http/Controllers/ClientPortal/SubscriptionPurchaseController.php @@ -71,6 +71,17 @@ class SubscriptionPurchaseController extends Controller ]); } + public function v3(Subscription $subscription, Request $request) + { + // Todo: Prerequirement checks for subscription. + + return view('billing-portal.v3.index', [ + 'subscription' => $subscription, + 'hash' => Str::uuid()->toString(), + 'request_data' => $request->all(), + ]); + } + /** * Set locale for incoming request. * From b290969ccf973fc1e381f18220ab03d158ebab6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Thu, 8 Feb 2024 19:55:33 +0100 Subject: [PATCH 002/188] Add Authentication component to BillingPortal --- app/Livewire/BillingPortal/Authentication.php | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 app/Livewire/BillingPortal/Authentication.php diff --git a/app/Livewire/BillingPortal/Authentication.php b/app/Livewire/BillingPortal/Authentication.php new file mode 100644 index 000000000000..fa31d4515b2f --- /dev/null +++ b/app/Livewire/BillingPortal/Authentication.php @@ -0,0 +1,66 @@ + false, + ]; + + public function authenticate() + { + $this->validateOnly('email', ['email' => 'required|bail|email:rfc']); + + $code = rand(100000, 999999); + $hash = sprintf('subscriptions:otp:%s', $code); + + cache()->put($hash, $code, ttl: 120); + + $cc = new ClientContact(); + $cc->email = $this->email; + + $nmo = new NinjaMailerObject(); + $nmo->mailable = new OtpCode($this->subscription->company, $this->contact, $code); + $nmo->company = $this->subscription->company; + $nmo->settings = $this->subscription->company->settings; + $nmo->to_user = $cc; + + NinjaMailerJob::dispatch($nmo); + + $this->state['code'] = true; + } + + public function mount() + { + if (auth()->guard('contact')->check()) { + $this->dispatch('purchase.next'); + } + } + + public function render() + { + return view('billing-portal.v3.authentication'); + } +} From 2c1cb88783b8c6a319c33fd2e5a99b590399b942 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Thu, 8 Feb 2024 19:55:35 +0100 Subject: [PATCH 003/188] Add Purchase component to BillingPortal --- app/Livewire/BillingPortal/Purchase.php | 77 +++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 app/Livewire/BillingPortal/Purchase.php diff --git a/app/Livewire/BillingPortal/Purchase.php b/app/Livewire/BillingPortal/Purchase.php new file mode 100644 index 000000000000..0b8fd934249e --- /dev/null +++ b/app/Livewire/BillingPortal/Purchase.php @@ -0,0 +1,77 @@ +context[$property] = $value; + } + + #[On('purchase.next')] + public function handleNext(): void + { + if ($this->step < count($this->steps) - 1) { + $this->step++; + } + } + + #[On('purchase.forward')] + public function handleForward(string $component): void + { + $this->step = array_search($component, $this->steps); + } + + #[Computed()] + public function component(): string + { + return $this->steps[$this->step]; + } + + public function mount() + { + MultiDB::setDb($this->db); + + $this->context = [ + 'quantity' => 1, + ]; + } + + public function render() + { + return view('billing-portal.v3.purchase'); + } +} From 1a2f6ea48d1ff6402bc97779ce982d309c4a3a9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Thu, 8 Feb 2024 19:55:38 +0100 Subject: [PATCH 004/188] Add BillingPortal Setup component --- app/Livewire/BillingPortal/Setup.php | 33 ++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 app/Livewire/BillingPortal/Setup.php diff --git a/app/Livewire/BillingPortal/Setup.php b/app/Livewire/BillingPortal/Setup.php new file mode 100644 index 000000000000..bee2f4c21419 --- /dev/null +++ b/app/Livewire/BillingPortal/Setup.php @@ -0,0 +1,33 @@ +dispatch('purchase.context', property: 'quantity', value: 1); + $this->dispatch('purchase.next'); + } + + public function render() + { + return <<<'HTML' + + HTML; + } +} From 631b7f7b2568301846fb8dbc61e93a29d9a930aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Thu, 8 Feb 2024 19:55:41 +0100 Subject: [PATCH 005/188] Add authentication view for billing portal v3 --- .../v3/authentication.blade.php | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 resources/views/billing-portal/v3/authentication.blade.php diff --git a/resources/views/billing-portal/v3/authentication.blade.php b/resources/views/billing-portal/v3/authentication.blade.php new file mode 100644 index 000000000000..a5ff7c372193 --- /dev/null +++ b/resources/views/billing-portal/v3/authentication.blade.php @@ -0,0 +1,32 @@ +
+ @if (session()->has('message')) + @component('portal.ninja2020.components.message') + {{ session('message') }} + @endcomponent + @endif + + @if($state['code'] === false) +
+ @csrf + + + + +
+ @else +

We have sent a code to {{ $email }} enter this code to proceed.

+ @endif +
From 9877c5ab3dbd2daf50ae6cbdb72255526f82e547 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Thu, 8 Feb 2024 19:55:44 +0100 Subject: [PATCH 006/188] Add billing portal purchase view --- resources/views/billing-portal/v3/index.blade.php | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 resources/views/billing-portal/v3/index.blade.php diff --git a/resources/views/billing-portal/v3/index.blade.php b/resources/views/billing-portal/v3/index.blade.php new file mode 100644 index 000000000000..47273a4cfed6 --- /dev/null +++ b/resources/views/billing-portal/v3/index.blade.php @@ -0,0 +1,6 @@ +@extends('portal.ninja2020.layout.clean') +@section('meta_title', ctrans('texts.purchase')) + +@section('body') + @livewire('billing-portal.purchase', ['subscription' => $subscription, 'db' => $subscription->company->db, 'hash' => $hash, 'request_data' => $request_data, 'campaign' => request()->query('campaign') ?? null]) +@stop From b9f49f5e05851b865f7cc3d6757246ba29d16838 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Thu, 8 Feb 2024 19:55:47 +0100 Subject: [PATCH 007/188] Add purchase view for billing portal v3 --- resources/views/billing-portal/v3/purchase.blade.php | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 resources/views/billing-portal/v3/purchase.blade.php diff --git a/resources/views/billing-portal/v3/purchase.blade.php b/resources/views/billing-portal/v3/purchase.blade.php new file mode 100644 index 000000000000..810277993f10 --- /dev/null +++ b/resources/views/billing-portal/v3/purchase.blade.php @@ -0,0 +1,11 @@ +
+
+

{{ $subscription->name }}

+

C: {{ $this->component }}

+

Quantity: {{ $this->context['quantity'] }}

+
+ +
+ @livewire($this->component, ['context' => $context, 'subscription' => $this->subscription], key($this->component)) +
+
From 86f54470bb6a015d9e4a2ff59758c23ddbb6c15a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Thu, 8 Feb 2024 19:55:50 +0100 Subject: [PATCH 008/188] Add v3 endpoint for subscription purchase --- routes/client.php | 1 + 1 file changed, 1 insertion(+) diff --git a/routes/client.php b/routes/client.php index 0e3fdd729a89..bfd6aa2a8701 100644 --- a/routes/client.php +++ b/routes/client.php @@ -120,6 +120,7 @@ Route::get('payments/process/response', [App\Http\Controllers\ClientPortal\Payme Route::get('client/subscriptions/{subscription}/purchase', [App\Http\Controllers\ClientPortal\SubscriptionPurchaseController::class, 'index'])->name('client.subscription.purchase')->middleware('domain_db'); Route::get('client/subscriptions/{subscription}/purchase/v2', [App\Http\Controllers\ClientPortal\SubscriptionPurchaseController::class, 'upgrade'])->name('client.subscription.upgrade')->middleware('domain_db'); +Route::get('client/subscriptions/{subscription}/purchase/v3', [App\Http\Controllers\ClientPortal\SubscriptionPurchaseController::class, 'v3'])->name('client.subscription.v3')->middleware('domain_db'); Route::group(['middleware' => ['invite_db'], 'prefix' => 'client', 'as' => 'client.'], function () { /*Invitation catches*/ From 05f8e2a72cfd23c4f41875f00763d595aefb6aa1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Fri, 9 Feb 2024 17:53:31 +0100 Subject: [PATCH 009/188] Refactor purchase.blade.php layout --- .../billing-portal/v3/purchase.blade.php | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/resources/views/billing-portal/v3/purchase.blade.php b/resources/views/billing-portal/v3/purchase.blade.php index 810277993f10..4d3a6950d8d8 100644 --- a/resources/views/billing-portal/v3/purchase.blade.php +++ b/resources/views/billing-portal/v3/purchase.blade.php @@ -1,11 +1,20 @@ -
-
-

{{ $subscription->name }}

-

C: {{ $this->component }}

-

Quantity: {{ $this->context['quantity'] }}

+
+
+
+ {{ $subscription->company->present()->name }} + +
+ @livewire($this->component, ['context' => $context, 'subscription' => $this->subscription], key($this->component)) +
+
-
- @livewire($this->component, ['context' => $context, 'subscription' => $this->subscription], key($this->component)) +
+
+

{{ $subscription->name }}

+
From a5672187c4847e2da3519559ce5695bfb07e4b6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Fri, 9 Feb 2024 17:53:34 +0100 Subject: [PATCH 010/188] Refactor authentication view in billing portal v3 --- .../v3/authentication.blade.php | 71 +++++++++++++++++-- 1 file changed, 65 insertions(+), 6 deletions(-) diff --git a/resources/views/billing-portal/v3/authentication.blade.php b/resources/views/billing-portal/v3/authentication.blade.php index a5ff7c372193..f1261a101643 100644 --- a/resources/views/billing-portal/v3/authentication.blade.php +++ b/resources/views/billing-portal/v3/authentication.blade.php @@ -5,11 +5,38 @@ @endcomponent @endif - @if($state['code'] === false) -
+
+

{{ ctrans('texts.contact') }}

+
+ + @if($state['initial_completed'] === false) + + @csrf + + + + +
+ @endif + + @if($state['login_form']) +
@csrf -
From f55cfd9179a506019ddb5c6fa71ca220fa9ccb1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Fri, 9 Feb 2024 17:53:38 +0100 Subject: [PATCH 011/188] Add request_data and campaign properties to Purchase class --- app/Livewire/BillingPortal/Purchase.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/Livewire/BillingPortal/Purchase.php b/app/Livewire/BillingPortal/Purchase.php index 0b8fd934249e..baf6f41877a1 100644 --- a/app/Livewire/BillingPortal/Purchase.php +++ b/app/Livewire/BillingPortal/Purchase.php @@ -24,12 +24,17 @@ class Purchase extends Component public string $db; + public array $request_data; + + public ?string $campaign; + + // + public int $step = 0; public array $steps = [ Setup::class, Authentication::class, - RFF::class, Example::class, ]; @@ -67,6 +72,8 @@ class Purchase extends Component $this->context = [ 'quantity' => 1, + 'request_data' => $this->request_data, + 'campaign' => $this->campaign, ]; } From 4b971157c1bb5c392170bd293019dda95299a400 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Fri, 9 Feb 2024 17:53:41 +0100 Subject: [PATCH 012/188] Add BillingPortal Example component --- app/Livewire/BillingPortal/Example.php | 33 ++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 app/Livewire/BillingPortal/Example.php diff --git a/app/Livewire/BillingPortal/Example.php b/app/Livewire/BillingPortal/Example.php new file mode 100644 index 000000000000..00cdba32fe2e --- /dev/null +++ b/app/Livewire/BillingPortal/Example.php @@ -0,0 +1,33 @@ +dispatch('purchase.context', property: 'quantity', value: 1); + $this->dispatch('purchase.next'); + } + + public function render() + { + return <<<'HTML' +
This is step after auth. Currently logged in user is {{ $context['contact']['email'] }}.
+ HTML; + } +} From 4013437115b713f9404a50b1ceb7653e61e99793 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Fri, 9 Feb 2024 17:53:44 +0100 Subject: [PATCH 013/188] Refactor authentication logic in Authentication component --- app/Livewire/BillingPortal/Authentication.php | 148 ++++++++++++++++-- 1 file changed, 139 insertions(+), 9 deletions(-) diff --git a/app/Livewire/BillingPortal/Authentication.php b/app/Livewire/BillingPortal/Authentication.php index fa31d4515b2f..edc7fb9c69c0 100644 --- a/app/Livewire/BillingPortal/Authentication.php +++ b/app/Livewire/BillingPortal/Authentication.php @@ -12,49 +12,179 @@ namespace App\Livewire\BillingPortal; +use App\DataMapper\ClientSettings; +use App\Factory\ClientFactory; use App\Jobs\Mail\NinjaMailerJob; use App\Jobs\Mail\NinjaMailerObject; use App\Mail\Subscription\OtpCode; use App\Models\ClientContact; use App\Models\Subscription; +use App\Repositories\ClientContactRepository; +use App\Repositories\ClientRepository; use Livewire\Component; +use Illuminate\Support\Str; class Authentication extends Component { public Subscription $subscription; - public string $email; + public array $context; + + public ?string $email; + + public ?string $password; + + public ?int $otp; public array $state = [ - 'code' => false, + 'otp' => true, // Use as preference. E-mail/password or OTP. + 'login_form' => false, + 'otp_form' => false, + 'initial_completed' => false, ]; - public function authenticate() + public function initial() { $this->validateOnly('email', ['email' => 'required|bail|email:rfc']); - $code = rand(100000, 999999); - $hash = sprintf('subscriptions:otp:%s', $code); + $this->state['initial_completed'] = true; - cache()->put($hash, $code, ttl: 120); + if ($this->state['otp']) { + return $this->withOtp(); + } + + return $this->withPassword(); + } + + public function withPassword() + { + $contact = ClientContact::where('email', $this->email) + ->where('company_id', $this->subscription->company_id) + ->first(); + + if ($contact) { + return $this->state['login_form'] = true; + } + + $this->state['login_form'] = false; + + $contact = $this->createClientContact(); + + auth()->guard('contact')->loginUsingId($contact->id, true); + + $this->dispatch('purchase.context', property: 'contact', value: $contact); + $this->dispatch('purchase.next'); + } + + public function handlePassword() + { + $this->validate([ + 'email' => 'required|bail|email:rfc', + 'password' => 'required', + ]); + + $attempt = auth()->guard('contact')->attempt([ + 'email' => $this->email, + 'password' => $this->password, + 'company_id' => $this->subscription->company_id, + ]); + + if ($attempt) { + $this->dispatch('purchase.next'); + } + + session()->flash('message', 'These credentials do not match our records.'); + } + + public function withOtp() + { + $code = rand(100000, 999999); + $email_hash = "subscriptions:otp:{$this->email}"; + + cache()->put($email_hash, $code, 120); $cc = new ClientContact(); $cc->email = $this->email; $nmo = new NinjaMailerObject(); - $nmo->mailable = new OtpCode($this->subscription->company, $this->contact, $code); + $nmo->mailable = new OtpCode($this->subscription->company, $this->context['contact'] ?? null, $code); $nmo->company = $this->subscription->company; $nmo->settings = $this->subscription->company->settings; $nmo->to_user = $cc; - + NinjaMailerJob::dispatch($nmo); - $this->state['code'] = true; + if (app()->environment('local')) { + session()->flash('message', "[dev]: Your OTP is: {$code}"); + } + + $this->state['otp_form'] = true; + } + + public function handleOtp() + { + $this->validate([ + 'otp' => 'required|numeric|digits:6', + ]); + + $code = cache()->get("subscriptions:otp:{$this->email}"); + + if ($this->otp !== $code) { + $errors = $this->getErrorBag(); + $errors->add('otp', ctrans('texts.invalid_code')); + + return; + } + + $contact = ClientContact::where('email', $this->email) + ->where('company_id', $this->subscription->company_id) + ->first(); + + if ($contact) { + auth()->guard('contact')->loginUsingId($contact->id, true); + + $this->dispatch('purchase.context', property: 'contact', value: $contact); + $this->dispatch('purchase.next'); + + return; + } + + $contact = $this->createClientContact(); + + auth()->guard('contact')->loginUsingId($contact->id, true); + + $this->dispatch('purchase.context', property: 'contact', value: $contact); + $this->dispatch('purchase.next'); + } + + private function createClientContact() + { + $company = $this->subscription->company; + $user = $this->subscription->user; + $user->setCompany($company); + + $client_repo = new ClientRepository(new ClientContactRepository()); + $data = [ + 'name' => '', + 'group_settings_id' => $this->subscription->group_id, + 'contacts' => [ + ['email' => $this->email], + ], + 'client_hash' => Str::random(40), + 'settings' => ClientSettings::defaults(), + ]; + + $client = $client_repo->save($data, ClientFactory::create($company->id, $user->id)); + + $contact = $client->fresh()->contacts()->first(); + + return $contact; } public function mount() { if (auth()->guard('contact')->check()) { + $this->dispatch('purchase.context', property: 'contact', value: auth()->guard('contact')->user()); $this->dispatch('purchase.next'); } } From 5bac6253f9bce9db69055680462e570d0b2c8208 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Fri, 9 Feb 2024 17:54:00 +0100 Subject: [PATCH 014/188] Assets build --- public/build/assets/app-01291e40.js | 109 ---------------------------- public/build/assets/app-688ad9ec.js | 109 ++++++++++++++++++++++++++++ public/build/manifest.json | 2 +- 3 files changed, 110 insertions(+), 110 deletions(-) delete mode 100644 public/build/assets/app-01291e40.js create mode 100644 public/build/assets/app-688ad9ec.js diff --git a/public/build/assets/app-01291e40.js b/public/build/assets/app-01291e40.js deleted file mode 100644 index 9dc720821003..000000000000 --- a/public/build/assets/app-01291e40.js +++ /dev/null @@ -1,109 +0,0 @@ -import{A as ll}from"./index-08e160a7.js";import{c as Ut,g as ul}from"./_commonjsHelpers-725317a4.js";var cl={visa:{niceType:"Visa",type:"visa",patterns:[4],gaps:[4,8,12],lengths:[16,18,19],code:{name:"CVV",size:3}},mastercard:{niceType:"Mastercard",type:"mastercard",patterns:[[51,55],[2221,2229],[223,229],[23,26],[270,271],2720],gaps:[4,8,12],lengths:[16],code:{name:"CVC",size:3}},"american-express":{niceType:"American Express",type:"american-express",patterns:[34,37],gaps:[4,10],lengths:[15],code:{name:"CID",size:4}},"diners-club":{niceType:"Diners Club",type:"diners-club",patterns:[[300,305],36,38,39],gaps:[4,10],lengths:[14,16,19],code:{name:"CVV",size:3}},discover:{niceType:"Discover",type:"discover",patterns:[6011,[644,649],65],gaps:[4,8,12],lengths:[16,19],code:{name:"CID",size:3}},jcb:{niceType:"JCB",type:"jcb",patterns:[2131,1800,[3528,3589]],gaps:[4,8,12],lengths:[16,17,18,19],code:{name:"CVV",size:3}},unionpay:{niceType:"UnionPay",type:"unionpay",patterns:[620,[624,626],[62100,62182],[62184,62187],[62185,62197],[62200,62205],[622010,622999],622018,[622019,622999],[62207,62209],[622126,622925],[623,626],6270,6272,6276,[627700,627779],[627781,627799],[6282,6289],6291,6292,810,[8110,8131],[8132,8151],[8152,8163],[8164,8171]],gaps:[4,8,12],lengths:[14,15,16,17,18,19],code:{name:"CVN",size:3}},maestro:{niceType:"Maestro",type:"maestro",patterns:[493698,[5e5,504174],[504176,506698],[506779,508999],[56,59],63,67,6],gaps:[4,8,12],lengths:[12,13,14,15,16,17,18,19],code:{name:"CVC",size:3}},elo:{niceType:"Elo",type:"elo",patterns:[401178,401179,438935,457631,457632,431274,451416,457393,504175,[506699,506778],[509e3,509999],627780,636297,636368,[650031,650033],[650035,650051],[650405,650439],[650485,650538],[650541,650598],[650700,650718],[650720,650727],[650901,650978],[651652,651679],[655e3,655019],[655021,655058]],gaps:[4,8,12],lengths:[16],code:{name:"CVE",size:3}},mir:{niceType:"Mir",type:"mir",patterns:[[2200,2204]],gaps:[4,8,12],lengths:[16,17,18,19],code:{name:"CVP2",size:3}},hiper:{niceType:"Hiper",type:"hiper",patterns:[637095,63737423,63743358,637568,637599,637609,637612],gaps:[4,8,12],lengths:[16],code:{name:"CVC",size:3}},hipercard:{niceType:"Hipercard",type:"hipercard",patterns:[606282],gaps:[4,8,12],lengths:[16],code:{name:"CVC",size:3}}},fl=cl,Qn={},mn={};Object.defineProperty(mn,"__esModule",{value:!0});mn.clone=void 0;function dl(e){return e?JSON.parse(JSON.stringify(e)):null}mn.clone=dl;var Zn={};Object.defineProperty(Zn,"__esModule",{value:!0});Zn.matches=void 0;function pl(e,r,n){var a=String(r).length,s=e.substr(0,a),u=parseInt(s,10);return r=parseInt(String(r).substr(0,s.length),10),n=parseInt(String(n).substr(0,s.length),10),u>=r&&u<=n}function hl(e,r){return r=String(r),r.substring(0,e.length)===e.substring(0,r.length)}function gl(e,r){return Array.isArray(r)?pl(e,r[0],r[1]):hl(e,r)}Zn.matches=gl;Object.defineProperty(Qn,"__esModule",{value:!0});Qn.addMatchingCardsToResults=void 0;var ml=mn,vl=Zn;function yl(e,r,n){var a,s;for(a=0;a=s&&(b.matchStrength=s),n.push(b);break}}}Qn.addMatchingCardsToResults=yl;var ei={};Object.defineProperty(ei,"__esModule",{value:!0});ei.isValidInputType=void 0;function bl(e){return typeof e=="string"||e instanceof String}ei.isValidInputType=bl;var ti={};Object.defineProperty(ti,"__esModule",{value:!0});ti.findBestMatch=void 0;function _l(e){var r=e.filter(function(n){return n.matchStrength}).length;return r>0&&r===e.length}function wl(e){return _l(e)?e.reduce(function(r,n){return!r||Number(r.matchStrength)Al?fn(!1,!1):Cl.test(e)?fn(!1,!0):fn(!0,!0)}ri.cardholderName=Tl;var ni={};function Pl(e){for(var r=0,n=!1,a=e.length-1,s;a>=0;)s=parseInt(e.charAt(a),10),n&&(s*=2,s>9&&(s=s%10+1)),n=!n,r+=s,a--;return r%10===0}var Rl=Pl;Object.defineProperty(ni,"__esModule",{value:!0});ni.cardNumber=void 0;var Nl=Rl,qa=Oo;function hr(e,r,n){return{card:e,isPotentiallyValid:r,isValid:n}}function Ml(e,r){r===void 0&&(r={});var n,a,s;if(typeof e!="string"&&typeof e!="number")return hr(null,!1,!1);var u=String(e).replace(/-|\s/g,"");if(!/^\d*$/.test(u))return hr(null,!1,!1);var b=qa(u);if(b.length===0)return hr(null,!1,!1);if(b.length!==1)return hr(null,!0,!1);var m=b[0];if(r.maxLength&&u.length>r.maxLength)return hr(m,!1,!1);m.type===qa.types.UNIONPAY&&r.luhnValidateUnionPay!==!0?a=!0:a=Nl(u),s=Math.max.apply(null,m.lengths),r.maxLength&&(s=Math.min(r.maxLength,s));for(var I=0;I4)return er(!1,!1);var m=parseInt(e,10),I=Number(String(s).substr(2,2)),U=!1;if(a===2){if(String(s).substr(0,2)===e)return er(!1,!0);n=I===m,U=m>=I&&m<=I+r}else a===4&&(n=s===m,U=m>=s&&m<=s+r);return er(U,U,n)}Vr.expirationYear=Ll;var oi={};Object.defineProperty(oi,"__esModule",{value:!0});oi.isArray=void 0;oi.isArray=Array.isArray||function(e){return Object.prototype.toString.call(e)==="[object Array]"};Object.defineProperty(ai,"__esModule",{value:!0});ai.parseDate=void 0;var jl=Vr,Il=oi;function Dl(e){var r=Number(e[0]),n;return r===0?2:r>1||r===1&&Number(e[1])>2?1:r===1?(n=e.substr(1),jl.expirationYear(n).isPotentiallyValid?1:2):e.length===5?1:e.length>5?2:1}function $l(e){var r;if(/^\d{4}-\d{1,2}$/.test(e)?r=e.split("-").reverse():/\//.test(e)?r=e.split(/\s*\/\s*/g):/\s/.test(e)&&(r=e.split(/ +/g)),Il.isArray(r))return{month:r[0]||"",year:r.slice(1).join()};var n=Dl(e),a=e.substr(0,n);return{month:a,year:e.substr(a.length)}}ai.parseDate=$l;var yn={};Object.defineProperty(yn,"__esModule",{value:!0});yn.expirationMonth=void 0;function dn(e,r,n){return{isValid:e,isPotentiallyValid:r,isValidForThisYear:n||!1}}function Fl(e){var r=new Date().getMonth()+1;if(typeof e!="string")return dn(!1,!1);if(e.replace(/\s/g,"")===""||e==="0")return dn(!1,!0);if(!/^\d*$/.test(e))return dn(!1,!1);var n=parseInt(e,10);if(isNaN(Number(e)))return dn(!1,!1);var a=n>0&&n<13;return dn(a,a,a&&n>=r)}yn.expirationMonth=Fl;var Xi=Ut&&Ut.__assign||function(){return Xi=Object.assign||function(e){for(var r,n=1,a=arguments.length;nr?e[n]:r;return r}function jr(e,r){return{isValid:e,isPotentiallyValid:r}}function Wl(e,r){return r===void 0&&(r=Co),r=r instanceof Array?r:[r],typeof e!="string"||!/^\d*$/.test(e)?jr(!1,!1):ql(r,e.length)?jr(!0,!0):e.lengthzl(r)?jr(!1,!1):jr(!0,!0)}si.cvv=Wl;var li={};Object.defineProperty(li,"__esModule",{value:!0});li.postalCode=void 0;var Kl=3;function qi(e,r){return{isValid:e,isPotentiallyValid:r}}function Jl(e,r){r===void 0&&(r={});var n=r.minLength||Kl;return typeof e!="string"?qi(!1,!1):e.lengthfunction(){return r||(0,e[To(e)[0]])((r={exports:{}}).exports,r),r.exports},pu=(e,r,n,a)=>{if(r&&typeof r=="object"||typeof r=="function")for(let s of To(r))!du.call(e,s)&&s!==n&&Ao(e,s,{get:()=>r[s],enumerable:!(a=cu(r,s))||a.enumerable});return e},Je=(e,r,n)=>(n=e!=null?uu(fu(e)):{},pu(r||!e||!e.__esModule?Ao(n,"default",{value:e,enumerable:!0}):n,e)),ft=Yt({"../alpine/packages/alpinejs/dist/module.cjs.js"(e,r){var n=Object.create,a=Object.defineProperty,s=Object.getOwnPropertyDescriptor,u=Object.getOwnPropertyNames,b=Object.getPrototypeOf,m=Object.prototype.hasOwnProperty,I=(t,i)=>function(){return i||(0,t[u(t)[0]])((i={exports:{}}).exports,i),i.exports},U=(t,i)=>{for(var o in i)a(t,o,{get:i[o],enumerable:!0})},se=(t,i,o,c)=>{if(i&&typeof i=="object"||typeof i=="function")for(let p of u(i))!m.call(t,p)&&p!==o&&a(t,p,{get:()=>i[p],enumerable:!(c=s(i,p))||c.enumerable});return t},V=(t,i,o)=>(o=t!=null?n(b(t)):{},se(i||!t||!t.__esModule?a(o,"default",{value:t,enumerable:!0}):o,t)),ce=t=>se(a({},"__esModule",{value:!0}),t),re=I({"node_modules/@vue/shared/dist/shared.cjs.js"(t){Object.defineProperty(t,"__esModule",{value:!0});function i(y,K){const Z=Object.create(null),ue=y.split(",");for(let Ue=0;Ue!!Z[Ue.toLowerCase()]:Ue=>!!Z[Ue]}var o={1:"TEXT",2:"CLASS",4:"STYLE",8:"PROPS",16:"FULL_PROPS",32:"HYDRATE_EVENTS",64:"STABLE_FRAGMENT",128:"KEYED_FRAGMENT",256:"UNKEYED_FRAGMENT",512:"NEED_PATCH",1024:"DYNAMIC_SLOTS",2048:"DEV_ROOT_FRAGMENT",[-1]:"HOISTED",[-2]:"BAIL"},c={1:"STABLE",2:"DYNAMIC",3:"FORWARDED"},p="Infinity,undefined,NaN,isFinite,isNaN,parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,BigInt",d=i(p),g=2;function x(y,K=0,Z=y.length){let ue=y.split(/(\r?\n)/);const Ue=ue.filter((bt,ct)=>ct%2===1);ue=ue.filter((bt,ct)=>ct%2===0);let rt=0;const yt=[];for(let bt=0;bt=K){for(let ct=bt-g;ct<=bt+g||Z>rt;ct++){if(ct<0||ct>=ue.length)continue;const un=ct+1;yt.push(`${un}${" ".repeat(Math.max(3-String(un).length,0))}| ${ue[ct]}`);const kr=ue[ct].length,Wn=Ue[ct]&&Ue[ct].length||0;if(ct===bt){const Lr=K-(rt-(kr+Wn)),Ui=Math.max(1,Z>rt?kr-Lr:Z-K);yt.push(" | "+" ".repeat(Lr)+"^".repeat(Ui))}else if(ct>bt){if(Z>rt){const Lr=Math.max(Math.min(Z-rt,kr),1);yt.push(" | "+"^".repeat(Lr))}rt+=kr+Wn}}break}return yt.join(` -`)}var k="itemscope,allowfullscreen,formnovalidate,ismap,nomodule,novalidate,readonly",Q=i(k),Ie=i(k+",async,autofocus,autoplay,controls,default,defer,disabled,hidden,loop,open,required,reversed,scoped,seamless,checked,muted,multiple,selected"),Ze=/[>/="'\u0009\u000a\u000c\u0020]/,$e={};function Ke(y){if($e.hasOwnProperty(y))return $e[y];const K=Ze.test(y);return K&&console.error(`unsafe attribute name: ${y}`),$e[y]=!K}var At={acceptCharset:"accept-charset",className:"class",htmlFor:"for",httpEquiv:"http-equiv"},$t=i("animation-iteration-count,border-image-outset,border-image-slice,border-image-width,box-flex,box-flex-group,box-ordinal-group,column-count,columns,flex,flex-grow,flex-positive,flex-shrink,flex-negative,flex-order,grid-row,grid-row-end,grid-row-span,grid-row-start,grid-column,grid-column-end,grid-column-span,grid-column-start,font-weight,line-clamp,line-height,opacity,order,orphans,tab-size,widows,z-index,zoom,fill-opacity,flood-opacity,stop-opacity,stroke-dasharray,stroke-dashoffset,stroke-miterlimit,stroke-opacity,stroke-width"),Se=i("accept,accept-charset,accesskey,action,align,allow,alt,async,autocapitalize,autocomplete,autofocus,autoplay,background,bgcolor,border,buffered,capture,challenge,charset,checked,cite,class,code,codebase,color,cols,colspan,content,contenteditable,contextmenu,controls,coords,crossorigin,csp,data,datetime,decoding,default,defer,dir,dirname,disabled,download,draggable,dropzone,enctype,enterkeyhint,for,form,formaction,formenctype,formmethod,formnovalidate,formtarget,headers,height,hidden,high,href,hreflang,http-equiv,icon,id,importance,integrity,ismap,itemprop,keytype,kind,label,lang,language,loading,list,loop,low,manifest,max,maxlength,minlength,media,min,multiple,muted,name,novalidate,open,optimum,pattern,ping,placeholder,poster,preload,radiogroup,readonly,referrerpolicy,rel,required,reversed,rows,rowspan,sandbox,scope,scoped,selected,shape,size,sizes,slot,span,spellcheck,src,srcdoc,srclang,srcset,start,step,style,summary,tabindex,target,title,translate,type,usemap,value,width,wrap");function He(y){if(Rt(y)){const K={};for(let Z=0;Z{if(Z){const ue=Z.split(qe);ue.length>1&&(K[ue[0].trim()]=ue[1].trim())}}),K}function Pt(y){let K="";if(!y)return K;for(const Z in y){const ue=y[Z],Ue=Z.startsWith("--")?Z:qn(Z);(cr(ue)||typeof ue=="number"&&$t(Ue))&&(K+=`${Ue}:${ue};`)}return K}function Ft(y){let K="";if(cr(y))K=y;else if(Rt(y))for(let Z=0;Z]/;function Mi(y){const K=""+y,Z=Ni.exec(K);if(!Z)return K;let ue="",Ue,rt,yt=0;for(rt=Z.index;rt||--!>|Er(Z,K))}var kn=y=>y==null?"":Bt(y)?JSON.stringify(y,ji,2):String(y),ji=(y,K)=>ur(K)?{[`Map(${K.size})`]:[...K.entries()].reduce((Z,[ue,Ue])=>(Z[`${ue} =>`]=Ue,Z),{})}:Nt(K)?{[`Set(${K.size})`]:[...K.values()]}:Bt(K)&&!Rt(K)&&!Fn(K)?String(K):K,Ii=["bigInt","optionalChaining","nullishCoalescingOperator"],rn=Object.freeze({}),nn=Object.freeze([]),an=()=>{},Or=()=>!1,Cr=/^on[^a-z]/,Ar=y=>Cr.test(y),Tr=y=>y.startsWith("onUpdate:"),Ln=Object.assign,jn=(y,K)=>{const Z=y.indexOf(K);Z>-1&&y.splice(Z,1)},In=Object.prototype.hasOwnProperty,Dn=(y,K)=>In.call(y,K),Rt=Array.isArray,ur=y=>fr(y)==="[object Map]",Nt=y=>fr(y)==="[object Set]",on=y=>y instanceof Date,sn=y=>typeof y=="function",cr=y=>typeof y=="string",Di=y=>typeof y=="symbol",Bt=y=>y!==null&&typeof y=="object",Pr=y=>Bt(y)&&sn(y.then)&&sn(y.catch),$n=Object.prototype.toString,fr=y=>$n.call(y),$i=y=>fr(y).slice(8,-1),Fn=y=>fr(y)==="[object Object]",Bn=y=>cr(y)&&y!=="NaN"&&y[0]!=="-"&&""+parseInt(y,10)===y,Hn=i(",key,ref,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted"),dr=y=>{const K=Object.create(null);return Z=>K[Z]||(K[Z]=y(Z))},Un=/-(\w)/g,Vn=dr(y=>y.replace(Un,(K,Z)=>Z?Z.toUpperCase():"")),Fi=/\B([A-Z])/g,qn=dr(y=>y.replace(Fi,"-$1").toLowerCase()),pr=dr(y=>y.charAt(0).toUpperCase()+y.slice(1)),Bi=dr(y=>y?`on${pr(y)}`:""),ln=(y,K)=>y!==K&&(y===y||K===K),Hi=(y,K)=>{for(let Z=0;Z{Object.defineProperty(y,K,{configurable:!0,enumerable:!1,value:Z})},Nr=y=>{const K=parseFloat(y);return isNaN(K)?y:K},Mr,zn=()=>Mr||(Mr=typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:typeof global<"u"?global:{});t.EMPTY_ARR=nn,t.EMPTY_OBJ=rn,t.NO=Or,t.NOOP=an,t.PatchFlagNames=o,t.babelParserDefaultPlugins=Ii,t.camelize=Vn,t.capitalize=pr,t.def=Rr,t.escapeHtml=Mi,t.escapeHtmlComment=ki,t.extend=Ln,t.generateCodeFrame=x,t.getGlobalThis=zn,t.hasChanged=ln,t.hasOwn=Dn,t.hyphenate=qn,t.invokeArrayFns=Hi,t.isArray=Rt,t.isBooleanAttr=Ie,t.isDate=on,t.isFunction=sn,t.isGloballyWhitelisted=d,t.isHTMLTag=xr,t.isIntegerKey=Bn,t.isKnownAttr=Se,t.isMap=ur,t.isModelListener=Tr,t.isNoUnitNumericStyleProp=$t,t.isObject=Bt,t.isOn=Ar,t.isPlainObject=Fn,t.isPromise=Pr,t.isReservedProp=Hn,t.isSSRSafeAttrName=Ke,t.isSVGTag=Ri,t.isSet=Nt,t.isSpecialBooleanAttr=Q,t.isString=cr,t.isSymbol=Di,t.isVoidTag=Sr,t.looseEqual=Er,t.looseIndexOf=Mn,t.makeMap=i,t.normalizeClass=Ft,t.normalizeStyle=He,t.objectToString=$n,t.parseStringStyle=vt,t.propsToAttrMap=At,t.remove=jn,t.slotFlagsText=c,t.stringifyStyle=Pt,t.toDisplayString=kn,t.toHandlerKey=Bi,t.toNumber=Nr,t.toRawType=$i,t.toTypeString=fr}}),E=I({"node_modules/@vue/shared/index.js"(t,i){i.exports=re()}}),v=I({"node_modules/@vue/reactivity/dist/reactivity.cjs.js"(t){Object.defineProperty(t,"__esModule",{value:!0});var i=E(),o=new WeakMap,c=[],p,d=Symbol("iterate"),g=Symbol("Map key iterate");function x(l){return l&&l._isEffect===!0}function k(l,T=i.EMPTY_OBJ){x(l)&&(l=l.raw);const R=Ze(l,T);return T.lazy||R(),R}function Q(l){l.active&&($e(l),l.options.onStop&&l.options.onStop(),l.active=!1)}var Ie=0;function Ze(l,T){const R=function(){if(!R.active)return l();if(!c.includes(R)){$e(R);try{return Se(),c.push(R),p=R,l()}finally{c.pop(),He(),p=c[c.length-1]}}};return R.id=Ie++,R.allowRecurse=!!T.allowRecurse,R._isEffect=!0,R.active=!0,R.raw=l,R.deps=[],R.options=T,R}function $e(l){const{deps:T}=l;if(T.length){for(let R=0;R{gt&>.forEach(Mt=>{(Mt!==p||Mt.allowRecurse)&&it.add(Mt)})};if(T==="clear")ke.forEach(_t);else if(R==="length"&&i.isArray(l))ke.forEach((gt,Mt)=>{(Mt==="length"||Mt>=ne)&&_t(gt)});else switch(R!==void 0&&_t(ke.get(R)),T){case"add":i.isArray(l)?i.isIntegerKey(R)&&_t(ke.get("length")):(_t(ke.get(d)),i.isMap(l)&&_t(ke.get(g)));break;case"delete":i.isArray(l)||(_t(ke.get(d)),i.isMap(l)&&_t(ke.get(g)));break;case"set":i.isMap(l)&&_t(ke.get(d));break}const cn=gt=>{gt.options.onTrigger&>.options.onTrigger({effect:gt,target:l,key:R,type:T,newValue:ne,oldValue:J,oldTarget:ge}),gt.options.scheduler?gt.options.scheduler(gt):gt()};it.forEach(cn)}var vt=i.makeMap("__proto__,__v_isRef,__isVue"),Pt=new Set(Object.getOwnPropertyNames(Symbol).map(l=>Symbol[l]).filter(i.isSymbol)),Ft=Sr(),wr=Sr(!1,!0),en=Sr(!0),tn=Sr(!0,!0),xr=Ri();function Ri(){const l={};return["includes","indexOf","lastIndexOf"].forEach(T=>{l[T]=function(...R){const ne=y(this);for(let ge=0,ke=this.length;ge{l[T]=function(...R){$t();const ne=y(this)[T].apply(this,R);return He(),ne}}),l}function Sr(l=!1,T=!1){return function(ne,J,ge){if(J==="__v_isReactive")return!l;if(J==="__v_isReadonly")return l;if(J==="__v_raw"&&ge===(l?T?Vn:Un:T?dr:Hn).get(ne))return ne;const ke=i.isArray(ne);if(!l&&ke&&i.hasOwn(xr,J))return Reflect.get(xr,J,ge);const it=Reflect.get(ne,J,ge);return(i.isSymbol(J)?Pt.has(J):vt(J))||(l||Le(ne,"get",J),T)?it:ue(it)?!ke||!i.isIntegerKey(J)?it.value:it:i.isObject(it)?l?ln(it):pr(it):it}}var Ni=Nn(),Mi=Nn(!0);function Nn(l=!1){return function(R,ne,J,ge){let ke=R[ne];if(!l&&(J=y(J),ke=y(ke),!i.isArray(R)&&ue(ke)&&!ue(J)))return ke.value=J,!0;const it=i.isArray(R)&&i.isIntegerKey(ne)?Number(ne)i.isObject(l)?pr(l):l,nn=l=>i.isObject(l)?ln(l):l,an=l=>l,Or=l=>Reflect.getPrototypeOf(l);function Cr(l,T,R=!1,ne=!1){l=l.__v_raw;const J=y(l),ge=y(T);T!==ge&&!R&&Le(J,"get",T),!R&&Le(J,"get",ge);const{has:ke}=Or(J),it=ne?an:R?nn:rn;if(ke.call(J,T))return it(l.get(T));if(ke.call(J,ge))return it(l.get(ge));l!==J&&l.get(T)}function Ar(l,T=!1){const R=this.__v_raw,ne=y(R),J=y(l);return l!==J&&!T&&Le(ne,"has",l),!T&&Le(ne,"has",J),l===J?R.has(l):R.has(l)||R.has(J)}function Tr(l,T=!1){return l=l.__v_raw,!T&&Le(y(l),"iterate",d),Reflect.get(l,"size",l)}function Ln(l){l=y(l);const T=y(this);return Or(T).has.call(T,l)||(T.add(l),qe(T,"add",l,l)),this}function jn(l,T){T=y(T);const R=y(this),{has:ne,get:J}=Or(R);let ge=ne.call(R,l);ge?Bn(R,ne,l):(l=y(l),ge=ne.call(R,l));const ke=J.call(R,l);return R.set(l,T),ge?i.hasChanged(T,ke)&&qe(R,"set",l,T,ke):qe(R,"add",l,T),this}function In(l){const T=y(this),{has:R,get:ne}=Or(T);let J=R.call(T,l);J?Bn(T,R,l):(l=y(l),J=R.call(T,l));const ge=ne?ne.call(T,l):void 0,ke=T.delete(l);return J&&qe(T,"delete",l,void 0,ge),ke}function Dn(){const l=y(this),T=l.size!==0,R=i.isMap(l)?new Map(l):new Set(l),ne=l.clear();return T&&qe(l,"clear",void 0,void 0,R),ne}function Rt(l,T){return function(ne,J){const ge=this,ke=ge.__v_raw,it=y(ke),_t=T?an:l?nn:rn;return!l&&Le(it,"iterate",d),ke.forEach((cn,gt)=>ne.call(J,_t(cn),_t(gt),ge))}}function ur(l,T,R){return function(...ne){const J=this.__v_raw,ge=y(J),ke=i.isMap(ge),it=l==="entries"||l===Symbol.iterator&&ke,_t=l==="keys"&&ke,cn=J[l](...ne),gt=R?an:T?nn:rn;return!T&&Le(ge,"iterate",_t?g:d),{next(){const{value:Mt,done:Vi}=cn.next();return Vi?{value:Mt,done:Vi}:{value:it?[gt(Mt[0]),gt(Mt[1])]:gt(Mt),done:Vi}},[Symbol.iterator](){return this}}}}function Nt(l){return function(...T){{const R=T[0]?`on key "${T[0]}" `:"";console.warn(`${i.capitalize(l)} operation ${R}failed: target is readonly.`,y(this))}return l==="delete"?!1:this}}function on(){const l={get(ge){return Cr(this,ge)},get size(){return Tr(this)},has:Ar,add:Ln,set:jn,delete:In,clear:Dn,forEach:Rt(!1,!1)},T={get(ge){return Cr(this,ge,!1,!0)},get size(){return Tr(this)},has:Ar,add:Ln,set:jn,delete:In,clear:Dn,forEach:Rt(!1,!0)},R={get(ge){return Cr(this,ge,!0)},get size(){return Tr(this,!0)},has(ge){return Ar.call(this,ge,!0)},add:Nt("add"),set:Nt("set"),delete:Nt("delete"),clear:Nt("clear"),forEach:Rt(!0,!1)},ne={get(ge){return Cr(this,ge,!0,!0)},get size(){return Tr(this,!0)},has(ge){return Ar.call(this,ge,!0)},add:Nt("add"),set:Nt("set"),delete:Nt("delete"),clear:Nt("clear"),forEach:Rt(!0,!0)};return["keys","values","entries",Symbol.iterator].forEach(ge=>{l[ge]=ur(ge,!1,!1),R[ge]=ur(ge,!0,!1),T[ge]=ur(ge,!1,!0),ne[ge]=ur(ge,!0,!0)}),[l,R,T,ne]}var[sn,cr,Di,Bt]=on();function Pr(l,T){const R=T?l?Bt:Di:l?cr:sn;return(ne,J,ge)=>J==="__v_isReactive"?!l:J==="__v_isReadonly"?l:J==="__v_raw"?ne:Reflect.get(i.hasOwn(R,J)&&J in ne?R:ne,J,ge)}var $n={get:Pr(!1,!1)},fr={get:Pr(!1,!0)},$i={get:Pr(!0,!1)},Fn={get:Pr(!0,!0)};function Bn(l,T,R){const ne=y(R);if(ne!==R&&T.call(l,ne)){const J=i.toRawType(l);console.warn(`Reactive ${J} contains both the raw and reactive versions of the same object${J==="Map"?" as keys":""}, which can lead to inconsistencies. Avoid differentiating between the raw and reactive versions of an object and only use the reactive version if possible.`)}}var Hn=new WeakMap,dr=new WeakMap,Un=new WeakMap,Vn=new WeakMap;function Fi(l){switch(l){case"Object":case"Array":return 1;case"Map":case"Set":case"WeakMap":case"WeakSet":return 2;default:return 0}}function qn(l){return l.__v_skip||!Object.isExtensible(l)?0:Fi(i.toRawType(l))}function pr(l){return l&&l.__v_isReadonly?l:Rr(l,!1,Mn,$n,Hn)}function Bi(l){return Rr(l,!1,ji,fr,dr)}function ln(l){return Rr(l,!0,kn,$i,Un)}function Hi(l){return Rr(l,!0,Ii,Fn,Vn)}function Rr(l,T,R,ne,J){if(!i.isObject(l))return console.warn(`value cannot be made reactive: ${String(l)}`),l;if(l.__v_raw&&!(T&&l.__v_isReactive))return l;const ge=J.get(l);if(ge)return ge;const ke=qn(l);if(ke===0)return l;const it=new Proxy(l,ke===2?ne:R);return J.set(l,it),it}function Nr(l){return Mr(l)?Nr(l.__v_raw):!!(l&&l.__v_isReactive)}function Mr(l){return!!(l&&l.__v_isReadonly)}function zn(l){return Nr(l)||Mr(l)}function y(l){return l&&y(l.__v_raw)||l}function K(l){return i.def(l,"__v_skip",!0),l}var Z=l=>i.isObject(l)?pr(l):l;function ue(l){return!!(l&&l.__v_isRef===!0)}function Ue(l){return bt(l)}function rt(l){return bt(l,!0)}var yt=class{constructor(l,T=!1){this._shallow=T,this.__v_isRef=!0,this._rawValue=T?l:y(l),this._value=T?l:Z(l)}get value(){return Le(y(this),"get","value"),this._value}set value(l){l=this._shallow?l:y(l),i.hasChanged(l,this._rawValue)&&(this._rawValue=l,this._value=this._shallow?l:Z(l),qe(y(this),"set","value",l))}};function bt(l,T=!1){return ue(l)?l:new yt(l,T)}function ct(l){qe(y(l),"set","value",l.value)}function un(l){return ue(l)?l.value:l}var kr={get:(l,T,R)=>un(Reflect.get(l,T,R)),set:(l,T,R,ne)=>{const J=l[T];return ue(J)&&!ue(R)?(J.value=R,!0):Reflect.set(l,T,R,ne)}};function Wn(l){return Nr(l)?l:new Proxy(l,kr)}var Lr=class{constructor(l){this.__v_isRef=!0;const{get:T,set:R}=l(()=>Le(this,"get","value"),()=>qe(this,"set","value"));this._get=T,this._set=R}get value(){return this._get()}set value(l){this._set(l)}};function Ui(l){return new Lr(l)}function il(l){zn(l)||console.warn("toRefs() expects a reactive object but received a plain one.");const T=i.isArray(l)?new Array(l.length):{};for(const R in l)T[R]=Va(l,R);return T}var al=class{constructor(l,T){this._object=l,this._key=T,this.__v_isRef=!0}get value(){return this._object[this._key]}set value(l){this._object[this._key]=l}};function Va(l,T){return ue(l[T])?l[T]:new al(l,T)}var ol=class{constructor(l,T,R){this._setter=T,this._dirty=!0,this.__v_isRef=!0,this.effect=k(l,{lazy:!0,scheduler:()=>{this._dirty||(this._dirty=!0,qe(y(this),"set","value"))}}),this.__v_isReadonly=R}get value(){const l=y(this);return l._dirty&&(l._value=this.effect(),l._dirty=!1),Le(l,"get","value"),l._value}set value(l){this._setter(l)}};function sl(l){let T,R;return i.isFunction(l)?(T=l,R=()=>{console.warn("Write operation failed: computed value is readonly")}):(T=l.get,R=l.set),new ol(T,R,i.isFunction(l)||!l.set)}t.ITERATE_KEY=d,t.computed=sl,t.customRef=Ui,t.effect=k,t.enableTracking=Se,t.isProxy=zn,t.isReactive=Nr,t.isReadonly=Mr,t.isRef=ue,t.markRaw=K,t.pauseTracking=$t,t.proxyRefs=Wn,t.reactive=pr,t.readonly=ln,t.ref=Ue,t.resetTracking=He,t.shallowReactive=Bi,t.shallowReadonly=Hi,t.shallowRef=rt,t.stop=Q,t.toRaw=y,t.toRef=Va,t.toRefs=il,t.track=Le,t.trigger=qe,t.triggerRef=ct,t.unref=un}}),_=I({"node_modules/@vue/reactivity/index.js"(t,i){i.exports=v()}}),O={};U(O,{default:()=>nl}),r.exports=ce(O);var M=!1,j=!1,H=[],Te=-1;function D(t){C(t)}function C(t){H.includes(t)||H.push(t),ee()}function N(t){let i=H.indexOf(t);i!==-1&&i>Te&&H.splice(i,1)}function ee(){!j&&!M&&(M=!0,queueMicrotask(be))}function be(){M=!1,j=!0;for(let t=0;tt.effect(i,{scheduler:o=>{Ye?D(o):o()}}),Ge=t.raw}function ht(t){X=t}function xt(t){let i=()=>{};return[c=>{let p=X(c);return t._x_effects||(t._x_effects=new Set,t._x_runEffects=()=>{t._x_effects.forEach(d=>d())}),t._x_effects.add(p),i=()=>{p!==void 0&&(t._x_effects.delete(p),Re(p))},p},()=>{i()}]}function Et(t,i){let o=!0,c,p=X(()=>{let d=t();JSON.stringify(d),o?c=d:queueMicrotask(()=>{i(d,c),c=d}),o=!1});return()=>Re(p)}function we(t,i,o={}){t.dispatchEvent(new CustomEvent(i,{detail:o,bubbles:!0,composed:!0,cancelable:!0}))}function le(t,i){if(typeof ShadowRoot=="function"&&t instanceof ShadowRoot){Array.from(t.children).forEach(p=>le(p,i));return}let o=!1;if(i(t,()=>o=!0),o)return;let c=t.firstElementChild;for(;c;)le(c,i),c=c.nextElementSibling}function Ee(t,...i){console.warn(`Alpine Warning: ${t}`,...i)}var me=!1;function ve(){me&&Ee("Alpine has already been initialized on this page. Calling Alpine.start() more than once can cause problems."),me=!0,document.body||Ee("Unable to initialize. Trying to load Alpine before `` is available. Did you forget to add `defer` in Alpine's ` +@endpush From f119c269054b67855feb1bb3e2b0f1d372511982 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Mon, 19 Feb 2024 18:16:32 +0100 Subject: [PATCH 051/188] Fix cost calculation in SubscriptionCalculator --- app/Services/Subscription/SubscriptionCalculator.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/Services/Subscription/SubscriptionCalculator.php b/app/Services/Subscription/SubscriptionCalculator.php index be968dba1452..4135e3c0297b 100644 --- a/app/Services/Subscription/SubscriptionCalculator.php +++ b/app/Services/Subscription/SubscriptionCalculator.php @@ -70,11 +70,10 @@ class SubscriptionCalculator }); return collect($items)->map(function ($item){ - $line_item = new InvoiceItem(); $line_item->product_key = $item['product']['product_key']; $line_item->quantity = (float) $item['quantity']; - $line_item->cost = (float) ['product']['price']; + $line_item->cost = (float) $item['product']['price']; $line_item->notes = $item['product']['notes']; return $line_item; From 9acdb89ab80bf0ff799410cb59353452b2222d58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Mon, 19 Feb 2024 18:16:37 +0100 Subject: [PATCH 052/188] Refactor purchase context and dispatch purchase next --- .../BillingPortal/Payments/Methods.php | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/app/Livewire/BillingPortal/Payments/Methods.php b/app/Livewire/BillingPortal/Payments/Methods.php index e0dc3c275bd4..895d09d75b71 100644 --- a/app/Livewire/BillingPortal/Payments/Methods.php +++ b/app/Livewire/BillingPortal/Payments/Methods.php @@ -46,21 +46,21 @@ class Methods extends Component { /** @var \App\Models\ClientContact $contact */ $contact = auth()->guard('contact')->user(); - + $this->dispatch('purchase.context', property: 'client_id', value: $contact->client->hashed_id); - + $this->context['client_id'] = $contact->client->hashed_id; nlog($this->context); $invoice = $this->subscription - ->calc() - ->buildPurchaseInvoice($this->context) - ->service() - ->fillDefaults() - ->adjustInventory() - ->save(); - + ->calc() + ->buildPurchaseInvoice($this->context) + ->service() + ->fillDefaults() + ->adjustInventory() + ->save(); + Cache::put($this->context['hash'], [ 'subscription_id' => $this->subscription->hashed_id, 'email' => $contact->email, @@ -71,7 +71,16 @@ class Methods extends Component 'bundle' => $this->context['bundle'], ], now()->addMinutes(60)); + $payable_amount = $invoice->partial > 0 + ? \App\Utils\Number::formatValue($invoice->partial, $invoice->client->currency()) + : \App\Utils\Number::formatValue($invoice->balance, $invoice->client->currency()); + $this->dispatch('purchase.context', property: 'form.company_gateway_id', value: $company_gateway_id); + $this->dispatch('purchase.context', property: 'form.payment_method_id', value: $gateway_type_id); + $this->dispatch('purchase.context', property: 'form.invoice_hashed_id', value: $invoice->hashed_id); + $this->dispatch('purchase.context', property: 'form.payable_amount', value: $payable_amount); + + $this->dispatch('purchase.next'); } public function render() From 7d5a8f57873996a9da3793c430abac2765850b96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Mon, 19 Feb 2024 18:16:40 +0100 Subject: [PATCH 053/188] Add BillingPortal Submit component --- app/Livewire/BillingPortal/Submit.php | 34 +++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 app/Livewire/BillingPortal/Submit.php diff --git a/app/Livewire/BillingPortal/Submit.php b/app/Livewire/BillingPortal/Submit.php new file mode 100644 index 000000000000..20b2070c2e70 --- /dev/null +++ b/app/Livewire/BillingPortal/Submit.php @@ -0,0 +1,34 @@ +dispatch('purchase.submit'); + } + + public function render() + { + return <<<'HTML' +
+ HTML; + } +} From 09af7474e4086ff83d227cad7116146fc20890c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Mon, 19 Feb 2024 18:16:44 +0100 Subject: [PATCH 054/188] Add Submit class to Purchase component --- app/Livewire/BillingPortal/Purchase.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/Livewire/BillingPortal/Purchase.php b/app/Livewire/BillingPortal/Purchase.php index 7f6e1a1ab4ce..a50a944313d1 100644 --- a/app/Livewire/BillingPortal/Purchase.php +++ b/app/Livewire/BillingPortal/Purchase.php @@ -43,6 +43,7 @@ class Purchase extends Component Authentication::class, RFF::class, Methods::class, + Submit::class, ]; public string $id; From f3316277c3220ff65db6560ccb757698c985a555 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 20 Feb 2024 15:33:10 +1100 Subject: [PATCH 055/188] Fixes for line items --- app/Services/Subscription/SubscriptionCalculator.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/Services/Subscription/SubscriptionCalculator.php b/app/Services/Subscription/SubscriptionCalculator.php index 4135e3c0297b..223fb5b67c36 100644 --- a/app/Services/Subscription/SubscriptionCalculator.php +++ b/app/Services/Subscription/SubscriptionCalculator.php @@ -69,6 +69,9 @@ class SubscriptionCalculator return $product['quantity'] >= 1; }); + nlog("items"); + nlog($items); + return collect($items)->map(function ($item){ $line_item = new InvoiceItem(); $line_item->product_key = $item['product']['product_key']; @@ -78,7 +81,7 @@ class SubscriptionCalculator return $line_item; - })->toArray(); + })->flatten()->toArray(); } From c8f7e9e25014378f49207450094dc36caadd86fc Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 20 Feb 2024 16:05:00 +1100 Subject: [PATCH 056/188] Working on payment submissions --- .../BillingPortal/Payments/Methods.php | 6 ++--- app/Livewire/BillingPortal/Submit.php | 23 +++++++++++++++++++ app/Services/ClientPortal/InstantPayment.php | 1 + .../Subscription/SubscriptionCalculator.php | 15 +++++++----- 4 files changed, 36 insertions(+), 9 deletions(-) diff --git a/app/Livewire/BillingPortal/Payments/Methods.php b/app/Livewire/BillingPortal/Payments/Methods.php index 895d09d75b71..1d0eba9259e6 100644 --- a/app/Livewire/BillingPortal/Payments/Methods.php +++ b/app/Livewire/BillingPortal/Payments/Methods.php @@ -29,7 +29,6 @@ class Methods extends Component { if (auth()->guard('contact')->guest()) { $this->dispatch('purchase.forward', component: Authentication::class); - return; } @@ -51,12 +50,11 @@ class Methods extends Component $this->context['client_id'] = $contact->client->hashed_id; - nlog($this->context); - $invoice = $this->subscription ->calc() ->buildPurchaseInvoice($this->context) ->service() + ->markSent() ->fillDefaults() ->adjustInventory() ->save(); @@ -75,6 +73,8 @@ class Methods extends Component ? \App\Utils\Number::formatValue($invoice->partial, $invoice->client->currency()) : \App\Utils\Number::formatValue($invoice->balance, $invoice->client->currency()); + nlog($invoice->toArray()); + $this->dispatch('purchase.context', property: 'form.company_gateway_id', value: $company_gateway_id); $this->dispatch('purchase.context', property: 'form.payment_method_id', value: $gateway_type_id); $this->dispatch('purchase.context', property: 'form.invoice_hashed_id', value: $invoice->hashed_id); diff --git a/app/Livewire/BillingPortal/Submit.php b/app/Livewire/BillingPortal/Submit.php index 20b2070c2e70..cc9d03520a9f 100644 --- a/app/Livewire/BillingPortal/Submit.php +++ b/app/Livewire/BillingPortal/Submit.php @@ -13,6 +13,7 @@ namespace App\Livewire\BillingPortal; use Livewire\Component; +use App\Services\ClientPortal\InstantPayment; class Submit extends Component { @@ -21,6 +22,28 @@ class Submit extends Component public function mount() { // This is right place to check if everything is set up correctly. + // + // + // + // + // + // + //hash + //sidebar = h + + // $request = new \Illuminate\Http\Request([ + // 'sidebar' => 'hidden', + // 'hash' => $this->context['hash'], + // 'action' => 'payment', + // 'invoices[]' => $this->context['form']['invoice_hashed_id'], + // 'payable_invoices[0][amount]' => $this->context['form']['payable_amount'], + // 'payable_invoices[0][invoice_id]' => $this->context['form']['invoice_hashed_id'], + // 'company_gateway_id' => $this->context['form']['company_gateway_id'], + // 'payment_method_id' => $this->context['form']['payment_method_id'], + // ]); + + // return (new InstantPayment($request))->run(); + $this->dispatch('purchase.submit'); } diff --git a/app/Services/ClientPortal/InstantPayment.php b/app/Services/ClientPortal/InstantPayment.php index 8868a3df8ddc..3b5451cb54aa 100644 --- a/app/Services/ClientPortal/InstantPayment.php +++ b/app/Services/ClientPortal/InstantPayment.php @@ -46,6 +46,7 @@ class InstantPayment { nlog($this->request->all()); + /** @var \App\Models\ClientContact $cc */ $cc = auth()->guard('contact')->user(); $cc->first_name = $this->request->contact_first_name; diff --git a/app/Services/Subscription/SubscriptionCalculator.php b/app/Services/Subscription/SubscriptionCalculator.php index 223fb5b67c36..59a152cee167 100644 --- a/app/Services/Subscription/SubscriptionCalculator.php +++ b/app/Services/Subscription/SubscriptionCalculator.php @@ -13,14 +13,12 @@ namespace App\Services\Subscription; use App\Models\Invoice; use App\Models\Subscription; -use App\Models\ClientContact; use Illuminate\Support\Carbon; use App\DataMapper\InvoiceItem; use App\Factory\InvoiceFactory; use App\Utils\Traits\MakesHash; use App\Helpers\Invoice\ProRata; use App\Repositories\InvoiceRepository; -use App\Repositories\SubscriptionRepository; /** * SubscriptionCalculator. @@ -30,7 +28,13 @@ class SubscriptionCalculator use MakesHash; public function __construct(public Subscription $subscription){} - + + /** + * BuildPurchaseInvoice + * + * @param array $context + * @return Invoice + */ public function buildPurchaseInvoice(array $context): Invoice { @@ -54,7 +58,9 @@ class SubscriptionCalculator /** * Build Line Items + * * @param array $context + * * @return array */ private function buildItems(array $context): array @@ -69,9 +75,6 @@ class SubscriptionCalculator return $product['quantity'] >= 1; }); - nlog("items"); - nlog($items); - return collect($items)->map(function ($item){ $line_item = new InvoiceItem(); $line_item->product_key = $item['product']['product_key']; From fb92f8fe9dd6fc92fa46c61ed71624faeca01612 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 20 Feb 2024 17:29:38 +1100 Subject: [PATCH 057/188] Stubs for form submissions --- app/Livewire/BillingPortal/Submit.php | 32 +++++++++++--------- app/Services/ClientPortal/InstantPayment.php | 3 ++ 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/app/Livewire/BillingPortal/Submit.php b/app/Livewire/BillingPortal/Submit.php index cc9d03520a9f..629c434f085c 100644 --- a/app/Livewire/BillingPortal/Submit.php +++ b/app/Livewire/BillingPortal/Submit.php @@ -13,43 +13,47 @@ namespace App\Livewire\BillingPortal; use Livewire\Component; +use Livewire\Attributes\Lazy; use App\Services\ClientPortal\InstantPayment; +#[Lazy] class Submit extends Component { public array $context; public function mount() { - // This is right place to check if everything is set up correctly. - // - // - // - // - // - // - //hash - //sidebar = h // $request = new \Illuminate\Http\Request([ // 'sidebar' => 'hidden', // 'hash' => $this->context['hash'], // 'action' => 'payment', - // 'invoices[]' => $this->context['form']['invoice_hashed_id'], - // 'payable_invoices[0][amount]' => $this->context['form']['payable_amount'], - // 'payable_invoices[0][invoice_id]' => $this->context['form']['invoice_hashed_id'], + // 'invoices' => [ + // $this->context['form']['invoice_hashed_id'], + // ], + // 'payable_invoices' => [ + // [ + // 'amount' => $this->context['form']['payable_amount'], + // 'invoice_id' => $this->context['form']['invoice_hashed_id'], + // ], + // ], // 'company_gateway_id' => $this->context['form']['company_gateway_id'], // 'payment_method_id' => $this->context['form']['payment_method_id'], + // 'contact_first_name' => $this->context['contact']['first_name'], + // 'contact_last_name' => $this->context['contact']['last_name'], + // 'contact_email' => $this->context['contact']['email'], // ]); - // return (new InstantPayment($request))->run(); - + // return redirect((new InstantPayment($request))->run()); $this->dispatch('purchase.submit'); + + } public function render() { + return <<<'HTML'
HTML; diff --git a/app/Services/ClientPortal/InstantPayment.php b/app/Services/ClientPortal/InstantPayment.php index 3b5451cb54aa..990a95635417 100644 --- a/app/Services/ClientPortal/InstantPayment.php +++ b/app/Services/ClientPortal/InstantPayment.php @@ -71,6 +71,9 @@ class InstantPayment * ['invoice_id' => xxx, 'amount' => 22.00] */ $payable_invoices = collect($this->request->payable_invoices); + + nlog($payable_invoices); + $invoices = Invoice::query()->whereIn('id', $this->transformKeys($payable_invoices->pluck('invoice_id')->toArray()))->withTrashed()->get(); $invoices->each(function ($invoice) { From ef0807ea59d96b91f6c074d8b1dc543650a6f0ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Tue, 20 Feb 2024 09:56:44 +0100 Subject: [PATCH 058/188] Add contact fields --- resources/views/billing-portal/v3/purchase.blade.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/resources/views/billing-portal/v3/purchase.blade.php b/resources/views/billing-portal/v3/purchase.blade.php index 7060475748f3..82aad49b9e26 100644 --- a/resources/views/billing-portal/v3/purchase.blade.php +++ b/resources/views/billing-portal/v3/purchase.blade.php @@ -4,6 +4,14 @@ class="col-span-12 xl:col-span-6 bg-white flex flex-col items-center lg:h-screen" >
+ @if($errors->any()) + @foreach($errors as $error) +
+ {{ $error }} +
+ @endforeach + @endif + + + +
From ac9a9fe437ef9db64333055dbdbe5619864a0a09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Tue, 20 Feb 2024 17:10:17 +0100 Subject: [PATCH 059/188] Add billing portal blade views to file watchlist --- tailwind.config.cjs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tailwind.config.cjs b/tailwind.config.cjs index 7144e9e4413f..00b25740a4c8 100644 --- a/tailwind.config.cjs +++ b/tailwind.config.cjs @@ -7,7 +7,8 @@ module.exports = { './resources/views/email/components/**/*.blade.php', './resources/views/themes/ninja2020/**/*.blade.php', './resources/views/auth/**/*.blade.php', - './resources/views/setup/**/*.blade.php' + './resources/views/setup/**/*.blade.php', + './resources/views/billing-portal/**/*.blade.php', ], theme: { extend: { From 4fbbf52c4e2976222e6f10ae0e421212a154400f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Tue, 20 Feb 2024 17:10:22 +0100 Subject: [PATCH 060/188] Add loading spinner to BillingPortal/Submit component --- app/Livewire/BillingPortal/Submit.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/app/Livewire/BillingPortal/Submit.php b/app/Livewire/BillingPortal/Submit.php index 629c434f085c..3cdd270fac26 100644 --- a/app/Livewire/BillingPortal/Submit.php +++ b/app/Livewire/BillingPortal/Submit.php @@ -47,15 +47,19 @@ class Submit extends Component // return redirect((new InstantPayment($request))->run()); $this->dispatch('purchase.submit'); - - } public function render() { return <<<'HTML' -
+ + + + HTML; } } From 68a0db8725f1374ff6021174c9e7b53d0093b8a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Tue, 20 Feb 2024 17:10:35 +0100 Subject: [PATCH 061/188] Refactored payment methods display --- .../billing-portal/v3/payments/methods.blade.php | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/resources/views/billing-portal/v3/payments/methods.blade.php b/resources/views/billing-portal/v3/payments/methods.blade.php index 427cd1de03d8..fb14733f033a 100644 --- a/resources/views/billing-portal/v3/payments/methods.blade.php +++ b/resources/views/billing-portal/v3/payments/methods.blade.php @@ -1,9 +1,11 @@

{{ ctrans('texts.payment_methods') }}

- @foreach($methods as $method) - - @endforeach +
+ @foreach($methods as $method) + + @endforeach +
From 8e4e6c4f704b9999f3ab2b8fdf9c0a69b29ce3da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Tue, 20 Feb 2024 17:10:38 +0100 Subject: [PATCH 062/188] Assets production build --- public/build/assets/app-8c56d3d4.css | 1 + public/build/assets/app-ec429add.css | 1 - public/build/manifest.json | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 public/build/assets/app-8c56d3d4.css delete mode 100644 public/build/assets/app-ec429add.css diff --git a/public/build/assets/app-8c56d3d4.css b/public/build/assets/app-8c56d3d4.css new file mode 100644 index 000000000000..71a5b80bf6a7 --- /dev/null +++ b/public/build/assets/app-8c56d3d4.css @@ -0,0 +1 @@ +/*! tailwindcss v2.2.19 | MIT License | https://tailwindcss.com *//*! modern-normalize v1.1.0 | MIT License | https://github.com/sindresorhus/modern-normalize */*,:before,:after{box-sizing:border-box}html{-moz-tab-size:4;-o-tab-size:4;tab-size:4}html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}body{font-family:system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji"}hr{height:0;color:inherit}abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}::-moz-focus-inner{border-style:none;padding:0}legend{padding:0}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}button{background-color:transparent;background-image:none}fieldset{margin:0;padding:0}ol,ul{list-style:none;margin:0;padding:0}html{font-family:Open Sans,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";line-height:1.5}body{font-family:inherit;line-height:inherit}*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:currentColor}hr{border-top-width:1px}img{border-style:solid}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}table{border-collapse:collapse}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}button,input,optgroup,select,textarea{padding:0;line-height:inherit;color:inherit}pre,code,kbd,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}*,:before,:after{--tw-border-opacity: 1;border-color:rgba(229,231,235,var(--tw-border-opacity))}[type=text],[type=email],[type=url],[type=password],[type=number],[type=date],[type=month],[type=search],[type=time],textarea,select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;border-color:#6b7280;border-width:1px;border-radius:0;padding:.5rem .75rem;font-size:1rem;line-height:1.5rem;--tw-shadow: 0 0 #0000}[type=text]:focus,[type=email]:focus,[type=url]:focus,[type=password]:focus,[type=number]:focus,[type=date]:focus,[type=month]:focus,[type=search]:focus,[type=time]:focus,textarea:focus,select:focus{outline:2px solid transparent;outline-offset:2px;--tw-ring-inset: var(--tw-empty, );--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: #2563eb;--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);border-color:#2563eb}input::-moz-placeholder,textarea::-moz-placeholder{color:#6b7280;opacity:1}input::placeholder,textarea::placeholder{color:#6b7280;opacity:1}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-date-and-time-value{min-height:1.5em}select{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");background-position:right .5rem center;background-repeat:no-repeat;background-size:1.5em 1.5em;padding-right:2.5rem;-webkit-print-color-adjust:exact;color-adjust:exact}[type=checkbox],[type=radio]{-webkit-appearance:none;-moz-appearance:none;appearance:none;padding:0;-webkit-print-color-adjust:exact;color-adjust:exact;display:inline-block;vertical-align:middle;background-origin:border-box;-webkit-user-select:none;-moz-user-select:none;user-select:none;flex-shrink:0;height:1rem;width:1rem;color:#2563eb;background-color:#fff;border-color:#6b7280;border-width:1px;--tw-shadow: 0 0 #0000}[type=checkbox]{border-radius:0}[type=radio]{border-radius:100%}[type=checkbox]:focus,[type=radio]:focus{outline:2px solid transparent;outline-offset:2px;--tw-ring-inset: var(--tw-empty, );--tw-ring-offset-width: 2px;--tw-ring-offset-color: #fff;--tw-ring-color: #2563eb;--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}[type=checkbox]:checked,[type=radio]:checked{border-color:transparent;background-color:currentColor;background-size:100% 100%;background-position:center;background-repeat:no-repeat}[type=checkbox]:checked{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e")}[type=radio]:checked{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='8' cy='8' r='3'/%3e%3c/svg%3e")}[type=checkbox]:checked:hover,[type=checkbox]:checked:focus,[type=radio]:checked:hover,[type=radio]:checked:focus{border-color:transparent;background-color:currentColor}[type=checkbox]:indeterminate{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 16'%3e%3cpath stroke='white' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8h8'/%3e%3c/svg%3e");border-color:transparent;background-color:currentColor;background-size:100% 100%;background-position:center;background-repeat:no-repeat}[type=checkbox]:indeterminate:hover,[type=checkbox]:indeterminate:focus{border-color:transparent;background-color:currentColor}[type=file]{background:unset;border-color:inherit;border-width:0;border-radius:0;padding:0;font-size:unset;line-height:inherit}[type=file]:focus{outline:1px auto -webkit-focus-ring-color}.container{width:100%}@media (min-width: 640px){.container{max-width:640px}}@media (min-width: 768px){.container{max-width:768px}}@media (min-width: 1024px){.container{max-width:1024px}}@media (min-width: 1280px){.container{max-width:1280px}}@media (min-width: 1536px){.container{max-width:1536px}}.prose{color:#374151;max-width:65ch}.prose [class~=lead]{color:#4b5563;font-size:1.25em;line-height:1.6;margin-top:1.2em;margin-bottom:1.2em}.prose a{color:#111827;text-decoration:underline;font-weight:500}.prose strong{color:#111827;font-weight:600}.prose ol[type=A]{--list-counter-style: upper-alpha}.prose ol[type=a]{--list-counter-style: lower-alpha}.prose ol[type=A s]{--list-counter-style: upper-alpha}.prose ol[type=a s]{--list-counter-style: lower-alpha}.prose ol[type=i]{--list-counter-style: lower-roman}.prose ol[type=i s]{--list-counter-style: lower-roman}.prose ol[type="1"]{--list-counter-style: decimal}.prose ol>li{position:relative;padding-left:1.75em}.prose ol>li:before{content:counter(list-item,var(--list-counter-style, decimal)) ".";position:absolute;font-weight:400;color:#6b7280;left:0}.prose ul>li{position:relative;padding-left:1.75em}.prose ul>li:before{content:"";position:absolute;background-color:#d1d5db;border-radius:50%;width:.375em;height:.375em;top:.6875em;left:.25em}.prose hr{border-color:#e5e7eb;border-top-width:1px;margin-top:3em;margin-bottom:3em}.prose blockquote{font-weight:500;font-style:italic;color:#111827;border-left-width:.25rem;border-left-color:#e5e7eb;quotes:"“""”""‘""’";margin-top:1.6em;margin-bottom:1.6em;padding-left:1em}.prose blockquote p:first-of-type:before{content:open-quote}.prose blockquote p:last-of-type:after{content:close-quote}.prose h1{color:#111827;font-weight:800;font-size:2.25em;margin-top:0;margin-bottom:.8888889em;line-height:1.1111111}.prose h2{color:#111827;font-weight:700;font-size:1.5em;margin-top:2em;margin-bottom:1em;line-height:1.3333333}.prose h3{color:#111827;font-weight:600;font-size:1.25em;margin-top:1.6em;margin-bottom:.6em;line-height:1.6}.prose h4{color:#111827;font-weight:600;margin-top:1.5em;margin-bottom:.5em;line-height:1.5}.prose figure figcaption{color:#6b7280;font-size:.875em;line-height:1.4285714;margin-top:.8571429em}.prose code{color:#111827;font-weight:600;font-size:.875em}.prose code:before{content:"`"}.prose code:after{content:"`"}.prose a code{color:#111827}.prose pre{color:#e5e7eb;background-color:#1f2937;overflow-x:auto;font-size:.875em;line-height:1.7142857;margin-top:1.7142857em;margin-bottom:1.7142857em;border-radius:.375rem;padding:.8571429em 1.1428571em}.prose pre code{background-color:transparent;border-width:0;border-radius:0;padding:0;font-weight:400;color:inherit;font-size:inherit;font-family:inherit;line-height:inherit}.prose pre code:before{content:none}.prose pre code:after{content:none}.prose table{width:100%;table-layout:auto;text-align:left;margin-top:2em;margin-bottom:2em;font-size:.875em;line-height:1.7142857}.prose thead{color:#111827;font-weight:600;border-bottom-width:1px;border-bottom-color:#d1d5db}.prose thead th{vertical-align:bottom;padding-right:.5714286em;padding-bottom:.5714286em;padding-left:.5714286em}.prose tbody tr{border-bottom-width:1px;border-bottom-color:#e5e7eb}.prose tbody tr:last-child{border-bottom-width:0}.prose tbody td{vertical-align:top;padding:.5714286em}.prose{font-size:1rem;line-height:1.75}.prose p{margin-top:1.25em;margin-bottom:1.25em}.prose img{margin-top:2em;margin-bottom:2em}.prose video{margin-top:2em;margin-bottom:2em}.prose figure{margin-top:2em;margin-bottom:2em}.prose figure>*{margin-top:0;margin-bottom:0}.prose h2 code{font-size:.875em}.prose h3 code{font-size:.9em}.prose ol,.prose ul{margin-top:1.25em;margin-bottom:1.25em}.prose li{margin-top:.5em;margin-bottom:.5em}.prose>ul>li p{margin-top:.75em;margin-bottom:.75em}.prose>ul>li>*:first-child{margin-top:1.25em}.prose>ul>li>*:last-child{margin-bottom:1.25em}.prose>ol>li>*:first-child{margin-top:1.25em}.prose>ol>li>*:last-child{margin-bottom:1.25em}.prose ul ul,.prose ul ol,.prose ol ul,.prose ol ol{margin-top:.75em;margin-bottom:.75em}.prose hr+*{margin-top:0}.prose h2+*{margin-top:0}.prose h3+*{margin-top:0}.prose h4+*{margin-top:0}.prose thead th:first-child{padding-left:0}.prose thead th:last-child{padding-right:0}.prose tbody td:first-child{padding-left:0}.prose tbody td:last-child{padding-right:0}.prose>:first-child{margin-top:0}.prose>:last-child{margin-bottom:0}.button{border-radius:.25rem;padding:.75rem 1rem;font-size:.875rem;line-height:1.25rem;line-height:1rem;transition-property:background-color,border-color,color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:background-color,border-color,color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:background-color,border-color,color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-duration:.15s;transition-timing-function:cubic-bezier(.4,0,.2,1)}button:disabled{cursor:not-allowed;opacity:.5}.button-primary{--tw-text-opacity: 1;color:rgba(255,255,255,var(--tw-text-opacity))}.button-primary:hover{font-weight:600}.button-block{display:block;width:100%}.button-danger{--tw-bg-opacity: 1;background-color:rgba(239,68,68,var(--tw-bg-opacity));--tw-text-opacity: 1;color:rgba(255,255,255,var(--tw-text-opacity))}.button-danger:hover{--tw-bg-opacity: 1;background-color:rgba(220,38,38,var(--tw-bg-opacity))}.button-secondary{--tw-bg-opacity: 1;background-color:rgba(243,244,246,var(--tw-bg-opacity))}.button-secondary:hover{--tw-bg-opacity: 1;background-color:rgba(229,231,235,var(--tw-bg-opacity))}.button-link{--tw-text-opacity: 1;color:rgba(55,65,81,var(--tw-text-opacity))}.button-link:hover{--tw-text-opacity: 1;color:rgba(17,24,39,var(--tw-text-opacity));text-decoration:underline}.button-link:focus{text-decoration:underline;outline:2px solid transparent;outline-offset:2px}.validation{margin-top:.5rem;margin-bottom:.25rem;border-left-width:2px;--tw-bg-opacity: 1;background-color:rgba(243,244,246,var(--tw-bg-opacity));padding:.25rem .75rem}.validation-fail{--tw-border-opacity: 1;border-color:rgba(239,68,68,var(--tw-border-opacity));font-size:.875rem;line-height:1.25rem;--tw-text-opacity: 1;color:rgba(55,65,81,var(--tw-text-opacity))}.validation-pass{--tw-border-opacity: 1;border-color:rgba(16,185,129,var(--tw-border-opacity));font-size:.875rem;line-height:1.25rem;--tw-text-opacity: 1;color:rgba(55,65,81,var(--tw-text-opacity))}.input{margin-top:.5rem;align-items:center;border-radius:.25rem;border-width:1px;--tw-border-opacity: 1;border-color:rgba(209,213,219,var(--tw-border-opacity));padding:.5rem 1rem;font-size:.875rem;line-height:1.25rem}.input:focus{--tw-bg-opacity: 1;background-color:rgba(249,250,251,var(--tw-bg-opacity));outline:2px solid transparent;outline-offset:2px}.input-label{font-size:.875rem;line-height:1.25rem;--tw-text-opacity: 1;color:rgba(75,85,99,var(--tw-text-opacity))}.input-slim{padding-top:.5rem;padding-bottom:.5rem}.form-checkbox{cursor:pointer;border-radius:.25rem;border-width:1px;--tw-border-opacity: 1;border-color:rgba(209,213,219,var(--tw-border-opacity))}.form-select{border-width:1px;--tw-border-opacity: 1;border-color:rgba(209,213,219,var(--tw-border-opacity))}.alert{margin-top:.5rem;margin-top:1rem;margin-bottom:.25rem;border-left-width:2px;--tw-border-opacity: 1;border-color:rgba(156,163,175,var(--tw-border-opacity));--tw-bg-opacity: 1;background-color:rgba(243,244,246,var(--tw-bg-opacity));padding:.75rem 1rem;font-size:.875rem;line-height:1.25rem}.alert-success{--tw-border-opacity: 1;border-color:rgba(16,185,129,var(--tw-border-opacity))}.alert-failure{--tw-border-opacity: 1;border-color:rgba(239,68,68,var(--tw-border-opacity))}.badge{display:inline-flex;align-items:center;border-radius:9999px;padding:.125rem .625rem;font-size:.75rem;font-weight:500;line-height:1rem}.badge-light{--tw-bg-opacity: 1;background-color:rgba(243,244,246,var(--tw-bg-opacity));--tw-text-opacity: 1;color:rgba(31,41,55,var(--tw-text-opacity))}.badge-primary{--tw-bg-opacity: 1;background-color:rgba(191,219,254,var(--tw-bg-opacity));--tw-text-opacity: 1;color:rgba(59,130,246,var(--tw-text-opacity))}.badge-danger{--tw-bg-opacity: 1;background-color:rgba(254,226,226,var(--tw-bg-opacity));--tw-text-opacity: 1;color:rgba(239,68,68,var(--tw-text-opacity))}.badge-success{--tw-bg-opacity: 1;background-color:rgba(209,250,229,var(--tw-bg-opacity));--tw-text-opacity: 1;color:rgba(16,185,129,var(--tw-text-opacity))}.badge-secondary{--tw-bg-opacity: 1;background-color:rgba(31,41,55,var(--tw-bg-opacity));--tw-text-opacity: 1;color:rgba(229,231,235,var(--tw-text-opacity))}.badge-warning{--tw-bg-opacity: 1;background-color:rgba(59,130,246,var(--tw-bg-opacity));--tw-text-opacity: 1;color:rgba(217,119,6,var(--tw-text-opacity))}.badge-info{--tw-bg-opacity: 1;background-color:rgba(219,234,254,var(--tw-bg-opacity));--tw-text-opacity: 1;color:rgba(59,130,246,var(--tw-text-opacity))}@media (min-width: 640px){.dataTables_length{margin-top:1.25rem!important;margin-bottom:1.25rem!important}}@media (min-width: 1024px){.dataTables_length{margin-top:1rem!important;margin-bottom:1rem!important}}.dataTables_length select{margin-top:.5rem;align-items:center;border-radius:.25rem;border-width:1px;--tw-border-opacity: 1;border-color:rgba(209,213,219,var(--tw-border-opacity));padding:.5rem 1rem;font-size:.875rem;line-height:1.25rem}.dataTables_length select:focus{--tw-bg-opacity: 1 !important;background-color:rgba(249,250,251,var(--tw-bg-opacity))!important;outline:2px solid transparent!important;outline-offset:2px!important}.dataTables_length select{margin-left:.5rem!important;margin-right:.5rem!important;--tw-bg-opacity: 1 !important;background-color:rgba(255,255,255,var(--tw-bg-opacity))!important;padding:.5rem!important}.dataTables_filter{margin-bottom:1rem}.dataTables_filter input{margin-top:.5rem;align-items:center;border-radius:.25rem;border-width:1px;--tw-border-opacity: 1;border-color:rgba(209,213,219,var(--tw-border-opacity));padding:.5rem 1rem;font-size:.875rem;line-height:1.25rem}.dataTables_filter input:focus{--tw-bg-opacity: 1 !important;background-color:rgba(249,250,251,var(--tw-bg-opacity))!important;outline:2px solid transparent!important;outline-offset:2px!important}@media (min-width: 1024px){.dataTables_filter{margin-top:-3rem!important}}.dataTables_paginate{padding-top:.5rem!important;padding-bottom:1.5rem!important}.dataTables_paginate .paginate_button{border-radius:.25rem;padding:.75rem 1rem;font-size:.875rem;line-height:1.25rem;line-height:1rem;transition-property:background-color,border-color,color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:background-color,border-color,color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:background-color,border-color,color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-duration:.15s;transition-timing-function:cubic-bezier(.4,0,.2,1);margin-right:.25rem!important;cursor:pointer!important;border-radius:.25rem!important;border-width:1px!important;--tw-border-opacity: 1 !important;border-color:rgba(209,213,219,var(--tw-border-opacity))!important;--tw-bg-opacity: 1 !important;background-color:rgba(255,255,255,var(--tw-bg-opacity))!important;padding-top:.5rem!important;padding-bottom:.5rem!important;font-size:.875rem!important;line-height:1.25rem!important;font-weight:500!important;line-height:1rem!important;--tw-text-opacity: 1 !important;color:rgba(55,65,81,var(--tw-text-opacity))!important}.dataTables_paginate .current{--tw-bg-opacity: 1 !important;background-color:rgba(37,99,235,var(--tw-bg-opacity))!important;--tw-text-opacity: 1 !important;color:rgba(255,255,255,var(--tw-text-opacity))!important}.dataTables_info{font-size:.875rem!important;line-height:1.25rem!important}.dataTables_empty{padding-top:1rem!important;padding-bottom:1rem!important}.pagination{display:flex!important;align-items:center!important}.pagination .page-link{margin-top:-1px!important;display:inline-flex!important;cursor:pointer!important;align-items:center!important;border-top-width:2px!important;border-color:transparent!important;padding-left:1rem!important;padding-right:1rem!important;padding-top:1rem!important;font-size:.875rem!important;font-weight:500!important;line-height:1.25rem!important;--tw-text-opacity: 1 !important;color:rgba(107,114,128,var(--tw-text-opacity))!important;transition-property:background-color,border-color,color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter!important;transition-property:background-color,border-color,color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter!important;transition-property:background-color,border-color,color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter!important;transition-duration:.15s!important;transition-timing-function:cubic-bezier(.4,0,.2,1)!important}.pagination .page-link:hover{--tw-border-opacity: 1 !important;border-color:rgba(209,213,219,var(--tw-border-opacity))!important;--tw-text-opacity: 1 !important;color:rgba(55,65,81,var(--tw-text-opacity))!important}.pagination .page-link:focus{--tw-border-opacity: 1;border-color:rgba(156,163,175,var(--tw-border-opacity));--tw-text-opacity: 1;color:rgba(55,65,81,var(--tw-text-opacity));outline:2px solid transparent;outline-offset:2px}.pagination .active>span{--tw-border-opacity: 1 !important;border-color:rgba(37,99,235,var(--tw-border-opacity))!important;--tw-text-opacity: 1 !important;color:rgba(37,99,235,var(--tw-text-opacity))!important}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.pointer-events-none{pointer-events:none}.pointer-events-auto{pointer-events:auto}.visible{visibility:visible}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{top:0;right:0;bottom:0;left:0}.inset-x-0{left:0;right:0}.inset-y-0{top:0;bottom:0}.top-0{top:0}.top-1{top:.25rem}.right-0{right:0}.bottom-0{bottom:0}.left-0{left:0}.left-1{left:.25rem}.z-0{z-index:0}.z-10{z-index:10}.z-30{z-index:30}.z-40{z-index:40}.z-50{z-index:50}.focus-within\:z-10:focus-within{z-index:10}.col-auto{grid-column:auto}.col-span-1{grid-column:span 1 / span 1}.col-span-2{grid-column:span 2 / span 2}.col-span-3{grid-column:span 3 / span 3}.col-span-4{grid-column:span 4 / span 4}.col-span-6{grid-column:span 6 / span 6}.col-span-8{grid-column:span 8 / span 8}.col-span-12{grid-column:span 12 / span 12}.float-right{float:right}.m-0{margin:0}.m-auto{margin:auto}.mx-2{margin-left:.5rem;margin-right:.5rem}.mx-4{margin-left:1rem;margin-right:1rem}.mx-6{margin-left:1.5rem;margin-right:1.5rem}.mx-auto{margin-left:auto;margin-right:auto}.my-2{margin-top:.5rem;margin-bottom:.5rem}.my-3{margin-top:.75rem;margin-bottom:.75rem}.my-4{margin-top:1rem;margin-bottom:1rem}.my-6{margin-top:1.5rem;margin-bottom:1.5rem}.my-10{margin-top:2.5rem;margin-bottom:2.5rem}.-my-2{margin-top:-.5rem;margin-bottom:-.5rem}.-my-6{margin-top:-1.5rem;margin-bottom:-1.5rem}.mt-0{margin-top:0}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-5{margin-top:1.25rem}.mt-6{margin-top:1.5rem}.mt-8{margin-top:2rem}.mt-10{margin-top:2.5rem}.mt-20{margin-top:5rem}.-mt-4{margin-top:-1rem}.-mt-6{margin-top:-1.5rem}.mr-0{margin-right:0}.mr-1{margin-right:.25rem}.mr-2{margin-right:.5rem}.mr-3{margin-right:.75rem}.mr-4{margin-right:1rem}.mr-5{margin-right:1.25rem}.-mr-1{margin-right:-.25rem}.-mr-14{margin-right:-3.5rem}.mb-0{margin-bottom:0}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-5{margin-bottom:1.25rem}.mb-6{margin-bottom:1.5rem}.mb-8{margin-bottom:2rem}.mb-10{margin-bottom:2.5rem}.mb-12{margin-bottom:3rem}.mb-1\.5{margin-bottom:.375rem}.ml-0{margin-left:0}.ml-1{margin-left:.25rem}.ml-2{margin-left:.5rem}.ml-3{margin-left:.75rem}.ml-4{margin-left:1rem}.ml-5{margin-left:1.25rem}.-ml-1{margin-left:-.25rem}.-ml-4{margin-left:-1rem}.-ml-px{margin-left:-1px}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.contents{display:contents}.hidden{display:none}.h-0{height:0px}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-8{height:2rem}.h-10{height:2.5rem}.h-12{height:3rem}.h-14{height:3.5rem}.h-16{height:4rem}.h-24{height:6rem}.h-64{height:16rem}.h-auto{height:auto}.h-full{height:100%}.h-screen{height:100vh}.min-h-screen{min-height:100vh}.w-0{width:0px}.w-4{width:1rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-8{width:2rem}.w-10{width:2.5rem}.w-12{width:3rem}.w-14{width:3.5rem}.w-16{width:4rem}.w-24{width:6rem}.w-48{width:12rem}.w-56{width:14rem}.w-64{width:16rem}.w-80{width:20rem}.w-auto{width:auto}.w-1\/2{width:50%}.w-3\/4{width:75%}.w-4\/5{width:80%}.w-1\/6{width:16.666667%}.w-4\/6{width:66.666667%}.w-5\/6{width:83.333333%}.w-full{width:100%}.w-screen{width:100vw}.min-w-full{min-width:100%}.max-w-xs{max-width:20rem}.max-w-xl{max-width:36rem}.max-w-2xl{max-width:42rem}.max-w-4xl{max-width:56rem}.flex-1{flex:1 1 0%}.flex-shrink-0{flex-shrink:0}.flex-shrink{flex-shrink:1}.flex-grow{flex-grow:1}.table-auto{table-layout:auto}.border-collapse{border-collapse:collapse}.origin-top-right{transform-origin:top right}.transform{--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;transform:translate(var(--tw-translate-x)) translateY(var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-0{--tw-translate-x: 0px}.-translate-x-full{--tw-translate-x: -100%}.translate-y-0{--tw-translate-y: 0px}.translate-y-4{--tw-translate-y: 1rem}.scale-95{--tw-scale-x: .95;--tw-scale-y: .95}.scale-100{--tw-scale-x: 1;--tw-scale-y: 1}@keyframes spin{to{transform:rotate(360deg)}}@keyframes ping{75%,to{transform:scale(2);opacity:0}}@keyframes pulse{50%{opacity:.5}}@keyframes bounce{0%,to{transform:translateY(-25%);animation-timing-function:cubic-bezier(.8,0,1,1)}50%{transform:none;animation-timing-function:cubic-bezier(0,0,.2,1)}}.animate-spin{animation:spin 1s linear infinite}.cursor-pointer{cursor:pointer}.resize{resize:both}.list-none{list-style-type:none}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.flex-row{flex-direction:row}.flex-col{flex-direction:column}.flex-col-reverse{flex-direction:column-reverse}.flex-wrap{flex-wrap:wrap}.place-content-end{place-content:end}.place-items-center{place-items:center}.content-center{align-content:center}.content-start{align-content:flex-start}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.items-stretch{align-items:stretch}.justify-start{justify-content:flex-start}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-4{gap:1rem}.gap-5{gap:1.25rem}.gap-6{gap:1.5rem}.gap-8{gap:2rem}.gap-x-2{-moz-column-gap:.5rem;column-gap:.5rem}.space-x-1>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(.25rem * var(--tw-space-x-reverse));margin-left:calc(.25rem * calc(1 - var(--tw-space-x-reverse)))}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(.5rem * var(--tw-space-x-reverse));margin-left:calc(.5rem * calc(1 - var(--tw-space-x-reverse)))}.space-x-4>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(1rem * var(--tw-space-x-reverse));margin-left:calc(1rem * calc(1 - var(--tw-space-x-reverse)))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.75rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.space-y-5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.25rem * var(--tw-space-y-reverse))}.space-y-10>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(2.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(2.5rem * var(--tw-space-y-reverse))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse: 0;border-top-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(1px * var(--tw-divide-y-reverse))}.divide-gray-200>:not([hidden])~:not([hidden]){--tw-divide-opacity: 1;border-color:rgba(229,231,235,var(--tw-divide-opacity))}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.overflow-y-scroll{overflow-y:scroll}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.overflow-ellipsis{text-overflow:ellipsis}.whitespace-nowrap{white-space:nowrap}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.rounded-none{border-radius:0}.rounded-sm{border-radius:.125rem}.rounded{border-radius:.25rem}.rounded-md{border-radius:.375rem}.rounded-lg{border-radius:.5rem}.rounded-full{border-radius:9999px}.rounded-t-lg{border-top-left-radius:.5rem;border-top-right-radius:.5rem}.rounded-r-md{border-top-right-radius:.375rem;border-bottom-right-radius:.375rem}.rounded-b-lg{border-bottom-right-radius:.5rem;border-bottom-left-radius:.5rem}.rounded-l-md{border-top-left-radius:.375rem;border-bottom-left-radius:.375rem}.border-0{border-width:0px}.border-2{border-width:2px}.border-4{border-width:4px}.border{border-width:1px}.border-t-2{border-top-width:2px}.border-t-4{border-top-width:4px}.border-t{border-top-width:1px}.border-r{border-right-width:1px}.border-b-2{border-bottom-width:2px}.border-b{border-bottom-width:1px}.border-l-2{border-left-width:2px}.border-solid{border-style:solid}.border-dashed{border-style:dashed}.border-none{border-style:none}.border-transparent{border-color:transparent}.border-gray-100{--tw-border-opacity: 1;border-color:rgba(243,244,246,var(--tw-border-opacity))}.border-gray-200{--tw-border-opacity: 1;border-color:rgba(229,231,235,var(--tw-border-opacity))}.border-gray-300{--tw-border-opacity: 1;border-color:rgba(209,213,219,var(--tw-border-opacity))}.border-gray-500{--tw-border-opacity: 1;border-color:rgba(107,114,128,var(--tw-border-opacity))}.border-gray-600{--tw-border-opacity: 1;border-color:rgba(75,85,99,var(--tw-border-opacity))}.border-red-300{--tw-border-opacity: 1;border-color:rgba(252,165,165,var(--tw-border-opacity))}.border-red-400{--tw-border-opacity: 1;border-color:rgba(248,113,113,var(--tw-border-opacity))}.border-red-900{--tw-border-opacity: 1;border-color:rgba(127,29,29,var(--tw-border-opacity))}.border-green-500{--tw-border-opacity: 1;border-color:rgba(16,185,129,var(--tw-border-opacity))}.border-blue-500{--tw-border-opacity: 1;border-color:rgba(59,130,246,var(--tw-border-opacity))}.group:hover .group-hover\:border-transparent,.hover\:border-transparent:hover{border-color:transparent}.hover\:border-gray-600:hover{--tw-border-opacity: 1;border-color:rgba(75,85,99,var(--tw-border-opacity))}.hover\:border-gray-800:hover{--tw-border-opacity: 1;border-color:rgba(31,41,55,var(--tw-border-opacity))}.hover\:border-blue-600:hover{--tw-border-opacity: 1;border-color:rgba(37,99,235,var(--tw-border-opacity))}.focus\:border-red-500:focus{--tw-border-opacity: 1;border-color:rgba(239,68,68,var(--tw-border-opacity))}.focus\:border-blue-300:focus{--tw-border-opacity: 1;border-color:rgba(147,197,253,var(--tw-border-opacity))}.focus\:border-blue-500:focus{--tw-border-opacity: 1;border-color:rgba(59,130,246,var(--tw-border-opacity))}.focus\:border-indigo-500:focus{--tw-border-opacity: 1;border-color:rgba(99,102,241,var(--tw-border-opacity))}.border-opacity-50{--tw-border-opacity: .5}.bg-transparent{background-color:transparent}.bg-white{--tw-bg-opacity: 1;background-color:rgba(255,255,255,var(--tw-bg-opacity))}.bg-gray-50{--tw-bg-opacity: 1;background-color:rgba(249,250,251,var(--tw-bg-opacity))}.bg-gray-100{--tw-bg-opacity: 1;background-color:rgba(243,244,246,var(--tw-bg-opacity))}.bg-gray-200{--tw-bg-opacity: 1;background-color:rgba(229,231,235,var(--tw-bg-opacity))}.bg-gray-500{--tw-bg-opacity: 1;background-color:rgba(107,114,128,var(--tw-bg-opacity))}.bg-gray-600{--tw-bg-opacity: 1;background-color:rgba(75,85,99,var(--tw-bg-opacity))}.bg-red-100{--tw-bg-opacity: 1;background-color:rgba(254,226,226,var(--tw-bg-opacity))}.bg-red-500{--tw-bg-opacity: 1;background-color:rgba(239,68,68,var(--tw-bg-opacity))}.bg-green-600{--tw-bg-opacity: 1;background-color:rgba(5,150,105,var(--tw-bg-opacity))}.bg-blue-50{--tw-bg-opacity: 1;background-color:rgba(239,246,255,var(--tw-bg-opacity))}.bg-blue-500{--tw-bg-opacity: 1;background-color:rgba(59,130,246,var(--tw-bg-opacity))}.bg-blue-600{--tw-bg-opacity: 1;background-color:rgba(37,99,235,var(--tw-bg-opacity))}.bg-blue-700{--tw-bg-opacity: 1;background-color:rgba(29,78,216,var(--tw-bg-opacity))}.hover\:bg-gray-100:hover{--tw-bg-opacity: 1;background-color:rgba(243,244,246,var(--tw-bg-opacity))}.hover\:bg-gray-200:hover{--tw-bg-opacity: 1;background-color:rgba(229,231,235,var(--tw-bg-opacity))}.hover\:bg-red-900:hover{--tw-bg-opacity: 1;background-color:rgba(127,29,29,var(--tw-bg-opacity))}.hover\:bg-blue-500:hover{--tw-bg-opacity: 1;background-color:rgba(59,130,246,var(--tw-bg-opacity))}.hover\:bg-blue-600:hover{--tw-bg-opacity: 1;background-color:rgba(37,99,235,var(--tw-bg-opacity))}.focus\:bg-white:focus{--tw-bg-opacity: 1;background-color:rgba(255,255,255,var(--tw-bg-opacity))}.focus\:bg-gray-100:focus{--tw-bg-opacity: 1;background-color:rgba(243,244,246,var(--tw-bg-opacity))}.focus\:bg-gray-600:focus{--tw-bg-opacity: 1;background-color:rgba(75,85,99,var(--tw-bg-opacity))}.bg-opacity-100{--tw-bg-opacity: 1}.bg-clip-padding{background-clip:padding-box}.fill-current{fill:currentColor}.object-cover{-o-object-fit:cover;object-fit:cover}.object-scale-down{-o-object-fit:scale-down;object-fit:scale-down}.object-center{-o-object-position:center;object-position:center}.p-1{padding:.25rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-8{padding:2rem}.p-10{padding:2.5rem}.p-2\.5{padding:.625rem}.px-0{padding-left:0;padding-right:0}.px-1{padding-left:.25rem;padding-right:.25rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.px-12{padding-left:3rem;padding-right:3rem}.py-0{padding-top:0;padding-bottom:0}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-5{padding-top:1.25rem;padding-bottom:1.25rem}.py-6{padding-top:1.5rem;padding-bottom:1.5rem}.py-8{padding-top:2rem;padding-bottom:2rem}.py-10{padding-top:2.5rem;padding-bottom:2.5rem}.pt-0{padding-top:0}.pt-2{padding-top:.5rem}.pt-4{padding-top:1rem}.pt-5{padding-top:1.25rem}.pt-6{padding-top:1.5rem}.pr-2{padding-right:.5rem}.pr-4{padding-right:1rem}.pr-10{padding-right:2.5rem}.pb-2{padding-bottom:.5rem}.pb-3{padding-bottom:.75rem}.pb-4{padding-bottom:1rem}.pb-6{padding-bottom:1.5rem}.pb-10{padding-bottom:2.5rem}.pb-20{padding-bottom:5rem}.pl-0{padding-left:0}.pl-2{padding-left:.5rem}.pl-3{padding-left:.75rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.align-middle{vertical-align:middle}.align-bottom{vertical-align:bottom}.text-xs{font-size:.75rem;line-height:1rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-5xl{font-size:3rem;line-height:1}.font-light{font-weight:300}.font-normal{font-weight:400}.font-medium{font-weight:500}.font-semibold{font-weight:600}.font-bold{font-weight:700}.uppercase{text-transform:uppercase}.lowercase{text-transform:lowercase}.capitalize{text-transform:capitalize}.italic{font-style:italic}.leading-4{line-height:1rem}.leading-5{line-height:1.25rem}.leading-6{line-height:1.5rem}.leading-9{line-height:2.25rem}.leading-tight{line-height:1.25}.tracking-wide{letter-spacing:.025em}.tracking-wider{letter-spacing:.05em}.text-black{--tw-text-opacity: 1;color:rgba(0,0,0,var(--tw-text-opacity))}.text-white{--tw-text-opacity: 1;color:rgba(255,255,255,var(--tw-text-opacity))}.text-gray-300{--tw-text-opacity: 1;color:rgba(209,213,219,var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity: 1;color:rgba(156,163,175,var(--tw-text-opacity))}.text-gray-500{--tw-text-opacity: 1;color:rgba(107,114,128,var(--tw-text-opacity))}.text-gray-600{--tw-text-opacity: 1;color:rgba(75,85,99,var(--tw-text-opacity))}.text-gray-700{--tw-text-opacity: 1;color:rgba(55,65,81,var(--tw-text-opacity))}.text-gray-800{--tw-text-opacity: 1;color:rgba(31,41,55,var(--tw-text-opacity))}.text-gray-900{--tw-text-opacity: 1;color:rgba(17,24,39,var(--tw-text-opacity))}.text-red-400{--tw-text-opacity: 1;color:rgba(248,113,113,var(--tw-text-opacity))}.text-red-500{--tw-text-opacity: 1;color:rgba(239,68,68,var(--tw-text-opacity))}.text-red-600{--tw-text-opacity: 1;color:rgba(220,38,38,var(--tw-text-opacity))}.text-red-700{--tw-text-opacity: 1;color:rgba(185,28,28,var(--tw-text-opacity))}.text-red-900{--tw-text-opacity: 1;color:rgba(127,29,29,var(--tw-text-opacity))}.text-green-600{--tw-text-opacity: 1;color:rgba(5,150,105,var(--tw-text-opacity))}.text-blue-500{--tw-text-opacity: 1;color:rgba(59,130,246,var(--tw-text-opacity))}.text-blue-600{--tw-text-opacity: 1;color:rgba(37,99,235,var(--tw-text-opacity))}.text-blue-700{--tw-text-opacity: 1;color:rgba(29,78,216,var(--tw-text-opacity))}.text-indigo-600{--tw-text-opacity: 1;color:rgba(79,70,229,var(--tw-text-opacity))}.group:hover .group-hover\:text-white,.hover\:text-white:hover{--tw-text-opacity: 1;color:rgba(255,255,255,var(--tw-text-opacity))}.hover\:text-gray-300:hover{--tw-text-opacity: 1;color:rgba(209,213,219,var(--tw-text-opacity))}.hover\:text-gray-500:hover{--tw-text-opacity: 1;color:rgba(107,114,128,var(--tw-text-opacity))}.hover\:text-gray-600:hover{--tw-text-opacity: 1;color:rgba(75,85,99,var(--tw-text-opacity))}.hover\:text-gray-700:hover{--tw-text-opacity: 1;color:rgba(55,65,81,var(--tw-text-opacity))}.hover\:text-gray-800:hover{--tw-text-opacity: 1;color:rgba(31,41,55,var(--tw-text-opacity))}.hover\:text-gray-900:hover{--tw-text-opacity: 1;color:rgba(17,24,39,var(--tw-text-opacity))}.hover\:text-red-500:hover{--tw-text-opacity: 1;color:rgba(239,68,68,var(--tw-text-opacity))}.hover\:text-blue-600:hover{--tw-text-opacity: 1;color:rgba(37,99,235,var(--tw-text-opacity))}.hover\:text-indigo-900:hover{--tw-text-opacity: 1;color:rgba(49,46,129,var(--tw-text-opacity))}.focus\:text-gray-500:focus{--tw-text-opacity: 1;color:rgba(107,114,128,var(--tw-text-opacity))}.focus\:text-gray-600:focus{--tw-text-opacity: 1;color:rgba(75,85,99,var(--tw-text-opacity))}.focus\:text-gray-900:focus{--tw-text-opacity: 1;color:rgba(17,24,39,var(--tw-text-opacity))}.underline{text-decoration:underline}.line-through{text-decoration:line-through}.no-underline{text-decoration:none}.hover\:underline:hover{text-decoration:underline}.focus\:underline:focus{text-decoration:underline}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.opacity-0{opacity:0}.opacity-25{opacity:.25}.opacity-75{opacity:.75}.opacity-100{opacity:1}.hover\:opacity-80:hover{opacity:.8}*,:before,:after{--tw-shadow: 0 0 #0000}.shadow-sm{--tw-shadow: 0 1px 2px 0 rgba(0, 0, 0, .05);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow{--tw-shadow: 0 1px 3px 0 rgba(0, 0, 0, .1), 0 1px 2px 0 rgba(0, 0, 0, .06);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-lg{--tw-shadow: 0 10px 15px -3px rgba(0, 0, 0, .1), 0 4px 6px -2px rgba(0, 0, 0, .05);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-xl{--tw-shadow: 0 20px 25px -5px rgba(0, 0, 0, .1), 0 10px 10px -5px rgba(0, 0, 0, .04);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.hover\:shadow-lg:hover{--tw-shadow: 0 10px 15px -3px rgba(0, 0, 0, .1), 0 4px 6px -2px rgba(0, 0, 0, .05);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.hover\:shadow-2xl:hover{--tw-shadow: 0 25px 50px -12px rgba(0, 0, 0, .25);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.outline-none{outline:2px solid transparent;outline-offset:2px}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}*,:before,:after{--tw-ring-inset: var(--tw-empty, );--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgba(59, 130, 246, .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000}.ring-1{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-1:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-black{--tw-ring-opacity: 1;--tw-ring-color: rgba(0, 0, 0, var(--tw-ring-opacity))}.focus\:ring-red-500:focus{--tw-ring-opacity: 1;--tw-ring-color: rgba(239, 68, 68, var(--tw-ring-opacity))}.focus\:ring-blue-500:focus{--tw-ring-opacity: 1;--tw-ring-color: rgba(59, 130, 246, var(--tw-ring-opacity))}.focus\:ring-indigo-500:focus{--tw-ring-opacity: 1;--tw-ring-color: rgba(99, 102, 241, var(--tw-ring-opacity))}.focus\:ring-indigo-600:focus{--tw-ring-opacity: 1;--tw-ring-color: rgba(79, 70, 229, var(--tw-ring-opacity))}.ring-opacity-5{--tw-ring-opacity: .05}.focus\:ring-opacity-50:focus{--tw-ring-opacity: .5}.filter{--tw-blur: var(--tw-empty, );--tw-brightness: var(--tw-empty, );--tw-contrast: var(--tw-empty, );--tw-grayscale: var(--tw-empty, );--tw-hue-rotate: var(--tw-empty, );--tw-invert: var(--tw-empty, );--tw-saturate: var(--tw-empty, );--tw-sepia: var(--tw-empty, );--tw-drop-shadow: var(--tw-empty, );filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.invert{--tw-invert: invert(100%)}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition{transition-property:background-color,border-color,color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:background-color,border-color,color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:background-color,border-color,color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-75{transition-duration:75ms}.duration-100{transition-duration:.1s}.duration-150{transition-duration:.15s}.duration-200{transition-duration:.2s}.duration-300{transition-duration:.3s}.ease-linear{transition-timing-function:linear}.ease-in{transition-timing-function:cubic-bezier(.4,0,1,1)}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}@media (min-width: 640px){.sm\:inset-0{top:0;right:0;bottom:0;left:0}.sm\:col-span-2{grid-column:span 2 / span 2}.sm\:col-span-3{grid-column:span 3 / span 3}.sm\:col-span-4{grid-column:span 4 / span 4}.sm\:col-span-6{grid-column:span 6 / span 6}.sm\:mx-0{margin-left:0;margin-right:0}.sm\:-mx-6{margin-left:-1.5rem;margin-right:-1.5rem}.sm\:my-8{margin-top:2rem;margin-bottom:2rem}.sm\:mt-0{margin-top:0}.sm\:mt-4{margin-top:1rem}.sm\:mt-6{margin-top:1.5rem}.sm\:ml-3{margin-left:.75rem}.sm\:ml-4{margin-left:1rem}.sm\:ml-6{margin-left:1.5rem}.sm\:block{display:block}.sm\:inline-block{display:inline-block}.sm\:inline{display:inline}.sm\:flex{display:flex}.sm\:grid{display:grid}.sm\:hidden{display:none}.sm\:h-10{height:2.5rem}.sm\:h-screen{height:100vh}.sm\:w-10{width:2.5rem}.sm\:w-auto{width:auto}.sm\:w-full{width:100%}.sm\:max-w-sm{max-width:24rem}.sm\:max-w-lg{max-width:32rem}.sm\:flex-shrink-0{flex-shrink:0}.sm\:translate-y-0{--tw-translate-y: 0px}.sm\:scale-95{--tw-scale-x: .95;--tw-scale-y: .95}.sm\:scale-100{--tw-scale-x: 1;--tw-scale-y: 1}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:flex-row-reverse{flex-direction:row-reverse}.sm\:flex-nowrap{flex-wrap:nowrap}.sm\:items-start{align-items:flex-start}.sm\:items-center{align-items:center}.sm\:justify-center{justify-content:center}.sm\:justify-between{justify-content:space-between}.sm\:gap-4{gap:1rem}.sm\:rounded-lg{border-radius:.5rem}.sm\:p-0{padding:0}.sm\:p-6{padding:1.5rem}.sm\:px-0{padding-left:0;padding-right:0}.sm\:px-6{padding-left:1.5rem;padding-right:1.5rem}.sm\:text-left{text-align:left}.sm\:align-middle{vertical-align:middle}.sm\:text-sm{font-size:.875rem;line-height:1.25rem}}@media (min-width: 768px){.md\:col-span-1{grid-column:span 1 / span 1}.md\:col-span-2{grid-column:span 2 / span 2}.md\:col-span-4{grid-column:span 4 / span 4}.md\:col-span-5{grid-column:span 5 / span 5}.md\:col-span-6{grid-column:span 6 / span 6}.md\:col-start-2{grid-column-start:2}.md\:col-start-4{grid-column-start:4}.md\:mx-0{margin-left:0;margin-right:0}.md\:mt-0{margin-top:0}.md\:mt-5{margin-top:1.25rem}.md\:mt-10{margin-top:2.5rem}.md\:mr-2{margin-right:.5rem}.md\:-mr-1{margin-right:-.25rem}.md\:ml-2{margin-left:.5rem}.md\:ml-6{margin-left:1.5rem}.md\:block{display:block}.md\:flex{display:flex}.md\:grid{display:grid}.md\:hidden{display:none}.md\:w-1\/2{width:50%}.md\:w-1\/3{width:33.333333%}.md\:max-w-xl{max-width:36rem}.md\:max-w-3xl{max-width:48rem}.md\:flex-shrink-0{flex-shrink:0}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.md\:flex-row{flex-direction:row}.md\:items-center{align-items:center}.md\:justify-between{justify-content:space-between}.md\:gap-6{gap:1.5rem}.md\:p-24{padding:6rem}.md\:px-8{padding-left:2rem;padding-right:2rem}.md\:text-left{text-align:left}.md\:text-sm{font-size:.875rem;line-height:1.25rem}.md\:text-2xl{font-size:1.5rem;line-height:2rem}}@media (min-width: 1024px){.lg\:col-span-3{grid-column:span 3 / span 3}.lg\:col-span-6{grid-column:span 6 / span 6}.lg\:col-span-7{grid-column:span 7 / span 7}.lg\:col-span-8{grid-column:span 8 / span 8}.lg\:col-start-3{grid-column-start:3}.lg\:col-start-4{grid-column-start:4}.lg\:-mx-8{margin-left:-2rem;margin-right:-2rem}.lg\:mt-24{margin-top:6rem}.lg\:block{display:block}.lg\:flex{display:flex}.lg\:grid{display:grid}.lg\:hidden{display:none}.lg\:h-screen{height:100vh}.lg\:w-1\/2{width:50%}.lg\:w-1\/4{width:25%}.lg\:w-1\/5{width:20%}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.lg\:items-center{align-items:center}.lg\:gap-4{gap:1rem}.lg\:rounded-lg{border-radius:.5rem}.lg\:px-2{padding-left:.5rem;padding-right:.5rem}.lg\:px-4{padding-left:1rem;padding-right:1rem}.lg\:px-8{padding-left:2rem;padding-right:2rem}.lg\:px-16{padding-left:4rem;padding-right:4rem}.lg\:py-2{padding-top:.5rem;padding-bottom:.5rem}}@media (min-width: 1280px){.xl\:col-span-4{grid-column:span 4 / span 4}.xl\:col-span-6{grid-column:span 6 / span 6}.xl\:col-span-8{grid-column:span 8 / span 8}.xl\:col-span-9{grid-column:span 9 / span 9}.xl\:col-start-4{grid-column-start:4}.xl\:mt-32{margin-top:8rem}.xl\:ml-5{margin-left:1.25rem}.xl\:flex{display:flex}.xl\:justify-center{justify-content:center}.xl\:px-16{padding-left:4rem;padding-right:4rem}} diff --git a/public/build/assets/app-ec429add.css b/public/build/assets/app-ec429add.css deleted file mode 100644 index 1e2f40671d7f..000000000000 --- a/public/build/assets/app-ec429add.css +++ /dev/null @@ -1 +0,0 @@ -/*! tailwindcss v2.2.19 | MIT License | https://tailwindcss.com *//*! modern-normalize v1.1.0 | MIT License | https://github.com/sindresorhus/modern-normalize */*,:before,:after{box-sizing:border-box}html{-moz-tab-size:4;-o-tab-size:4;tab-size:4}html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}body{font-family:system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji"}hr{height:0;color:inherit}abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}::-moz-focus-inner{border-style:none;padding:0}legend{padding:0}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}button{background-color:transparent;background-image:none}fieldset{margin:0;padding:0}ol,ul{list-style:none;margin:0;padding:0}html{font-family:Open Sans,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";line-height:1.5}body{font-family:inherit;line-height:inherit}*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:currentColor}hr{border-top-width:1px}img{border-style:solid}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}table{border-collapse:collapse}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}button,input,optgroup,select,textarea{padding:0;line-height:inherit;color:inherit}pre,code,kbd,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}*,:before,:after{--tw-border-opacity: 1;border-color:rgba(229,231,235,var(--tw-border-opacity))}[type=text],[type=email],[type=url],[type=password],[type=number],[type=date],[type=month],[type=search],[type=time],textarea,select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;border-color:#6b7280;border-width:1px;border-radius:0;padding:.5rem .75rem;font-size:1rem;line-height:1.5rem;--tw-shadow: 0 0 #0000}[type=text]:focus,[type=email]:focus,[type=url]:focus,[type=password]:focus,[type=number]:focus,[type=date]:focus,[type=month]:focus,[type=search]:focus,[type=time]:focus,textarea:focus,select:focus{outline:2px solid transparent;outline-offset:2px;--tw-ring-inset: var(--tw-empty, );--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: #2563eb;--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);border-color:#2563eb}input::-moz-placeholder,textarea::-moz-placeholder{color:#6b7280;opacity:1}input::placeholder,textarea::placeholder{color:#6b7280;opacity:1}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-date-and-time-value{min-height:1.5em}select{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");background-position:right .5rem center;background-repeat:no-repeat;background-size:1.5em 1.5em;padding-right:2.5rem;-webkit-print-color-adjust:exact;color-adjust:exact}[type=checkbox],[type=radio]{-webkit-appearance:none;-moz-appearance:none;appearance:none;padding:0;-webkit-print-color-adjust:exact;color-adjust:exact;display:inline-block;vertical-align:middle;background-origin:border-box;-webkit-user-select:none;-moz-user-select:none;user-select:none;flex-shrink:0;height:1rem;width:1rem;color:#2563eb;background-color:#fff;border-color:#6b7280;border-width:1px;--tw-shadow: 0 0 #0000}[type=checkbox]{border-radius:0}[type=radio]{border-radius:100%}[type=checkbox]:focus,[type=radio]:focus{outline:2px solid transparent;outline-offset:2px;--tw-ring-inset: var(--tw-empty, );--tw-ring-offset-width: 2px;--tw-ring-offset-color: #fff;--tw-ring-color: #2563eb;--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}[type=checkbox]:checked,[type=radio]:checked{border-color:transparent;background-color:currentColor;background-size:100% 100%;background-position:center;background-repeat:no-repeat}[type=checkbox]:checked{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e")}[type=radio]:checked{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='8' cy='8' r='3'/%3e%3c/svg%3e")}[type=checkbox]:checked:hover,[type=checkbox]:checked:focus,[type=radio]:checked:hover,[type=radio]:checked:focus{border-color:transparent;background-color:currentColor}[type=checkbox]:indeterminate{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 16'%3e%3cpath stroke='white' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8h8'/%3e%3c/svg%3e");border-color:transparent;background-color:currentColor;background-size:100% 100%;background-position:center;background-repeat:no-repeat}[type=checkbox]:indeterminate:hover,[type=checkbox]:indeterminate:focus{border-color:transparent;background-color:currentColor}[type=file]{background:unset;border-color:inherit;border-width:0;border-radius:0;padding:0;font-size:unset;line-height:inherit}[type=file]:focus{outline:1px auto -webkit-focus-ring-color}.container{width:100%}@media (min-width: 640px){.container{max-width:640px}}@media (min-width: 768px){.container{max-width:768px}}@media (min-width: 1024px){.container{max-width:1024px}}@media (min-width: 1280px){.container{max-width:1280px}}@media (min-width: 1536px){.container{max-width:1536px}}.prose{color:#374151;max-width:65ch}.prose [class~=lead]{color:#4b5563;font-size:1.25em;line-height:1.6;margin-top:1.2em;margin-bottom:1.2em}.prose a{color:#111827;text-decoration:underline;font-weight:500}.prose strong{color:#111827;font-weight:600}.prose ol[type=A]{--list-counter-style: upper-alpha}.prose ol[type=a]{--list-counter-style: lower-alpha}.prose ol[type=A s]{--list-counter-style: upper-alpha}.prose ol[type=a s]{--list-counter-style: lower-alpha}.prose ol[type=i]{--list-counter-style: lower-roman}.prose ol[type=i s]{--list-counter-style: lower-roman}.prose ol[type="1"]{--list-counter-style: decimal}.prose ol>li{position:relative;padding-left:1.75em}.prose ol>li:before{content:counter(list-item,var(--list-counter-style, decimal)) ".";position:absolute;font-weight:400;color:#6b7280;left:0}.prose ul>li{position:relative;padding-left:1.75em}.prose ul>li:before{content:"";position:absolute;background-color:#d1d5db;border-radius:50%;width:.375em;height:.375em;top:.6875em;left:.25em}.prose hr{border-color:#e5e7eb;border-top-width:1px;margin-top:3em;margin-bottom:3em}.prose blockquote{font-weight:500;font-style:italic;color:#111827;border-left-width:.25rem;border-left-color:#e5e7eb;quotes:"“""”""‘""’";margin-top:1.6em;margin-bottom:1.6em;padding-left:1em}.prose blockquote p:first-of-type:before{content:open-quote}.prose blockquote p:last-of-type:after{content:close-quote}.prose h1{color:#111827;font-weight:800;font-size:2.25em;margin-top:0;margin-bottom:.8888889em;line-height:1.1111111}.prose h2{color:#111827;font-weight:700;font-size:1.5em;margin-top:2em;margin-bottom:1em;line-height:1.3333333}.prose h3{color:#111827;font-weight:600;font-size:1.25em;margin-top:1.6em;margin-bottom:.6em;line-height:1.6}.prose h4{color:#111827;font-weight:600;margin-top:1.5em;margin-bottom:.5em;line-height:1.5}.prose figure figcaption{color:#6b7280;font-size:.875em;line-height:1.4285714;margin-top:.8571429em}.prose code{color:#111827;font-weight:600;font-size:.875em}.prose code:before{content:"`"}.prose code:after{content:"`"}.prose a code{color:#111827}.prose pre{color:#e5e7eb;background-color:#1f2937;overflow-x:auto;font-size:.875em;line-height:1.7142857;margin-top:1.7142857em;margin-bottom:1.7142857em;border-radius:.375rem;padding:.8571429em 1.1428571em}.prose pre code{background-color:transparent;border-width:0;border-radius:0;padding:0;font-weight:400;color:inherit;font-size:inherit;font-family:inherit;line-height:inherit}.prose pre code:before{content:none}.prose pre code:after{content:none}.prose table{width:100%;table-layout:auto;text-align:left;margin-top:2em;margin-bottom:2em;font-size:.875em;line-height:1.7142857}.prose thead{color:#111827;font-weight:600;border-bottom-width:1px;border-bottom-color:#d1d5db}.prose thead th{vertical-align:bottom;padding-right:.5714286em;padding-bottom:.5714286em;padding-left:.5714286em}.prose tbody tr{border-bottom-width:1px;border-bottom-color:#e5e7eb}.prose tbody tr:last-child{border-bottom-width:0}.prose tbody td{vertical-align:top;padding:.5714286em}.prose{font-size:1rem;line-height:1.75}.prose p{margin-top:1.25em;margin-bottom:1.25em}.prose img{margin-top:2em;margin-bottom:2em}.prose video{margin-top:2em;margin-bottom:2em}.prose figure{margin-top:2em;margin-bottom:2em}.prose figure>*{margin-top:0;margin-bottom:0}.prose h2 code{font-size:.875em}.prose h3 code{font-size:.9em}.prose ol,.prose ul{margin-top:1.25em;margin-bottom:1.25em}.prose li{margin-top:.5em;margin-bottom:.5em}.prose>ul>li p{margin-top:.75em;margin-bottom:.75em}.prose>ul>li>*:first-child{margin-top:1.25em}.prose>ul>li>*:last-child{margin-bottom:1.25em}.prose>ol>li>*:first-child{margin-top:1.25em}.prose>ol>li>*:last-child{margin-bottom:1.25em}.prose ul ul,.prose ul ol,.prose ol ul,.prose ol ol{margin-top:.75em;margin-bottom:.75em}.prose hr+*{margin-top:0}.prose h2+*{margin-top:0}.prose h3+*{margin-top:0}.prose h4+*{margin-top:0}.prose thead th:first-child{padding-left:0}.prose thead th:last-child{padding-right:0}.prose tbody td:first-child{padding-left:0}.prose tbody td:last-child{padding-right:0}.prose>:first-child{margin-top:0}.prose>:last-child{margin-bottom:0}.button{border-radius:.25rem;padding:.75rem 1rem;font-size:.875rem;line-height:1.25rem;line-height:1rem;transition-property:background-color,border-color,color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:background-color,border-color,color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:background-color,border-color,color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-duration:.15s;transition-timing-function:cubic-bezier(.4,0,.2,1)}button:disabled{cursor:not-allowed;opacity:.5}.button-primary{--tw-text-opacity: 1;color:rgba(255,255,255,var(--tw-text-opacity))}.button-primary:hover{font-weight:600}.button-block{display:block;width:100%}.button-danger{--tw-bg-opacity: 1;background-color:rgba(239,68,68,var(--tw-bg-opacity));--tw-text-opacity: 1;color:rgba(255,255,255,var(--tw-text-opacity))}.button-danger:hover{--tw-bg-opacity: 1;background-color:rgba(220,38,38,var(--tw-bg-opacity))}.button-secondary{--tw-bg-opacity: 1;background-color:rgba(243,244,246,var(--tw-bg-opacity))}.button-secondary:hover{--tw-bg-opacity: 1;background-color:rgba(229,231,235,var(--tw-bg-opacity))}.button-link{--tw-text-opacity: 1;color:rgba(55,65,81,var(--tw-text-opacity))}.button-link:hover{--tw-text-opacity: 1;color:rgba(17,24,39,var(--tw-text-opacity));text-decoration:underline}.button-link:focus{text-decoration:underline;outline:2px solid transparent;outline-offset:2px}.validation{margin-top:.5rem;margin-bottom:.25rem;border-left-width:2px;--tw-bg-opacity: 1;background-color:rgba(243,244,246,var(--tw-bg-opacity));padding:.25rem .75rem}.validation-fail{--tw-border-opacity: 1;border-color:rgba(239,68,68,var(--tw-border-opacity));font-size:.875rem;line-height:1.25rem;--tw-text-opacity: 1;color:rgba(55,65,81,var(--tw-text-opacity))}.validation-pass{--tw-border-opacity: 1;border-color:rgba(16,185,129,var(--tw-border-opacity));font-size:.875rem;line-height:1.25rem;--tw-text-opacity: 1;color:rgba(55,65,81,var(--tw-text-opacity))}.input{margin-top:.5rem;align-items:center;border-radius:.25rem;border-width:1px;--tw-border-opacity: 1;border-color:rgba(209,213,219,var(--tw-border-opacity));padding:.5rem 1rem;font-size:.875rem;line-height:1.25rem}.input:focus{--tw-bg-opacity: 1;background-color:rgba(249,250,251,var(--tw-bg-opacity));outline:2px solid transparent;outline-offset:2px}.input-label{font-size:.875rem;line-height:1.25rem;--tw-text-opacity: 1;color:rgba(75,85,99,var(--tw-text-opacity))}.input-slim{padding-top:.5rem;padding-bottom:.5rem}.form-checkbox{cursor:pointer;border-radius:.25rem;border-width:1px;--tw-border-opacity: 1;border-color:rgba(209,213,219,var(--tw-border-opacity))}.form-select{border-width:1px;--tw-border-opacity: 1;border-color:rgba(209,213,219,var(--tw-border-opacity))}.alert{margin-top:.5rem;margin-top:1rem;margin-bottom:.25rem;border-left-width:2px;--tw-border-opacity: 1;border-color:rgba(156,163,175,var(--tw-border-opacity));--tw-bg-opacity: 1;background-color:rgba(243,244,246,var(--tw-bg-opacity));padding:.75rem 1rem;font-size:.875rem;line-height:1.25rem}.alert-success{--tw-border-opacity: 1;border-color:rgba(16,185,129,var(--tw-border-opacity))}.alert-failure{--tw-border-opacity: 1;border-color:rgba(239,68,68,var(--tw-border-opacity))}.badge{display:inline-flex;align-items:center;border-radius:9999px;padding:.125rem .625rem;font-size:.75rem;font-weight:500;line-height:1rem}.badge-light{--tw-bg-opacity: 1;background-color:rgba(243,244,246,var(--tw-bg-opacity));--tw-text-opacity: 1;color:rgba(31,41,55,var(--tw-text-opacity))}.badge-primary{--tw-bg-opacity: 1;background-color:rgba(191,219,254,var(--tw-bg-opacity));--tw-text-opacity: 1;color:rgba(59,130,246,var(--tw-text-opacity))}.badge-danger{--tw-bg-opacity: 1;background-color:rgba(254,226,226,var(--tw-bg-opacity));--tw-text-opacity: 1;color:rgba(239,68,68,var(--tw-text-opacity))}.badge-success{--tw-bg-opacity: 1;background-color:rgba(209,250,229,var(--tw-bg-opacity));--tw-text-opacity: 1;color:rgba(16,185,129,var(--tw-text-opacity))}.badge-secondary{--tw-bg-opacity: 1;background-color:rgba(31,41,55,var(--tw-bg-opacity));--tw-text-opacity: 1;color:rgba(229,231,235,var(--tw-text-opacity))}.badge-warning{--tw-bg-opacity: 1;background-color:rgba(59,130,246,var(--tw-bg-opacity));--tw-text-opacity: 1;color:rgba(217,119,6,var(--tw-text-opacity))}.badge-info{--tw-bg-opacity: 1;background-color:rgba(219,234,254,var(--tw-bg-opacity));--tw-text-opacity: 1;color:rgba(59,130,246,var(--tw-text-opacity))}@media (min-width: 640px){.dataTables_length{margin-top:1.25rem!important;margin-bottom:1.25rem!important}}@media (min-width: 1024px){.dataTables_length{margin-top:1rem!important;margin-bottom:1rem!important}}.dataTables_length select{margin-top:.5rem;align-items:center;border-radius:.25rem;border-width:1px;--tw-border-opacity: 1;border-color:rgba(209,213,219,var(--tw-border-opacity));padding:.5rem 1rem;font-size:.875rem;line-height:1.25rem}.dataTables_length select:focus{--tw-bg-opacity: 1 !important;background-color:rgba(249,250,251,var(--tw-bg-opacity))!important;outline:2px solid transparent!important;outline-offset:2px!important}.dataTables_length select{margin-left:.5rem!important;margin-right:.5rem!important;--tw-bg-opacity: 1 !important;background-color:rgba(255,255,255,var(--tw-bg-opacity))!important;padding:.5rem!important}.dataTables_filter{margin-bottom:1rem}.dataTables_filter input{margin-top:.5rem;align-items:center;border-radius:.25rem;border-width:1px;--tw-border-opacity: 1;border-color:rgba(209,213,219,var(--tw-border-opacity));padding:.5rem 1rem;font-size:.875rem;line-height:1.25rem}.dataTables_filter input:focus{--tw-bg-opacity: 1 !important;background-color:rgba(249,250,251,var(--tw-bg-opacity))!important;outline:2px solid transparent!important;outline-offset:2px!important}@media (min-width: 1024px){.dataTables_filter{margin-top:-3rem!important}}.dataTables_paginate{padding-top:.5rem!important;padding-bottom:1.5rem!important}.dataTables_paginate .paginate_button{border-radius:.25rem;padding:.75rem 1rem;font-size:.875rem;line-height:1.25rem;line-height:1rem;transition-property:background-color,border-color,color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:background-color,border-color,color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:background-color,border-color,color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-duration:.15s;transition-timing-function:cubic-bezier(.4,0,.2,1);margin-right:.25rem!important;cursor:pointer!important;border-radius:.25rem!important;border-width:1px!important;--tw-border-opacity: 1 !important;border-color:rgba(209,213,219,var(--tw-border-opacity))!important;--tw-bg-opacity: 1 !important;background-color:rgba(255,255,255,var(--tw-bg-opacity))!important;padding-top:.5rem!important;padding-bottom:.5rem!important;font-size:.875rem!important;line-height:1.25rem!important;font-weight:500!important;line-height:1rem!important;--tw-text-opacity: 1 !important;color:rgba(55,65,81,var(--tw-text-opacity))!important}.dataTables_paginate .current{--tw-bg-opacity: 1 !important;background-color:rgba(37,99,235,var(--tw-bg-opacity))!important;--tw-text-opacity: 1 !important;color:rgba(255,255,255,var(--tw-text-opacity))!important}.dataTables_info{font-size:.875rem!important;line-height:1.25rem!important}.dataTables_empty{padding-top:1rem!important;padding-bottom:1rem!important}.pagination{display:flex!important;align-items:center!important}.pagination .page-link{margin-top:-1px!important;display:inline-flex!important;cursor:pointer!important;align-items:center!important;border-top-width:2px!important;border-color:transparent!important;padding-left:1rem!important;padding-right:1rem!important;padding-top:1rem!important;font-size:.875rem!important;font-weight:500!important;line-height:1.25rem!important;--tw-text-opacity: 1 !important;color:rgba(107,114,128,var(--tw-text-opacity))!important;transition-property:background-color,border-color,color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter!important;transition-property:background-color,border-color,color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter!important;transition-property:background-color,border-color,color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter!important;transition-duration:.15s!important;transition-timing-function:cubic-bezier(.4,0,.2,1)!important}.pagination .page-link:hover{--tw-border-opacity: 1 !important;border-color:rgba(209,213,219,var(--tw-border-opacity))!important;--tw-text-opacity: 1 !important;color:rgba(55,65,81,var(--tw-text-opacity))!important}.pagination .page-link:focus{--tw-border-opacity: 1;border-color:rgba(156,163,175,var(--tw-border-opacity));--tw-text-opacity: 1;color:rgba(55,65,81,var(--tw-text-opacity));outline:2px solid transparent;outline-offset:2px}.pagination .active>span{--tw-border-opacity: 1 !important;border-color:rgba(37,99,235,var(--tw-border-opacity))!important;--tw-text-opacity: 1 !important;color:rgba(37,99,235,var(--tw-text-opacity))!important}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.pointer-events-none{pointer-events:none}.pointer-events-auto{pointer-events:auto}.visible{visibility:visible}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.inset-0{top:0;right:0;bottom:0;left:0}.inset-x-0{left:0;right:0}.inset-y-0{top:0;bottom:0}.top-0{top:0}.top-1{top:.25rem}.right-0{right:0}.bottom-0{bottom:0}.left-0{left:0}.left-1{left:.25rem}.z-0{z-index:0}.z-10{z-index:10}.z-30{z-index:30}.z-40{z-index:40}.z-50{z-index:50}.focus-within\:z-10:focus-within{z-index:10}.col-span-1{grid-column:span 1 / span 1}.col-span-2{grid-column:span 2 / span 2}.col-span-3{grid-column:span 3 / span 3}.col-span-4{grid-column:span 4 / span 4}.col-span-6{grid-column:span 6 / span 6}.col-span-8{grid-column:span 8 / span 8}.col-span-12{grid-column:span 12 / span 12}.float-right{float:right}.m-0{margin:0}.m-auto{margin:auto}.mx-2{margin-left:.5rem;margin-right:.5rem}.mx-4{margin-left:1rem;margin-right:1rem}.mx-6{margin-left:1.5rem;margin-right:1.5rem}.mx-auto{margin-left:auto;margin-right:auto}.my-2{margin-top:.5rem;margin-bottom:.5rem}.my-3{margin-top:.75rem;margin-bottom:.75rem}.my-4{margin-top:1rem;margin-bottom:1rem}.my-10{margin-top:2.5rem;margin-bottom:2.5rem}.-my-2{margin-top:-.5rem;margin-bottom:-.5rem}.-my-6{margin-top:-1.5rem;margin-bottom:-1.5rem}.mt-0{margin-top:0}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-5{margin-top:1.25rem}.mt-6{margin-top:1.5rem}.mt-8{margin-top:2rem}.mt-10{margin-top:2.5rem}.mt-20{margin-top:5rem}.-mt-4{margin-top:-1rem}.-mt-6{margin-top:-1.5rem}.mr-0{margin-right:0}.mr-1{margin-right:.25rem}.mr-2{margin-right:.5rem}.mr-3{margin-right:.75rem}.mr-4{margin-right:1rem}.mr-5{margin-right:1.25rem}.-mr-1{margin-right:-.25rem}.-mr-14{margin-right:-3.5rem}.mb-0{margin-bottom:0}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-5{margin-bottom:1.25rem}.mb-6{margin-bottom:1.5rem}.mb-8{margin-bottom:2rem}.mb-10{margin-bottom:2.5rem}.mb-12{margin-bottom:3rem}.mb-1\.5{margin-bottom:.375rem}.ml-0{margin-left:0}.ml-1{margin-left:.25rem}.ml-2{margin-left:.5rem}.ml-3{margin-left:.75rem}.ml-4{margin-left:1rem}.ml-5{margin-left:1.25rem}.-ml-1{margin-left:-.25rem}.-ml-4{margin-left:-1rem}.-ml-px{margin-left:-1px}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.contents{display:contents}.hidden{display:none}.h-0{height:0px}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-8{height:2rem}.h-10{height:2.5rem}.h-12{height:3rem}.h-14{height:3.5rem}.h-16{height:4rem}.h-24{height:6rem}.h-64{height:16rem}.h-auto{height:auto}.h-full{height:100%}.h-screen{height:100vh}.min-h-screen{min-height:100vh}.w-0{width:0px}.w-4{width:1rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-8{width:2rem}.w-10{width:2.5rem}.w-12{width:3rem}.w-14{width:3.5rem}.w-24{width:6rem}.w-48{width:12rem}.w-56{width:14rem}.w-64{width:16rem}.w-80{width:20rem}.w-auto{width:auto}.w-1\/2{width:50%}.w-3\/4{width:75%}.w-4\/5{width:80%}.w-1\/6{width:16.666667%}.w-4\/6{width:66.666667%}.w-5\/6{width:83.333333%}.w-full{width:100%}.w-screen{width:100vw}.min-w-full{min-width:100%}.max-w-xs{max-width:20rem}.max-w-xl{max-width:36rem}.max-w-2xl{max-width:42rem}.max-w-4xl{max-width:56rem}.flex-1{flex:1 1 0%}.flex-shrink-0{flex-shrink:0}.flex-shrink{flex-shrink:1}.flex-grow{flex-grow:1}.table-auto{table-layout:auto}.border-collapse{border-collapse:collapse}.origin-top-right{transform-origin:top right}.transform{--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;transform:translate(var(--tw-translate-x)) translateY(var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-0{--tw-translate-x: 0px}.-translate-x-full{--tw-translate-x: -100%}.translate-y-0{--tw-translate-y: 0px}.translate-y-4{--tw-translate-y: 1rem}.scale-95{--tw-scale-x: .95;--tw-scale-y: .95}.scale-100{--tw-scale-x: 1;--tw-scale-y: 1}@keyframes spin{to{transform:rotate(360deg)}}@keyframes ping{75%,to{transform:scale(2);opacity:0}}@keyframes pulse{50%{opacity:.5}}@keyframes bounce{0%,to{transform:translateY(-25%);animation-timing-function:cubic-bezier(.8,0,1,1)}50%{transform:none;animation-timing-function:cubic-bezier(0,0,.2,1)}}.animate-spin{animation:spin 1s linear infinite}.cursor-pointer{cursor:pointer}.resize{resize:both}.list-none{list-style-type:none}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.flex-row{flex-direction:row}.flex-col{flex-direction:column}.flex-col-reverse{flex-direction:column-reverse}.flex-wrap{flex-wrap:wrap}.place-content-end{place-content:end}.place-items-center{place-items:center}.content-center{align-content:center}.content-start{align-content:flex-start}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.items-stretch{align-items:stretch}.justify-start{justify-content:flex-start}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-4{gap:1rem}.gap-5{gap:1.25rem}.gap-6{gap:1.5rem}.gap-8{gap:2rem}.gap-x-2{-moz-column-gap:.5rem;column-gap:.5rem}.space-x-1>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(.25rem * var(--tw-space-x-reverse));margin-left:calc(.25rem * calc(1 - var(--tw-space-x-reverse)))}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(.5rem * var(--tw-space-x-reverse));margin-left:calc(.5rem * calc(1 - var(--tw-space-x-reverse)))}.space-x-4>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(1rem * var(--tw-space-x-reverse));margin-left:calc(1rem * calc(1 - var(--tw-space-x-reverse)))}.space-y-5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.25rem * var(--tw-space-y-reverse))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse: 0;border-top-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(1px * var(--tw-divide-y-reverse))}.divide-gray-200>:not([hidden])~:not([hidden]){--tw-divide-opacity: 1;border-color:rgba(229,231,235,var(--tw-divide-opacity))}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.overflow-y-scroll{overflow-y:scroll}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.overflow-ellipsis{text-overflow:ellipsis}.whitespace-nowrap{white-space:nowrap}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.rounded-none{border-radius:0}.rounded-sm{border-radius:.125rem}.rounded{border-radius:.25rem}.rounded-md{border-radius:.375rem}.rounded-lg{border-radius:.5rem}.rounded-full{border-radius:9999px}.rounded-t-lg{border-top-left-radius:.5rem;border-top-right-radius:.5rem}.rounded-r-md{border-top-right-radius:.375rem;border-bottom-right-radius:.375rem}.rounded-b-lg{border-bottom-right-radius:.5rem;border-bottom-left-radius:.5rem}.rounded-l-md{border-top-left-radius:.375rem;border-bottom-left-radius:.375rem}.border-0{border-width:0px}.border-2{border-width:2px}.border-4{border-width:4px}.border{border-width:1px}.border-t-2{border-top-width:2px}.border-t-4{border-top-width:4px}.border-t{border-top-width:1px}.border-r{border-right-width:1px}.border-b-2{border-bottom-width:2px}.border-b{border-bottom-width:1px}.border-l-2{border-left-width:2px}.border-solid{border-style:solid}.border-dashed{border-style:dashed}.border-none{border-style:none}.border-transparent{border-color:transparent}.border-gray-100{--tw-border-opacity: 1;border-color:rgba(243,244,246,var(--tw-border-opacity))}.border-gray-200{--tw-border-opacity: 1;border-color:rgba(229,231,235,var(--tw-border-opacity))}.border-gray-300{--tw-border-opacity: 1;border-color:rgba(209,213,219,var(--tw-border-opacity))}.border-gray-500{--tw-border-opacity: 1;border-color:rgba(107,114,128,var(--tw-border-opacity))}.border-gray-600{--tw-border-opacity: 1;border-color:rgba(75,85,99,var(--tw-border-opacity))}.border-red-300{--tw-border-opacity: 1;border-color:rgba(252,165,165,var(--tw-border-opacity))}.border-red-400{--tw-border-opacity: 1;border-color:rgba(248,113,113,var(--tw-border-opacity))}.border-red-900{--tw-border-opacity: 1;border-color:rgba(127,29,29,var(--tw-border-opacity))}.border-green-500{--tw-border-opacity: 1;border-color:rgba(16,185,129,var(--tw-border-opacity))}.border-blue-500{--tw-border-opacity: 1;border-color:rgba(59,130,246,var(--tw-border-opacity))}.group:hover .group-hover\:border-transparent,.hover\:border-transparent:hover{border-color:transparent}.hover\:border-gray-600:hover{--tw-border-opacity: 1;border-color:rgba(75,85,99,var(--tw-border-opacity))}.hover\:border-gray-800:hover{--tw-border-opacity: 1;border-color:rgba(31,41,55,var(--tw-border-opacity))}.hover\:border-blue-600:hover{--tw-border-opacity: 1;border-color:rgba(37,99,235,var(--tw-border-opacity))}.focus\:border-red-500:focus{--tw-border-opacity: 1;border-color:rgba(239,68,68,var(--tw-border-opacity))}.focus\:border-blue-300:focus{--tw-border-opacity: 1;border-color:rgba(147,197,253,var(--tw-border-opacity))}.focus\:border-blue-500:focus{--tw-border-opacity: 1;border-color:rgba(59,130,246,var(--tw-border-opacity))}.focus\:border-indigo-500:focus{--tw-border-opacity: 1;border-color:rgba(99,102,241,var(--tw-border-opacity))}.border-opacity-50{--tw-border-opacity: .5}.bg-transparent{background-color:transparent}.bg-white{--tw-bg-opacity: 1;background-color:rgba(255,255,255,var(--tw-bg-opacity))}.bg-gray-50{--tw-bg-opacity: 1;background-color:rgba(249,250,251,var(--tw-bg-opacity))}.bg-gray-100{--tw-bg-opacity: 1;background-color:rgba(243,244,246,var(--tw-bg-opacity))}.bg-gray-200{--tw-bg-opacity: 1;background-color:rgba(229,231,235,var(--tw-bg-opacity))}.bg-gray-500{--tw-bg-opacity: 1;background-color:rgba(107,114,128,var(--tw-bg-opacity))}.bg-gray-600{--tw-bg-opacity: 1;background-color:rgba(75,85,99,var(--tw-bg-opacity))}.bg-red-100{--tw-bg-opacity: 1;background-color:rgba(254,226,226,var(--tw-bg-opacity))}.bg-red-500{--tw-bg-opacity: 1;background-color:rgba(239,68,68,var(--tw-bg-opacity))}.bg-green-600{--tw-bg-opacity: 1;background-color:rgba(5,150,105,var(--tw-bg-opacity))}.bg-blue-50{--tw-bg-opacity: 1;background-color:rgba(239,246,255,var(--tw-bg-opacity))}.bg-blue-500{--tw-bg-opacity: 1;background-color:rgba(59,130,246,var(--tw-bg-opacity))}.bg-blue-600{--tw-bg-opacity: 1;background-color:rgba(37,99,235,var(--tw-bg-opacity))}.bg-blue-700{--tw-bg-opacity: 1;background-color:rgba(29,78,216,var(--tw-bg-opacity))}.hover\:bg-gray-100:hover{--tw-bg-opacity: 1;background-color:rgba(243,244,246,var(--tw-bg-opacity))}.hover\:bg-gray-200:hover{--tw-bg-opacity: 1;background-color:rgba(229,231,235,var(--tw-bg-opacity))}.hover\:bg-red-900:hover{--tw-bg-opacity: 1;background-color:rgba(127,29,29,var(--tw-bg-opacity))}.hover\:bg-blue-500:hover{--tw-bg-opacity: 1;background-color:rgba(59,130,246,var(--tw-bg-opacity))}.hover\:bg-blue-600:hover{--tw-bg-opacity: 1;background-color:rgba(37,99,235,var(--tw-bg-opacity))}.focus\:bg-white:focus{--tw-bg-opacity: 1;background-color:rgba(255,255,255,var(--tw-bg-opacity))}.focus\:bg-gray-100:focus{--tw-bg-opacity: 1;background-color:rgba(243,244,246,var(--tw-bg-opacity))}.focus\:bg-gray-600:focus{--tw-bg-opacity: 1;background-color:rgba(75,85,99,var(--tw-bg-opacity))}.bg-opacity-100{--tw-bg-opacity: 1}.bg-clip-padding{background-clip:padding-box}.fill-current{fill:currentColor}.object-cover{-o-object-fit:cover;object-fit:cover}.object-scale-down{-o-object-fit:scale-down;object-fit:scale-down}.object-center{-o-object-position:center;object-position:center}.p-1{padding:.25rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-8{padding:2rem}.p-10{padding:2.5rem}.p-2\.5{padding:.625rem}.px-0{padding-left:0;padding-right:0}.px-1{padding-left:.25rem;padding-right:.25rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.px-12{padding-left:3rem;padding-right:3rem}.py-0{padding-top:0;padding-bottom:0}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-5{padding-top:1.25rem;padding-bottom:1.25rem}.py-6{padding-top:1.5rem;padding-bottom:1.5rem}.py-8{padding-top:2rem;padding-bottom:2rem}.py-10{padding-top:2.5rem;padding-bottom:2.5rem}.pt-0{padding-top:0}.pt-2{padding-top:.5rem}.pt-4{padding-top:1rem}.pt-5{padding-top:1.25rem}.pt-6{padding-top:1.5rem}.pr-2{padding-right:.5rem}.pr-4{padding-right:1rem}.pr-10{padding-right:2.5rem}.pb-2{padding-bottom:.5rem}.pb-3{padding-bottom:.75rem}.pb-4{padding-bottom:1rem}.pb-6{padding-bottom:1.5rem}.pb-10{padding-bottom:2.5rem}.pb-20{padding-bottom:5rem}.pl-0{padding-left:0}.pl-2{padding-left:.5rem}.pl-3{padding-left:.75rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.align-middle{vertical-align:middle}.align-bottom{vertical-align:bottom}.text-xs{font-size:.75rem;line-height:1rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-5xl{font-size:3rem;line-height:1}.font-light{font-weight:300}.font-normal{font-weight:400}.font-medium{font-weight:500}.font-semibold{font-weight:600}.font-bold{font-weight:700}.uppercase{text-transform:uppercase}.capitalize{text-transform:capitalize}.italic{font-style:italic}.leading-4{line-height:1rem}.leading-5{line-height:1.25rem}.leading-6{line-height:1.5rem}.leading-9{line-height:2.25rem}.leading-tight{line-height:1.25}.tracking-wide{letter-spacing:.025em}.tracking-wider{letter-spacing:.05em}.text-black{--tw-text-opacity: 1;color:rgba(0,0,0,var(--tw-text-opacity))}.text-white{--tw-text-opacity: 1;color:rgba(255,255,255,var(--tw-text-opacity))}.text-gray-300{--tw-text-opacity: 1;color:rgba(209,213,219,var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity: 1;color:rgba(156,163,175,var(--tw-text-opacity))}.text-gray-500{--tw-text-opacity: 1;color:rgba(107,114,128,var(--tw-text-opacity))}.text-gray-600{--tw-text-opacity: 1;color:rgba(75,85,99,var(--tw-text-opacity))}.text-gray-700{--tw-text-opacity: 1;color:rgba(55,65,81,var(--tw-text-opacity))}.text-gray-800{--tw-text-opacity: 1;color:rgba(31,41,55,var(--tw-text-opacity))}.text-gray-900{--tw-text-opacity: 1;color:rgba(17,24,39,var(--tw-text-opacity))}.text-red-400{--tw-text-opacity: 1;color:rgba(248,113,113,var(--tw-text-opacity))}.text-red-500{--tw-text-opacity: 1;color:rgba(239,68,68,var(--tw-text-opacity))}.text-red-600{--tw-text-opacity: 1;color:rgba(220,38,38,var(--tw-text-opacity))}.text-red-700{--tw-text-opacity: 1;color:rgba(185,28,28,var(--tw-text-opacity))}.text-red-900{--tw-text-opacity: 1;color:rgba(127,29,29,var(--tw-text-opacity))}.text-green-600{--tw-text-opacity: 1;color:rgba(5,150,105,var(--tw-text-opacity))}.text-blue-500{--tw-text-opacity: 1;color:rgba(59,130,246,var(--tw-text-opacity))}.text-blue-600{--tw-text-opacity: 1;color:rgba(37,99,235,var(--tw-text-opacity))}.text-blue-700{--tw-text-opacity: 1;color:rgba(29,78,216,var(--tw-text-opacity))}.text-indigo-600{--tw-text-opacity: 1;color:rgba(79,70,229,var(--tw-text-opacity))}.group:hover .group-hover\:text-white,.hover\:text-white:hover{--tw-text-opacity: 1;color:rgba(255,255,255,var(--tw-text-opacity))}.hover\:text-gray-300:hover{--tw-text-opacity: 1;color:rgba(209,213,219,var(--tw-text-opacity))}.hover\:text-gray-500:hover{--tw-text-opacity: 1;color:rgba(107,114,128,var(--tw-text-opacity))}.hover\:text-gray-600:hover{--tw-text-opacity: 1;color:rgba(75,85,99,var(--tw-text-opacity))}.hover\:text-gray-700:hover{--tw-text-opacity: 1;color:rgba(55,65,81,var(--tw-text-opacity))}.hover\:text-gray-800:hover{--tw-text-opacity: 1;color:rgba(31,41,55,var(--tw-text-opacity))}.hover\:text-gray-900:hover{--tw-text-opacity: 1;color:rgba(17,24,39,var(--tw-text-opacity))}.hover\:text-red-500:hover{--tw-text-opacity: 1;color:rgba(239,68,68,var(--tw-text-opacity))}.hover\:text-blue-600:hover{--tw-text-opacity: 1;color:rgba(37,99,235,var(--tw-text-opacity))}.hover\:text-indigo-900:hover{--tw-text-opacity: 1;color:rgba(49,46,129,var(--tw-text-opacity))}.focus\:text-gray-500:focus{--tw-text-opacity: 1;color:rgba(107,114,128,var(--tw-text-opacity))}.focus\:text-gray-600:focus{--tw-text-opacity: 1;color:rgba(75,85,99,var(--tw-text-opacity))}.focus\:text-gray-900:focus{--tw-text-opacity: 1;color:rgba(17,24,39,var(--tw-text-opacity))}.underline{text-decoration:underline}.line-through{text-decoration:line-through}.no-underline{text-decoration:none}.hover\:underline:hover{text-decoration:underline}.focus\:underline:focus{text-decoration:underline}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.opacity-0{opacity:0}.opacity-25{opacity:.25}.opacity-75{opacity:.75}.opacity-100{opacity:1}.hover\:opacity-80:hover{opacity:.8}*,:before,:after{--tw-shadow: 0 0 #0000}.shadow-sm{--tw-shadow: 0 1px 2px 0 rgba(0, 0, 0, .05);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow{--tw-shadow: 0 1px 3px 0 rgba(0, 0, 0, .1), 0 1px 2px 0 rgba(0, 0, 0, .06);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-lg{--tw-shadow: 0 10px 15px -3px rgba(0, 0, 0, .1), 0 4px 6px -2px rgba(0, 0, 0, .05);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-xl{--tw-shadow: 0 20px 25px -5px rgba(0, 0, 0, .1), 0 10px 10px -5px rgba(0, 0, 0, .04);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.hover\:shadow-lg:hover{--tw-shadow: 0 10px 15px -3px rgba(0, 0, 0, .1), 0 4px 6px -2px rgba(0, 0, 0, .05);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.hover\:shadow-2xl:hover{--tw-shadow: 0 25px 50px -12px rgba(0, 0, 0, .25);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.outline-none{outline:2px solid transparent;outline-offset:2px}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}*,:before,:after{--tw-ring-inset: var(--tw-empty, );--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgba(59, 130, 246, .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000}.ring-1{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-1:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-black{--tw-ring-opacity: 1;--tw-ring-color: rgba(0, 0, 0, var(--tw-ring-opacity))}.focus\:ring-red-500:focus{--tw-ring-opacity: 1;--tw-ring-color: rgba(239, 68, 68, var(--tw-ring-opacity))}.focus\:ring-blue-500:focus{--tw-ring-opacity: 1;--tw-ring-color: rgba(59, 130, 246, var(--tw-ring-opacity))}.focus\:ring-indigo-500:focus{--tw-ring-opacity: 1;--tw-ring-color: rgba(99, 102, 241, var(--tw-ring-opacity))}.focus\:ring-indigo-600:focus{--tw-ring-opacity: 1;--tw-ring-color: rgba(79, 70, 229, var(--tw-ring-opacity))}.ring-opacity-5{--tw-ring-opacity: .05}.focus\:ring-opacity-50:focus{--tw-ring-opacity: .5}.filter{--tw-blur: var(--tw-empty, );--tw-brightness: var(--tw-empty, );--tw-contrast: var(--tw-empty, );--tw-grayscale: var(--tw-empty, );--tw-hue-rotate: var(--tw-empty, );--tw-invert: var(--tw-empty, );--tw-saturate: var(--tw-empty, );--tw-sepia: var(--tw-empty, );--tw-drop-shadow: var(--tw-empty, );filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.invert{--tw-invert: invert(100%)}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition{transition-property:background-color,border-color,color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:background-color,border-color,color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:background-color,border-color,color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-75{transition-duration:75ms}.duration-100{transition-duration:.1s}.duration-150{transition-duration:.15s}.duration-200{transition-duration:.2s}.duration-300{transition-duration:.3s}.ease-linear{transition-timing-function:linear}.ease-in{transition-timing-function:cubic-bezier(.4,0,1,1)}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}@media (min-width: 640px){.sm\:inset-0{top:0;right:0;bottom:0;left:0}.sm\:col-span-2{grid-column:span 2 / span 2}.sm\:col-span-3{grid-column:span 3 / span 3}.sm\:col-span-4{grid-column:span 4 / span 4}.sm\:col-span-6{grid-column:span 6 / span 6}.sm\:mx-0{margin-left:0;margin-right:0}.sm\:-mx-6{margin-left:-1.5rem;margin-right:-1.5rem}.sm\:my-8{margin-top:2rem;margin-bottom:2rem}.sm\:mt-0{margin-top:0}.sm\:mt-4{margin-top:1rem}.sm\:mt-6{margin-top:1.5rem}.sm\:ml-3{margin-left:.75rem}.sm\:ml-4{margin-left:1rem}.sm\:ml-6{margin-left:1.5rem}.sm\:block{display:block}.sm\:inline-block{display:inline-block}.sm\:inline{display:inline}.sm\:flex{display:flex}.sm\:grid{display:grid}.sm\:hidden{display:none}.sm\:h-10{height:2.5rem}.sm\:h-screen{height:100vh}.sm\:w-10{width:2.5rem}.sm\:w-auto{width:auto}.sm\:w-full{width:100%}.sm\:max-w-sm{max-width:24rem}.sm\:max-w-lg{max-width:32rem}.sm\:flex-shrink-0{flex-shrink:0}.sm\:translate-y-0{--tw-translate-y: 0px}.sm\:scale-95{--tw-scale-x: .95;--tw-scale-y: .95}.sm\:scale-100{--tw-scale-x: 1;--tw-scale-y: 1}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:flex-row-reverse{flex-direction:row-reverse}.sm\:flex-nowrap{flex-wrap:nowrap}.sm\:items-start{align-items:flex-start}.sm\:items-center{align-items:center}.sm\:justify-center{justify-content:center}.sm\:justify-between{justify-content:space-between}.sm\:gap-4{gap:1rem}.sm\:rounded-lg{border-radius:.5rem}.sm\:p-0{padding:0}.sm\:p-6{padding:1.5rem}.sm\:px-0{padding-left:0;padding-right:0}.sm\:px-6{padding-left:1.5rem;padding-right:1.5rem}.sm\:text-left{text-align:left}.sm\:align-middle{vertical-align:middle}.sm\:text-sm{font-size:.875rem;line-height:1.25rem}}@media (min-width: 768px){.md\:col-span-1{grid-column:span 1 / span 1}.md\:col-span-2{grid-column:span 2 / span 2}.md\:col-span-4{grid-column:span 4 / span 4}.md\:col-span-5{grid-column:span 5 / span 5}.md\:col-span-6{grid-column:span 6 / span 6}.md\:col-start-2{grid-column-start:2}.md\:col-start-4{grid-column-start:4}.md\:mx-0{margin-left:0;margin-right:0}.md\:mt-0{margin-top:0}.md\:mt-5{margin-top:1.25rem}.md\:mt-10{margin-top:2.5rem}.md\:mr-2{margin-right:.5rem}.md\:-mr-1{margin-right:-.25rem}.md\:ml-2{margin-left:.5rem}.md\:ml-6{margin-left:1.5rem}.md\:block{display:block}.md\:flex{display:flex}.md\:grid{display:grid}.md\:hidden{display:none}.md\:w-1\/2{width:50%}.md\:w-1\/3{width:33.333333%}.md\:max-w-3xl{max-width:48rem}.md\:flex-shrink-0{flex-shrink:0}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.md\:flex-row{flex-direction:row}.md\:items-center{align-items:center}.md\:justify-between{justify-content:space-between}.md\:gap-6{gap:1.5rem}.md\:p-24{padding:6rem}.md\:px-8{padding-left:2rem;padding-right:2rem}.md\:text-left{text-align:left}.md\:text-sm{font-size:.875rem;line-height:1.25rem}.md\:text-2xl{font-size:1.5rem;line-height:2rem}}@media (min-width: 1024px){.lg\:col-span-3{grid-column:span 3 / span 3}.lg\:col-span-6{grid-column:span 6 / span 6}.lg\:col-span-7{grid-column:span 7 / span 7}.lg\:col-span-8{grid-column:span 8 / span 8}.lg\:col-start-3{grid-column-start:3}.lg\:col-start-4{grid-column-start:4}.lg\:-mx-8{margin-left:-2rem;margin-right:-2rem}.lg\:mt-24{margin-top:6rem}.lg\:block{display:block}.lg\:flex{display:flex}.lg\:grid{display:grid}.lg\:hidden{display:none}.lg\:h-screen{height:100vh}.lg\:w-1\/2{width:50%}.lg\:w-1\/4{width:25%}.lg\:w-1\/5{width:20%}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.lg\:items-center{align-items:center}.lg\:gap-4{gap:1rem}.lg\:rounded-lg{border-radius:.5rem}.lg\:px-2{padding-left:.5rem;padding-right:.5rem}.lg\:px-4{padding-left:1rem;padding-right:1rem}.lg\:px-8{padding-left:2rem;padding-right:2rem}.lg\:px-16{padding-left:4rem;padding-right:4rem}.lg\:py-2{padding-top:.5rem;padding-bottom:.5rem}}@media (min-width: 1280px){.xl\:col-span-4{grid-column:span 4 / span 4}.xl\:col-span-6{grid-column:span 6 / span 6}.xl\:col-span-8{grid-column:span 8 / span 8}.xl\:col-span-9{grid-column:span 9 / span 9}.xl\:col-start-4{grid-column-start:4}.xl\:mt-32{margin-top:8rem}.xl\:flex{display:flex}.xl\:justify-center{justify-content:center}.xl\:px-16{padding-left:4rem;padding-right:4rem}} diff --git a/public/build/manifest.json b/public/build/manifest.json index df2a90f9dcc9..cf45a391cee6 100644 --- a/public/build/manifest.json +++ b/public/build/manifest.json @@ -240,7 +240,7 @@ "src": "resources/js/setup/setup.js" }, "resources/sass/app.scss": { - "file": "assets/app-ec429add.css", + "file": "assets/app-8c56d3d4.css", "isEntry": true, "src": "resources/sass/app.scss" } From 1cc17a4af46b950cd0ec453ef862acfa4ff40aa2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Tue, 20 Feb 2024 17:24:42 +0100 Subject: [PATCH 063/188] Add spinner and observer for loading screen --- resources/views/billing-portal/v3/index.blade.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/resources/views/billing-portal/v3/index.blade.php b/resources/views/billing-portal/v3/index.blade.php index b32611c932d6..9b6c3ddc6135 100644 --- a/resources/views/billing-portal/v3/index.blade.php +++ b/resources/views/billing-portal/v3/index.blade.php @@ -13,6 +13,21 @@ document.getElementById('payment-method-form').submit() }, 2000); }); + + const target = document.getElementById('container'); + + const observer = new MutationObserver((mutationsList) => { + for (const mutation of mutationsList) { + if (mutation.type === 'childList' || mutation.type === 'subtree') { + setTimeout(() => { + document.getElementById('spinner').classList.add('hidden'); + document.getElementById('container').classList.remove('hidden'); + }, 1500); + } + } + }); + + observer.observe(target, { childList: true, subtree: true }) }); @endpush From 21c920ad716b8162d4ceb3dbd07ffa2556714fa3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Tue, 20 Feb 2024 17:24:47 +0100 Subject: [PATCH 064/188] Add spinner animation to purchase page --- resources/views/billing-portal/v3/purchase.blade.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/resources/views/billing-portal/v3/purchase.blade.php b/resources/views/billing-portal/v3/purchase.blade.php index 82aad49b9e26..b27b5ff01b39 100644 --- a/resources/views/billing-portal/v3/purchase.blade.php +++ b/resources/views/billing-portal/v3/purchase.blade.php @@ -18,7 +18,12 @@ alt="{{ $subscription->company->present()->name }}" /> -
+ + + + + +
From 14a93eda0bcbe002a6762235c97f334776c5b749 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 21 Feb 2024 09:13:40 +1100 Subject: [PATCH 065/188] Minor fixes --- .../ClientPortal/PrePaymentController.php | 21 +++++++++++++------ app/Repositories/SubscriptionRepository.php | 2 +- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/app/Http/Controllers/ClientPortal/PrePaymentController.php b/app/Http/Controllers/ClientPortal/PrePaymentController.php index 2d80027f0650..61a21ce9d5e7 100644 --- a/app/Http/Controllers/ClientPortal/PrePaymentController.php +++ b/app/Http/Controllers/ClientPortal/PrePaymentController.php @@ -12,16 +12,17 @@ namespace App\Http\Controllers\ClientPortal; +use App\Utils\Number; +use App\Utils\HtmlEngine; +use Illuminate\View\View; use App\DataMapper\InvoiceItem; use App\Factory\InvoiceFactory; -use App\Http\Controllers\Controller; -use App\Http\Requests\ClientPortal\PrePayments\StorePrePaymentRequest; -use App\Repositories\InvoiceRepository; -use App\Utils\Number; -use App\Utils\Traits\MakesDates; use App\Utils\Traits\MakesHash; +use App\Utils\Traits\MakesDates; +use App\Http\Controllers\Controller; use Illuminate\Contracts\View\Factory; -use Illuminate\View\View; +use App\Repositories\InvoiceRepository; +use App\Http\Requests\ClientPortal\PrePayments\StorePrePaymentRequest; /** * Class PrePaymentController. @@ -102,6 +103,13 @@ class PrePaymentController extends Controller return $invoice; }); + + $variables = false; + + if(($invitation = $invoices->first()->invitations()->first() ?? false) && $invoice->client->getSetting('show_accept_invoice_terms')) { + $variables = (new HtmlEngine($invitation))->generateLabelsAndValues(); + } + $data = [ 'settings' => auth()->guard('contact')->user()->client->getMergedSettings(), 'invoices' => $invoices, @@ -113,6 +121,7 @@ class PrePaymentController extends Controller 'frequency_id' => $request->frequency_id, 'remaining_cycles' => $request->remaining_cycles, 'is_recurring' => $request->is_recurring == 'on' ? true : false, + 'variables' => $variables, ]; return $this->render('invoices.payment', $data); diff --git a/app/Repositories/SubscriptionRepository.php b/app/Repositories/SubscriptionRepository.php index f59992211c89..52b2fdcc52a5 100644 --- a/app/Repositories/SubscriptionRepository.php +++ b/app/Repositories/SubscriptionRepository.php @@ -126,7 +126,7 @@ class SubscriptionRepository extends BaseRepository $line_items = []; $line_items = collect($bundle)->filter(function ($item) { - return $item->is_recurring; + return $item->is_recurring ?? false; })->map(function ($item) { $line_item = new InvoiceItem(); $line_item->product_key = $item->product_key; From 57159ca31f05c4a13a2f328e82b5315716d628b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Wed, 21 Feb 2024 17:55:08 +0100 Subject: [PATCH 066/188] Refactor purchase submission in Submit.php --- app/Livewire/BillingPortal/Submit.php | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/app/Livewire/BillingPortal/Submit.php b/app/Livewire/BillingPortal/Submit.php index 3cdd270fac26..4779164377bc 100644 --- a/app/Livewire/BillingPortal/Submit.php +++ b/app/Livewire/BillingPortal/Submit.php @@ -43,15 +43,24 @@ class Submit extends Component // 'contact_last_name' => $this->context['contact']['last_name'], // 'contact_email' => $this->context['contact']['email'], // ]); - + // return redirect((new InstantPayment($request))->run()); - $this->dispatch('purchase.submit'); + $this->dispatch( + 'purchase.submit', + invoice_hashed_id: $this->context['form']['invoice_hashed_id'], + payable_amount: $this->context['form']['payable_amount'], + company_gateway_id: $this->context['form']['company_gateway_id'], + payment_method_id: $this->context['form']['payment_method_id'], + contact_first_name: $this->context['contact']['first_name'], + contact_last_name: $this->context['contact']['last_name'], + contact_email: $this->context['contact']['email'], + ); } public function render() { - + return <<<'HTML' From ec88825c2a6d1e76a5ff6c464b5aebf4067bc2b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Wed, 21 Feb 2024 17:55:12 +0100 Subject: [PATCH 067/188] Update purchase.submit event listener in billing portal view --- resources/views/billing-portal/v3/index.blade.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/resources/views/billing-portal/v3/index.blade.php b/resources/views/billing-portal/v3/index.blade.php index 9b6c3ddc6135..4a477a9fa1a8 100644 --- a/resources/views/billing-portal/v3/index.blade.php +++ b/resources/views/billing-portal/v3/index.blade.php @@ -8,7 +8,16 @@ @push('footer')
From e426fad38da0de10bb1e67affa8d528c23e6d2d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Wed, 21 Feb 2024 18:00:06 +0100 Subject: [PATCH 069/188] Add showOptionalProductsLabel method to Cart class --- app/Livewire/BillingPortal/Cart/Cart.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/Livewire/BillingPortal/Cart/Cart.php b/app/Livewire/BillingPortal/Cart/Cart.php index 77c461bccc4f..f8fae332df76 100644 --- a/app/Livewire/BillingPortal/Cart/Cart.php +++ b/app/Livewire/BillingPortal/Cart/Cart.php @@ -27,6 +27,16 @@ class Cart extends Component $this->dispatch('purchase.next'); } + public function showOptionalProductsLabel() + { + $optional = [ + ...$this->context['bundle']['optional_recurring_products'] ?? [], + ...$this->context['bundle']['optional_one_time_products'] ?? [], + ]; + + return count($optional) > 0; + } + public function render() { return view('billing-portal.v3.cart.cart'); From d3154c640986724aa50afb7e773cd4ac58413357 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Wed, 21 Feb 2024 18:00:10 +0100 Subject: [PATCH 070/188] Add conditional rendering for optional products label --- resources/views/billing-portal/v3/cart/cart.blade.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/resources/views/billing-portal/v3/cart/cart.blade.php b/resources/views/billing-portal/v3/cart/cart.blade.php index 00bb7523367c..39c846df96c8 100644 --- a/resources/views/billing-portal/v3/cart/cart.blade.php +++ b/resources/views/billing-portal/v3/cart/cart.blade.php @@ -9,7 +9,9 @@ :context="$context" /> -

{{ ctrans('texts.optional_products') }}

+ @if($this->showOptionalProductsLabel()) +

{{ ctrans('texts.optional_products') }}

+ @endif Date: Thu, 22 Feb 2024 09:35:02 +1100 Subject: [PATCH 071/188] Fixes for column sorting for user list --- app/Filters/UserFilters.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Filters/UserFilters.php b/app/Filters/UserFilters.php index c745f498e302..002562933ad9 100644 --- a/app/Filters/UserFilters.php +++ b/app/Filters/UserFilters.php @@ -50,7 +50,7 @@ class UserFilters extends QueryFilters { $sort_col = explode('|', $sort); - if (!is_array($sort_col) || count($sort_col) != 2) { + if (!is_array($sort_col) || count($sort_col) != 2 || !in_array($sort_col, \Illuminate\Support\Facades\Schema::getColumnListing('users'))) { return $this->builder; } From 55d512b0a569364b009ed56cb4f19609b1d04872 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 22 Feb 2024 14:00:39 +1100 Subject: [PATCH 072/188] Fixes for wrong product types being used cost => price --- .../Cart/OptionalRecurringProducts.php | 2 + .../BillingPortal/Payments/Methods.php | 2 - app/Livewire/BillingPortal/Purchase.php | 1 + app/Livewire/BillingPortal/RFF.php | 24 +++++----- app/Livewire/BillingPortal/Summary.php | 28 +++++++----- app/Repositories/SubscriptionRepository.php | 45 +++++++++++++++++++ .../Subscription/SubscriptionCalculator.php | 31 ++++++++++--- .../v3/payments/methods.blade.php | 2 +- 8 files changed, 104 insertions(+), 31 deletions(-) diff --git a/app/Livewire/BillingPortal/Cart/OptionalRecurringProducts.php b/app/Livewire/BillingPortal/Cart/OptionalRecurringProducts.php index 261fcdf71947..9193ba59ae7c 100644 --- a/app/Livewire/BillingPortal/Cart/OptionalRecurringProducts.php +++ b/app/Livewire/BillingPortal/Cart/OptionalRecurringProducts.php @@ -23,7 +23,9 @@ class OptionalRecurringProducts extends Component public function quantity($id, $value): void { + $this->dispatch('purchase.context', property: "bundle.optional_recurring_products.{$id}.quantity", value: $value); + } public function render(): \Illuminate\View\View diff --git a/app/Livewire/BillingPortal/Payments/Methods.php b/app/Livewire/BillingPortal/Payments/Methods.php index 1d0eba9259e6..2143631355d7 100644 --- a/app/Livewire/BillingPortal/Payments/Methods.php +++ b/app/Livewire/BillingPortal/Payments/Methods.php @@ -73,8 +73,6 @@ class Methods extends Component ? \App\Utils\Number::formatValue($invoice->partial, $invoice->client->currency()) : \App\Utils\Number::formatValue($invoice->balance, $invoice->client->currency()); - nlog($invoice->toArray()); - $this->dispatch('purchase.context', property: 'form.company_gateway_id', value: $company_gateway_id); $this->dispatch('purchase.context', property: 'form.payment_method_id', value: $gateway_type_id); $this->dispatch('purchase.context', property: 'form.invoice_hashed_id', value: $invoice->hashed_id); diff --git a/app/Livewire/BillingPortal/Purchase.php b/app/Livewire/BillingPortal/Purchase.php index a50a944313d1..eb898c7d71b7 100644 --- a/app/Livewire/BillingPortal/Purchase.php +++ b/app/Livewire/BillingPortal/Purchase.php @@ -161,6 +161,7 @@ class Purchase extends Component #[On('purchase.next')] public function handleNext(): void { + if ($this->step < count($this->steps) - 1) { $this->step++; } diff --git a/app/Livewire/BillingPortal/RFF.php b/app/Livewire/BillingPortal/RFF.php index 6ad6122c337a..facd64b24ed7 100644 --- a/app/Livewire/BillingPortal/RFF.php +++ b/app/Livewire/BillingPortal/RFF.php @@ -30,30 +30,34 @@ class RFF extends Component $validated = $this->validate([ 'contact_first_name' => ['required'], 'contact_last_name' => ['required'], - 'contact_email' => ['required', 'email'], + // 'contact_email' => ['sometimes', 'email'], ]); + $this->contact = auth()->guard('contact')->user(); $this->contact->first_name = $validated['contact_first_name']; $this->contact->last_name = $validated['contact_last_name']; - $this->contact->email = $validated['contact_email']; $this->contact->save(); + $this->contact_first_name = $this->contact->first_name; + $this->contact_last_name = $this->contact->last_name; + $this->contact_email = $this->contact->email; + + $this->dispatch('purchase.context', property: 'contact.first_name', value: $this->contact->first_name); + $this->dispatch('purchase.context', property: 'contact.last_name', value: $this->contact->last_name); + $this->dispatch('purchase.next'); } public function mount() { - if (auth()->guard('contact')->user()->showRff() === false) { + /** @var \App\Models\ClientContact $contact */ + $contact = auth()->guard('contact')->user(); + + if ($contact->showRff() === false) { $this->dispatch('purchase.next'); } - - $this->contact = auth()->guard('contact')->user(); - - $this->contact_first_name = $this->contact->first_name; - $this->contact_last_name = $this->contact->last_name; - $this->contact_email = $this->contact->email; } - + public function render() { return view('billing-portal.v3.rff'); diff --git a/app/Livewire/BillingPortal/Summary.php b/app/Livewire/BillingPortal/Summary.php index aa10e53b6794..25e7c86a7684 100644 --- a/app/Livewire/BillingPortal/Summary.php +++ b/app/Livewire/BillingPortal/Summary.php @@ -40,6 +40,7 @@ class Summary extends Component 'quantity' => $bundle['recurring_products'][$product->hashed_id]['quantity'] ?? 1, 'notes' => $product->markdownNotes(), ]; + $bundle['recurring_products'][$product->hashed_id]['product']['is_recurring'] = true; } foreach ($this->subscription->service()->products() as $key => $product) { @@ -48,6 +49,7 @@ class Summary extends Component 'quantity' => $bundle['one_time_products'][$product->hashed_id]['quantity'] ?? 1, 'notes' => $product->markdownNotes(), ]; + $bundle['one_time_products'][$product->hashed_id]['product']['is_recurring'] = false; } foreach ($this->subscription->service()->optional_recurring_products() as $key => $product) { @@ -56,6 +58,7 @@ class Summary extends Component 'quantity' => $bundle['optional_recurring_products'][$product->hashed_id]['quantity'] ?? 0, 'notes' => $product->markdownNotes(), ]; + $bundle['optional_recurring_products'][$product->hashed_id]['product']['is_recurring'] = true; } foreach ($this->subscription->service()->optional_products() as $key => $product) { @@ -64,6 +67,7 @@ class Summary extends Component 'quantity' => $bundle['optional_one_time_products'][$product->hashed_id]['quantity'] ?? 0, 'notes' => $product->markdownNotes(), ]; + $bundle['optional_one_time_products'][$product->hashed_id]['product']['is_recurring'] = false; } $this->dispatch('purchase.context', property: 'bundle', value: $bundle); @@ -76,11 +80,11 @@ class Summary extends Component } $one_time = collect($this->context['bundle']['one_time_products'])->sum(function ($item) { - return $item['product']['cost'] * $item['quantity']; + return $item['product']['price'] * $item['quantity']; }); $one_time_optional = collect($this->context['bundle']['optional_one_time_products'])->sum(function ($item) { - return $item['product']['cost'] * $item['quantity']; + return $item['product']['price'] * $item['quantity']; }); if ($raw) { @@ -98,11 +102,11 @@ class Summary extends Component } $recurring = collect($this->context['bundle']['recurring_products'])->sum(function ($item) { - return $item['product']['cost'] * $item['quantity']; + return $item['product']['price'] * $item['quantity']; }); $recurring_optional = collect($this->context['bundle']['optional_recurring_products'])->sum(function ($item) { - return $item['product']['cost'] * $item['quantity']; + return $item['product']['price'] * $item['quantity']; }); if ($raw) { @@ -140,8 +144,8 @@ class Summary extends Component $products[] = [ 'product_key' => $item['product']['product_key'], 'quantity' => $item['quantity'], - 'total_raw' => $item['product']['cost'] * $item['quantity'], - 'total' => Number::formatMoney($item['product']['cost'] * $item['quantity'], $this->subscription->company) . ' / ' . RecurringInvoice::frequencyForKey($this->subscription->frequency_id), + 'total_raw' => $item['product']['price'] * $item['quantity'], + 'total' => Number::formatMoney($item['product']['price'] * $item['quantity'], $this->subscription->company) . ' / ' . RecurringInvoice::frequencyForKey($this->subscription->frequency_id), ]; } @@ -149,8 +153,8 @@ class Summary extends Component $products[] = [ 'product_key' => $item['product']['product_key'], 'quantity' => $item['quantity'], - 'total_raw' => $item['product']['cost'] * $item['quantity'], - 'total' => Number::formatMoney($item['product']['cost'] * $item['quantity'], $this->subscription->company) . ' / ' . RecurringInvoice::frequencyForKey($this->subscription->frequency_id), + 'total_raw' => $item['product']['price'] * $item['quantity'], + 'total' => Number::formatMoney($item['product']['price'] * $item['quantity'], $this->subscription->company) . ' / ' . RecurringInvoice::frequencyForKey($this->subscription->frequency_id), ]; } @@ -158,8 +162,8 @@ class Summary extends Component $products[] = [ 'product_key' => $item['product']['product_key'], 'quantity' => $item['quantity'], - 'total_raw' => $item['product']['cost'] * $item['quantity'], - 'total' => Number::formatMoney($item['product']['cost'] * $item['quantity'], $this->subscription->company), + 'total_raw' => $item['product']['price'] * $item['quantity'], + 'total' => Number::formatMoney($item['product']['price'] * $item['quantity'], $this->subscription->company), ]; } @@ -167,8 +171,8 @@ class Summary extends Component $products[] = [ 'product_key' => $item['product']['product_key'], 'quantity' => $item['quantity'], - 'total_raw' => $item['product']['cost'] * $item['quantity'], - 'total' => Number::formatMoney($item['product']['cost'] * $item['quantity'], $this->subscription->company), + 'total_raw' => $item['product']['price'] * $item['quantity'], + 'total' => Number::formatMoney($item['product']['price'] * $item['quantity'], $this->subscription->company), ]; } diff --git a/app/Repositories/SubscriptionRepository.php b/app/Repositories/SubscriptionRepository.php index 52b2fdcc52a5..d8780746ccf3 100644 --- a/app/Repositories/SubscriptionRepository.php +++ b/app/Repositories/SubscriptionRepository.php @@ -118,9 +118,54 @@ class SubscriptionRepository extends BaseRepository return $line_items; } + + /** + * ConvertV3Bundle + * + * Removing the nested keys of the items array + * + * @param array $bundle + * @return array + */ + private function convertV3Bundle($bundle): array + { + if(is_object($bundle)) + $bundle = json_decode(json_encode($bundle),1); + + $items = []; + + foreach($bundle['recurring_products'] as $key => $value) { + + $line_item = new \stdClass; + $line_item->product_key = $value['product']['product_key']; + $line_item->qty = (float) $value['quantity']; + $line_item->unit_cost = (float) $value['product']['price']; + $line_item->description = $value['product']['notes']; + $line_item->is_recurring = $value['product']['is_recurring'] ?? false; + $items[] = $line_item; + } + + foreach($bundle['recurring_products'] as $key => $value) { + + $line_item = new \stdClass; + $line_item->product_key = $value['product']['product_key']; + $line_item->qty = (float) $value['quantity']; + $line_item->unit_cost = (float) $value['product']['price']; + $line_item->description = $value['product']['notes']; + $line_item->is_recurring = $value['product']['is_recurring'] ?? false; + + } + + return $items; + + } public function generateBundleLineItems($bundle, $is_recurring = false, $is_credit = false) { + + if(isset($bundle->recurring_products)) + $bundle = $this->convertV3Bundle($bundle); + $multiplier = $is_credit ? -1 : 1; $line_items = []; diff --git a/app/Services/Subscription/SubscriptionCalculator.php b/app/Services/Subscription/SubscriptionCalculator.php index 59a152cee167..77b444ac770e 100644 --- a/app/Services/Subscription/SubscriptionCalculator.php +++ b/app/Services/Subscription/SubscriptionCalculator.php @@ -71,21 +71,40 @@ class SubscriptionCalculator $recurring = array_merge(isset($bundle['recurring_products']) ? $bundle['recurring_products'] : [], isset($bundle['optional_recurring_products']) ? $bundle['optional_recurring_products'] : []); $one_time = array_merge(isset($bundle['one_time_products']) ? $bundle['one_time_products'] : [], isset($bundle['optional_one_time_products']) ? $bundle['optional_one_time_products'] : []); - $items = array_filter(array_merge($recurring, $one_time), function ($product) { - return $product['quantity'] >= 1; - }); + $items = []; + + foreach($recurring as $item) { + + if($item['quantity'] < 1) + continue; - return collect($items)->map(function ($item){ $line_item = new InvoiceItem(); $line_item->product_key = $item['product']['product_key']; $line_item->quantity = (float) $item['quantity']; $line_item->cost = (float) $item['product']['price']; $line_item->notes = $item['product']['notes']; - return $line_item; + $items[] = $line_item; - })->flatten()->toArray(); + } + foreach($one_time as $item) { + + if($item['quantity'] < 1) { + continue; + } + + $line_item = new InvoiceItem(); + $line_item->product_key = $item['product']['product_key']; + $line_item->quantity = (float) $item['quantity']; + $line_item->cost = (float) $item['product']['price']; + $line_item->notes = $item['product']['notes']; + + $items[] = $line_item; + + } + + return $items; } diff --git a/resources/views/billing-portal/v3/payments/methods.blade.php b/resources/views/billing-portal/v3/payments/methods.blade.php index fb14733f033a..b2eab9f9f9ab 100644 --- a/resources/views/billing-portal/v3/payments/methods.blade.php +++ b/resources/views/billing-portal/v3/payments/methods.blade.php @@ -3,7 +3,7 @@
@foreach($methods as $method) - @endforeach From 87df61fa6713e3ea32546f360e94b269dcc74fdc Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 22 Feb 2024 14:36:59 +1100 Subject: [PATCH 073/188] Fixes for broken Markdown Parsing --- app/Models/Product.php | 14 +++++++ lang/en/texts.php | 1 + .../v3/cart/one-time-products.blade.php | 42 +++++++++++++++---- .../cart/optional-one-time-products.blade.php | 26 ++++++------ .../optional-recurring-products.blade.php | 28 ++++++------- .../v3/cart/recurring-products.blade.php | 8 ++-- 6 files changed, 78 insertions(+), 41 deletions(-) diff --git a/app/Models/Product.php b/app/Models/Product.php index e4736a0f7ca7..7c49afc9338f 100644 --- a/app/Models/Product.php +++ b/app/Models/Product.php @@ -148,6 +148,20 @@ class Product extends BaseModel return $converter->convert($this->notes ?? ''); } + public static function markdownHelp(string $notes = '') + { + + $converter = new CommonMarkConverter([ + 'allow_unsafe_links' => false, + 'renderer' => [ + 'soft_break' => '
', + ], + ]); + + return $converter->convert($notes); + + } + public function portalUrl($use_react_url): string { return $use_react_url ? config('ninja.react_url') . "/#/products/{$this->hashed_id}/edit" : config('ninja.app_url'); diff --git a/lang/en/texts.php b/lang/en/texts.php index 8b6027093966..fbcda6317ac5 100644 --- a/lang/en/texts.php +++ b/lang/en/texts.php @@ -5237,6 +5237,7 @@ $lang = array( 'user_sales' => 'User Sales', 'iframe_url' => 'iFrame URL', 'user_unsubscribed' => 'User unsubscribed from emails :link', + 'out_of_stock' => 'Out of stock', ); return $lang; diff --git a/resources/views/billing-portal/v3/cart/one-time-products.blade.php b/resources/views/billing-portal/v3/cart/one-time-products.blade.php index a2da7c89101c..12ed06f652ca 100644 --- a/resources/views/billing-portal/v3/cart/one-time-products.blade.php +++ b/resources/views/billing-portal/v3/cart/one-time-products.blade.php @@ -1,15 +1,20 @@
- @unless(empty($subscription->product_ids)) - @foreach($subscription->service()->products() as $index => $product) + @isset($context['bundle']['one_time_products']) + @foreach($context['bundle']['one_time_products'] as $key => $entry) + + @php + $product = $entry['product']; + @endphp +
- @if(filter_var($product->product_image, FILTER_VALIDATE_URL)) + @if(filter_var($product['product_image'], FILTER_VALIDATE_URL))
@@ -17,16 +22,35 @@ @endif
-

{{ $product->product_key }}

-

{{ \App\Utils\Number::formatMoney($product->price, $subscription->company) }}

+

{{ $product['product_key'] }}

+

{{ \App\Utils\Number::formatMoney($product['price'], $subscription['company']) }}

+
+
+ +
+
+ @if($subscription->per_seat_enabled) + @if($subscription->use_inventory_management && $product['in_stock_quantity'] <= 0) +

{{ ctrans('texts.out_of_stock') }}

+ @else +

{{ ctrans('texts.qty') }}

+ @endif + + + @endif
- {!! $product->markdownNotes() !!} + {!! \App\Models\Product::markdownHelp($product['notes']) !!}
@endforeach - @endunless -
+ @endisset +
\ No newline at end of file diff --git a/resources/views/billing-portal/v3/cart/optional-one-time-products.blade.php b/resources/views/billing-portal/v3/cart/optional-one-time-products.blade.php index 0e206333710b..5b5839c1cac4 100644 --- a/resources/views/billing-portal/v3/cart/optional-one-time-products.blade.php +++ b/resources/views/billing-portal/v3/cart/optional-one-time-products.blade.php @@ -29,26 +29,24 @@
- @if($subscription->per_seat_enabled) - @if($subscription->use_inventory_management && $product['in_stock_quantity'] == 0) -

{{ ctrans('texts.out_of_stock') }}

- @else -

{{ ctrans('texts.qty') }}

- @endif - - + @if($subscription->use_inventory_management && $product['in_stock_quantity'] <= 0) +

{{ ctrans('texts.out_of_stock') }}

+ @else +

{{ ctrans('texts.qty') }}

@endif + +
- {!! $product['notes'] !!} + {!! \App\Models\Product::markdownHelp($product['notes']) !!}
@endforeach diff --git a/resources/views/billing-portal/v3/cart/optional-recurring-products.blade.php b/resources/views/billing-portal/v3/cart/optional-recurring-products.blade.php index 5a4b0b129c50..92b3bdba98cb 100644 --- a/resources/views/billing-portal/v3/cart/optional-recurring-products.blade.php +++ b/resources/views/billing-portal/v3/cart/optional-recurring-products.blade.php @@ -29,26 +29,26 @@
- @if($subscription->per_seat_enabled) - @if($subscription->use_inventory_management && $product['in_stock_quantity'] == 0) -

{{ ctrans('texts.out_of_stock') }}

- @else -

{{ ctrans('texts.qty') }}

- @endif - - + + @if($subscription->use_inventory_management && $product['in_stock_quantity'] <= 0) +

{{ ctrans('texts.out_of_stock') }}

+ @else +

{{ ctrans('texts.qty') }}

@endif + + +
- {!! $product['notes'] !!} + {!! \App\Models\Product::markdownHelp($product['notes']) !!}
@endforeach diff --git a/resources/views/billing-portal/v3/cart/recurring-products.blade.php b/resources/views/billing-portal/v3/cart/recurring-products.blade.php index 1ecbe0020ba5..d7793f383fe2 100644 --- a/resources/views/billing-portal/v3/cart/recurring-products.blade.php +++ b/resources/views/billing-portal/v3/cart/recurring-products.blade.php @@ -30,7 +30,7 @@
@if($subscription->per_seat_enabled) - @if($subscription->use_inventory_management && $product['in_stock_quantity'] == 0) + @if($subscription->use_inventory_management && $product['in_stock_quantity'] < 1)

{{ ctrans('texts.out_of_stock') }}

@else

{{ ctrans('texts.qty') }}

@@ -40,7 +40,7 @@ id="{{ $product['hashed_id'] }}" class="rounded-md border-gray-300 shadow-sm sm:text-sm" wire:change="quantity($event.target.id, $event.target.value)" - @if($subscription->use_inventory_management && $product['in_stock_quantity'] == 0) disabled @endif + {{ $subscription->use_inventory_management && $product['in_stock_quantity'] < 1 ? 'disabled' : '' }} > @@ -49,7 +49,7 @@ @endfor @else - @for ($i = 2; $i <= ($subscription->use_inventory_management ? min($product['in_stock_quantity'], max(100,$product['max_quantity'])) : max(100,$product['max_quantity'])); $i++) + @for ($i = 2; $i <= ($subscription->use_inventory_management ? min($product['in_stock_quantity'], min(100,$product['max_quantity'])) : min(100,$product['max_quantity'])); $i++) @endfor @endif @@ -60,7 +60,7 @@
- {!! $product['notes'] !!} + {!! \App\Models\Product::markdownHelp($product['notes']) !!}
@endforeach From fd4f24c9adb90565e8ab1abad38e11e6d9ce452c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Thu, 22 Feb 2024 11:21:45 +0100 Subject: [PATCH 074/188] Update spinner and container visibility --- resources/views/billing-portal/v3/purchase.blade.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/views/billing-portal/v3/purchase.blade.php b/resources/views/billing-portal/v3/purchase.blade.php index 81665c1135ec..f8916b33b40a 100644 --- a/resources/views/billing-portal/v3/purchase.blade.php +++ b/resources/views/billing-portal/v3/purchase.blade.php @@ -18,12 +18,12 @@ alt="{{ $subscription->company->present()->name }}" /> - + - From b3a5b65af26f3bcd8ec8abf1d89443dc93ba8ea9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Thu, 22 Feb 2024 11:21:50 +0100 Subject: [PATCH 075/188] Refactor purchase.next event handling in billing portal view --- .../views/billing-portal/v3/index.blade.php | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/resources/views/billing-portal/v3/index.blade.php b/resources/views/billing-portal/v3/index.blade.php index 4a477a9fa1a8..d95f36c87e20 100644 --- a/resources/views/billing-portal/v3/index.blade.php +++ b/resources/views/billing-portal/v3/index.blade.php @@ -25,18 +25,15 @@ const target = document.getElementById('container'); - const observer = new MutationObserver((mutationsList) => { - for (const mutation of mutationsList) { - if (mutation.type === 'childList' || mutation.type === 'subtree') { - setTimeout(() => { - document.getElementById('spinner').classList.add('hidden'); - document.getElementById('container').classList.remove('hidden'); - }, 1500); - } - } - }); + Livewire.on('purchase.next', (event) => { + document.getElementById('spinner').classList.remove('hidden'); + document.getElementById('container').classList.add('hidden'); - observer.observe(target, { childList: true, subtree: true }) + setTimeout(() => { + document.getElementById('spinner').classList.add('hidden'); + document.getElementById('container').classList.remove('hidden'); + }, 1500); + }) }); @endpush From 39f33a511395c9a19915d6f7dbbb996198557556 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Thu, 22 Feb 2024 14:07:15 +0100 Subject: [PATCH 076/188] Require RFF after fetching methods --- app/Livewire/BillingPortal/Purchase.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Livewire/BillingPortal/Purchase.php b/app/Livewire/BillingPortal/Purchase.php index eb898c7d71b7..13cd5d6410b4 100644 --- a/app/Livewire/BillingPortal/Purchase.php +++ b/app/Livewire/BillingPortal/Purchase.php @@ -41,8 +41,8 @@ class Purchase extends Component Setup::class, Cart::class, Authentication::class, - RFF::class, Methods::class, + RFF::class, Submit::class, ]; From 938ffbe66b2392cce1cf11d9c44e17964d56673d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Thu, 22 Feb 2024 14:07:34 +0100 Subject: [PATCH 077/188] Form only view for required client info --- app/Livewire/RequiredClientInfo.php | 11 ++++++++++ .../livewire/required-client-info.blade.php | 22 ++++++++++--------- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/app/Livewire/RequiredClientInfo.php b/app/Livewire/RequiredClientInfo.php index a4595b771ef2..80e2654fdcd7 100644 --- a/app/Livewire/RequiredClientInfo.php +++ b/app/Livewire/RequiredClientInfo.php @@ -143,6 +143,8 @@ class RequiredClientInfo extends Component public $company_gateway_id; + public bool $form_only = false; + public function mount() { MultiDB::setDb($this->company->db); @@ -162,6 +164,15 @@ class RequiredClientInfo extends Component count($this->fields) > 0 || $this->show_terms ? $this->checkFields() : $this->show_form = false; + + if (request()->query('source') === 'subscriptions') { + $this->show_form = false; + + $this->dispatch( + 'passed-required-fields-check', + client_postal_code: $this->contact->client->postal_code + ); + } } public function toggleTermsAccepted() diff --git a/resources/views/portal/ninja2020/components/livewire/required-client-info.blade.php b/resources/views/portal/ninja2020/components/livewire/required-client-info.blade.php index ba6e6ac7175c..6dde0bba9fa7 100644 --- a/resources/views/portal/ninja2020/components/livewire/required-client-info.blade.php +++ b/resources/views/portal/ninja2020/components/livewire/required-client-info.blade.php @@ -1,14 +1,16 @@ -
-
-
-

- {{ ctrans('texts.required_payment_information') }} -

+
+
+ @unless($form_only) +
+

+ {{ ctrans('texts.required_payment_information') }} +

-

- {{ ctrans('texts.required_payment_information_more') }} -

-
+

+ {{ ctrans('texts.required_payment_information_more') }} +

+
+ @endunless
@foreach($fields as $field) From 6346e7a92f19e7f1ee16232fa53a9d49e8dc200e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Thu, 22 Feb 2024 14:07:57 +0100 Subject: [PATCH 078/188] Make RFF proxy for RequiredClientInfo --- app/Livewire/BillingPortal/RFF.php | 52 ++++++------------- .../views/billing-portal/v3/rff.blade.php | 42 ++++----------- 2 files changed, 26 insertions(+), 68 deletions(-) diff --git a/app/Livewire/BillingPortal/RFF.php b/app/Livewire/BillingPortal/RFF.php index facd64b24ed7..5c63d314a943 100644 --- a/app/Livewire/BillingPortal/RFF.php +++ b/app/Livewire/BillingPortal/RFF.php @@ -12,54 +12,32 @@ namespace App\Livewire\BillingPortal; -use App\Models\ClientContact; +use App\Models\CompanyGateway; +use Illuminate\Support\Facades\Cache; +use Livewire\Attributes\On; use Livewire\Component; class RFF extends Component { public array $context; - public ClientContact $contact; - - public ?string $contact_first_name; - public ?string $contact_last_name; - public ?string $contact_email; - - public function handleRff() + #[On('passed-required-fields-check')] + public function continue(): void { - $validated = $this->validate([ - 'contact_first_name' => ['required'], - 'contact_last_name' => ['required'], - // 'contact_email' => ['sometimes', 'email'], - ]); - - $this->contact = auth()->guard('contact')->user(); - $this->contact->first_name = $validated['contact_first_name']; - $this->contact->last_name = $validated['contact_last_name']; - $this->contact->save(); - - $this->contact_first_name = $this->contact->first_name; - $this->contact_last_name = $this->contact->last_name; - $this->contact_email = $this->contact->email; - - $this->dispatch('purchase.context', property: 'contact.first_name', value: $this->contact->first_name); - $this->dispatch('purchase.context', property: 'contact.last_name', value: $this->contact->last_name); - $this->dispatch('purchase.next'); } - - public function mount() - { - /** @var \App\Models\ClientContact $contact */ - $contact = auth()->guard('contact')->user(); - - if ($contact->showRff() === false) { - $this->dispatch('purchase.next'); - } - } public function render() { - return view('billing-portal.v3.rff'); + $gateway = CompanyGateway::findOrFail($this->context['form']['company_gateway_id']); + $countries = Cache::get('countries'); + + return view('billing-portal.v3.rff', [ + 'gateway' => $gateway->driver( + auth()->guard('contact')->user()->client + ), + 'countries' => $countries, + 'company' => $gateway->company, + ]); } } diff --git a/resources/views/billing-portal/v3/rff.blade.php b/resources/views/billing-portal/v3/rff.blade.php index eb4260cfbc45..6c999e1fcaf5 100644 --- a/resources/views/billing-portal/v3/rff.blade.php +++ b/resources/views/billing-portal/v3/rff.blade.php @@ -9,34 +9,14 @@
@endif - - @csrf - - @if(strlen(auth()->guard('contact')->user()->first_name) === 0) -
- - -
- @endif - - @if(strlen(auth()->guard('contact')->user()->last_name) === 0) - - @endif - - @if(strlen(auth()->guard('contact')->user()->email) === 0) - - @endif - - - -
\ No newline at end of file +
+ @livewire('required-client-info', [ + 'fields' => method_exists($gateway, 'getClientRequiredFields') ? $gateway->getClientRequiredFields() : [], + 'contact' => auth()->guard('contact')->user(), + 'countries' => $countries, + 'company' => $company, + 'company_gateway_id' => $gateway->company_gateway ? $gateway->company_gateway->id : $gateway->id, + 'form_only' => true + ]) +
+
From b063234307296ca0fed07265e7f9e1c93c9b2f42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Thu, 22 Feb 2024 14:08:09 +0100 Subject: [PATCH 079/188] Fixes for double submissions --- .../views/billing-portal/v3/index.blade.php | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/resources/views/billing-portal/v3/index.blade.php b/resources/views/billing-portal/v3/index.blade.php index d95f36c87e20..89f8c3002d0b 100644 --- a/resources/views/billing-portal/v3/index.blade.php +++ b/resources/views/billing-portal/v3/index.blade.php @@ -18,9 +18,7 @@ document.querySelector('input[name=contact_last_name]').value = event.contact_last_name; document.querySelector('input[name=contact_email]').value = event.contact_email; - setTimeout(() => { - document.getElementById('payment-method-form').submit() - }, 2000); + document.getElementById('payment-method-form').submit() }); const target = document.getElementById('container'); @@ -34,6 +32,18 @@ document.getElementById('container').classList.remove('hidden'); }, 1500); }) + + Livewire.on('update-shipping-data', (event) => { + console.log(event); + + for (field in event) { + let element = document.querySelector(`input[name=${field}]`); + + if (element) { + element.value = event[field]; + } + } + }); }); @endpush From 5ece68f884c55bc8af3db1a32fed70e7a734d3c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Thu, 22 Feb 2024 14:08:18 +0100 Subject: [PATCH 080/188] Cast source to payment checkout page --- resources/views/billing-portal/v3/purchase.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/views/billing-portal/v3/purchase.blade.php b/resources/views/billing-portal/v3/purchase.blade.php index f8916b33b40a..8a40a802aac6 100644 --- a/resources/views/billing-portal/v3/purchase.blade.php +++ b/resources/views/billing-portal/v3/purchase.blade.php @@ -40,7 +40,7 @@
@csrf From d28061507ef2f734170bf4ed6c8481feb8039ee4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Thu, 22 Feb 2024 14:08:33 +0100 Subject: [PATCH 081/188] Pass `form_only=false` to original required client --- resources/views/portal/ninja2020/layout/payments.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/views/portal/ninja2020/layout/payments.blade.php b/resources/views/portal/ninja2020/layout/payments.blade.php index ec8c52479595..17e0bb5aa198 100644 --- a/resources/views/portal/ninja2020/layout/payments.blade.php +++ b/resources/views/portal/ninja2020/layout/payments.blade.php @@ -11,7 +11,7 @@ @endpush @section('body') - @livewire('required-client-info', ['fields' => method_exists($gateway, 'getClientRequiredFields') ? $gateway->getClientRequiredFields() : [], 'contact' => auth()->guard('contact')->user(), 'countries' => $countries, 'company' => $company, 'company_gateway_id' => $gateway->company_gateway ? $gateway->company_gateway->id : $gateway->id]) + @livewire('required-client-info', ['fields' => method_exists($gateway, 'getClientRequiredFields') ? $gateway->getClientRequiredFields() : [], 'contact' => auth()->guard('contact')->user(), 'countries' => $countries, 'company' => $company, 'company_gateway_id' => $gateway->company_gateway ? $gateway->company_gateway->id : $gateway->id, 'form_only' => false])
From 1f833398e6f1ce7b4cdac2f26d22cd05b60613e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Thu, 22 Feb 2024 14:11:50 +0100 Subject: [PATCH 082/188] Remove console log --- resources/views/billing-portal/v3/index.blade.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/resources/views/billing-portal/v3/index.blade.php b/resources/views/billing-portal/v3/index.blade.php index 89f8c3002d0b..df3113a94ad0 100644 --- a/resources/views/billing-portal/v3/index.blade.php +++ b/resources/views/billing-portal/v3/index.blade.php @@ -34,8 +34,6 @@ }) Livewire.on('update-shipping-data', (event) => { - console.log(event); - for (field in event) { let element = document.querySelector(`input[name=${field}]`); From 2dcc070af29a552851a60ff8b50422cd1a2d39cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Wed, 28 Feb 2024 17:55:13 +0100 Subject: [PATCH 083/188] Remove Authentication --- app/Livewire/BillingPortal/Authentication.php | 197 ------------------ .../v3/authentication.blade.php | 91 -------- 2 files changed, 288 deletions(-) delete mode 100644 app/Livewire/BillingPortal/Authentication.php delete mode 100644 resources/views/billing-portal/v3/authentication.blade.php diff --git a/app/Livewire/BillingPortal/Authentication.php b/app/Livewire/BillingPortal/Authentication.php deleted file mode 100644 index d567c4eb159f..000000000000 --- a/app/Livewire/BillingPortal/Authentication.php +++ /dev/null @@ -1,197 +0,0 @@ - true, // Use as preference. E-mail/password or OTP. - 'login_form' => false, - 'otp_form' => false, - 'initial_completed' => false, - ]; - - public function initial() - { - $this->validateOnly('email', ['email' => 'required|bail|email:rfc']); - - $this->state['initial_completed'] = true; - - if ($this->state['otp']) { - return $this->withOtp(); - } - - return $this->withPassword(); - } - - public function withPassword() - { - $contact = ClientContact::where('email', $this->email) - ->where('company_id', $this->subscription->company_id) - ->first(); - - if ($contact) { - return $this->state['login_form'] = true; - } - - $this->state['login_form'] = false; - - $contact = $this->createClientContact(); - - auth()->guard('contact')->loginUsingId($contact->id, true); - - $this->dispatch('purchase.context', property: 'contact', value: $contact); - $this->dispatch('purchase.next'); - } - - public function handlePassword() - { - $this->validate([ - 'email' => 'required|bail|email:rfc', - 'password' => 'required', - ]); - - $attempt = auth()->guard('contact')->attempt([ - 'email' => $this->email, - 'password' => $this->password, - 'company_id' => $this->subscription->company_id, - ]); - - if ($attempt) { - $this->dispatch('purchase.next'); - } - - session()->flash('message', 'These credentials do not match our records.'); - } - - public function withOtp() - { - $code = rand(100000, 999999); - $email_hash = "subscriptions:otp:{$this->email}"; - - Cache::put($email_hash, $code, 600); - - $cc = new ClientContact(); - $cc->email = $this->email; - - $nmo = new NinjaMailerObject(); - $nmo->mailable = new OtpCode($this->subscription->company, $this->context['contact'] ?? null, $code); - $nmo->company = $this->subscription->company; - $nmo->settings = $this->subscription->company->settings; - $nmo->to_user = $cc; - - NinjaMailerJob::dispatch($nmo); - - if (app()->environment('local')) { - session()->flash('message', "[dev]: Your OTP is: {$code}"); - } - - $this->state['otp_form'] = true; - } - - public function handleOtp() - { - $this->validate([ - 'otp' => 'required|numeric|digits:6', - ]); - - $code = Cache::get("subscriptions:otp:{$this->email}"); - - if ($this->otp != $code) { //loose comparison prevents edge cases - $errors = $this->getErrorBag(); - $errors->add('otp', ctrans('texts.invalid_code')); - - return; - } - - $contact = ClientContact::where('email', $this->email) - ->where('company_id', $this->subscription->company_id) - ->first(); - - if ($contact) { - auth()->guard('contact')->loginUsingId($contact->id, true); - - $this->dispatch('purchase.context', property: 'contact', value: $contact); - $this->dispatch('purchase.next'); - - return; - } - - $contact = $this->createClientContact(); - - auth()->guard('contact')->loginUsingId($contact->id, true); - - $this->dispatch('purchase.context', property: 'contact', value: $contact); - $this->dispatch('purchase.next'); - } - - private function createClientContact() - { - $company = $this->subscription->company; - $user = $this->subscription->user; - $user->setCompany($company); - - $client_repo = new ClientRepository(new ClientContactRepository()); - $data = [ - 'name' => '', - 'group_settings_id' => $this->subscription->group_id, - 'contacts' => [ - ['email' => $this->email], - ], - 'client_hash' => Str::random(40), - 'settings' => ClientSettings::defaults(), - ]; - - $client = $client_repo->save($data, ClientFactory::create($company->id, $user->id)); - - $contact = $client->fresh()->contacts()->first(); - - return $contact; - } - - public function mount() - { - if (auth()->guard('contact')->check()) { - $this->dispatch('purchase.context', property: 'contact', value: auth()->guard('contact')->user()); - $this->dispatch('purchase.next'); - } - } - - public function render() - { - return view('billing-portal.v3.authentication'); - } -} diff --git a/resources/views/billing-portal/v3/authentication.blade.php b/resources/views/billing-portal/v3/authentication.blade.php deleted file mode 100644 index f1261a101643..000000000000 --- a/resources/views/billing-portal/v3/authentication.blade.php +++ /dev/null @@ -1,91 +0,0 @@ -
- @if (session()->has('message')) - @component('portal.ninja2020.components.message') - {{ session('message') }} - @endcomponent - @endif - -
-

{{ ctrans('texts.contact') }}

-
- - @if($state['initial_completed'] === false) - - @csrf - - - - - - @endif - - @if($state['login_form']) -
- @csrf - -
- {{ ctrans('texts.email_address') }} - - - @error('email') - - @enderror -
- -
- {{ ctrans('texts.password') }} - - - @error('password') - - @enderror -
- - -
- @endif - - @if($state['otp_form']) -
- @csrf - -
- {{ ctrans('texts.code') }} - - - @error('otp') - - @enderror -
- - -
- @endif -
From 70fdc73b2d2389d0365b3a6da84724739e8354c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Wed, 28 Feb 2024 17:55:37 +0100 Subject: [PATCH 084/188] Add dependencies and update steps in Purchase.php --- app/Livewire/BillingPortal/Purchase.php | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/app/Livewire/BillingPortal/Purchase.php b/app/Livewire/BillingPortal/Purchase.php index 13cd5d6410b4..2e59c9c0ee41 100644 --- a/app/Livewire/BillingPortal/Purchase.php +++ b/app/Livewire/BillingPortal/Purchase.php @@ -13,6 +13,7 @@ namespace App\Livewire\BillingPortal; use App\Libraries\MultiDB; +use App\Livewire\BillingPortal\Authentication\RegisterOrLogin; use App\Livewire\BillingPortal\Cart\Cart; use App\Livewire\BillingPortal\Payments\Methods; use App\Models\Subscription; @@ -35,12 +36,21 @@ class Purchase extends Component // + public static array $dependencies = [ + Login::class => [], + RegisterOrLogin::class => [], + Register::class => [], + Cart::class => [], + Methods::class => [Login::class, RegisterOrLogin::class, Register::class], + RFF::class => [Login::class, RegisterOrLogin::class, Register::class], + ]; + public int $step = 0; - public array $steps = [ + public static array $steps = [ Setup::class, + RegisterOrLogin::class, Cart::class, - Authentication::class, Methods::class, RFF::class, Submit::class, From cfd33bd4fc5bbab627e5c9cb967ed7114846e03c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Wed, 28 Feb 2024 17:55:42 +0100 Subject: [PATCH 085/188] Add Billing Portal Login functionality --- .../BillingPortal/Authentication/Login.php | 162 ++++++++++++++++++ .../v3/authentication/login.blade.php | 91 ++++++++++ 2 files changed, 253 insertions(+) create mode 100644 app/Livewire/BillingPortal/Authentication/Login.php create mode 100644 resources/views/billing-portal/v3/authentication/login.blade.php diff --git a/app/Livewire/BillingPortal/Authentication/Login.php b/app/Livewire/BillingPortal/Authentication/Login.php new file mode 100644 index 000000000000..8217da3ca428 --- /dev/null +++ b/app/Livewire/BillingPortal/Authentication/Login.php @@ -0,0 +1,162 @@ + false, // Use as preference. E-mail/password or OTP. + 'login_form' => false, + 'otp_form' => false, + 'initial_completed' => false, + ]; + + public function initial() + { + $this->validateOnly('email', ['email' => 'required|bail|email:rfc|exists:client_contacts,email']); + + $this->state['initial_completed'] = true; + + if ($this->state['otp']) { + return $this->withOtp(); + } + + return $this->withPassword(); + } + + public function withPassword() + { + $contact = ClientContact::where('email', $this->email) + ->where('company_id', $this->subscription->company_id) + ->first(); + + if ($contact) { + return $this->state['login_form'] = true; + } + + $this->state['login_form'] = false; + + $contact = $this->createClientContact(); + + auth()->guard('contact')->loginUsingId($contact->id, true); + + $this->dispatch('purchase.context', property: 'contact', value: $contact); + $this->dispatch('purchase.next'); + } + + public function withOtp() + { + $code = rand(100000, 999999); + $email_hash = "subscriptions:otp:{$this->email}"; + + Cache::put($email_hash, $code, 600); + + $cc = new ClientContact(); + $cc->email = $this->email; + + $nmo = new NinjaMailerObject(); + $nmo->mailable = new OtpCode($this->subscription->company, $this->context['contact'] ?? null, $code); + $nmo->company = $this->subscription->company; + $nmo->settings = $this->subscription->company->settings; + $nmo->to_user = $cc; + + NinjaMailerJob::dispatch($nmo); + + if (app()->environment('local')) { + session()->flash('message', "[dev]: Your OTP is: {$code}"); + } + + $this->state['otp_form'] = true; + } + + public function handleOtp() + { + $this->validate([ + 'otp' => 'required|numeric|digits:6', + 'email' => 'required|bail|email:rfc|exists:client_contacts,email', + ]); + + $code = Cache::get("subscriptions:otp:{$this->email}"); + + if ($this->otp != $code) { //loose comparison prevents edge cases + $errors = $this->getErrorBag(); + $errors->add('otp', ctrans('texts.invalid_code')); + + return; + } + + $contact = ClientContact::where('email', $this->email) + ->where('company_id', $this->subscription->company_id) + ->first(); + + if ($contact) { + auth()->guard('contact')->loginUsingId($contact->id, true); + + $this->dispatch('purchase.context', property: 'contact', value: $contact); + $this->dispatch('purchase.next'); + + return; + } + } + + public function handlePassword() + { + $this->validate([ + 'email' => 'required|bail|email:rfc', + 'password' => 'required', + ]); + + $attempt = auth()->guard('contact')->attempt([ + 'email' => $this->email, + 'password' => $this->password, + 'company_id' => $this->subscription->company_id, + ]); + + if ($attempt) { + $this->dispatch('purchase.next'); + } + + session()->flash('message', 'These credentials do not match our records.'); + } + + public function mount() + { + if (auth()->guard('contact')->check()) { + $this->dispatch('purchase.context', property: 'contact', value: auth()->guard('contact')->user()); + $this->dispatch('purchase.next'); + } + } + + public function render(): \Illuminate\View\View + { + return view('billing-portal.v3.authentication.login'); + } +} \ No newline at end of file diff --git a/resources/views/billing-portal/v3/authentication/login.blade.php b/resources/views/billing-portal/v3/authentication/login.blade.php new file mode 100644 index 000000000000..f1261a101643 --- /dev/null +++ b/resources/views/billing-portal/v3/authentication/login.blade.php @@ -0,0 +1,91 @@ +
+ @if (session()->has('message')) + @component('portal.ninja2020.components.message') + {{ session('message') }} + @endcomponent + @endif + +
+

{{ ctrans('texts.contact') }}

+
+ + @if($state['initial_completed'] === false) +
+ @csrf + + + + +
+ @endif + + @if($state['login_form']) +
+ @csrf + +
+ {{ ctrans('texts.email_address') }} + + + @error('email') + + @enderror +
+ +
+ {{ ctrans('texts.password') }} + + + @error('password') + + @enderror +
+ + +
+ @endif + + @if($state['otp_form']) +
+ @csrf + +
+ {{ ctrans('texts.code') }} + + + @error('otp') + + @enderror +
+ + +
+ @endif +
From b17221f3dcab6158a399e3b176fe14fb722807ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Wed, 28 Feb 2024 17:55:47 +0100 Subject: [PATCH 086/188] Add BillingPortal registration functionality --- .../BillingPortal/Authentication/Register.php | 152 ++++++++++++++++++ .../v3/authentication/register.blade.php | 57 +++++++ 2 files changed, 209 insertions(+) create mode 100644 app/Livewire/BillingPortal/Authentication/Register.php create mode 100644 resources/views/billing-portal/v3/authentication/register.blade.php diff --git a/app/Livewire/BillingPortal/Authentication/Register.php b/app/Livewire/BillingPortal/Authentication/Register.php new file mode 100644 index 000000000000..65fb21f598df --- /dev/null +++ b/app/Livewire/BillingPortal/Authentication/Register.php @@ -0,0 +1,152 @@ + true, // Use as preference. E-mail/password or OTP. + 'login_form' => false, + 'otp_form' => false, + 'initial_completed' => false, + ]; + + public function initial() + { + $this->validateOnly('email', ['email' => 'required|bail|email:rfc']); + + $contact = ClientContact::where('email', $this->email) + ->where('company_id', $this->subscription->company_id) + ->first(); + + if ($contact) { + $this->addError('email', ctrans('texts.email_already_exists')); + + return; + } + + $this->state['initial_completed'] = true; + + return $this->withOtp(); + } + + public function withOtp() + { + $code = rand(100000, 999999); + $email_hash = "subscriptions:otp:{$this->email}"; + + Cache::put($email_hash, $code, 600); + + $cc = new ClientContact(); + $cc->email = $this->email; + + $nmo = new NinjaMailerObject(); + $nmo->mailable = new OtpCode($this->subscription->company, $this->context['contact'] ?? null, $code); + $nmo->company = $this->subscription->company; + $nmo->settings = $this->subscription->company->settings; + $nmo->to_user = $cc; + + NinjaMailerJob::dispatch($nmo); + + if (app()->environment('local')) { + session()->flash('message', "[dev]: Your OTP is: {$code}"); + } + + $this->state['otp_form'] = true; + } + + public function handleOtp() + { + $this->validate([ + 'otp' => 'required|numeric|digits:6', + ]); + + $code = Cache::get("subscriptions:otp:{$this->email}"); + + if ($this->otp != $code) { //loose comparison prevents edge cases + $errors = $this->getErrorBag(); + $errors->add('otp', ctrans('texts.invalid_code')); + + return; + } + + $contact = $this->createClientContact(); + + auth()->guard('contact')->loginUsingId($contact->id, true); + + $this->dispatch('purchase.context', property: 'contact', value: $contact); + $this->dispatch('purchase.next'); + + return; + } + + private function createClientContact() + { + $company = $this->subscription->company; + $user = $this->subscription->user; + $user->setCompany($company); + + $client_repo = new ClientRepository(new ClientContactRepository()); + $data = [ + 'name' => '', + 'group_settings_id' => $this->subscription->group_id, + 'contacts' => [ + ['email' => $this->email], + ], + 'client_hash' => Str::random(40), + 'settings' => ClientSettings::defaults(), + ]; + + $client = $client_repo->save($data, ClientFactory::create($company->id, $user->id)); + + $contact = $client->fresh()->contacts()->first(); + + return $contact; + } + + public function mount() + { + if (auth()->guard('contact')->check()) { + $this->dispatch('purchase.context', property: 'contact', value: auth()->guard('contact')->user()); + $this->dispatch('purchase.next'); + } + } + + public function render() + { + return view('billing-portal.v3.authentication.register'); + } +} diff --git a/resources/views/billing-portal/v3/authentication/register.blade.php b/resources/views/billing-portal/v3/authentication/register.blade.php new file mode 100644 index 000000000000..714fe83f63f3 --- /dev/null +++ b/resources/views/billing-portal/v3/authentication/register.blade.php @@ -0,0 +1,57 @@ +
+ @if (session()->has('message')) + @component('portal.ninja2020.components.message') + {{ session('message') }} + @endcomponent + @endif + +
+

{{ ctrans('texts.contact') }}

+
+ + @if($state['initial_completed'] === false) +
+ @csrf + + + + +
+ @endif + + @if($state['otp_form']) +
+ @csrf + +
+ {{ ctrans('texts.code') }} + + + @error('otp') + + @enderror +
+ + +
+ @endif +
From 8ebc425fd5f8cd8a907439ab0f4ace17b257ea6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Wed, 28 Feb 2024 17:55:51 +0100 Subject: [PATCH 087/188] Add registration and login functionality to billing portal --- .../Authentication/RegisterOrLogin.php | 197 ++++++++++++++++++ .../register-or-login.blade.php | 91 ++++++++ 2 files changed, 288 insertions(+) create mode 100644 app/Livewire/BillingPortal/Authentication/RegisterOrLogin.php create mode 100644 resources/views/billing-portal/v3/authentication/register-or-login.blade.php diff --git a/app/Livewire/BillingPortal/Authentication/RegisterOrLogin.php b/app/Livewire/BillingPortal/Authentication/RegisterOrLogin.php new file mode 100644 index 000000000000..053266ab0310 --- /dev/null +++ b/app/Livewire/BillingPortal/Authentication/RegisterOrLogin.php @@ -0,0 +1,197 @@ + false, // Use as preference. E-mail/password or OTP. + 'login_form' => false, + 'otp_form' => false, + 'initial_completed' => false, + ]; + + public function initial() + { + $this->validateOnly('email', ['email' => 'required|bail|email:rfc']); + + $this->state['initial_completed'] = true; + + if ($this->state['otp']) { + return $this->withOtp(); + } + + return $this->withPassword(); + } + + public function withPassword() + { + $contact = ClientContact::where('email', $this->email) + ->where('company_id', $this->subscription->company_id) + ->first(); + + if ($contact) { + return $this->state['login_form'] = true; + } + + $this->state['login_form'] = false; + + $contact = $this->createClientContact(); + + auth()->guard('contact')->loginUsingId($contact->id, true); + + $this->dispatch('purchase.context', property: 'contact', value: $contact); + $this->dispatch('purchase.next'); + } + + public function handlePassword() + { + $this->validate([ + 'email' => 'required|bail|email:rfc', + 'password' => 'required', + ]); + + $attempt = auth()->guard('contact')->attempt([ + 'email' => $this->email, + 'password' => $this->password, + 'company_id' => $this->subscription->company_id, + ]); + + if ($attempt) { + $this->dispatch('purchase.next'); + } + + session()->flash('message', 'These credentials do not match our records.'); + } + + public function withOtp() + { + $code = rand(100000, 999999); + $email_hash = "subscriptions:otp:{$this->email}"; + + Cache::put($email_hash, $code, 600); + + $cc = new ClientContact(); + $cc->email = $this->email; + + $nmo = new NinjaMailerObject(); + $nmo->mailable = new OtpCode($this->subscription->company, $this->context['contact'] ?? null, $code); + $nmo->company = $this->subscription->company; + $nmo->settings = $this->subscription->company->settings; + $nmo->to_user = $cc; + + NinjaMailerJob::dispatch($nmo); + + if (app()->environment('local')) { + session()->flash('message', "[dev]: Your OTP is: {$code}"); + } + + $this->state['otp_form'] = true; + } + + public function handleOtp() + { + $this->validate([ + 'otp' => 'required|numeric|digits:6', + ]); + + $code = Cache::get("subscriptions:otp:{$this->email}"); + + if ($this->otp != $code) { //loose comparison prevents edge cases + $errors = $this->getErrorBag(); + $errors->add('otp', ctrans('texts.invalid_code')); + + return; + } + + $contact = ClientContact::where('email', $this->email) + ->where('company_id', $this->subscription->company_id) + ->first(); + + if ($contact) { + auth()->guard('contact')->loginUsingId($contact->id, true); + + $this->dispatch('purchase.context', property: 'contact', value: $contact); + $this->dispatch('purchase.next'); + + return; + } + + $contact = $this->createClientContact(); + + auth()->guard('contact')->loginUsingId($contact->id, true); + + $this->dispatch('purchase.context', property: 'contact', value: $contact); + $this->dispatch('purchase.next'); + } + + private function createClientContact() + { + $company = $this->subscription->company; + $user = $this->subscription->user; + $user->setCompany($company); + + $client_repo = new ClientRepository(new ClientContactRepository()); + $data = [ + 'name' => '', + 'group_settings_id' => $this->subscription->group_id, + 'contacts' => [ + ['email' => $this->email], + ], + 'client_hash' => Str::random(40), + 'settings' => ClientSettings::defaults(), + ]; + + $client = $client_repo->save($data, ClientFactory::create($company->id, $user->id)); + + $contact = $client->fresh()->contacts()->first(); + + return $contact; + } + + public function mount() + { + if (auth()->guard('contact')->check()) { + $this->dispatch('purchase.context', property: 'contact', value: auth()->guard('contact')->user()); + $this->dispatch('purchase.next'); + } + } + + public function render() + { + return view('billing-portal.v3.authentication.register-or-login'); + } +} diff --git a/resources/views/billing-portal/v3/authentication/register-or-login.blade.php b/resources/views/billing-portal/v3/authentication/register-or-login.blade.php new file mode 100644 index 000000000000..f1261a101643 --- /dev/null +++ b/resources/views/billing-portal/v3/authentication/register-or-login.blade.php @@ -0,0 +1,91 @@ +
+ @if (session()->has('message')) + @component('portal.ninja2020.components.message') + {{ session('message') }} + @endcomponent + @endif + +
+

{{ ctrans('texts.contact') }}

+
+ + @if($state['initial_completed'] === false) +
+ @csrf + + + + +
+ @endif + + @if($state['login_form']) +
+ @csrf + +
+ {{ ctrans('texts.email_address') }} + + + @error('email') + + @enderror +
+ +
+ {{ ctrans('texts.password') }} + + + @error('password') + + @enderror +
+ + +
+ @endif + + @if($state['otp_form']) +
+ @csrf + +
+ {{ ctrans('texts.code') }} + + + @error('otp') + + @enderror +
+ + +
+ @endif +
From b4b1ecd6d3d4dfbbf961c6e5379f42d325c84a18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Wed, 28 Feb 2024 17:55:55 +0100 Subject: [PATCH 088/188] Add DependencyTest.php for unit testing --- tests/Unit/BillingPortal/DependencyTest.php | 125 ++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 tests/Unit/BillingPortal/DependencyTest.php diff --git a/tests/Unit/BillingPortal/DependencyTest.php b/tests/Unit/BillingPortal/DependencyTest.php new file mode 100644 index 000000000000..2cdad70fd21a --- /dev/null +++ b/tests/Unit/BillingPortal/DependencyTest.php @@ -0,0 +1,125 @@ +checkDependencies([ + RFF::class, + RegisterOrLogin::class, + Cart::class, + ]); + + $this->assertCount(1, $results); + + $results = $this->checkDependencies([ + RegisterOrLogin::class, + Cart::class, + RFF::class, + ]); + + $this->assertCount(0, $results); + + $results = $this->checkDependencies([ + RegisterOrLogin::class, + RFF::class, + Cart::class, + ]); + + $this->assertCount(0, $results); + } + + public function testSorting() + { + $results = $this->sort([ + RFF::class, + RegisterOrLogin::class, + Cart::class, + ]); + + $this->assertEquals(Purchase::$steps, $results); + + $results = $this->sort([ + RegisterOrLogin::class, + Cart::class, + RFF::class, + ]); + + $this->assertEquals([ + Setup::class, + RegisterOrLogin::class, + Cart::class, + RFF::class, + Submit::class, + ], $results); + + $results = $this->sort([ + RegisterOrLogin::class, + RFF::class, + Cart::class, + ]); + + $this->assertEquals([ + Setup::class, + RegisterOrLogin::class, + RFF::class, + Cart::class, + Submit::class, + ], $results); + } + + private function checkDependencies(array $steps): array + { + $dependencies = Purchase::$dependencies; + $stepOrder = array_flip($steps); + $errors = []; + + foreach ($steps as $step) { + $dependentClasses = $dependencies[$step] ?? []; + + foreach ($dependentClasses as $dependency) { + if (in_array($dependency, $steps) && $stepOrder[$dependency] > $stepOrder[$step]) { + $errors[] = "Dependency error: $step depends on $dependency"; + } + } + } + + return $errors; + } + + private function sort(array $dependencies): array + { + $errors = $this->checkDependencies($dependencies); + + if (count($errors)) { + return Purchase::$steps; + } + + return [Setup::class, ...$dependencies, Submit::class]; + } +} From 173d327e7b9a42efc8307dfeea28c85222567477 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Wed, 28 Feb 2024 17:56:41 +0100 Subject: [PATCH 089/188] Note about re-indexing --- tests/Unit/BillingPortal/DependencyTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Unit/BillingPortal/DependencyTest.php b/tests/Unit/BillingPortal/DependencyTest.php index 2cdad70fd21a..caff37c36b4f 100644 --- a/tests/Unit/BillingPortal/DependencyTest.php +++ b/tests/Unit/BillingPortal/DependencyTest.php @@ -120,6 +120,6 @@ class DependencyTest extends TestCase return Purchase::$steps; } - return [Setup::class, ...$dependencies, Submit::class]; + return [Setup::class, ...$dependencies, Submit::class]; // Note: Re-index if you're doing any index-based checking/comparision. } } From 93a52db7130607c8c15228caa0c0367d55f9124b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Wed, 28 Feb 2024 17:59:27 +0100 Subject: [PATCH 090/188] Apply style changes --- tests/Unit/BillingPortal/DependencyTest.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/Unit/BillingPortal/DependencyTest.php b/tests/Unit/BillingPortal/DependencyTest.php index caff37c36b4f..1f1958632571 100644 --- a/tests/Unit/BillingPortal/DependencyTest.php +++ b/tests/Unit/BillingPortal/DependencyTest.php @@ -96,14 +96,14 @@ class DependencyTest extends TestCase private function checkDependencies(array $steps): array { $dependencies = Purchase::$dependencies; - $stepOrder = array_flip($steps); + $step_order = array_flip($steps); $errors = []; foreach ($steps as $step) { - $dependentClasses = $dependencies[$step] ?? []; + $dependent = $dependencies[$step] ?? []; - foreach ($dependentClasses as $dependency) { - if (in_array($dependency, $steps) && $stepOrder[$dependency] > $stepOrder[$step]) { + foreach ($dependent as $dependency) { + if (in_array($dependency, $steps) && $step_order[$dependency] > $step_order[$step]) { $errors[] = "Dependency error: $step depends on $dependency"; } } From f19d3369d6fb0d0c99270ae2e05d65d7f803cfd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Wed, 28 Feb 2024 18:48:49 +0100 Subject: [PATCH 091/188] Add RFF to Methods class --- app/Livewire/BillingPortal/Purchase.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Livewire/BillingPortal/Purchase.php b/app/Livewire/BillingPortal/Purchase.php index 2e59c9c0ee41..2ec05f5544cc 100644 --- a/app/Livewire/BillingPortal/Purchase.php +++ b/app/Livewire/BillingPortal/Purchase.php @@ -41,7 +41,7 @@ class Purchase extends Component RegisterOrLogin::class => [], Register::class => [], Cart::class => [], - Methods::class => [Login::class, RegisterOrLogin::class, Register::class], + Methods::class => [Login::class, RegisterOrLogin::class, Register::class, RFF::class], RFF::class => [Login::class, RegisterOrLogin::class, Register::class], ]; From 63c0e825c076c4fb2097e690920d19a96f4b3d62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Wed, 28 Feb 2024 18:48:52 +0100 Subject: [PATCH 092/188] Add Methods class to DependencyTest.php --- tests/Unit/BillingPortal/DependencyTest.php | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/tests/Unit/BillingPortal/DependencyTest.php b/tests/Unit/BillingPortal/DependencyTest.php index 1f1958632571..ecd46f4dfbd1 100644 --- a/tests/Unit/BillingPortal/DependencyTest.php +++ b/tests/Unit/BillingPortal/DependencyTest.php @@ -14,6 +14,7 @@ namespace Tests\Unit\BillingPortal; use App\Livewire\BillingPortal\Authentication\RegisterOrLogin; use App\Livewire\BillingPortal\Cart\Cart; +use App\Livewire\BillingPortal\Payments\Methods; use App\Livewire\BillingPortal\Purchase; use App\Livewire\BillingPortal\RFF; use App\Livewire\BillingPortal\Setup; @@ -58,23 +59,26 @@ class DependencyTest extends TestCase { $results = $this->sort([ RFF::class, + Methods::class, RegisterOrLogin::class, Cart::class, ]); - + $this->assertEquals(Purchase::$steps, $results); - + $results = $this->sort([ RegisterOrLogin::class, - Cart::class, RFF::class, + Methods::class, + Cart::class, ]); - + $this->assertEquals([ Setup::class, RegisterOrLogin::class, - Cart::class, RFF::class, + Methods::class, + Cart::class, Submit::class, ], $results); From 6c2d62f0c894d61c71e72c7ea5e24e99cc068cfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Wed, 28 Feb 2024 18:56:46 +0100 Subject: [PATCH 093/188] Clean up --- app/Livewire/BillingPortal/Purchase.php | 98 +------------------------ 1 file changed, 4 insertions(+), 94 deletions(-) diff --git a/app/Livewire/BillingPortal/Purchase.php b/app/Livewire/BillingPortal/Purchase.php index 2ec05f5544cc..0c585697667f 100644 --- a/app/Livewire/BillingPortal/Purchase.php +++ b/app/Livewire/BillingPortal/Purchase.php @@ -36,6 +36,10 @@ class Purchase extends Component // + public int $step = 0; + + public string $id; + public static array $dependencies = [ Login::class => [], RegisterOrLogin::class => [], @@ -45,7 +49,6 @@ class Purchase extends Component RFF::class => [Login::class, RegisterOrLogin::class, Register::class], ]; - public int $step = 0; public static array $steps = [ Setup::class, @@ -56,99 +59,6 @@ class Purchase extends Component Submit::class, ]; - public string $id; - - /** $context = [ - * 'hash' => string, - * 'quantity' => int, - * 'request_data' => [ - * 'q' => string, - * ], - * 'campaign => ?string, - * 'bundle' => $bundle, - * 'contact' => [ - * 'first_name' => ?string, - * 'last_name' => ?string, - * 'phone' => ?string, - * 'custom_value1' => ?string, - * 'custom_value2' => ?string, - * 'custom_value3' => ?string, - * 'custom_value4' => ?string, - * 'email' => ?string, - * 'email_verified_at' => ?int, - * 'confirmation_code' => ?string, - * 'is_primary' => bool, - * 'confirmed' => bool, - * 'last_login' => ?datetime, - * 'failed_logins' => ?int, - * 'accepted_terms_version' => ?string, - * 'avatar' => ?string, - * 'avatar_type' => ?string, - * 'avatar_size' => ?string, - * 'is_locked' => bool, - * 'send_email' => bool, - * 'contact_key' => string, - * 'created_at' => int, - * 'updated_at' => int, - * 'deleted_at' => int, - * 'hashed_id' => string, - * ], - * 'client_id' => string, - * 'quantity' => int, - * 'products' => [ - * [ - * 'product_key' => string, - * 'quantity' => int, - * 'total_raw' => float, - * 'total' => string, - * ], - * ] - * ]; - * - * $bundle =[ - * 'optional_one_time_products' => array, - * 'one_time_products' => array, - * 'optional_recurring_products' => array, - * 'recurring_products' => [ - * 'hashed_id' => [ - * 'product' => [ - * 'id' => int, - * 'company_id' => int, - * 'user_id' => int, - * 'assigned_user_id' => ?int, - * 'project_id' => ?int, - * 'vendor_id' => ?int, - * 'custom_value1' => ?string, - * 'custom_value2' => ?string, - * 'custom_value3' => ?string, - * 'custom_value4' => ?string, - * 'product_key' => ?string, - * 'notes' => ?string, - * 'cost' => float, - * 'price' => float, - * 'quantity' => float, - * 'tax_name1' => ?string, - * 'tax_rate1' => float, - * 'tax_name2' => ?string, - * 'tax_rate2' => float, - * 'tax_name3' => ?string, - * 'tax_rate3' => float, - * 'deleted_at' => ?int, - * 'created_at' => ?int, - * 'updated_at' => ?int, - * 'is_deleted' => bool, - * 'in_stock_quantity' => ?int, - * 'stock_notification' => bool, - * 'stock_notification_threshold' => ?int, - * 'max_quantity' => ?int, - * 'product_image' => ?string, - * 'tax_id' => ?int, - * 'hashed_id' => ?string, - * ], - * ], - * ], - * ] $bundle - */ public array $context = []; #[On('purchase.context')] From f67b6649cbda0f5c35c8a50ac77c9bd8628723f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Wed, 28 Feb 2024 19:02:08 +0100 Subject: [PATCH 094/188] Refactor dependencies in Purchase class --- app/Livewire/BillingPortal/Purchase.php | 30 ++++++++++++++++++++----- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/app/Livewire/BillingPortal/Purchase.php b/app/Livewire/BillingPortal/Purchase.php index 0c585697667f..63049cae6fcc 100644 --- a/app/Livewire/BillingPortal/Purchase.php +++ b/app/Livewire/BillingPortal/Purchase.php @@ -41,12 +41,30 @@ class Purchase extends Component public string $id; public static array $dependencies = [ - Login::class => [], - RegisterOrLogin::class => [], - Register::class => [], - Cart::class => [], - Methods::class => [Login::class, RegisterOrLogin::class, Register::class, RFF::class], - RFF::class => [Login::class, RegisterOrLogin::class, Register::class], + Login::class => [ + 'id' => 'auth.login', + 'dependencies' => [], + ], + RegisterOrLogin::class => [ + 'id' => 'auth.login-or-register', + 'dependencies' => [], + ], + Register::class => [ + 'id' => 'auth.register', + 'dependencies' => [], + ], + Cart::class => [ + 'id' => 'cart', + 'dependencies' => [], + ], + Methods::class => [ + 'id' => 'methods', + 'dependencies' => [Login::class, RegisterOrLogin::class, Register::class, RFF::class], + ], + RFF::class => [ + 'id' => 'rff', + 'dependencies' => [Login::class, RegisterOrLogin::class, Register::class], + ], ]; From 82ba61a31f57199561155d32a5c581c5a5576dcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Wed, 28 Feb 2024 19:02:12 +0100 Subject: [PATCH 095/188] Fix dependency retrieval in DependencyTest.php --- tests/Unit/BillingPortal/DependencyTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Unit/BillingPortal/DependencyTest.php b/tests/Unit/BillingPortal/DependencyTest.php index ecd46f4dfbd1..aa60748e829d 100644 --- a/tests/Unit/BillingPortal/DependencyTest.php +++ b/tests/Unit/BillingPortal/DependencyTest.php @@ -104,7 +104,7 @@ class DependencyTest extends TestCase $errors = []; foreach ($steps as $step) { - $dependent = $dependencies[$step] ?? []; + $dependent = $dependencies[$step]['dependencies'] ?? []; foreach ($dependent as $dependency) { if (in_array($dependency, $steps) && $step_order[$dependency] > $step_order[$step]) { From 9bafff750501da822c84e401afee94dc104bfee0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Wed, 28 Feb 2024 19:08:02 +0100 Subject: [PATCH 096/188] Add steps column to subscriptions table --- ...2_28_180250_add_steps_to_subscriptions.php | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 database/migrations/2024_02_28_180250_add_steps_to_subscriptions.php diff --git a/database/migrations/2024_02_28_180250_add_steps_to_subscriptions.php b/database/migrations/2024_02_28_180250_add_steps_to_subscriptions.php new file mode 100644 index 000000000000..b2a73d857a6a --- /dev/null +++ b/database/migrations/2024_02_28_180250_add_steps_to_subscriptions.php @@ -0,0 +1,29 @@ +pluck('id') + ->implode(','); + + $table->string('steps')->default($steps); + }); + } +}; From 7f97fc1bbaa8fa4e668ff3f34d42559957dd5039 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 29 Feb 2024 14:13:10 +1100 Subject: [PATCH 097/188] Fixes for migration logic --- app/Factory/SubscriptionFactory.php | 3 +++ ...02_28_180250_add_steps_to_subscriptions.php | 18 ++++++++++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/app/Factory/SubscriptionFactory.php b/app/Factory/SubscriptionFactory.php index 8395afb10d83..fb8d431e2b4a 100644 --- a/app/Factory/SubscriptionFactory.php +++ b/app/Factory/SubscriptionFactory.php @@ -20,6 +20,9 @@ class SubscriptionFactory $billing_subscription = new Subscription(); $billing_subscription->company_id = $company_id; $billing_subscription->user_id = $user_id; + $billing_subscription->steps = collect(\App\Livewire\BillingPortal\Purchase::$dependencies) + ->pluck('id') + ->implode(','); return $billing_subscription; } diff --git a/database/migrations/2024_02_28_180250_add_steps_to_subscriptions.php b/database/migrations/2024_02_28_180250_add_steps_to_subscriptions.php index b2a73d857a6a..eba29dc3a3e7 100644 --- a/database/migrations/2024_02_28_180250_add_steps_to_subscriptions.php +++ b/database/migrations/2024_02_28_180250_add_steps_to_subscriptions.php @@ -19,11 +19,21 @@ return new class extends Migration { public function up(): void { Schema::table('subscriptions', function (Blueprint $table) { - $steps = collect(Purchase::$dependencies) - ->pluck('id') - ->implode(','); + $table->string('text')->nullable(); + }); + + $steps = collect(Purchase::$dependencies) + ->pluck('id') + ->implode(','); + + \App\Models\Subscription::query() + ->withTrashed() + ->cursor() + ->each(function ($subscription) use ($steps){ + + $subscription->steps = $steps; + $subscription->save(); - $table->string('steps')->default($steps); }); } }; From ecda90d5f21781f63fc8c5817126bef3beb533e8 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 29 Feb 2024 14:16:22 +1100 Subject: [PATCH 098/188] Fixes for migration logic --- .../2024_02_28_180250_add_steps_to_subscriptions.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/database/migrations/2024_02_28_180250_add_steps_to_subscriptions.php b/database/migrations/2024_02_28_180250_add_steps_to_subscriptions.php index eba29dc3a3e7..031494233f57 100644 --- a/database/migrations/2024_02_28_180250_add_steps_to_subscriptions.php +++ b/database/migrations/2024_02_28_180250_add_steps_to_subscriptions.php @@ -10,7 +10,6 @@ * @license https://www.elastic.co/licensing/elastic-license */ -use App\Livewire\BillingPortal\Purchase; use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; @@ -19,10 +18,10 @@ return new class extends Migration { public function up(): void { Schema::table('subscriptions', function (Blueprint $table) { - $table->string('text')->nullable(); + $table->string('steps')->nullable(); }); - $steps = collect(Purchase::$dependencies) + $steps = collect(\App\Livewire\BillingPortal\Purchase::$dependencies) ->pluck('id') ->implode(','); From a18f675e64fb3f4452ea8408f0b23d09bc2b7d73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Thu, 29 Feb 2024 17:36:21 +0100 Subject: [PATCH 099/188] Add SubscriptionStepsController to handle subscription steps --- .../SubscriptionStepsController.php | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 app/Http/Controllers/SubscriptionStepsController.php diff --git a/app/Http/Controllers/SubscriptionStepsController.php b/app/Http/Controllers/SubscriptionStepsController.php new file mode 100644 index 000000000000..757ef993dab2 --- /dev/null +++ b/app/Http/Controllers/SubscriptionStepsController.php @@ -0,0 +1,25 @@ +map(fn($dependency) => [ + 'id' => $dependency['id'], + 'dependencies' => collect($dependency['dependencies']) + ->map(fn($dependency) => Purchase::$dependencies[$dependency]['id']) + ->toArray(), + ]) + ->toArray(); + + return response()->json($dependencies); + } +} From 06fec908c60e8bf908d6ae1afddf68a8b0f381f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Thu, 29 Feb 2024 17:36:25 +0100 Subject: [PATCH 100/188] Add custom validation rule for 'steps' field --- app/Http/Requests/Subscription/StoreSubscriptionRequest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/Http/Requests/Subscription/StoreSubscriptionRequest.php b/app/Http/Requests/Subscription/StoreSubscriptionRequest.php index 2a358ac37a94..569c368b6a92 100644 --- a/app/Http/Requests/Subscription/StoreSubscriptionRequest.php +++ b/app/Http/Requests/Subscription/StoreSubscriptionRequest.php @@ -63,7 +63,8 @@ class StoreSubscriptionRequest extends Request 'registration_required' => 'bail|sometimes|bool', 'optional_recurring_product_ids' => 'bail|sometimes|nullable|string', 'optional_product_ids' => 'bail|sometimes|nullable|string', - 'use_inventory_management' => 'bail|sometimes|bool' + 'use_inventory_management' => 'bail|sometimes|bool', + 'steps' => 'sometimes', // @todo: build custom validation rule to ensure order & deps are right. ]; return $this->globalRules($rules); From 4f21b6e3d827c283dca57e161f3412bd7ab2faca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Thu, 29 Feb 2024 17:36:32 +0100 Subject: [PATCH 101/188] Add authentication classes and update steps in Purchase.php --- app/Livewire/BillingPortal/Purchase.php | 47 ++++++++++++++++++------- 1 file changed, 34 insertions(+), 13 deletions(-) diff --git a/app/Livewire/BillingPortal/Purchase.php b/app/Livewire/BillingPortal/Purchase.php index 63049cae6fcc..ecb924bee254 100644 --- a/app/Livewire/BillingPortal/Purchase.php +++ b/app/Livewire/BillingPortal/Purchase.php @@ -13,6 +13,8 @@ namespace App\Livewire\BillingPortal; use App\Libraries\MultiDB; +use App\Livewire\BillingPortal\Authentication\Login; +use App\Livewire\BillingPortal\Authentication\Register; use App\Livewire\BillingPortal\Authentication\RegisterOrLogin; use App\Livewire\BillingPortal\Cart\Cart; use App\Livewire\BillingPortal\Payments\Methods; @@ -65,17 +67,13 @@ class Purchase extends Component 'id' => 'rff', 'dependencies' => [Login::class, RegisterOrLogin::class, Register::class], ], + Submit::class => [ + 'id' => 'submit', + 'dependencies' => [Methods::class], + ], ]; - - public static array $steps = [ - Setup::class, - RegisterOrLogin::class, - Cart::class, - Methods::class, - RFF::class, - Submit::class, - ]; + public array $steps = []; public array $context = []; @@ -99,12 +97,10 @@ class Purchase extends Component #[On('purchase.next')] public function handleNext(): void { - - if ($this->step < count($this->steps) - 1) { + if (count($this->steps) >= 1 && $this->step < count($this->steps) - 1) { $this->step++; + $this->id = Str::uuid(); } - - $this->id = Str::uuid(); } #[On('purchase.forward')] @@ -133,8 +129,33 @@ class Purchase extends Component return "summary-{$this->id}"; } + public static function defaultSteps() { + return [ + Setup::class, + Cart::class, + RegisterOrLogin::class, + Methods::class, + Submit::class, + ]; + } + public function mount() { + $classes = collect(self::$dependencies)->mapWithKeys(fn($dependency, $class) => [$dependency['id'] => $class])->toArray(); + + if ($this->subscription->steps) { + $steps = collect(explode(',', $this->subscription->steps)) + ->map(fn($step) => $classes[$step]) + ->toArray(); + + $this->steps = [ + Setup::class, + ...$steps, + ]; + } else { + $this->steps = self::defaultSteps(); + } + $this->id = Str::uuid(); MultiDB::setDb($this->db); From 7c127972e7e6833cb4a6daf0ba7b32baa0d7ea34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Thu, 29 Feb 2024 17:36:37 +0100 Subject: [PATCH 102/188] Add 'steps' attribute to Subscription model --- app/Models/Subscription.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/Models/Subscription.php b/app/Models/Subscription.php index 50d922264240..51a577c1c699 100644 --- a/app/Models/Subscription.php +++ b/app/Models/Subscription.php @@ -116,6 +116,7 @@ class Subscription extends BaseModel 'optional_product_ids', 'optional_recurring_product_ids', 'use_inventory_management', + 'steps', ]; protected $casts = [ From 86f6f9d166b5ff3301042c85e354277973a80da8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Thu, 29 Feb 2024 17:36:42 +0100 Subject: [PATCH 103/188] Add 'steps' field to SubscriptionTransformer --- app/Transformers/SubscriptionTransformer.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/Transformers/SubscriptionTransformer.php b/app/Transformers/SubscriptionTransformer.php index f06537d2421f..c2e50eb16e15 100644 --- a/app/Transformers/SubscriptionTransformer.php +++ b/app/Transformers/SubscriptionTransformer.php @@ -72,6 +72,7 @@ class SubscriptionTransformer extends EntityTransformer 'optional_recurring_product_ids' => (string)$subscription->optional_recurring_product_ids, 'optional_product_ids' => (string) $subscription->optional_product_ids, 'registration_required' => (bool) $subscription->registration_required, + 'steps' => $subscription->steps, ]; } } From 0380a14232105b8dcfb3c01b8d3813a599ae6337 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Thu, 29 Feb 2024 17:36:45 +0100 Subject: [PATCH 104/188] Add SubscriptionStepsController to API routes --- routes/api.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/routes/api.php b/routes/api.php index 311360ae66d8..a78608c3b6fb 100644 --- a/routes/api.php +++ b/routes/api.php @@ -10,6 +10,7 @@ | is assigned the "api" middleware group. Enjoy building your API! | */ +use App\Http\Controllers\SubscriptionStepsController; use Illuminate\Support\Facades\Route; use App\Http\Controllers\BaseController; use App\Http\Controllers\PingController; @@ -401,7 +402,9 @@ Route::group(['middleware' => ['throttle:api', 'api_db', 'token_auth', 'locale'] Route::post('stripe/verify', [StripeController::class, 'verify'])->middleware('password_protected')->name('stripe.verify'); Route::post('stripe/disconnect/{company_gateway_id}', [StripeController::class, 'disconnect'])->middleware('password_protected')->name('stripe.disconnect'); + Route::get('subscriptions/steps', [SubscriptionStepsController::class, 'index']); Route::resource('subscriptions', SubscriptionController::class); + Route::post('subscriptions/bulk', [SubscriptionController::class, 'bulk'])->name('subscriptions.bulk'); Route::get('statics', StaticController::class); // Route::post('apple_pay/upload_file','ApplyPayController::class, 'upload'); From 33e8743024f5e3a471e923f93eea7f20737a3cdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Thu, 29 Feb 2024 17:36:52 +0100 Subject: [PATCH 105/188] Refactor dependency checking in DependencyTest.php --- tests/Unit/BillingPortal/DependencyTest.php | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/tests/Unit/BillingPortal/DependencyTest.php b/tests/Unit/BillingPortal/DependencyTest.php index aa60748e829d..b8612c442b32 100644 --- a/tests/Unit/BillingPortal/DependencyTest.php +++ b/tests/Unit/BillingPortal/DependencyTest.php @@ -64,7 +64,7 @@ class DependencyTest extends TestCase Cart::class, ]); - $this->assertEquals(Purchase::$steps, $results); + $this->assertEquals(Purchase::defaultSteps(), $results); $results = $this->sort([ RegisterOrLogin::class, @@ -100,19 +100,23 @@ class DependencyTest extends TestCase private function checkDependencies(array $steps): array { $dependencies = Purchase::$dependencies; - $step_order = array_flip($steps); + $stepOrder = array_flip($steps); $errors = []; - + foreach ($steps as $step) { $dependent = $dependencies[$step]['dependencies'] ?? []; - + + if (!empty($dependent) && !array_intersect($dependent, $steps)) { + $errors[] = "Dependency error: [$step] requires at least one of its dependencies [" . implode(', ', $dependent) . "] in the list."; + } + foreach ($dependent as $dependency) { - if (in_array($dependency, $steps) && $step_order[$dependency] > $step_order[$step]) { + if (in_array($dependency, $steps) && $stepOrder[$dependency] > $stepOrder[$step]) { $errors[] = "Dependency error: $step depends on $dependency"; } } } - + return $errors; } @@ -121,7 +125,7 @@ class DependencyTest extends TestCase $errors = $this->checkDependencies($dependencies); if (count($errors)) { - return Purchase::$steps; + return Purchase::defaultSteps(); } return [Setup::class, ...$dependencies, Submit::class]; // Note: Re-index if you're doing any index-based checking/comparision. From 89e5de0c3776894c39390b29c1080bc9935240c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Thu, 29 Feb 2024 17:37:01 +0100 Subject: [PATCH 106/188] Fix variable naming in DependencyTest.php --- tests/Unit/BillingPortal/DependencyTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Unit/BillingPortal/DependencyTest.php b/tests/Unit/BillingPortal/DependencyTest.php index b8612c442b32..23453f5ee271 100644 --- a/tests/Unit/BillingPortal/DependencyTest.php +++ b/tests/Unit/BillingPortal/DependencyTest.php @@ -100,7 +100,7 @@ class DependencyTest extends TestCase private function checkDependencies(array $steps): array { $dependencies = Purchase::$dependencies; - $stepOrder = array_flip($steps); + $step_order = array_flip($steps); $errors = []; foreach ($steps as $step) { @@ -111,7 +111,7 @@ class DependencyTest extends TestCase } foreach ($dependent as $dependency) { - if (in_array($dependency, $steps) && $stepOrder[$dependency] > $stepOrder[$step]) { + if (in_array($dependency, $steps) && $step_order[$dependency] > $step_order[$step]) { $errors[] = "Dependency error: $step depends on $dependency"; } } From 1f2205116974f40c40e7bc9ae21353ce520a2fcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Fri, 1 Mar 2024 13:42:39 +0100 Subject: [PATCH 107/188] Update .gitignore file --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 2b9f631c7963..0b3c53481c27 100644 --- a/.gitignore +++ b/.gitignore @@ -39,4 +39,5 @@ public/storage/test.pdf _ide_helper_models.php _ide_helper.php /composer.phar -.tx/ \ No newline at end of file +.tx/ +.phpunit.cache \ No newline at end of file From 70aa749f28813e3ff67d0875f45a3c437d1d15ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Fri, 1 Mar 2024 13:42:44 +0100 Subject: [PATCH 108/188] Add validation for subscription steps --- .../SubscriptionStepsController.php | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/SubscriptionStepsController.php b/app/Http/Controllers/SubscriptionStepsController.php index 757ef993dab2..81243ffbbba4 100644 --- a/app/Http/Controllers/SubscriptionStepsController.php +++ b/app/Http/Controllers/SubscriptionStepsController.php @@ -1,16 +1,25 @@ map(fn($dependency) => [ 'id' => $dependency['id'], @@ -22,4 +31,13 @@ class SubscriptionStepsController extends BaseController return response()->json($dependencies); } + + public function check(): JsonResponse + { + request()->validate(([ + 'steps' => ['required', new Steps()] + ])); + + return response()->json([], 200); + } } From b3a58bc2b0294803cd36c31ce17259bfa3fb28f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Fri, 1 Mar 2024 13:42:48 +0100 Subject: [PATCH 109/188] Add custom validation rule for 'steps' field --- app/Http/Requests/Subscription/StoreSubscriptionRequest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/Http/Requests/Subscription/StoreSubscriptionRequest.php b/app/Http/Requests/Subscription/StoreSubscriptionRequest.php index 569c368b6a92..1831366c7414 100644 --- a/app/Http/Requests/Subscription/StoreSubscriptionRequest.php +++ b/app/Http/Requests/Subscription/StoreSubscriptionRequest.php @@ -14,6 +14,7 @@ namespace App\Http\Requests\Subscription; use App\Http\Requests\Request; use App\Models\Account; use App\Models\Subscription; +use App\Rules\Subscriptions\Steps; use Illuminate\Validation\Rule; class StoreSubscriptionRequest extends Request @@ -64,7 +65,7 @@ class StoreSubscriptionRequest extends Request 'optional_recurring_product_ids' => 'bail|sometimes|nullable|string', 'optional_product_ids' => 'bail|sometimes|nullable|string', 'use_inventory_management' => 'bail|sometimes|bool', - 'steps' => 'sometimes', // @todo: build custom validation rule to ensure order & deps are right. + 'steps' => ['required', new Steps()], ]; return $this->globalRules($rules); From 8ccd2d96ec85d481769c0b4a3199a2702d3a3f96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Fri, 1 Mar 2024 13:42:51 +0100 Subject: [PATCH 110/188] Add 'steps' validation rule to UpdateSubscriptionRequest.php --- app/Http/Requests/Subscription/UpdateSubscriptionRequest.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/Http/Requests/Subscription/UpdateSubscriptionRequest.php b/app/Http/Requests/Subscription/UpdateSubscriptionRequest.php index 811d38000c67..c21af7024223 100644 --- a/app/Http/Requests/Subscription/UpdateSubscriptionRequest.php +++ b/app/Http/Requests/Subscription/UpdateSubscriptionRequest.php @@ -12,6 +12,7 @@ namespace App\Http\Requests\Subscription; use App\Http\Requests\Request; +use App\Rules\Subscriptions\Steps; use App\Utils\Traits\ChecksEntityStatus; use Illuminate\Validation\Rule; @@ -65,6 +66,7 @@ class UpdateSubscriptionRequest extends Request 'optional_recurring_product_ids' => 'bail|sometimes|nullable|string', 'optional_product_ids' => 'bail|sometimes|nullable|string', 'use_inventory_management' => 'bail|sometimes|bool', + 'steps' => ['required', new Steps()], ]; return $this->globalRules($rules); From b12fd9a1de0b35f951e6a3dbe7a7ff73506fc6c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Fri, 1 Mar 2024 13:42:56 +0100 Subject: [PATCH 111/188] Add RFF class to Purchase component --- app/Livewire/BillingPortal/Purchase.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/Livewire/BillingPortal/Purchase.php b/app/Livewire/BillingPortal/Purchase.php index ecb924bee254..5bd546c61fc5 100644 --- a/app/Livewire/BillingPortal/Purchase.php +++ b/app/Livewire/BillingPortal/Purchase.php @@ -134,6 +134,7 @@ class Purchase extends Component Setup::class, Cart::class, RegisterOrLogin::class, + RFF::class, Methods::class, Submit::class, ]; From 45f3846feb6e5dad0a554fa0500df13b6e2ae488 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Fri, 1 Mar 2024 13:43:00 +0100 Subject: [PATCH 112/188] Add validation rule for subscription steps --- app/Rules/Subscriptions/Steps.php | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 app/Rules/Subscriptions/Steps.php diff --git a/app/Rules/Subscriptions/Steps.php b/app/Rules/Subscriptions/Steps.php new file mode 100644 index 000000000000..3273bc2c3e80 --- /dev/null +++ b/app/Rules/Subscriptions/Steps.php @@ -0,0 +1,26 @@ + 0) { + $fail($errors[0]); + } + } +} From a1220b45f2fb8af92199114f99d25a5c4a4d0db7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Fri, 1 Mar 2024 13:43:11 +0100 Subject: [PATCH 113/188] Add StepService class for subscription handling --- app/Services/Subscription/StepService.php | 61 +++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 app/Services/Subscription/StepService.php diff --git a/app/Services/Subscription/StepService.php b/app/Services/Subscription/StepService.php new file mode 100644 index 000000000000..25d4027df5b6 --- /dev/null +++ b/app/Services/Subscription/StepService.php @@ -0,0 +1,61 @@ +mapWithKeys(fn($dependency, $class) => [$dependency['id'] => $class])->toArray(); + + return array_map(fn($step) => $classes[$step], explode(',', $steps)); + } + + public static function check(array $steps): array + { + $dependencies = Purchase::$dependencies; + $step_order = array_flip($steps); + $errors = []; + + foreach ($steps as $step) { + $dependent = $dependencies[$step]['dependencies'] ?? []; + + if (!empty($dependent) && !array_intersect($dependent, $steps)) { + $errors[] = ctrans('texts.step_dependency_fail', [ + 'step' => ctrans('texts.' . self::mapClassNameToString($step)), + 'dependencies' => implode(', ', array_map(fn($dependency) => ctrans('texts.' . self::mapClassNameToString($dependency)), $dependent)), + ]); + } + + foreach ($dependent as $dependency) { + if (in_array($dependency, $steps) && $step_order[$dependency] > $step_order[$step]) { + $errors[] = ctrans('texts.step_dependency_order_fail', [ + 'step' => ctrans('texts.' . self::mapClassNameToString($step)), + 'dependency' => implode(', ', array_map(fn($dependency) => ctrans('texts.' . self::mapClassNameToString($dependency)), $dependent)), + ]); + } + } + } + + return $errors; + } + + public static function mapClassNameToString(string $class): string + { + $classes = collect(Purchase::$dependencies)->mapWithKeys(fn($dependency, $class) => [$class => $dependency['id']])->toArray(); + + return $classes[$class]; + } +} \ No newline at end of file From f1647fc0a5d3b5c6639b98fcf0387bdcd3b68328 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Fri, 1 Mar 2024 13:43:15 +0100 Subject: [PATCH 114/188] Add new language strings and update existing ones --- lang/en/texts.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lang/en/texts.php b/lang/en/texts.php index fbcda6317ac5..682aace2fd5d 100644 --- a/lang/en/texts.php +++ b/lang/en/texts.php @@ -5238,6 +5238,17 @@ $lang = array( 'iframe_url' => 'iFrame URL', 'user_unsubscribed' => 'User unsubscribed from emails :link', 'out_of_stock' => 'Out of stock', + 'step_dependency_fail' => 'Component ":step" requires at least one of it\'s dependencies (":dependencies") in the list.', + 'step_dependency_order_fail' => 'Component ":step" depends on ":dependency". Make component(s) order is correct.', + 'auth.login' => 'Login', + 'auth.login-or-register' => 'Login or Register', + 'auth.register' => 'Register', + 'cart' => 'Cart', + 'methods' => 'Methods', + 'rff' => 'Required fields form', + 'add_step' => 'Add step', + 'steps' => 'Steps', + 'steps_order_help' => 'The order of the steps is important. The first step should not depend on any other step. The second step should depend on the first step, and so on.', ); return $lang; From 13daee1d4fa668ae5ab5b09a2c881d881a4aa9e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Fri, 1 Mar 2024 13:43:18 +0100 Subject: [PATCH 115/188] Add check route for subscription steps --- routes/api.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/routes/api.php b/routes/api.php index a78608c3b6fb..d306df241f7a 100644 --- a/routes/api.php +++ b/routes/api.php @@ -403,6 +403,8 @@ Route::group(['middleware' => ['throttle:api', 'api_db', 'token_auth', 'locale'] Route::post('stripe/disconnect/{company_gateway_id}', [StripeController::class, 'disconnect'])->middleware('password_protected')->name('stripe.disconnect'); Route::get('subscriptions/steps', [SubscriptionStepsController::class, 'index']); + Route::post('subscriptions/steps/check', [SubscriptionStepsController::class, 'check']); + Route::resource('subscriptions', SubscriptionController::class); Route::post('subscriptions/bulk', [SubscriptionController::class, 'bulk'])->name('subscriptions.bulk'); From 408c8cb15fe3dc30b8b5a64f02d1ecf0fa2dbece Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Fri, 1 Mar 2024 13:43:23 +0100 Subject: [PATCH 116/188] Refactor dependency checking in DependencyTest.php --- tests/Unit/BillingPortal/DependencyTest.php | 39 +++++---------------- 1 file changed, 9 insertions(+), 30 deletions(-) diff --git a/tests/Unit/BillingPortal/DependencyTest.php b/tests/Unit/BillingPortal/DependencyTest.php index 23453f5ee271..087dd0af9e58 100644 --- a/tests/Unit/BillingPortal/DependencyTest.php +++ b/tests/Unit/BillingPortal/DependencyTest.php @@ -19,6 +19,8 @@ use App\Livewire\BillingPortal\Purchase; use App\Livewire\BillingPortal\RFF; use App\Livewire\BillingPortal\Setup; use App\Livewire\BillingPortal\Submit; +use App\Rules\Subscriptions\Steps; +use App\Services\Subscription\StepService; use Tests\TestCase; class DependencyTest extends TestCase @@ -30,7 +32,7 @@ class DependencyTest extends TestCase public function testDependencyOrder() { - $results = $this->checkDependencies([ + $results = StepService::check([ RFF::class, RegisterOrLogin::class, Cart::class, @@ -38,7 +40,7 @@ class DependencyTest extends TestCase $this->assertCount(1, $results); - $results = $this->checkDependencies([ + $results = StepService::check([ RegisterOrLogin::class, Cart::class, RFF::class, @@ -46,7 +48,7 @@ class DependencyTest extends TestCase $this->assertCount(0, $results); - $results = $this->checkDependencies([ + $results = StepService::check([ RegisterOrLogin::class, RFF::class, Cart::class, @@ -63,16 +65,16 @@ class DependencyTest extends TestCase RegisterOrLogin::class, Cart::class, ]); - + $this->assertEquals(Purchase::defaultSteps(), $results); - + $results = $this->sort([ RegisterOrLogin::class, RFF::class, Methods::class, Cart::class, ]); - + $this->assertEquals([ Setup::class, RegisterOrLogin::class, @@ -97,32 +99,9 @@ class DependencyTest extends TestCase ], $results); } - private function checkDependencies(array $steps): array - { - $dependencies = Purchase::$dependencies; - $step_order = array_flip($steps); - $errors = []; - - foreach ($steps as $step) { - $dependent = $dependencies[$step]['dependencies'] ?? []; - - if (!empty($dependent) && !array_intersect($dependent, $steps)) { - $errors[] = "Dependency error: [$step] requires at least one of its dependencies [" . implode(', ', $dependent) . "] in the list."; - } - - foreach ($dependent as $dependency) { - if (in_array($dependency, $steps) && $step_order[$dependency] > $step_order[$step]) { - $errors[] = "Dependency error: $step depends on $dependency"; - } - } - } - - return $errors; - } - private function sort(array $dependencies): array { - $errors = $this->checkDependencies($dependencies); + $errors = StepService::check($dependencies); if (count($errors)) { return Purchase::defaultSteps(); From a8490941925fcf58f3067c18b188cb15f28ec572 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Fri, 1 Mar 2024 13:44:02 +0100 Subject: [PATCH 117/188] Clean up --- tests/Unit/BillingPortal/DependencyTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Unit/BillingPortal/DependencyTest.php b/tests/Unit/BillingPortal/DependencyTest.php index 087dd0af9e58..fb109a916e80 100644 --- a/tests/Unit/BillingPortal/DependencyTest.php +++ b/tests/Unit/BillingPortal/DependencyTest.php @@ -19,7 +19,6 @@ use App\Livewire\BillingPortal\Purchase; use App\Livewire\BillingPortal\RFF; use App\Livewire\BillingPortal\Setup; use App\Livewire\BillingPortal\Submit; -use App\Rules\Subscriptions\Steps; use App\Services\Subscription\StepService; use Tests\TestCase; From 4f4a9fd4618186e5dba104c3b1fe2fb68e989565 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Fri, 1 Mar 2024 14:16:54 +0100 Subject: [PATCH 118/188] Update dependencies in Purchase.php --- app/Livewire/BillingPortal/Purchase.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Livewire/BillingPortal/Purchase.php b/app/Livewire/BillingPortal/Purchase.php index 5bd546c61fc5..a5f9ce794251 100644 --- a/app/Livewire/BillingPortal/Purchase.php +++ b/app/Livewire/BillingPortal/Purchase.php @@ -61,11 +61,11 @@ class Purchase extends Component ], Methods::class => [ 'id' => 'methods', - 'dependencies' => [Login::class, RegisterOrLogin::class, Register::class, RFF::class], + 'dependencies' => [Login::class, RegisterOrLogin::class, Register::class], ], RFF::class => [ 'id' => 'rff', - 'dependencies' => [Login::class, RegisterOrLogin::class, Register::class], + 'dependencies' => [Login::class, RegisterOrLogin::class, Register::class, Methods::class], ], Submit::class => [ 'id' => 'submit', From 1acaa33ef0b10369ea7fed0b2ba55bae9db18166 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Fri, 1 Mar 2024 14:16:59 +0100 Subject: [PATCH 119/188] Add dispatch to set purchase context for contact user --- app/Livewire/BillingPortal/RFF.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/Livewire/BillingPortal/RFF.php b/app/Livewire/BillingPortal/RFF.php index 5c63d314a943..9e759ca43991 100644 --- a/app/Livewire/BillingPortal/RFF.php +++ b/app/Livewire/BillingPortal/RFF.php @@ -24,6 +24,7 @@ class RFF extends Component #[On('passed-required-fields-check')] public function continue(): void { + $this->dispatch('purchase.context', property: 'contact', value: auth()->guard('contact')->user()); $this->dispatch('purchase.next'); } From 405052620f5618dcbe19b2ade658dd032d78e6bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Fri, 1 Mar 2024 14:17:19 +0100 Subject: [PATCH 120/188] Remove unused code in Methods.php --- app/Livewire/BillingPortal/Payments/Methods.php | 6 ------ 1 file changed, 6 deletions(-) diff --git a/app/Livewire/BillingPortal/Payments/Methods.php b/app/Livewire/BillingPortal/Payments/Methods.php index 2143631355d7..d467b0d85aa5 100644 --- a/app/Livewire/BillingPortal/Payments/Methods.php +++ b/app/Livewire/BillingPortal/Payments/Methods.php @@ -12,7 +12,6 @@ namespace App\Livewire\BillingPortal\Payments; -use App\Livewire\BillingPortal\Authentication; use Livewire\Component; use App\Models\Subscription; use Illuminate\Support\Facades\Cache; @@ -27,11 +26,6 @@ class Methods extends Component public function mount(): void { - if (auth()->guard('contact')->guest()) { - $this->dispatch('purchase.forward', component: Authentication::class); - return; - } - $total = collect($this->context['products'])->sum('total_raw'); $methods = auth()->guard('contact')->user()->client->service()->getPaymentMethods( From 650d8732efdaf2b3050196dc29d554dd47076136 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Fri, 1 Mar 2024 14:17:30 +0100 Subject: [PATCH 121/188] Refactor purchase form submission in billing portal --- resources/views/billing-portal/v3/index.blade.php | 9 --------- 1 file changed, 9 deletions(-) diff --git a/resources/views/billing-portal/v3/index.blade.php b/resources/views/billing-portal/v3/index.blade.php index df3113a94ad0..267d7b9c10aa 100644 --- a/resources/views/billing-portal/v3/index.blade.php +++ b/resources/views/billing-portal/v3/index.blade.php @@ -9,15 +9,6 @@