mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-07 12:44:31 -04:00
Merge pull request #5279 from beganovich/v5-2903-billing-portal
V5 2903 billing portal
This commit is contained in:
commit
40e06ed430
@ -92,7 +92,7 @@ class BaseController extends Controller
|
|||||||
'company.quotes.invitations.company',
|
'company.quotes.invitations.company',
|
||||||
'company.quotes.documents',
|
'company.quotes.documents',
|
||||||
'company.tasks.documents',
|
'company.tasks.documents',
|
||||||
'company.subcsriptions',
|
'company.subscriptions',
|
||||||
'company.tax_rates',
|
'company.tax_rates',
|
||||||
'company.tokens_hashed',
|
'company.tokens_hashed',
|
||||||
'company.vendors.contacts.company',
|
'company.vendors.contacts.company',
|
||||||
@ -215,7 +215,7 @@ class BaseController extends Controller
|
|||||||
|
|
||||||
if(!$user->hasPermission('view_client'))
|
if(!$user->hasPermission('view_client'))
|
||||||
$query->where('clients.user_id', $user->id)->orWhere('clients.assigned_user_id', $user->id);
|
$query->where('clients.user_id', $user->id)->orWhere('clients.assigned_user_id', $user->id);
|
||||||
|
|
||||||
},
|
},
|
||||||
'company.company_gateways' => function ($query) use ($user) {
|
'company.company_gateways' => function ($query) use ($user) {
|
||||||
$query->whereNotNull('updated_at');
|
$query->whereNotNull('updated_at');
|
||||||
@ -340,14 +340,14 @@ class BaseController extends Controller
|
|||||||
|
|
||||||
if(!$user->isAdmin())
|
if(!$user->isAdmin())
|
||||||
$query->where('activities.user_id', $user->id);
|
$query->where('activities.user_id', $user->id);
|
||||||
|
|
||||||
},
|
},
|
||||||
'company.subscriptions'=> function ($query) use($updated_at, $user) {
|
'company.subscriptions'=> function ($query) use($updated_at, $user) {
|
||||||
$query->where('updated_at', '>=', $updated_at);
|
$query->where('updated_at', '>=', $updated_at);
|
||||||
|
|
||||||
if(!$user->isAdmin())
|
if(!$user->isAdmin())
|
||||||
$query->where('subscriptions.user_id', $user->id);
|
$query->where('subscriptions.user_id', $user->id);
|
||||||
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
@ -443,7 +443,7 @@ class BaseController extends Controller
|
|||||||
if ($this->serializer && $this->serializer != EntityTransformer::API_SERIALIZER_JSON) {
|
if ($this->serializer && $this->serializer != EntityTransformer::API_SERIALIZER_JSON) {
|
||||||
$this->entity_type = null;
|
$this->entity_type = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$resource = new Item($item, $transformer, $this->entity_type);
|
$resource = new Item($item, $transformer, $this->entity_type);
|
||||||
|
|
||||||
if (auth()->user() && request()->include_static) {
|
if (auth()->user() && request()->include_static) {
|
||||||
|
24
app/Http/Controllers/ClientPortal/SubscriptionController.php
Normal file
24
app/Http/Controllers/ClientPortal/SubscriptionController.php
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://opensource.org/licenses/AAL
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\ClientPortal;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class SubscriptionController extends Controller
|
||||||
|
{
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
return render('subscriptions.index');
|
||||||
|
}
|
||||||
|
}
|
@ -139,6 +139,16 @@ class BillingPortalPurchase extends Component
|
|||||||
*/
|
*/
|
||||||
public $request_data;
|
public $request_data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public $price;
|
||||||
|
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
$this->price = $this->subscription->service()->price();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle user authentication
|
* Handle user authentication
|
||||||
*
|
*
|
||||||
@ -331,6 +341,13 @@ class BillingPortalPurchase extends Component
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function handleCoupon()
|
||||||
|
{
|
||||||
|
if ($this->coupon == $this->subscription->promo_code) {
|
||||||
|
$this->price = $this->subscription->promo_price;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
if ($this->contact instanceof ClientContact) {
|
if ($this->contact instanceof ClientContact) {
|
||||||
|
39
app/Http/Livewire/SubscriptionInvoicesTable.php
Normal file
39
app/Http/Livewire/SubscriptionInvoicesTable.php
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://opensource.org/licenses/AAL
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Http\Livewire;
|
||||||
|
|
||||||
|
use App\Models\Invoice;
|
||||||
|
use App\Utils\Traits\WithSorting;
|
||||||
|
use Livewire\Component;
|
||||||
|
use Livewire\WithPagination;
|
||||||
|
|
||||||
|
class SubscriptionInvoicesTable extends Component
|
||||||
|
{
|
||||||
|
use WithPagination;
|
||||||
|
use WithSorting;
|
||||||
|
|
||||||
|
public $per_page = 10;
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
$query = Invoice::query()
|
||||||
|
->where('client_id', auth('contact')->user()->client->id)
|
||||||
|
->whereNotNull('subscription_id')
|
||||||
|
->orderBy($this->sort_field, $this->sort_asc ? 'asc' : 'desc')
|
||||||
|
->paginate($this->per_page);
|
||||||
|
|
||||||
|
return render('components.livewire.subscriptions-invoices-table', [
|
||||||
|
'invoices' => $query,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
39
app/Http/Livewire/SubscriptionRecurringInvoicesTable.php
Normal file
39
app/Http/Livewire/SubscriptionRecurringInvoicesTable.php
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://opensource.org/licenses/AAL
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Http\Livewire;
|
||||||
|
|
||||||
|
use App\Models\RecurringInvoice;
|
||||||
|
use App\Utils\Traits\WithSorting;
|
||||||
|
use Livewire\Component;
|
||||||
|
use Livewire\WithPagination;
|
||||||
|
|
||||||
|
class SubscriptionRecurringInvoicesTable extends Component
|
||||||
|
{
|
||||||
|
use WithPagination;
|
||||||
|
use WithSorting;
|
||||||
|
|
||||||
|
public $per_page = 10;
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
$query = RecurringInvoice::query()
|
||||||
|
->where('client_id', auth('contact')->user()->client->id)
|
||||||
|
->whereNotNull('subscription_id')
|
||||||
|
->orderBy($this->sort_field, $this->sort_asc ? 'asc' : 'desc')
|
||||||
|
->paginate($this->per_page);
|
||||||
|
|
||||||
|
return render('components.livewire.subscriptions-recurring-invoices-table', [
|
||||||
|
'recurring_invoices' => $query,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
@ -79,6 +79,7 @@ class PortalComposer
|
|||||||
$data[] = ['title' => ctrans('texts.credits'), 'url' => 'client.credits.index', 'icon' => 'credit-card'];
|
$data[] = ['title' => ctrans('texts.credits'), 'url' => 'client.credits.index', 'icon' => 'credit-card'];
|
||||||
$data[] = ['title' => ctrans('texts.payment_methods'), 'url' => 'client.payment_methods.index', 'icon' => 'shield'];
|
$data[] = ['title' => ctrans('texts.payment_methods'), 'url' => 'client.payment_methods.index', 'icon' => 'shield'];
|
||||||
$data[] = ['title' => ctrans('texts.documents'), 'url' => 'client.documents.index', 'icon' => 'download'];
|
$data[] = ['title' => ctrans('texts.documents'), 'url' => 'client.documents.index', 'icon' => 'download'];
|
||||||
|
$data[] = ['title' => ctrans('texts.subscriptions'), 'url' => 'client.subscriptions.index', 'icon' => 'calendar'];
|
||||||
|
|
||||||
if (auth()->user('contact')->client->getSetting('enable_client_portal_tasks')) {
|
if (auth()->user('contact')->client->getSetting('enable_client_portal_tasks')) {
|
||||||
$data[] = ['title' => ctrans('texts.tasks'), 'url' => 'client.dashboard', 'icon' => 'clock'];
|
$data[] = ['title' => ctrans('texts.tasks'), 'url' => 'client.dashboard', 'icon' => 'clock'];
|
||||||
|
@ -175,7 +175,6 @@ class StripePaymentDriver extends BaseDriver
|
|||||||
$fields[] = ['name' => 'client_address_line_2', 'label' => ctrans('texts.address2'), 'type' => 'text', 'validation' => 'required'];
|
$fields[] = ['name' => 'client_address_line_2', 'label' => ctrans('texts.address2'), 'type' => 'text', 'validation' => 'required'];
|
||||||
$fields[] = ['name' => 'client_city', 'label' => ctrans('texts.city'), 'type' => 'text', 'validation' => 'required'];
|
$fields[] = ['name' => 'client_city', 'label' => ctrans('texts.city'), 'type' => 'text', 'validation' => 'required'];
|
||||||
$fields[] = ['name' => 'client_state', 'label' => ctrans('texts.state'), 'type' => 'text', 'validation' => 'required'];
|
$fields[] = ['name' => 'client_state', 'label' => ctrans('texts.state'), 'type' => 'text', 'validation' => 'required'];
|
||||||
$fields[] = ['name' => 'client_postal_code', 'label' => ctrans('texts.postal_code'), 'type' => 'text', 'validation' => 'required'];
|
|
||||||
$fields[] = ['name' => 'client_country_id', 'label' => ctrans('texts.country'), 'type' => 'text', 'validation' => 'required'];
|
$fields[] = ['name' => 'client_country_id', 'label' => ctrans('texts.country'), 'type' => 'text', 'validation' => 'required'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
1
public/images/svg/calendar.svg
Normal file
1
public/images/svg/calendar.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-calendar"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect><line x1="16" y1="2" x2="16" y2="6"></line><line x1="8" y1="2" x2="8" y2="6"></line><line x1="3" y1="10" x2="21" y2="10"></line></svg>
|
After Width: | Height: | Size: 403 B |
2
public/vendor/livewire/livewire.js
vendored
2
public/vendor/livewire/livewire.js
vendored
File diff suppressed because one or more lines are too long
2
public/vendor/livewire/livewire.js.map
vendored
2
public/vendor/livewire/livewire.js.map
vendored
File diff suppressed because one or more lines are too long
2
public/vendor/livewire/manifest.json
vendored
2
public/vendor/livewire/manifest.json
vendored
@ -1 +1 @@
|
|||||||
{"/livewire.js":"/livewire.js?id=eb510e851dceb24afd36"}
|
{"/livewire.js":"/livewire.js?id=d9e06c155e467adb5de2"}
|
@ -3968,7 +3968,7 @@ $LANG = array(
|
|||||||
'list_of_recurring_invoices' => 'List of recurring invoices',
|
'list_of_recurring_invoices' => 'List of recurring invoices',
|
||||||
'details_of_recurring_invoice' => 'Here are some details about recurring invoice',
|
'details_of_recurring_invoice' => 'Here are some details about recurring invoice',
|
||||||
'cancellation' => 'Cancellation',
|
'cancellation' => 'Cancellation',
|
||||||
'about_cancellation' => 'In case you want to stop the recurring invoice,\n please click the request the cancellation.',
|
'about_cancellation' => 'In case you want to stop the recurring invoice, please click the request the cancellation.',
|
||||||
'cancellation_warning' => 'Warning! You are requesting a cancellation of this service.\n Your service may be cancelled with no further notification to you.',
|
'cancellation_warning' => 'Warning! You are requesting a cancellation of this service.\n Your service may be cancelled with no further notification to you.',
|
||||||
'cancellation_pending' => 'Cancellation pending, we\'ll be in touch!',
|
'cancellation_pending' => 'Cancellation pending, we\'ll be in touch!',
|
||||||
'list_of_payments' => 'List of payments',
|
'list_of_payments' => 'List of payments',
|
||||||
|
@ -5,60 +5,62 @@
|
|||||||
alt="{{ $subscription->company->present()->name }}">
|
alt="{{ $subscription->company->present()->name }}">
|
||||||
|
|
||||||
<div class="mt-6">
|
<div class="mt-6">
|
||||||
<p
|
@if(!empty($subscription->product_ids))
|
||||||
class="mb-4 uppercase leading-4 tracking-wide inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-primary text-white">
|
<p
|
||||||
Subscription
|
class="mb-4 uppercase leading-4 tracking-wide inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-primary text-white">
|
||||||
</p>
|
One-time purchase
|
||||||
|
</p>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if(!empty($subscription->recurring_product_ids))
|
||||||
|
<p
|
||||||
|
class="mb-4 uppercase leading-4 tracking-wide inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-primary text-white">
|
||||||
|
Subscription
|
||||||
|
</p>
|
||||||
|
@endif
|
||||||
|
|
||||||
<h1 id="billing-page-company-logo" class="text-3xl font-bold tracking-wide">
|
<h1 id="billing-page-company-logo" class="text-3xl font-bold tracking-wide">
|
||||||
Invoice Ninja Pro+
|
{{ $subscription->name }}
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if(!empty($subscription->product_ids))
|
@if(!empty($subscription->product_ids))
|
||||||
<div class="flex flex-col mt-8">
|
<div class="flex flex-col mt-8">
|
||||||
<span
|
<p
|
||||||
class="mb-4 uppercase leading-4 tracking-wide inline-flex items-center rounded-full text-xs font-medium">
|
class="mb-4 uppercase leading-4 tracking-wide inline-flex items-center rounded-full text-xs font-medium">
|
||||||
One-time purchases:
|
One-time purchases:
|
||||||
</span>
|
</p>
|
||||||
|
|
||||||
<div class="flex items-center justify-between mb-4 bg-white rounded px-6 py-4 shadow-sm border">
|
@foreach($subscription->service()->products() as $product)
|
||||||
<div class="text-sm">Pro+ plan</div>
|
<div class="flex items-center justify-between mb-4 bg-white rounded px-6 py-4 shadow-sm border">
|
||||||
<div data-ref="price-and-quantity-container">
|
<div class="text-sm">{{ $product->product_key }}</div>
|
||||||
<span data-ref="price">$10</span>
|
<div data-ref="price-and-quantity-container">
|
||||||
<span data-ref="quantity" class="text-sm">(1x)</span>
|
<span
|
||||||
|
data-ref="price">{{ \App\Utils\Number::formatMoney($product->price, $subscription->company) }}</span>
|
||||||
|
{{-- <span data-ref="quantity" class="text-sm">(1x)</span>--}}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
@endforeach
|
||||||
<div class="flex items-center justify-between mb-4 bg-white rounded px-6 py-4 shadow-sm border">
|
|
||||||
<div class="text-sm">Another awesome product</div>
|
|
||||||
<div data-ref="price-and-quantity-container">
|
|
||||||
<span data-ref="price">$5</span>
|
|
||||||
<span data-ref="quantity" class="text-sm">(2x)</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
@if(!empty($subscription->recurring_product_ids))
|
@if(!empty($subscription->recurring_product_ids))
|
||||||
<div class="flex flex-col mt-8">
|
<div class="flex flex-col mt-8">
|
||||||
<span
|
<p
|
||||||
class="mb-4 uppercase leading-4 tracking-wide inline-flex items-center rounded-full text-xs font-medium">
|
class="mb-4 uppercase leading-4 tracking-wide inline-flex items-center rounded-full text-xs font-medium">
|
||||||
Recurring purchases:
|
Recurring purchases:
|
||||||
</span>
|
</p>
|
||||||
|
|
||||||
<div class="flex items-center justify-between mb-4 bg-white rounded px-6 py-4 shadow-sm border">
|
@foreach($subscription->service()->recurring_products() as $product)
|
||||||
<div class="text-sm">Pro+ plan</div>
|
<div class="flex items-center justify-between mb-4 bg-white rounded px-6 py-4 shadow-sm border">
|
||||||
<div data-ref="price-and-quantity-container">
|
<div class="text-sm">{{ $product->product_key }}</div>
|
||||||
<span data-ref="price">$10</span>
|
<div data-ref="price-and-quantity-container">
|
||||||
<span data-ref="quantity" class="text-sm">(1x)</span>
|
<span
|
||||||
|
data-ref="price">{{ \App\Utils\Number::formatMoney($product->price, $subscription->company) }}</span>
|
||||||
|
{{-- <span data-ref="quantity" class="text-sm">(1x)</span>--}}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
@endforeach
|
||||||
<div class="flex items-center justify-between mb-4 bg-white rounded px-6 py-4 shadow-sm border">
|
|
||||||
<div class="text-sm">Another awesome product</div>
|
|
||||||
<div data-ref="price-and-quantity-container">
|
|
||||||
<span data-ref="price">$5</span>
|
|
||||||
<span data-ref="quantity" class="text-sm">(2x)</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
@ -69,7 +71,7 @@
|
|||||||
|
|
||||||
<div class="relative flex justify-center text-sm leading-5">
|
<div class="relative flex justify-center text-sm leading-5">
|
||||||
<h1 class="text-2xl font-bold tracking-wide bg-gray-50 px-6 py-0">
|
<h1 class="text-2xl font-bold tracking-wide bg-gray-50 px-6 py-0">
|
||||||
{{ ctrans('texts.total') }}: {{ App\Utils\Number::formatMoney(20, $subscription->company) }}
|
{{ ctrans('texts.total') }}: {{ $price }}
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -182,12 +184,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center mt-4">
|
<form wire:submit.prevent="handleCoupon" class="flex items-center mt-4">
|
||||||
|
@csrf
|
||||||
|
|
||||||
<label class="w-full mr-2">
|
<label class="w-full mr-2">
|
||||||
<input type="text" wire:model.lazy="coupon" class="input w-full m-0"/>
|
<input type="text" wire:model.lazy="coupon" class="input w-full m-0"/>
|
||||||
<small class="block text-gray-900 mt-2">{{ ctrans('texts.billing_coupon_notice') }}</small>
|
|
||||||
</label>
|
</label>
|
||||||
</div>
|
|
||||||
|
<button class="button button-primary bg-primary">Apply</button>
|
||||||
|
</form>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -0,0 +1,75 @@
|
|||||||
|
<div>
|
||||||
|
<p class="mb-4 uppercase leading-4 tracking-wide inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-primary text-white">
|
||||||
|
One-time payments
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="flex items-center justify-between mt-4">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<span class="mr-2 text-sm hidden md:block">{{ ctrans('texts.per_page') }}</span>
|
||||||
|
<select wire:model="per_page" class="form-select py-1 text-sm">
|
||||||
|
<option>5</option>
|
||||||
|
<option selected>10</option>
|
||||||
|
<option>15</option>
|
||||||
|
<option>20</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="-my-2 py-2 overflow-x-auto sm:-mx-6 sm:px-6 lg:-mx-8 lg:px-8">
|
||||||
|
<div class="align-middle inline-block min-w-full overflow-hidden rounded">
|
||||||
|
<table class="min-w-full shadow rounded border border-gray-200 mt-4 credits-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="px-6 py-3 text-xs font-medium leading-4 tracking-wider text-left text-white uppercase border-b border-gray-200 bg-primary">
|
||||||
|
<p role="button" wire:click="sortBy('number')" class="cursor-pointer">
|
||||||
|
{{ ctrans('texts.invoice') }}
|
||||||
|
</p>
|
||||||
|
</th>
|
||||||
|
<th class="px-6 py-3 border-b border-gray-200 bg-primary text-left text-xs leading-4 font-medium text-white uppercase tracking-wider">
|
||||||
|
<p role="button" wire:click="sortBy('amount')" class="cursor-pointer">
|
||||||
|
{{ ctrans('texts.total') }}
|
||||||
|
</p>
|
||||||
|
</th>
|
||||||
|
<th class="px-6 py-3 border-b border-gray-200 bg-primary text-left text-xs leading-4 font-medium text-white uppercase tracking-wider">
|
||||||
|
<p role="button" wire:click="sortBy('date')" class="cursor-pointer">
|
||||||
|
{{ ctrans('texts.date') }}
|
||||||
|
</p>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@forelse($invoices as $invoice)
|
||||||
|
<tr class="bg-white group hover:bg-gray-100">
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
|
||||||
|
<a href="{{ route('client.invoice.show', $invoice->hashed_id) }}"
|
||||||
|
class="button-link text-primary">
|
||||||
|
{{ $invoice->number }}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
|
||||||
|
{{ App\Utils\Number::formatMoney($invoice->amount, $invoice->client) }}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
|
||||||
|
{{ $invoice->formatDate($invoice->date, $invoice->client->date_format()) }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@empty
|
||||||
|
<tr class="bg-white group hover:bg-gray-100">
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500" colspan="100%">
|
||||||
|
{{ ctrans('texts.no_results') }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@endforelse
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex justify-center md:justify-between mt-6 mb-6">
|
||||||
|
@if($invoices->total() > 0)
|
||||||
|
<span class="text-gray-700 text-sm hidden md:block">
|
||||||
|
{{ ctrans('texts.showing_x_of', ['first' => $invoices->firstItem(), 'last' => $invoices->lastItem(), 'total' => $invoices->total()]) }}
|
||||||
|
</span>
|
||||||
|
@endif
|
||||||
|
{{ $invoices->links('portal/ninja2020/vendor/pagination') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -0,0 +1,75 @@
|
|||||||
|
<div>
|
||||||
|
<p class="mb-4 uppercase leading-4 tracking-wide inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-primary text-white">
|
||||||
|
Subscriptions
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="flex items-center justify-between mt-4">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<span class="mr-2 text-sm hidden md:block">{{ ctrans('texts.per_page') }}</span>
|
||||||
|
<select wire:model="per_page" class="form-select py-1 text-sm">
|
||||||
|
<option>5</option>
|
||||||
|
<option selected>10</option>
|
||||||
|
<option>15</option>
|
||||||
|
<option>20</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="-my-2 py-2 overflow-x-auto sm:-mx-6 sm:px-6 lg:-mx-8 lg:px-8">
|
||||||
|
<div class="align-middle inline-block min-w-full overflow-hidden rounded">
|
||||||
|
<table class="min-w-full shadow rounded border border-gray-200 mt-4 credits-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="px-6 py-3 text-xs font-medium leading-4 tracking-wider text-left text-white uppercase border-b border-gray-200 bg-primary">
|
||||||
|
<p role="button" wire:click="sortBy('number')" class="cursor-pointer">
|
||||||
|
{{ ctrans('texts.invoice') }}
|
||||||
|
</p>
|
||||||
|
</th>
|
||||||
|
<th class="px-6 py-3 border-b border-gray-200 bg-primary text-left text-xs leading-4 font-medium text-white uppercase tracking-wider">
|
||||||
|
<p role="button" wire:click="sortBy('amount')" class="cursor-pointer">
|
||||||
|
{{ ctrans('texts.total') }}
|
||||||
|
</p>
|
||||||
|
</th>
|
||||||
|
<th class="px-6 py-3 border-b border-gray-200 bg-primary text-left text-xs leading-4 font-medium text-white uppercase tracking-wider">
|
||||||
|
<p role="button" wire:click="sortBy('date')" class="cursor-pointer">
|
||||||
|
{{ ctrans('texts.date') }}
|
||||||
|
</p>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@forelse($recurring_invoices as $recurring_invoice)
|
||||||
|
<tr class="bg-white group hover:bg-gray-100">
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
|
||||||
|
<a href="{{ route('client.recurring_invoice.show', $recurring_invoice->hashed_id) }}"
|
||||||
|
class="button-link text-primary">
|
||||||
|
{{ $recurring_invoice->number }}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
|
||||||
|
{{ App\Utils\Number::formatMoney($recurring_invoice->amount, $recurring_invoice->client) }}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
|
||||||
|
{{ $recurring_invoice->formatDate($recurring_invoice->date, $recurring_invoice->client->date_format()) }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@empty
|
||||||
|
<tr class="bg-white group hover:bg-gray-100">
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500" colspan="100%">
|
||||||
|
{{ ctrans('texts.no_results') }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@endforelse
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex justify-center md:justify-between mt-6 mb-6">
|
||||||
|
@if($recurring_invoices->total() > 0)
|
||||||
|
<span class="text-gray-700 text-sm hidden md:block">
|
||||||
|
{{ ctrans('texts.showing_x_of', ['first' => $recurring_invoices->firstItem(), 'last' => $recurring_invoices->lastItem(), 'total' => $recurring_invoices->total()]) }}
|
||||||
|
</span>
|
||||||
|
@endif
|
||||||
|
{{ $recurring_invoices->links('portal/ninja2020/vendor/pagination') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -0,0 +1,150 @@
|
|||||||
|
<div>
|
||||||
|
<p class="mb-4 uppercase leading-4 tracking-wide inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-primary text-white"
|
||||||
|
translate="yes">
|
||||||
|
One-time payments
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="flex items-center justify-between mt-4">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<span class="mr-2 text-sm hidden md:block">{{ ctrans('texts.per_page') }}</span>
|
||||||
|
<select wire:model="per_page" class="form-select py-1 text-sm">
|
||||||
|
<option>5</option>
|
||||||
|
<option selected>10</option>
|
||||||
|
<option>15</option>
|
||||||
|
<option>20</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="-my-2 py-2 overflow-x-auto sm:-mx-6 sm:px-6 lg:-mx-8 lg:px-8">
|
||||||
|
<div class="align-middle inline-block min-w-full overflow-hidden rounded">
|
||||||
|
<table class="min-w-full shadow rounded border border-gray-200 mt-4 credits-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="px-6 py-3 text-xs font-medium leading-4 tracking-wider text-left text-white uppercase border-b border-gray-200 bg-primary">
|
||||||
|
<span role="button" wire:click="sortBy('number')" class="cursor-pointer">
|
||||||
|
{{ ctrans('texts.invoice') }}
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
|
<th class="px-6 py-3 border-b border-gray-200 bg-primary text-left text-xs leading-4 font-medium text-white uppercase tracking-wider">
|
||||||
|
<span role="button" wire:click="sortBy('amount')" class="cursor-pointer">
|
||||||
|
{{ ctrans('texts.total') }}
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
|
<th class="px-6 py-3 border-b border-gray-200 bg-primary text-left text-xs leading-4 font-medium text-white uppercase tracking-wider">
|
||||||
|
<span role="button" wire:click="sortBy('public_notes')" class="cursor-pointer">
|
||||||
|
{{ ctrans('texts.date') }}
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@forelse($invoices as $invoice)
|
||||||
|
<tr class="bg-white group hover:bg-gray-100">
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
|
||||||
|
<a href="{{ route('client.invoice.show', $invoice->hashed_id) }}"
|
||||||
|
class="button-link text-primary">
|
||||||
|
{{ $invoice->number }}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
|
||||||
|
{{ App\Utils\Number::formatMoney($invoice->amount, $invoice->client) }}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
|
||||||
|
{{ $invoice->formatDate($invoice->date, $invoice->client->date_format()) }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@empty
|
||||||
|
<tr class="bg-white group hover:bg-gray-100">
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500" colspan="100%">
|
||||||
|
{{ ctrans('texts.no_results') }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@endforelse
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-center md:justify-between mt-6 mb-6">
|
||||||
|
@if($invoices->total() > 0)
|
||||||
|
<span class="text-gray-700 text-sm hidden md:block">
|
||||||
|
{{ ctrans('texts.showing_x_of', ['first' => $invoices->firstItem(), 'last' => $invoices->lastItem(), 'total' => $invoices->total()]) }}
|
||||||
|
</span>
|
||||||
|
@endif
|
||||||
|
{{ $invoices->links('portal/ninja2020/vendor/pagination') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="mb-4 uppercase leading-4 tracking-wide inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-primary text-white mt-4"
|
||||||
|
translate="yes">
|
||||||
|
Subscriptions
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="flex items-center justify-between mt-4">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<span class="mr-2 text-sm hidden md:block">{{ ctrans('texts.per_page') }}</span>
|
||||||
|
<select wire:model="per_page" class="form-select py-1 text-sm">
|
||||||
|
<option>5</option>
|
||||||
|
<option selected>10</option>
|
||||||
|
<option>15</option>
|
||||||
|
<option>20</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="-my-2 py-2 overflow-x-auto sm:-mx-6 sm:px-6 lg:-mx-8 lg:px-8">
|
||||||
|
<div class="align-middle inline-block min-w-full overflow-hidden rounded">
|
||||||
|
<table class="min-w-full shadow rounded border border-gray-200 mt-4 credits-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="px-6 py-3 text-xs font-medium leading-4 tracking-wider text-left text-white uppercase border-b border-gray-200 bg-primary">
|
||||||
|
<span role="button" wire:click="sortBy('number')" class="cursor-pointer">
|
||||||
|
{{ ctrans('texts.invoice') }}
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
|
<th class="px-6 py-3 border-b border-gray-200 bg-primary text-left text-xs leading-4 font-medium text-white uppercase tracking-wider">
|
||||||
|
<span role="button" wire:click="sortBy('amount')" class="cursor-pointer">
|
||||||
|
{{ ctrans('texts.total') }}
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
|
<th class="px-6 py-3 border-b border-gray-200 bg-primary text-left text-xs leading-4 font-medium text-white uppercase tracking-wider">
|
||||||
|
<span role="button" wire:click="sortBy('public_notes')" class="cursor-pointer">
|
||||||
|
{{ ctrans('texts.date') }}
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@forelse($invoices as $invoice)
|
||||||
|
<tr class="bg-white group hover:bg-gray-100">
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
|
||||||
|
<a href="{{ route('client.invoice.show', $invoice->hashed_id) }}"
|
||||||
|
class="button-link text-primary">
|
||||||
|
{{ $invoice->number }}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
|
||||||
|
{{ App\Utils\Number::formatMoney($invoice->amount, $invoice->client) }}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
|
||||||
|
{{ $invoice->formatDate($invoice->date, $invoice->client->date_format()) }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@empty
|
||||||
|
<tr class="bg-white group hover:bg-gray-100">
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500" colspan="100%">
|
||||||
|
{{ ctrans('texts.no_results') }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@endforelse
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-center md:justify-between mt-6 mb-6">
|
||||||
|
@if($invoices->total() > 0)
|
||||||
|
<span class="text-gray-700 text-sm hidden md:block">
|
||||||
|
{{ ctrans('texts.showing_x_of', ['first' => $invoices->firstItem(), 'last' => $invoices->lastItem(), 'total' => $invoices->total()]) }}
|
||||||
|
</span>
|
||||||
|
@endif
|
||||||
|
{{ $invoices->links('portal/ninja2020/vendor/pagination') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
@ -57,7 +57,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="bg-white shadow sm:rounded-lg mb-4 mt-4" translate>
|
|
||||||
|
<div class="bg-white shadow sm:rounded-lg mb-4 mt-4">
|
||||||
<div class="px-4 py-5 sm:p-6">
|
<div class="px-4 py-5 sm:p-6">
|
||||||
<div class="sm:flex sm:items-start sm:justify-between">
|
<div class="sm:flex sm:items-start sm:justify-between">
|
||||||
<div>
|
<div>
|
||||||
@ -72,7 +73,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="mt-5 sm:mt-0 sm:ml-6 sm:flex-shrink-0 sm:flex sm:items-center">
|
<div class="mt-5 sm:mt-0 sm:ml-6 sm:flex-shrink-0 sm:flex sm:items-center">
|
||||||
<div class="inline-flex rounded-md shadow-sm" x-data="{ open: false }">
|
<div class="inline-flex rounded-md shadow-sm" x-data="{ open: false }">
|
||||||
<button class="button button-danger" translate @click="open = true">Request Cancellation</button>
|
<button class="button button-danger" translate @click="open = true">Request Cancellation
|
||||||
|
</button>
|
||||||
@include('portal.ninja2020.recurring_invoices.includes.modals.cancellation')
|
@include('portal.ninja2020.recurring_invoices.includes.modals.cancellation')
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
@extends('portal.ninja2020.layout.app')
|
||||||
|
@section('meta_title', ctrans('texts.subscriptions'))
|
||||||
|
|
||||||
|
@section('body')
|
||||||
|
<div class="flex flex-col">
|
||||||
|
@livewire('subscription-invoices-table')
|
||||||
|
@livewire('subscription-recurring-invoices-table')
|
||||||
|
</div>
|
||||||
|
@endsection
|
@ -71,6 +71,8 @@ Route::group(['middleware' => ['auth:contact', 'locale', 'check_client_existence
|
|||||||
Route::get('documents/{document}/download', 'ClientPortal\DocumentController@download')->name('documents.download');
|
Route::get('documents/{document}/download', 'ClientPortal\DocumentController@download')->name('documents.download');
|
||||||
Route::resource('documents', 'ClientPortal\DocumentController')->only(['index', 'show']);
|
Route::resource('documents', 'ClientPortal\DocumentController')->only(['index', 'show']);
|
||||||
|
|
||||||
|
Route::resource('subscriptions', 'ClientPortal\SubscriptionController')->only(['index']);
|
||||||
|
|
||||||
Route::post('upload', 'ClientPortal\UploadController')->name('upload.store');
|
Route::post('upload', 'ClientPortal\UploadController')->name('upload.store');
|
||||||
|
|
||||||
Route::get('logout', 'Auth\ContactLoginController@logout')->name('logout');
|
Route::get('logout', 'Auth\ContactLoginController@logout')->name('logout');
|
||||||
|
Loading…
x
Reference in New Issue
Block a user