mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-06-23 20:00:33 -04:00
Subscriptions v2
This commit is contained in:
parent
02d30ee778
commit
33d4402189
@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
namespace App\Http\Livewire;
|
namespace App\Http\Livewire;
|
||||||
|
|
||||||
|
use App\DataMapper\ClientSettings;
|
||||||
use App\Factory\ClientFactory;
|
use App\Factory\ClientFactory;
|
||||||
use App\Jobs\Mail\NinjaMailerJob;
|
use App\Jobs\Mail\NinjaMailerJob;
|
||||||
use App\Jobs\Mail\NinjaMailerObject;
|
use App\Jobs\Mail\NinjaMailerObject;
|
||||||
@ -19,15 +20,17 @@ use App\Mail\ContactPasswordlessLogin;
|
|||||||
use App\Models\Client;
|
use App\Models\Client;
|
||||||
use App\Models\ClientContact;
|
use App\Models\ClientContact;
|
||||||
use App\Models\Invoice;
|
use App\Models\Invoice;
|
||||||
|
use App\Models\RecurringInvoice;
|
||||||
use App\Models\Subscription;
|
use App\Models\Subscription;
|
||||||
use App\Repositories\ClientContactRepository;
|
use App\Repositories\ClientContactRepository;
|
||||||
use App\Repositories\ClientRepository;
|
use App\Repositories\ClientRepository;
|
||||||
|
use App\Utils\Number;
|
||||||
use Illuminate\Support\Facades\App;
|
use Illuminate\Support\Facades\App;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Facades\Cache;
|
use Illuminate\Support\Facades\Cache;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use App\DataMapper\ClientSettings;
|
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class BillingPortalPurchasev2 extends Component
|
class BillingPortalPurchasev2 extends Component
|
||||||
@ -179,6 +182,13 @@ class BillingPortalPurchasev2 extends Component
|
|||||||
*/
|
*/
|
||||||
public $campaign;
|
public $campaign;
|
||||||
|
|
||||||
|
public $bundle;
|
||||||
|
public $recurring_products;
|
||||||
|
public $products;
|
||||||
|
public $optional_recurring_products;
|
||||||
|
public $optional_products;
|
||||||
|
public $total;
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
MultiDB::setDb($this->company->db);
|
MultiDB::setDb($this->company->db);
|
||||||
@ -196,46 +206,156 @@ class BillingPortalPurchasev2 extends Component
|
|||||||
elseif(strlen($this->subscription->promo_code) == 0 && $this->subscription->promo_discount > 0){
|
elseif(strlen($this->subscription->promo_code) == 0 && $this->subscription->promo_discount > 0){
|
||||||
$this->price = $this->subscription->promo_price;
|
$this->price = $this->subscription->promo_price;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->recurring_products = $this->subscription->service()->recurring_products();
|
||||||
|
$this->products = $this->subscription->service()->products();
|
||||||
|
$this->optional_recurring_products = $this->subscription->service()->optional_recurring_products();
|
||||||
|
$this->optional_products = $this->subscription->service()->optional_products();
|
||||||
|
|
||||||
|
$this->buildBundle();
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function updatingData()
|
public function buildBundle()
|
||||||
{
|
{
|
||||||
nlog('updating');
|
$this->bundle = collect();
|
||||||
// nlog($this->data);
|
|
||||||
|
$data = $this->data;
|
||||||
|
|
||||||
|
foreach($this->recurring_products as $key => $p)
|
||||||
|
{
|
||||||
|
|
||||||
|
$qty = isset($data[$key]['recurring_qty']) ? $data[$key]['recurring_qty'] : 1;
|
||||||
|
$total = $p->price * $qty;
|
||||||
|
|
||||||
|
$this->bundle->push([
|
||||||
|
'product' => nl2br(substr($p->notes, 0, 50)),
|
||||||
|
'price' => Number::formatMoney($total, $this->subscription->company).' / '. RecurringInvoice::frequencyForKey($this->subscription->frequency_id),
|
||||||
|
'total' => $total,
|
||||||
|
'qty' => $qty,
|
||||||
|
'is_recurring' => true
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach($this->products as $key => $p)
|
||||||
|
{
|
||||||
|
|
||||||
|
$qty = 1;
|
||||||
|
$total = $p->price * $qty;
|
||||||
|
|
||||||
|
$this->bundle->push([
|
||||||
|
'product' => nl2br(substr($p->notes, 0, 50)),
|
||||||
|
'price' => Number::formatMoney($total, $this->subscription->company),
|
||||||
|
'total' => $total,
|
||||||
|
'qty' => $qty,
|
||||||
|
'is_recurring' => false
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach($this->data as $key => $value)
|
||||||
|
{
|
||||||
|
if(isset($this->data[$key]['optional_recurring_qty']))
|
||||||
|
{
|
||||||
|
$p = $this->optional_recurring_products->first(function ($v,$k) use($key){
|
||||||
|
return $k == $key;
|
||||||
|
});
|
||||||
|
|
||||||
|
$qty = isset($this->data[$key]['optional_recurring_qty']) ? $this->data[$key]['optional_recurring_qty'] : 0;
|
||||||
|
$total = $p->price * $qty;
|
||||||
|
|
||||||
|
if($qty == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
|
||||||
|
$this->bundle->push([
|
||||||
|
'product' => nl2br(substr($p->notes, 0, 50)),
|
||||||
|
'price' => Number::formatMoney($total, $this->subscription->company).' / '. RecurringInvoice::frequencyForKey($this->subscription->frequency_id),
|
||||||
|
'total' => $total,
|
||||||
|
'qty' => $qty,
|
||||||
|
'is_recurring' => true
|
||||||
|
]);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isset($this->data[$key]['optional_qty']))
|
||||||
|
{
|
||||||
|
$p = $this->optional_products->first(function ($v,$k) use($key){
|
||||||
|
return $k == $key;
|
||||||
|
});
|
||||||
|
|
||||||
|
$qty = isset($this->data[$key]['optional_qty']) ? $this->data[$key]['optional_qty'] : 0;
|
||||||
|
$total = $p->price * $qty;
|
||||||
|
|
||||||
|
if($qty == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
$this->bundle->push([
|
||||||
|
'product' => nl2br(substr($p->notes, 0, 50)),
|
||||||
|
'price' => Number::formatMoney($total, $this->subscription->company),
|
||||||
|
'total' => $total,
|
||||||
|
'qty' => $qty,
|
||||||
|
'is_recurring' => false
|
||||||
|
]);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->total = Number::formatMoney($this->bundle->sum('total'), $this->subscription->company);
|
||||||
|
|
||||||
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function updatedData()
|
public function updatedData()
|
||||||
{
|
{
|
||||||
nlog('updated');
|
}
|
||||||
nlog($this->data);
|
|
||||||
$validatedData = $this->validate();
|
public function updating($prop)
|
||||||
nlog( $validatedData );
|
{
|
||||||
|
// $this->resetValidation($prop);
|
||||||
|
// $this->resetErrorBag($prop);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function updated($propertyName)
|
public function updated($propertyName)
|
||||||
{
|
{
|
||||||
nlog("validating {$propertyName}");
|
$x = $this->validateOnly($propertyName, $this->rules(), [], $this->attributes());
|
||||||
$this->errors = $this->validateOnly($propertyName);
|
|
||||||
|
|
||||||
nlog($this->errors);
|
// // $validatedData = $this->validate();
|
||||||
$validatedData = $this->validate();
|
$this->buildBundle();
|
||||||
nlog( $validatedData );
|
|
||||||
|
// $order_validator = Validator::make($this->all(), $this->rules(), [], $this->attributes());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function rules()
|
public function rules()
|
||||||
{
|
{
|
||||||
$rules = [
|
$rules = [
|
||||||
'email' => ['required', 'email'],
|
'data.*.recurring_qty' => 'numeric|between:0,1000',
|
||||||
'data' => ['required', 'array'],
|
'data.*.optional_recurring_qty' => 'numeric|between:0,1000',
|
||||||
'data.*.recurring_qty' => ['required', 'between:100,1000'],
|
'data.*.optional_qty' => 'numeric|between:0,1000',
|
||||||
'data.*.optional_recurring_qty' => ['required', 'between:100,1000'],
|
|
||||||
'data.*.optional_qty' => ['required', 'between:100,1000'],
|
|
||||||
];
|
];
|
||||||
|
|
||||||
return $rules;
|
return $rules;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function attributes()
|
||||||
|
{
|
||||||
|
$attributes = [
|
||||||
|
'data.*.recurring_qty' => 'recurring_qty',
|
||||||
|
'data.*.optional_recurring_qty' => 'optional_recurring_qty',
|
||||||
|
'data.*.optional_qty' => 'optional_qty',
|
||||||
|
];
|
||||||
|
|
||||||
|
return $attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle user authentication
|
* Handle user authentication
|
||||||
*
|
*
|
||||||
|
@ -7,10 +7,12 @@
|
|||||||
{{ $subscription->name }}
|
{{ $subscription->name }}
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
<form wire:submit.prevent="submit">
|
||||||
|
|
||||||
<!-- Recurring Plan Products-->
|
<!-- Recurring Plan Products-->
|
||||||
<ul role="list" class="-my-6 divide-y divide-gray-200">
|
<ul role="list" class="-my-6 divide-y divide-gray-200">
|
||||||
@if(!empty($subscription->recurring_product_ids))
|
@if(!empty($subscription->recurring_product_ids))
|
||||||
@foreach($subscription->service()->recurring_products() as $index => $product)
|
@foreach($recurring_products as $index => $product)
|
||||||
<li class="flex py-6">
|
<li class="flex py-6">
|
||||||
@if(false)
|
@if(false)
|
||||||
<div class="h-24 w-24 flex-shrink-0 overflow-hidden rounded-md border border-gray-200">
|
<div class="h-24 w-24 flex-shrink-0 overflow-hidden rounded-md border border-gray-200">
|
||||||
@ -32,18 +34,11 @@
|
|||||||
<p class="text-gray-500 w-full"></p>
|
<p class="text-gray-500 w-full"></p>
|
||||||
<div class="flex place-content-end">
|
<div class="flex place-content-end">
|
||||||
<p class="text-sm font-light text-gray-700 text-right mr-2 mt-2">{{ ctrans('texts.qty') }}</p>
|
<p class="text-sm font-light text-gray-700 text-right mr-2 mt-2">{{ ctrans('texts.qty') }}</p>
|
||||||
<input wire:model="data.{{ $index }}.recurring_qty" type="text" class="w-1/4 rounded-md border-gray-300 shadow-sm sm:text-sm text-center" placeholder="0"/>
|
<input wire:model.debounce.300ms="data.{{ $index }}.recurring_qty" type="text" class="w-1/4 rounded-md border-gray-300 shadow-sm sm:text-sm text-center" placeholder="0"/>
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
{{ isset($data[$index]['recurring_qty']) ? $data[$index]['recurring_qty'] : 'merp' }}
|
@error("data.{$index}.recurring_qty")
|
||||||
|
|
||||||
@if($errors)
|
|
||||||
@foreach($errors as $error)
|
|
||||||
{{ $error }}
|
|
||||||
@endforeach
|
|
||||||
@endif
|
|
||||||
@error('data.{{$index}}.recurring_qty')
|
|
||||||
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative" role="alert">
|
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative" role="alert">
|
||||||
<span class="block sm:inline">{{ $message }} </span>
|
<span class="block sm:inline">{{ $message }} </span>
|
||||||
<span class="absolute top-0 bottom-0 right-0 px-4 py-3">
|
<span class="absolute top-0 bottom-0 right-0 px-4 py-3">
|
||||||
@ -55,7 +50,7 @@
|
|||||||
@endif
|
@endif
|
||||||
<!-- One Time Plan Products-->
|
<!-- One Time Plan Products-->
|
||||||
@if(!empty($subscription->product_ids))
|
@if(!empty($subscription->product_ids))
|
||||||
@foreach($subscription->service()->products() as $product)
|
@foreach($products as $product)
|
||||||
<li class="flex py-6">
|
<li class="flex py-6">
|
||||||
@if(false)
|
@if(false)
|
||||||
<div class="h-24 w-24 flex-shrink-0 overflow-hidden rounded-md border border-gray-200">
|
<div class="h-24 w-24 flex-shrink-0 overflow-hidden rounded-md border border-gray-200">
|
||||||
@ -93,7 +88,7 @@
|
|||||||
<!-- Optional Recurring Products-->
|
<!-- Optional Recurring Products-->
|
||||||
<ul role="list" class="-my-6 divide-y divide-gray-200">
|
<ul role="list" class="-my-6 divide-y divide-gray-200">
|
||||||
@if(!empty($subscription->optional_recurring_product_ids))
|
@if(!empty($subscription->optional_recurring_product_ids))
|
||||||
@foreach($subscription->service()->optional_recurring_products() as $index => $product)
|
@foreach($optional_recurring_products as $index => $product)
|
||||||
<li class="flex py-6">
|
<li class="flex py-6">
|
||||||
@if(false)
|
@if(false)
|
||||||
<div class="h-24 w-24 flex-shrink-0 overflow-hidden rounded-md border border-gray-200 mr-2">
|
<div class="h-24 w-24 flex-shrink-0 overflow-hidden rounded-md border border-gray-200 mr-2">
|
||||||
@ -112,21 +107,22 @@
|
|||||||
<p class="text-gray-500 w-full"></p>
|
<p class="text-gray-500 w-full"></p>
|
||||||
<div class="flex place-content-end">
|
<div class="flex place-content-end">
|
||||||
<p class="text-sm font-light text-gray-700 text-right mr-2 mt-2">{{ ctrans('texts.qty') }}</p>
|
<p class="text-sm font-light text-gray-700 text-right mr-2 mt-2">{{ ctrans('texts.qty') }}</p>
|
||||||
<input wire:model="data.{{ $index }}.optional_recurring_qty" type="text" class="w-1/4 rounded-md border-gray-300 shadow-sm sm:text-sm text-center" placeholder="0"/>
|
<input wire:model.debounce.300ms="data.{{ $index }}.optional_recurring_qty" type="text" class="w-1/4 rounded-md border-gray-300 shadow-sm sm:text-sm text-center" placeholder="0"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@error("data.{$index}.optional_recurring_qty")
|
||||||
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative" role="alert">
|
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative" role="alert">
|
||||||
<span class="block sm:inline">{{ isset($data[$index]['optional_recurring_qty']) ? $data[$index]['optional_recurring_qty'] : '' }}</span>
|
<span class="block sm:inline">{{ $message }} </span>
|
||||||
<span class="absolute top-0 bottom-0 right-0 px-4 py-3">
|
<span class="absolute top-0 bottom-0 right-0 px-4 py-3">
|
||||||
</div>
|
</div>
|
||||||
|
@enderror
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
@endforeach
|
@endforeach
|
||||||
@endif
|
@endif
|
||||||
@if(!empty($subscription->optional_product_ids))
|
@if(!empty($subscription->optional_product_ids))
|
||||||
@foreach($subscription->service()->optional_products() as $index => $product)
|
@foreach($optional_products as $index => $product)
|
||||||
<li class="flex py-6">
|
<li class="flex py-6">
|
||||||
@if(false)
|
@if(false)
|
||||||
<div class="h-24 w-24 flex-shrink-0 overflow-hidden rounded-md border border-gray-200 mr-2">
|
<div class="h-24 w-24 flex-shrink-0 overflow-hidden rounded-md border border-gray-200 mr-2">
|
||||||
@ -145,13 +141,15 @@
|
|||||||
<p class="text-gray-500 w-full"></p>
|
<p class="text-gray-500 w-full"></p>
|
||||||
<div class="flex place-content-end">
|
<div class="flex place-content-end">
|
||||||
<p class="text-sm font-light text-gray-700 text-right mr-2 mt-2">{{ ctrans('texts.qty') }}</p>
|
<p class="text-sm font-light text-gray-700 text-right mr-2 mt-2">{{ ctrans('texts.qty') }}</p>
|
||||||
<input type="text" wire:model="data.{{ $index }}.optional_qty" class="w-1/4 rounded-md border-gray-300 shadow-sm sm:text-sm text-center" placeholder="0">
|
<input type="text" wire:model.debounce.300ms="data.{{ $index }}.optional_qty" class="w-1/4 rounded-md border-gray-300 shadow-sm sm:text-sm text-center" placeholder="0">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@error("data.{$index}.optional_qty")
|
||||||
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative" role="alert">
|
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative" role="alert">
|
||||||
<span class="block sm:inline">{{ isset($data[$index]['optional_qty']) ? $data[$index]['optional_qty'] : '' }}</span>
|
<span class="block sm:inline">{{ $message }} </span>
|
||||||
<span class="absolute top-0 bottom-0 right-0 px-4 py-3">
|
<span class="absolute top-0 bottom-0 right-0 px-4 py-3">
|
||||||
</div>
|
</div>
|
||||||
|
@enderror
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
@endforeach
|
@endforeach
|
||||||
@ -160,27 +158,20 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
<div class="col-span-4 bg-blue-500 flex flex-col item-center p-2 h-screen">
|
<div class="col-span-4 bg-blue-500 flex flex-col item-center p-2 h-screen">
|
||||||
<div class="w-full p-4">
|
<div class="w-full p-4">
|
||||||
<div id="summary" class="px-4 text-white">
|
<div id="summary" class="px-4 text-white">
|
||||||
<h1 class="font-semibold text-2xl border-b-2 border-gray-200 border-opacity-50 pb-2 text-white">{{ ctrans('texts.order') }}</h1>
|
<h1 class="font-semibold text-2xl border-b-2 border-gray-200 border-opacity-50 pb-2 text-white">{{ ctrans('texts.order') }}</h1>
|
||||||
|
|
||||||
@foreach($subscription->service()->recurring_products() as $product)
|
@foreach($bundle as $item)
|
||||||
<div class="flex justify-between mt-1 mb-1">
|
<div class="flex justify-between mt-1 mb-1">
|
||||||
<span class="font-light text-sm uppercase">{!! nl2br(substr($product->notes, 0, 50)) !!}</span>
|
<span class="font-light text-sm uppercase">{{$item['product']}} x {{$item['qty']}}</span>
|
||||||
<span class="font-semibold text-sm">{{ \App\Utils\Number::formatMoney($product->price, $subscription->company) }}</span>
|
<span class="font-semibold text-sm">{{ $item['price'] }}</span>
|
||||||
</div>
|
</div>
|
||||||
@endforeach
|
@endforeach
|
||||||
|
|
||||||
@foreach($subscription->service()->products() as $product)
|
|
||||||
<div class="flex justify-between mt-1 mb-1">
|
|
||||||
<span class="font-light text-sm uppercase">{!! nl2br(substr($product->notes, 0, 50)) !!}</span>
|
|
||||||
<span class="font-semibold text-sm">{{ \App\Utils\Number::formatMoney($product->price, $subscription->company) }}</span>
|
|
||||||
</div>
|
|
||||||
@endforeach
|
|
||||||
|
|
||||||
|
|
||||||
@if(!empty($subscription->promo_code) && !$subscription->trial_enabled)
|
@if(!empty($subscription->promo_code) && !$subscription->trial_enabled)
|
||||||
<form wire:submit.prevent="handleCoupon" class="">
|
<form wire:submit.prevent="handleCoupon" class="">
|
||||||
@csrf
|
@csrf
|
||||||
@ -190,7 +181,7 @@
|
|||||||
<div class="relative flex flex-grow items-stretch focus-within:z-10">
|
<div class="relative flex flex-grow items-stretch focus-within:z-10">
|
||||||
<div class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
|
<div class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
|
||||||
</div>
|
</div>
|
||||||
<input type="text" wire:model.defer="coupon" class="block w-full rounded-none rounded-l-md border-gray-300 pl-10 focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm" placeholder="">
|
<input type="text" wire:model.debounce.300ms="coupon" class="block w-full rounded-none rounded-l-md border-gray-300 pl-10 focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm" placeholder="">
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="relative -ml-px inline-flex items-center space-x-2 rounded-r-md border border-gray-300 bg-gray-50 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500">
|
<button type="button" class="relative -ml-px inline-flex items-center space-x-2 rounded-r-md border border-gray-300 bg-gray-50 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500">
|
||||||
|
|
||||||
@ -204,7 +195,7 @@
|
|||||||
<div class="border-t-2 border-gray-200 border-opacity-50 mt-8">
|
<div class="border-t-2 border-gray-200 border-opacity-50 mt-8">
|
||||||
<div class="flex font-semibold justify-between py-6 text-sm uppercase">
|
<div class="flex font-semibold justify-between py-6 text-sm uppercase">
|
||||||
<span>{{ ctrans('texts.total') }}</span>
|
<span>{{ ctrans('texts.total') }}</span>
|
||||||
<span>{{ \App\Utils\Number::formatMoney($price, $subscription->company) }}</span>
|
<span>{{ $total }}</span>
|
||||||
</div>
|
</div>
|
||||||
<button class="bg-white font-semibold hover:bg-gray-600 py-3 text-sm text-blue-500 uppercase w-full">Checkout</button>
|
<button class="bg-white font-semibold hover:bg-gray-600 py-3 text-sm text-blue-500 uppercase w-full">Checkout</button>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user