[V2] Client portal rework (#3516)
* Client login, reset and update password page * Client dashboard, sidebar, PortalComposer.php * wip * Personal page & update for details * Invoices, paying & pagination.blade.php * Invoices, recurring invoice & buttons * Payments, link component * Payment methods * Breadcrums, clean up & wrap up * Remove format_date() method to formatDate on object * Payments - $this->render is now proxy for render() - Removed logic from Controller.php to ClientPortal.php - Added MakesDates to ClientGatewayToken.php - StripePaymentDriver.php now returns correct views - Refactor of adding new payment method - Ignoring all local builds for public/js/clients/* * Signature, wip * Fix "Pay now" on single invoice * Payments: - Added ProcessInvoicesInBulk request class - Refactor InvoiceController::bulk() - Displaying terms & payments - New signature.blade.php - Removed comment from webpack.mix.js * Quotes: - Refactor ProcessInvoicesInBulk.php to ProcessInvoicesInBulkRequest.php - Add new 'Quotes' field inside of PortalComposer.php - Added MakesDates to Quote.php - Added Quote::badgeForStatus() - Cleanup payment.blade.php - Quote showing and approving - New resource 'quotes' in client.php - New image for quotes, align-left.svg * Credits: - New 'credits' resource in client.php - Fixes for client.php typo * Breadcrumbs: - Quotes - Credits * Placeholder for translations. * Restore whereIn & client scope Co-authored-by: David Bomba <turbo124@gmail.com>
1
.gitignore
vendored
@ -30,3 +30,4 @@ storage/migrations
|
|||||||
# Ignore Tailwind & Javascript build file >2mb without PurgeCSS (development-only)
|
# Ignore Tailwind & Javascript build file >2mb without PurgeCSS (development-only)
|
||||||
public/css/app.css
|
public/css/app.css
|
||||||
public/js/app.js
|
public/js/app.js
|
||||||
|
public/js/clients/*
|
||||||
|
51
app/Helpers/ClientPortal.php
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://opensource.org/licenses/AAL
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if passed page is currently active.
|
||||||
|
*
|
||||||
|
* @param $page
|
||||||
|
* @param bool $boolean
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
function isActive($page, bool $boolean = false)
|
||||||
|
{
|
||||||
|
$current_page = Route::currentRouteName();
|
||||||
|
|
||||||
|
if ($page == $current_page && $boolean)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if ($page == $current_page)
|
||||||
|
return 'active-page';
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* New render method that works with themes.
|
||||||
|
*
|
||||||
|
* @param string $path
|
||||||
|
* @param array $options
|
||||||
|
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||||
|
*/
|
||||||
|
function render(string $path, array $options = [])
|
||||||
|
{
|
||||||
|
$theme = array_key_exists('theme', $options) ? $options['theme'] : 'ninja2020';
|
||||||
|
|
||||||
|
if (array_key_exists('root', $options)) {
|
||||||
|
return view(
|
||||||
|
sprintf('%s.%s.%s', $options['root'], $theme, $path)
|
||||||
|
, $options);
|
||||||
|
}
|
||||||
|
|
||||||
|
return view("portal.$theme.$path", $options);
|
||||||
|
}
|
@ -44,11 +44,11 @@ class ContactForgotPasswordController extends Controller
|
|||||||
/**
|
/**
|
||||||
* Show the reset email form.
|
* Show the reset email form.
|
||||||
*
|
*
|
||||||
* @return \Illuminate\Http\Response
|
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||||
*/
|
*/
|
||||||
public function showLinkRequestForm()
|
public function showLinkRequestForm()
|
||||||
{
|
{
|
||||||
return view('portal.default.auth.passwords.email', [
|
return $this->render('auth.passwords.request', [
|
||||||
'title' => 'Client Password Reset',
|
'title' => 'Client Password Reset',
|
||||||
'passwordEmailRoute' => 'client.password.email'
|
'passwordEmailRoute' => 'client.password.email'
|
||||||
]);
|
]);
|
||||||
|
@ -31,7 +31,7 @@ class ContactLoginController extends Controller
|
|||||||
|
|
||||||
public function showLoginForm()
|
public function showLoginForm()
|
||||||
{
|
{
|
||||||
return view('portal.default.auth.login');
|
return $this->render('auth.login');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -60,7 +60,7 @@ class ContactResetPasswordController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function showResetForm(Request $request, $token = null)
|
public function showResetForm(Request $request, $token = null)
|
||||||
{
|
{
|
||||||
return view('portal.default.auth.passwords.reset')->with(
|
return $this->render('auth.passwords.reset')->with(
|
||||||
['token' => $token, 'email' => $request->email]
|
['token' => $token, 'email' => $request->email]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
32
app/Http/Controllers/ClientPortal/CreditController.php
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\ClientPortal;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Http\Requests\ClientPortal\ShowCreditRequest;
|
||||||
|
use App\Models\Credit;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class CreditController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Display listing of client credits.
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||||
|
*/
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
$credits = auth()->user()->company->credits()->paginate(10);
|
||||||
|
|
||||||
|
return $this->render('credits.index', [
|
||||||
|
'credits' => $credits,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function show(ShowCreditRequest $request, Credit $credit)
|
||||||
|
{
|
||||||
|
return $this->render('credits.show', [
|
||||||
|
'credit' => $credit,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
@ -16,80 +16,11 @@ use App\Http\Controllers\Controller;
|
|||||||
|
|
||||||
class DashboardController extends Controller
|
class DashboardController extends Controller
|
||||||
{
|
{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display a listing of the resource.
|
|
||||||
*
|
|
||||||
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||||
*/
|
*/
|
||||||
public function index()
|
public function index()
|
||||||
{
|
{
|
||||||
return view('dashboard.index');
|
return $this->render('dashboard.index');
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show the form for creating a new resource.
|
|
||||||
*
|
|
||||||
* @return \Illuminate\Http\Response
|
|
||||||
*/
|
|
||||||
public function create()
|
|
||||||
{
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Store a newly created resource in storage.
|
|
||||||
*
|
|
||||||
* @param \Illuminate\Http\Request $request
|
|
||||||
* @return \Illuminate\Http\Response
|
|
||||||
*/
|
|
||||||
public function store(Request $request)
|
|
||||||
{
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Display the specified resource.
|
|
||||||
*
|
|
||||||
* @param int $id
|
|
||||||
* @return \Illuminate\Http\Response
|
|
||||||
*/
|
|
||||||
public function show($id)
|
|
||||||
{
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show the form for editing the specified resource.
|
|
||||||
*
|
|
||||||
* @param int $id
|
|
||||||
* @return \Illuminate\Http\Response
|
|
||||||
*/
|
|
||||||
public function edit($id)
|
|
||||||
{
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the specified resource in storage.
|
|
||||||
*
|
|
||||||
* @param \Illuminate\Http\Request $request
|
|
||||||
* @param int $id
|
|
||||||
* @return \Illuminate\Http\Response
|
|
||||||
*/
|
|
||||||
public function update(Request $request, $id)
|
|
||||||
{
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove the specified resource from storage.
|
|
||||||
*
|
|
||||||
* @param int $id
|
|
||||||
* @return \Illuminate\Http\Response
|
|
||||||
*/
|
|
||||||
public function destroy($id)
|
|
||||||
{
|
|
||||||
//
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,21 +13,18 @@ namespace App\Http\Controllers\ClientPortal;
|
|||||||
|
|
||||||
use App\Filters\InvoiceFilters;
|
use App\Filters\InvoiceFilters;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Http\Requests\ClientPortal\ProcessInvoicesInBulkRequest;
|
||||||
use App\Http\Requests\ClientPortal\ShowInvoiceRequest;
|
use App\Http\Requests\ClientPortal\ShowInvoiceRequest;
|
||||||
|
use App\Http\Requests\Request;
|
||||||
use App\Jobs\Entity\ActionEntity;
|
use App\Jobs\Entity\ActionEntity;
|
||||||
use App\Models\Invoice;
|
use App\Models\Invoice;
|
||||||
use App\Repositories\BaseRepository;
|
|
||||||
use App\Utils\Number;
|
use App\Utils\Number;
|
||||||
use App\Utils\Traits\MakesDates;
|
use App\Utils\Traits\MakesDates;
|
||||||
use App\Utils\Traits\MakesHash;
|
use App\Utils\Traits\MakesHash;
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Illuminate\Support\Facades\File;
|
|
||||||
use Illuminate\Support\Facades\Log;
|
|
||||||
use Illuminate\Support\Facades\Storage;
|
|
||||||
use Yajra\DataTables\Facades\DataTables;
|
|
||||||
use Yajra\DataTables\Html\Builder;
|
use Yajra\DataTables\Html\Builder;
|
||||||
use ZipStream\Option\Archive;
|
use ZipStream\Option\Archive;
|
||||||
use ZipStream\ZipStream;
|
use ZipStream\ZipStream;
|
||||||
|
use function GuzzleHttp\Promise\all;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class InvoiceController
|
* Class InvoiceController
|
||||||
@ -38,42 +35,20 @@ class InvoiceController extends Controller
|
|||||||
{
|
{
|
||||||
use MakesHash;
|
use MakesHash;
|
||||||
use MakesDates;
|
use MakesDates;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show the list of Invoices
|
* Show the list of Invoices
|
||||||
*
|
*
|
||||||
* @param \App\Filters\InvoiceFilters $filters The filters
|
* @param \App\Filters\InvoiceFilters $filters The filters
|
||||||
*
|
*
|
||||||
* @return \Illuminate\Http\Response
|
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||||
|
* @throws \Exception
|
||||||
*/
|
*/
|
||||||
public function index(InvoiceFilters $filters, Builder $builder)
|
public function index(InvoiceFilters $filters, Builder $builder)
|
||||||
{
|
{
|
||||||
$invoices = Invoice::filter($filters)->with('client', 'client.country');
|
$invoices = auth()->user()->client->company->invoices()->paginate(10);
|
||||||
|
|
||||||
if (request()->ajax()) {
|
return $this->render('invoices.index', ['invoices' => $invoices]);
|
||||||
return DataTables::of($invoices)->addColumn('action', function ($invoice) {
|
|
||||||
return $this->buildClientButtons($invoice);
|
|
||||||
})
|
|
||||||
->addColumn('checkbox', function ($invoice) {
|
|
||||||
return '<input type="checkbox" name="hashed_ids[]" value="'. $invoice->hashed_id .'"/>';
|
|
||||||
})
|
|
||||||
->editColumn('status_id', function ($invoice) {
|
|
||||||
return Invoice::badgeForStatus($invoice->status);
|
|
||||||
})->editColumn('date', function ($invoice) {
|
|
||||||
return $this->formatDate($invoice->date, $invoice->client->date_format());
|
|
||||||
})->editColumn('due_date', function ($invoice) {
|
|
||||||
return $this->formatDate($invoice->due_date, $invoice->client->date_format());
|
|
||||||
})->editColumn('balance', function ($invoice) {
|
|
||||||
return Number::formatMoney($invoice->balance, $invoice->client);
|
|
||||||
})->editColumn('amount', function ($invoice) {
|
|
||||||
return Number::formatMoney($invoice->amount, $invoice->client);
|
|
||||||
})
|
|
||||||
->rawColumns(['checkbox', 'action', 'status_id'])
|
|
||||||
->make(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
$data['html'] = $builder;
|
|
||||||
|
|
||||||
return view('portal.default.invoices.index', $data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function buildClientButtons($invoice)
|
private function buildClientButtons($invoice)
|
||||||
@ -96,7 +71,7 @@ class InvoiceController extends Controller
|
|||||||
*
|
*
|
||||||
* @param \App\Models\Invoice $invoice The invoice
|
* @param \App\Models\Invoice $invoice The invoice
|
||||||
*
|
*
|
||||||
* @return \Illuminate\Http\Response
|
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||||
*/
|
*/
|
||||||
public function show(ShowInvoiceRequest $request, Invoice $invoice)
|
public function show(ShowInvoiceRequest $request, Invoice $invoice)
|
||||||
{
|
{
|
||||||
@ -104,23 +79,26 @@ class InvoiceController extends Controller
|
|||||||
'invoice' => $invoice,
|
'invoice' => $invoice,
|
||||||
];
|
];
|
||||||
|
|
||||||
return view('portal.default.invoices.show', $data);
|
return $this->render('invoices.show', $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pay one or more invoices
|
* Pay one or more invoices
|
||||||
*
|
*
|
||||||
* @return View
|
* @param ProcessInvoicesInBulkRequest $request
|
||||||
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
public function bulk()
|
public function bulk(ProcessInvoicesInBulkRequest $request)
|
||||||
{
|
{
|
||||||
$transformed_ids = $this->transformKeys(explode(",", request()->input('hashed_ids')));
|
$transformed_ids = $this->transformKeys($request->invoices);
|
||||||
|
|
||||||
if (request()->input('action') == 'payment') {
|
if (request()->input('action') == 'payment') {
|
||||||
return $this->makePayment($transformed_ids);
|
return $this->makePayment((array)$transformed_ids);
|
||||||
} elseif (request()->input('action') == 'download') {
|
} elseif (request()->input('action') == 'download') {
|
||||||
return $this->downloadInvoicePDF($transformed_ids);
|
return $this->downloadInvoicePDF((array)$transformed_ids);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return redirect()->back();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -159,7 +137,7 @@ class InvoiceController extends Controller
|
|||||||
'total' => $total,
|
'total' => $total,
|
||||||
];
|
];
|
||||||
|
|
||||||
return view('portal.default.invoices.payment', $data);
|
return $this->render('invoices.payment', $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function downloadInvoicePDF(array $ids)
|
private function downloadInvoicePDF(array $ids)
|
||||||
|
@ -38,54 +38,35 @@ class PaymentController extends Controller
|
|||||||
/**
|
/**
|
||||||
* Show the list of Invoices
|
* Show the list of Invoices
|
||||||
*
|
*
|
||||||
* @param \App\Filters\InvoiceFilters $filters The filters
|
* @param PaymentFilters $filters The filters
|
||||||
*
|
*
|
||||||
* @return \Illuminate\Http\Response
|
* @param Builder $builder
|
||||||
|
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||||
*/
|
*/
|
||||||
public function index(PaymentFilters $filters, Builder $builder)
|
public function index(PaymentFilters $filters, Builder $builder)
|
||||||
{
|
{
|
||||||
//$payments = Payment::filter($filters);
|
//$payments = Payment::filter($filters);
|
||||||
$payments = Payment::with('type', 'client');
|
$payments = Payment::with('type', 'client')->paginate(10);
|
||||||
|
|
||||||
if (request()->ajax()) {
|
return $this->render('payments.index', [
|
||||||
return DataTables::of($payments)->addColumn('action', function ($payment) {
|
'payments' => $payments,
|
||||||
return '<a href="/client/payments/'. $payment->hashed_id .'" class="btn btn-xs btn-primary"><i class="glyphicon glyphicon-edit"></i>'.ctrans('texts.view').'</a>';
|
]);
|
||||||
})->editColumn('type_id', function ($payment) {
|
|
||||||
return $payment->type->name;
|
|
||||||
})
|
|
||||||
->editColumn('status_id', function ($payment) {
|
|
||||||
return Payment::badgeForStatus($payment->status_id);
|
|
||||||
})
|
|
||||||
->editColumn('date', function ($payment) {
|
|
||||||
//return $payment->date;
|
|
||||||
return $payment->formatDate($payment->date, $payment->client->date_format());
|
|
||||||
})
|
|
||||||
->editColumn('amount', function ($payment) {
|
|
||||||
return Number::formatMoney($payment->amount, $payment->client);
|
|
||||||
})
|
|
||||||
->rawColumns(['action', 'status_id','type_id'])
|
|
||||||
->make(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
$data['html'] = $builder;
|
|
||||||
|
|
||||||
return view('portal.default.payments.index', $data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display the specified resource.
|
* Display the specified resource.
|
||||||
*
|
*
|
||||||
* @param \App\Models\Invoice $invoice The invoice
|
* @param Request $request
|
||||||
*
|
* @param Payment $payment
|
||||||
* @return \Illuminate\Http\Response
|
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||||
*/
|
*/
|
||||||
public function show(Request $request, Payment $payment)
|
public function show(Request $request, Payment $payment)
|
||||||
{
|
{
|
||||||
$payment->load('invoices');
|
$payment->load('invoices');
|
||||||
|
|
||||||
$data['payment'] = $payment;
|
return $this->render('payments.show', [
|
||||||
|
'payment' => $payment,
|
||||||
return view('portal.default.payments.show', $data);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -16,9 +16,7 @@ use App\Http\Controllers\Controller;
|
|||||||
use App\Models\ClientGatewayToken;
|
use App\Models\ClientGatewayToken;
|
||||||
use App\Utils\Traits\MakesDates;
|
use App\Utils\Traits\MakesDates;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Http\Response;
|
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
use Yajra\DataTables\Facades\DataTables;
|
|
||||||
use Yajra\DataTables\Html\Builder;
|
use Yajra\DataTables\Html\Builder;
|
||||||
|
|
||||||
class PaymentMethodController extends Controller
|
class PaymentMethodController extends Controller
|
||||||
@ -28,49 +26,18 @@ class PaymentMethodController extends Controller
|
|||||||
/**
|
/**
|
||||||
* Display a listing of the resource.
|
* Display a listing of the resource.
|
||||||
*
|
*
|
||||||
* @return \Illuminate\Http\Response
|
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||||
|
* @throws \Exception
|
||||||
*/
|
*/
|
||||||
public function index(Builder $builder)
|
public function index(Builder $builder)
|
||||||
{
|
{
|
||||||
$payment_methods = ClientGatewayToken::whereClientId(auth()->user()->client->id);
|
$payment_methods = ClientGatewayToken::with('gateway_type')
|
||||||
$payment_methods->with('gateway_type');
|
->whereClientId(auth()->user()->client->id)
|
||||||
|
->paginate(10);
|
||||||
|
|
||||||
if (request()->ajax()) {
|
return $this->render('payment_methods.index', [
|
||||||
return DataTables::of($payment_methods)->addColumn('action', function ($payment_method) {
|
'payment_methods' => $payment_methods,
|
||||||
return '<a href="/client/payment_methods/' . $payment_method->hashed_id . '" class="btn btn-xs btn-primary"><i class="glyphicon glyphicon-edit"></i>' . ctrans('texts.view') . '</a>';
|
]);
|
||||||
})
|
|
||||||
->editColumn('gateway_type_id', function ($payment_method) {
|
|
||||||
return ctrans("texts.{$payment_method->gateway_type->alias}");
|
|
||||||
})->editColumn('created_at', function ($payment_method) {
|
|
||||||
return $this->formatDateTimestamp($payment_method->created_at, auth()->user()->client->date_format());
|
|
||||||
})->editColumn('is_default', function ($payment_method) {
|
|
||||||
return $payment_method->is_default ? ctrans('texts.default') : '';
|
|
||||||
})->editColumn('meta', function ($payment_method) {
|
|
||||||
if (isset($payment_method->meta->exp_month) && isset($payment_method->meta->exp_year)) {
|
|
||||||
return "{$payment_method->meta->exp_month}/{$payment_method->meta->exp_year}";
|
|
||||||
} else {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
})->addColumn('last4', function ($payment_method) {
|
|
||||||
if (isset($payment_method->meta->last4)) {
|
|
||||||
return $payment_method->meta->last4;
|
|
||||||
} else {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
})->addColumn('brand', function ($payment_method) {
|
|
||||||
if (isset($payment_method->meta->brand)) {
|
|
||||||
return $payment_method->meta->brand;
|
|
||||||
} else {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
})
|
|
||||||
->rawColumns(['action', 'status_id', 'last4', 'brand'])
|
|
||||||
->make(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
$data['html'] = $builder;
|
|
||||||
|
|
||||||
return view('portal.default.payment_methods.index', $data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -112,7 +79,9 @@ class PaymentMethodController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function show(ClientGatewayToken $payment_method)
|
public function show(ClientGatewayToken $payment_method)
|
||||||
{
|
{
|
||||||
return view('portal.default.payment_methods.show', compact('payment_method'));
|
return $this->render('payment_methods.show', [
|
||||||
|
'payment_method' => $payment_method,
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -154,6 +123,8 @@ class PaymentMethodController extends Controller
|
|||||||
return back();
|
return back();
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirect()->route('client.payment_methods.index');
|
return redirect()
|
||||||
|
->route('client.payment_methods.index')
|
||||||
|
->withSuccess('Payment method has been successfully removed.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,59 +22,39 @@ use Illuminate\Support\Facades\Log;
|
|||||||
|
|
||||||
class ProfileController extends Controller
|
class ProfileController extends Controller
|
||||||
{
|
{
|
||||||
|
|
||||||
/**
|
|
||||||
* Display the specified resource.
|
|
||||||
*
|
|
||||||
* @param int $id
|
|
||||||
* @return \Illuminate\Http\Response
|
|
||||||
*/
|
|
||||||
public function show($id)
|
|
||||||
{
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show the form for editing the specified resource.
|
* Show the form for editing the specified resource.
|
||||||
*
|
*
|
||||||
* @param int $id
|
* @param ClientContact $client_contact
|
||||||
* @return \Illuminate\Http\Response
|
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||||
*/
|
*/
|
||||||
public function edit(ClientContact $client_contact)
|
public function edit(ClientContact $client_contact)
|
||||||
{
|
{
|
||||||
/* Dropzone configuration */
|
return $this->render('profile.index');
|
||||||
$data = [
|
|
||||||
'params' => [
|
|
||||||
'is_avatar' => true,
|
|
||||||
],
|
|
||||||
'url' => '/client/document',
|
|
||||||
'multi_upload' => false,
|
|
||||||
];
|
|
||||||
|
|
||||||
return view('portal.default.profile.index', $data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the specified resource in storage.
|
* Update the specified resource in storage.
|
||||||
*
|
*
|
||||||
* @param \Illuminate\Http\Request $request
|
* @param UpdateContactRequest $request
|
||||||
* @param int $id
|
* @param ClientContact $client_contact
|
||||||
* @return \Illuminate\Http\Response
|
* @return \Illuminate\Http\RedirectResponse
|
||||||
*/
|
*/
|
||||||
public function update(UpdateContactRequest $request, ClientContact $client_contact)
|
public function update(UpdateContactRequest $request, ClientContact $client_contact)
|
||||||
{
|
{
|
||||||
$client_contact->fill($request->all());
|
$client_contact->fill($request->all());
|
||||||
|
|
||||||
//update password if needed
|
if ($request->has('password')) {
|
||||||
if ($request->input('password')) {
|
$client_contact->password = encrypt($request->password);
|
||||||
$client_contact->password = Hash::make($request->input('password'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$client_contact->save();
|
$client_contact->save();
|
||||||
|
|
||||||
// auth()->user()->fresh();
|
// auth()->user()->fresh();
|
||||||
|
|
||||||
return back();
|
return back()->withSuccess(
|
||||||
|
ctrans('texts.profile_updated_successfully')
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function updateClient(UpdateClientRequest $request, ClientContact $client_contact)
|
public function updateClient(UpdateClientRequest $request, ClientContact $client_contact)
|
||||||
@ -93,6 +73,8 @@ class ProfileController extends Controller
|
|||||||
$client->fill($request->all());
|
$client->fill($request->all());
|
||||||
$client->save();
|
$client->save();
|
||||||
|
|
||||||
return back();
|
return back()->withSuccess(
|
||||||
|
ctrans('texts.profile_updated_successfully')
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
114
app/Http/Controllers/ClientPortal/QuoteController.php
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\ClientPortal;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Http\Requests\ClientPortal\ProcessQuotesInBulkRequest;
|
||||||
|
use App\Http\Requests\ClientPortal\ShowQuoteRequest;
|
||||||
|
use App\Models\Company;
|
||||||
|
use App\Models\Quote;
|
||||||
|
use App\Utils\Traits\MakesHash;
|
||||||
|
use ZipStream\Option\Archive;
|
||||||
|
use ZipStream\ZipStream;
|
||||||
|
|
||||||
|
class QuoteController extends Controller
|
||||||
|
{
|
||||||
|
use MakesHash;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display a listing of the quotes.
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||||
|
*/
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
$quotes = auth()->user()->company->quotes()->paginate(10);
|
||||||
|
|
||||||
|
return $this->render('quotes.index', [
|
||||||
|
'quotes' => $quotes,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the specified resource.
|
||||||
|
*
|
||||||
|
* @param ShowQuoteRequest $request
|
||||||
|
* @param Quote $quote
|
||||||
|
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||||
|
*/
|
||||||
|
public function show(ShowQuoteRequest $request, Quote $quote)
|
||||||
|
{
|
||||||
|
return $this->render('quotes.show', [
|
||||||
|
'quote' => $quote,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function bulk(ProcessQuotesInBulkRequest $request)
|
||||||
|
{
|
||||||
|
$transformed_ids = $this->transformKeys($request->quotes);
|
||||||
|
|
||||||
|
if ($request->action == 'download') {
|
||||||
|
return $this->downloadQuotePdf((array)$transformed_ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->action = 'approve') {
|
||||||
|
return $this->approve((array)$transformed_ids, $request->has('process'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return back();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function downloadQuotePdf(array $ids)
|
||||||
|
{
|
||||||
|
$quotes = Quote::whereIn('id', $ids)
|
||||||
|
->whereClientId(auth()->user()->client->id)
|
||||||
|
->get();
|
||||||
|
|
||||||
|
if (!$quotes || $quotes->count() == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($quotes->count() == 1) {
|
||||||
|
return response()->download(public_path($quotes->first()->pdf_file_path()));
|
||||||
|
}
|
||||||
|
|
||||||
|
# enable output of HTTP headers
|
||||||
|
$options = new Archive();
|
||||||
|
$options->setSendHttpHeaders(true);
|
||||||
|
|
||||||
|
# create a new zipstream object
|
||||||
|
$zip = new ZipStream(date('Y-m-d') . '_' . str_replace(' ', '_', trans('texts.invoices')) . ".zip", $options);
|
||||||
|
|
||||||
|
foreach ($quotes as $quote) {
|
||||||
|
$zip->addFileFromPath(basename($quote->pdf_file_path()), public_path($quote->pdf_file_path()));
|
||||||
|
}
|
||||||
|
|
||||||
|
# finish the zip stream
|
||||||
|
$zip->finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function approve(array $ids, $process = false)
|
||||||
|
{
|
||||||
|
$quotes = Quote::whereIn('id', $ids)
|
||||||
|
->whereClientId(auth()->user()->client->id)
|
||||||
|
->get();
|
||||||
|
|
||||||
|
if (!$quotes || $quotes->count() == 0) {
|
||||||
|
return redirect()->route('client.quotes.index');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($process) {
|
||||||
|
|
||||||
|
foreach ($quotes as $quote) {
|
||||||
|
$quote->service()->approve()->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
return route('client.quotes.index')->withSuccess('Quote(s) approved successfully.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->render('quotes.approve', [
|
||||||
|
'quotes' => $quotes,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
@ -35,12 +35,12 @@ class RecurringInvoiceController extends Controller
|
|||||||
{
|
{
|
||||||
use MakesHash;
|
use MakesHash;
|
||||||
use MakesDates;
|
use MakesDates;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show the list of Invoices
|
* Show the list of recurring invoices.
|
||||||
*
|
*
|
||||||
* @param \App\Filters\InvoiceFilters $filters The filters
|
* @param Builder $builder
|
||||||
*
|
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||||
* @return \Illuminate\Http\Response
|
|
||||||
*/
|
*/
|
||||||
public function index(Builder $builder)
|
public function index(Builder $builder)
|
||||||
{
|
{
|
||||||
@ -48,62 +48,35 @@ class RecurringInvoiceController extends Controller
|
|||||||
->whereIn('status_id', [RecurringInvoice::STATUS_PENDING, RecurringInvoice::STATUS_ACTIVE, RecurringInvoice::STATUS_COMPLETED])
|
->whereIn('status_id', [RecurringInvoice::STATUS_PENDING, RecurringInvoice::STATUS_ACTIVE, RecurringInvoice::STATUS_COMPLETED])
|
||||||
->orderBy('status_id', 'asc')
|
->orderBy('status_id', 'asc')
|
||||||
->with('client')
|
->with('client')
|
||||||
->get();
|
->paginate(10);
|
||||||
|
|
||||||
if (request()->ajax()) {
|
return $this->render('recurring_invoices.index', [
|
||||||
return DataTables::of($invoices)->addColumn('action', function ($invoice) {
|
'invoices' => $invoices,
|
||||||
return '<a href="/client/recurring_invoices/'. $invoice->hashed_id .'" class="btn btn-xs btn-primary"><i class="glyphicon glyphicon-edit"></i>'.ctrans('texts.view').'</a>';
|
]);
|
||||||
})->addColumn('frequency_id', function ($invoice) {
|
|
||||||
return RecurringInvoice::frequencyForKey($invoice->frequency_id);
|
|
||||||
})
|
|
||||||
->editColumn('status_id', function ($invoice) {
|
|
||||||
return RecurringInvoice::badgeForStatus($invoice->status);
|
|
||||||
})
|
|
||||||
->editColumn('start_date', function ($invoice) {
|
|
||||||
return $this->formatDate($invoice->date, $invoice->client->date_format());
|
|
||||||
})
|
|
||||||
->editColumn('next_send_date', function ($invoice) {
|
|
||||||
return $this->formatDate($invoice->next_send_date, $invoice->client->date_format());
|
|
||||||
})
|
|
||||||
->editColumn('amount', function ($invoice) {
|
|
||||||
return Number::formatMoney($invoice->amount, $invoice->client);
|
|
||||||
})
|
|
||||||
->rawColumns(['action', 'status_id'])
|
|
||||||
->make(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
$data['html'] = $builder;
|
|
||||||
|
|
||||||
return view('portal.default.recurring_invoices.index', $data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display the specified resource.
|
* Display the recurring invoice.
|
||||||
*
|
*
|
||||||
* @param \App\Models\Invoice $invoice The invoice
|
* @param ShowRecurringInvoiceRequest $request
|
||||||
*
|
* @param RecurringInvoice $recurring_invoice
|
||||||
* @return \Illuminate\Http\Response
|
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||||
*/
|
*/
|
||||||
public function show(ShowRecurringInvoiceRequest $request, RecurringInvoice $recurring_invoice)
|
public function show(ShowRecurringInvoiceRequest $request, RecurringInvoice $recurring_invoice)
|
||||||
{
|
{
|
||||||
$data = [
|
return $this->render('recurring_invoices.show', [
|
||||||
'invoice' => $recurring_invoice->load('invoices'),
|
'invoice' => $recurring_invoice->load('invoices'),
|
||||||
];
|
]);
|
||||||
|
|
||||||
return view('portal.default.recurring_invoices.show', $data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function requestCancellation(Request $request, RecurringInvoice $recurring_invoice)
|
public function requestCancellation(Request $request, RecurringInvoice $recurring_invoice)
|
||||||
{
|
{
|
||||||
$data = [
|
|
||||||
'invoice' => $recurring_invoice
|
|
||||||
];
|
|
||||||
|
|
||||||
//todo double check the user is able to request a cancellation
|
//todo double check the user is able to request a cancellation
|
||||||
//can add locale specific by chaining ->locale();
|
//can add locale specific by chaining ->locale();
|
||||||
$recurring_invoice->user->notify(new ClientContactRequestCancellation($recurring_invoice, auth()->user()));
|
$recurring_invoice->user->notify(new ClientContactRequestCancellation($recurring_invoice, auth()->user()));
|
||||||
|
|
||||||
return view('portal.default.recurring_invoices.request_cancellation', $data);
|
return $this->render('recurring_invoices.cancellation.index', [
|
||||||
|
'invoice' => $recurring_invoice,
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,8 @@ class Controller extends BaseController
|
|||||||
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
|
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Proxy method for rendering views.
|
||||||
|
*
|
||||||
* @param string $path
|
* @param string $path
|
||||||
* @param array $options
|
* @param array $options
|
||||||
*
|
*
|
||||||
@ -29,15 +31,6 @@ class Controller extends BaseController
|
|||||||
*/
|
*/
|
||||||
public function render(string $path, array $options = [])
|
public function render(string $path, array $options = [])
|
||||||
{
|
{
|
||||||
$theme = array_key_exists('theme', $options) ? $options['theme'] : 'ninja2020';
|
return render($path, $options);
|
||||||
|
|
||||||
if (array_key_exists('root', $options)) {
|
|
||||||
return view(
|
|
||||||
sprintf('%s.%s.%s', $options['root'], $theme, $path),
|
|
||||||
$options
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return view("portal.$theme.$path", $options);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests\ClientPortal;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class ProcessInvoicesInBulkRequest extends FormRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine if the user is authorized to make this request.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function authorize()
|
||||||
|
{
|
||||||
|
return true; // TODO.
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function rules()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'invoices' => ['array'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests\ClientPortal;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class ProcessQuotesInBulkRequest extends FormRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine if the user is authorized to make this request.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function authorize()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function rules()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'quotes' => ['array'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
30
app/Http/Requests/ClientPortal/ShowCreditRequest.php
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests\ClientPortal;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class ShowCreditRequest extends FormRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine if the user is authorized to make this request.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function authorize()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function rules()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
//
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
32
app/Http/Requests/ClientPortal/ShowQuoteRequest.php
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests\ClientPortal;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class ShowQuoteRequest extends FormRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine if the user is authorized to make this request.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function authorize()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// return auth()->user()->client->id === $this->quote->client_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function rules()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
//
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -32,7 +32,7 @@ class UpdateClientRequest extends Request
|
|||||||
public function rules()
|
public function rules()
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'name' => 'required',
|
'name' => 'sometimes|required',
|
||||||
'file' => 'sometimes|nullable|max:100000|mimes:png,svg,jpeg,gif,jpg,bmp'
|
'file' => 'sometimes|nullable|max:100000|mimes:png,svg,jpeg,gif,jpg,bmp'
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -58,11 +58,13 @@ class PortalComposer
|
|||||||
{
|
{
|
||||||
$data = [];
|
$data = [];
|
||||||
|
|
||||||
$data[] = [ 'title' => ctrans('texts.dashboard'), 'url' => 'client.dashboard', 'icon' => 'fa fa-tachometer fa-fw fa-2x'];
|
$data[] = [ 'title' => ctrans('texts.dashboard'), 'url' => 'client.dashboard', 'icon' => 'activity'];
|
||||||
$data[] = [ 'title' => ctrans('texts.invoices'), 'url' => 'client.invoices.index', 'icon' => 'fa fa-file-pdf-o fa-fw fa-2x'];
|
$data[] = [ 'title' => ctrans('texts.invoices'), 'url' => 'client.invoices.index', 'icon' => 'file-text'];
|
||||||
$data[] = [ 'title' => ctrans('texts.recurring_invoices'), 'url' => 'client.recurring_invoices.index', 'icon' => 'fa fa-files-o fa-fw fa-2x'];
|
$data[] = [ 'title' => ctrans('texts.recurring_invoices'), 'url' => 'client.recurring_invoices.index', 'icon' => 'file'];
|
||||||
$data[] = [ 'title' => ctrans('texts.payments'), 'url' => 'client.payments.index', 'icon' => 'fa fa-credit-card fa-fw fa-2x'];
|
$data[] = [ 'title' => ctrans('texts.payments'), 'url' => 'client.payments.index', 'icon' => 'credit-card'];
|
||||||
$data[] = [ 'title' => ctrans('texts.payment_methods'), 'url' => 'client.payment_methods.index', 'icon' => 'fa fa-cc-stripe fa-fw fa-2x'];
|
$data[] = [ 'title' => ctrans('texts.payment_methods'), 'url' => 'client.payment_methods.index', 'icon' => 'shield'];
|
||||||
|
$data[] = [ 'title' => ctrans('texts.quotes'), 'url' => 'client.quotes.index', 'icon' => 'align-left'];
|
||||||
|
$data[] = [ 'title' => ctrans('texts.credits'), 'url' => 'client.credits.index', 'icon' => 'credit-card'];
|
||||||
|
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
@ -162,4 +162,15 @@ class ClientContact extends Authenticatable implements HasLocalePreference
|
|||||||
->withTrashed()
|
->withTrashed()
|
||||||
->where('id', $this->decodePrimaryKey($value))->firstOrFail();
|
->where('id', $this->decodePrimaryKey($value))->firstOrFail();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return mixed|string
|
||||||
|
*/
|
||||||
|
public function avatar()
|
||||||
|
{
|
||||||
|
if($this->avatar)
|
||||||
|
return $this->avatar;
|
||||||
|
|
||||||
|
return asset('images/svg/user.svg');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,9 +16,12 @@ use App\Models\Company;
|
|||||||
use App\Models\CompanyGateway;
|
use App\Models\CompanyGateway;
|
||||||
use App\Models\GatewayType;
|
use App\Models\GatewayType;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
use App\Utils\Traits\MakesDates;
|
||||||
|
|
||||||
class ClientGatewayToken extends BaseModel
|
class ClientGatewayToken extends BaseModel
|
||||||
{
|
{
|
||||||
|
use MakesDates;
|
||||||
|
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'meta' => 'object',
|
'meta' => 'object',
|
||||||
'updated_at' => 'timestamp',
|
'updated_at' => 'timestamp',
|
||||||
|
@ -114,6 +114,10 @@ class Invoice extends BaseModel
|
|||||||
'status'
|
'status'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
protected $dates = [
|
||||||
|
'date',
|
||||||
|
];
|
||||||
|
|
||||||
const STATUS_DRAFT = 1;
|
const STATUS_DRAFT = 1;
|
||||||
const STATUS_SENT = 2;
|
const STATUS_SENT = 2;
|
||||||
const STATUS_PARTIAL = 3;
|
const STATUS_PARTIAL = 3;
|
||||||
|
@ -17,6 +17,7 @@ use App\Jobs\Invoice\CreateInvoicePdf;
|
|||||||
use App\Jobs\Quote\CreateQuotePdf;
|
use App\Jobs\Quote\CreateQuotePdf;
|
||||||
use App\Models\Filterable;
|
use App\Models\Filterable;
|
||||||
use App\Services\Quote\QuoteService;
|
use App\Services\Quote\QuoteService;
|
||||||
|
use App\Utils\Traits\MakesDates;
|
||||||
use App\Utils\Traits\MakesHash;
|
use App\Utils\Traits\MakesHash;
|
||||||
use App\Utils\Traits\MakesInvoiceValues;
|
use App\Utils\Traits\MakesInvoiceValues;
|
||||||
use App\Utils\Traits\MakesReminders;
|
use App\Utils\Traits\MakesReminders;
|
||||||
@ -29,6 +30,7 @@ use Laracasts\Presenter\PresentableTrait;
|
|||||||
class Quote extends BaseModel
|
class Quote extends BaseModel
|
||||||
{
|
{
|
||||||
use MakesHash;
|
use MakesHash;
|
||||||
|
use MakesDates;
|
||||||
use Filterable;
|
use Filterable;
|
||||||
use SoftDeletes;
|
use SoftDeletes;
|
||||||
use MakesReminders;
|
use MakesReminders;
|
||||||
@ -150,7 +152,8 @@ class Quote extends BaseModel
|
|||||||
{
|
{
|
||||||
$storage_path = 'storage/' . $this->client->quote_filepath() . $this->number . '.pdf';
|
$storage_path = 'storage/' . $this->client->quote_filepath() . $this->number . '.pdf';
|
||||||
|
|
||||||
if (Storage::exists($storage_path)) {
|
if (Storage::exists($storage_path))
|
||||||
|
{
|
||||||
return $storage_path;
|
return $storage_path;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,4 +165,43 @@ class Quote extends BaseModel
|
|||||||
|
|
||||||
return $storage_path;
|
return $storage_path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $status
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function badgeForStatus(int $status)
|
||||||
|
{
|
||||||
|
switch ($status) {
|
||||||
|
case Quote::STATUS_DRAFT:
|
||||||
|
return '<h5><span class="badge badge-light">' . ctrans('texts.draft') . '</span></h5>';
|
||||||
|
break;
|
||||||
|
case Quote::STATUS_SENT:
|
||||||
|
return '<h5><span class="badge badge-primary">' . ctrans('texts.sent') . '</span></h5>';
|
||||||
|
break;
|
||||||
|
case Quote::STATUS_APPROVED:
|
||||||
|
return '<h5><span class="badge badge-success">' . ctrans('texts.approved') . '</span></h5>';
|
||||||
|
break;
|
||||||
|
case Quote::STATUS_EXPIRED:
|
||||||
|
return '<h5><span class="badge badge-danger">' . ctrans('texts.expired') . '</span></h5>';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
# code...
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the quote has been approved.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isApproved()
|
||||||
|
{
|
||||||
|
if($this->status_id === $this::STATUS_APPROVED) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -104,55 +104,48 @@ class StripePaymentDriver extends BasePaymentDriver
|
|||||||
{
|
{
|
||||||
switch ($gateway_type_id) {
|
switch ($gateway_type_id) {
|
||||||
case GatewayType::CREDIT_CARD:
|
case GatewayType::CREDIT_CARD:
|
||||||
return 'portal.default.gateways.stripe.credit_card';
|
|
||||||
break;
|
|
||||||
case GatewayType::TOKEN:
|
case GatewayType::TOKEN:
|
||||||
return 'portal.default.gateways.stripe.credit_card';
|
return 'gateways.stripe.credit_card';
|
||||||
break;
|
break;
|
||||||
case GatewayType::SOFORT:
|
case GatewayType::SOFORT:
|
||||||
return 'portal.default.gateways.stripe.sofort';
|
return 'gateways.stripe.sofort';
|
||||||
break;
|
break;
|
||||||
case GatewayType::BANK_TRANSFER:
|
case GatewayType::BANK_TRANSFER:
|
||||||
return 'portal.default.gateways.stripe.ach';
|
return 'gateways.stripe.ach';
|
||||||
break;
|
break;
|
||||||
case GatewayType::SEPA:
|
case GatewayType::SEPA:
|
||||||
return 'portal.default.gateways.stripe.sepa';
|
return 'gateways.stripe.sepa';
|
||||||
break;
|
break;
|
||||||
case GatewayType::CRYPTO:
|
case GatewayType::CRYPTO:
|
||||||
return 'portal.default.gateways.stripe.other';
|
|
||||||
break;
|
|
||||||
case GatewayType::ALIPAY:
|
case GatewayType::ALIPAY:
|
||||||
return 'portal.default.gateways.stripe.other';
|
|
||||||
break;
|
|
||||||
case GatewayType::APPLE_PAY:
|
case GatewayType::APPLE_PAY:
|
||||||
return 'portal.default.gateways.stripe.other';
|
return 'gateways.stripe.other';
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
# code...
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Authorises a credit card for future use.
|
||||||
*
|
*
|
||||||
* Authorises a credit card for future use
|
|
||||||
* @param array $data Array of variables needed for the view
|
* @param array $data Array of variables needed for the view
|
||||||
*
|
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||||
* @return view The gateway specific partial to be rendered
|
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
public function authorizeCreditCardView(array $data)
|
public function authorizeCreditCardView(array $data)
|
||||||
{
|
{
|
||||||
$intent['intent'] = $this->getSetupIntent();
|
$intent['intent'] = $this->getSetupIntent();
|
||||||
|
|
||||||
return view('portal.default.gateways.stripe.add_credit_card', array_merge($data, $intent));
|
return render('gateways.stripe.add_credit_card', array_merge($data, $intent));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processes the gateway response for credti card authorization
|
* Processes the gateway response for credit card authorization.
|
||||||
|
*
|
||||||
* @param Request $request The returning request object
|
* @param Request $request The returning request object
|
||||||
* @return view Returns the user to payment methods screen.
|
* @return view Returns the user to payment methods screen.
|
||||||
|
* @throws \Stripe\Exception\ApiErrorException
|
||||||
*/
|
*/
|
||||||
public function authorizeCreditCardResponse($request)
|
public function authorizeCreditCardResponse($request)
|
||||||
{
|
{
|
||||||
@ -202,18 +195,11 @@ class StripePaymentDriver extends BasePaymentDriver
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processes the payment with this gateway
|
* Process the payment with gateway.
|
||||||
*
|
*
|
||||||
* @var invoices
|
* @param array $data
|
||||||
* @var amount
|
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View|void
|
||||||
* @var fee
|
* @throws \Exception
|
||||||
* @var amount_with_fee
|
|
||||||
* @var token
|
|
||||||
* @var payment_method_id
|
|
||||||
* @var hashed_ids
|
|
||||||
*
|
|
||||||
* @param array $data variables required to build payment page
|
|
||||||
* @return view Gateway and payment method specific view
|
|
||||||
*/
|
*/
|
||||||
public function processPaymentView(array $data)
|
public function processPaymentView(array $data)
|
||||||
{
|
{
|
||||||
@ -237,7 +223,7 @@ class StripePaymentDriver extends BasePaymentDriver
|
|||||||
|
|
||||||
$data['gateway'] = $this;
|
$data['gateway'] = $this;
|
||||||
|
|
||||||
return view($this->viewForType($data['payment_method_id']), $data);
|
return render($this->viewForType($data['payment_method_id']), $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -22,7 +22,7 @@ return [
|
|||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'view' => 'breadcrumbs::bootstrap4',
|
'view' => 'portal.ninja2020.components.breadcrumbs',
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
1
public/images/svg/activity.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-activity"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"></polyline></svg>
|
After Width: | Height: | Size: 277 B |
1
public/images/svg/align-left.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-align-left"><line x1="17" y1="10" x2="3" y2="10"></line><line x1="21" y1="6" x2="3" y2="6"></line><line x1="21" y1="14" x2="3" y2="14"></line><line x1="17" y1="18" x2="3" y2="18"></line></svg>
|
After Width: | Height: | Size: 388 B |
1
public/images/svg/credit-card.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-credit-card"><rect x="1" y="4" width="22" height="16" rx="2" ry="2"></rect><line x1="1" y1="10" x2="23" y2="10"></line></svg>
|
After Width: | Height: | Size: 324 B |
1
public/images/svg/file-text.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg>
|
After Width: | Height: | Size: 468 B |
1
public/images/svg/file.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file"><path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"></path><polyline points="13 2 13 9 20 9"></polyline></svg>
|
After Width: | Height: | Size: 332 B |
1
public/images/svg/shield.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-shield"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path></svg>
|
After Width: | Height: | Size: 274 B |
1
public/images/svg/user.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-user"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path><circle cx="12" cy="7" r="4"></circle></svg>
|
After Width: | Height: | Size: 308 B |
69
resources/js/clients/invoices/action-selectors.js
vendored
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://opensource.org/licenses/AAL
|
||||||
|
*/
|
||||||
|
|
||||||
|
class ActionSelectors {
|
||||||
|
constructor() {
|
||||||
|
this.parentElement = document.querySelector(".form-check-parent");
|
||||||
|
this.parentForm = document.getElementById("bulkActions");
|
||||||
|
}
|
||||||
|
|
||||||
|
watchCheckboxes(parentElement) {
|
||||||
|
document.querySelectorAll(".form-check-child").forEach(child => {
|
||||||
|
if (parentElement.checked) {
|
||||||
|
child.checked = parentElement.checked;
|
||||||
|
this.processChildItem(child, document.getElementById("bulkActions"));
|
||||||
|
} else {
|
||||||
|
child.checked = false;
|
||||||
|
document
|
||||||
|
.querySelectorAll(".child-hidden-input")
|
||||||
|
.forEach(element => element.remove());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
processChildItem(element, parent, options = {}) {
|
||||||
|
if (options.hasOwnProperty("single")) {
|
||||||
|
document
|
||||||
|
.querySelectorAll(".child-hidden-input")
|
||||||
|
.forEach(element => element.remove());
|
||||||
|
}
|
||||||
|
|
||||||
|
let _temp = document.createElement("INPUT");
|
||||||
|
|
||||||
|
_temp.setAttribute("name", "invoices[]");
|
||||||
|
_temp.setAttribute("value", element.dataset.value);
|
||||||
|
_temp.setAttribute("class", "child-hidden-input");
|
||||||
|
_temp.hidden = true;
|
||||||
|
|
||||||
|
parent.append(_temp);
|
||||||
|
}
|
||||||
|
|
||||||
|
handle() {
|
||||||
|
this.parentElement.addEventListener("click", () => {
|
||||||
|
this.watchCheckboxes(this.parentElement);
|
||||||
|
});
|
||||||
|
|
||||||
|
for (let child of document.querySelectorAll(".pay-now-button")) {
|
||||||
|
child.addEventListener("click", () => {
|
||||||
|
this.processChildItem(child, this.parentForm, { single: true });
|
||||||
|
document.querySelector('button[value="payment"]').click();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let child of document.querySelectorAll(".form-check-child")) {
|
||||||
|
child.addEventListener("click", () => {
|
||||||
|
this.processChildItem(child, this.parentForm);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @handle **/
|
||||||
|
new ActionSelectors().handle();
|
91
resources/js/clients/invoices/payment.js
vendored
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://opensource.org/licenses/AAL
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Payment {
|
||||||
|
constructor(displayTerms, displaySignature) {
|
||||||
|
this.shouldDisplayTerms = displayTerms;
|
||||||
|
this.shouldDisplaySignature = displaySignature;
|
||||||
|
this.termsAccepted = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMethodSelect(element) {
|
||||||
|
|
||||||
|
if (this.shouldDisplaySignature && !this.shouldDisplayTerms) {
|
||||||
|
this.displayTerms();
|
||||||
|
|
||||||
|
document.getElementById('accept-terms-button').addEventListener('click', () => {
|
||||||
|
this.termsAccepted = true;
|
||||||
|
this.submitForm();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.shouldDisplaySignature && this.shouldDisplayTerms) {
|
||||||
|
this.displaySignature();
|
||||||
|
|
||||||
|
document.getElementById('signature-next-step').addEventListener('click', () => {
|
||||||
|
this.submitForm();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.shouldDisplaySignature && this.shouldDisplayTerms) {
|
||||||
|
this.displaySignature();
|
||||||
|
|
||||||
|
document.getElementById('signature-next-step').addEventListener('click', () => {
|
||||||
|
this.displayTerms();
|
||||||
|
|
||||||
|
document.getElementById('accept-terms-button').addEventListener('click', () => {
|
||||||
|
this.termsAccepted = true;
|
||||||
|
this.submitForm();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.shouldDisplaySignature && !this.shouldDisplayTerms) {
|
||||||
|
this.submitForm();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
submitForm() {
|
||||||
|
document.getElementById('payment-form').submit();
|
||||||
|
}
|
||||||
|
|
||||||
|
displayTerms() {
|
||||||
|
let displayTermsModal = document.getElementById('displayTermsModal');
|
||||||
|
displayTermsModal.removeAttribute('style');
|
||||||
|
}
|
||||||
|
|
||||||
|
displaySignature() {
|
||||||
|
let displaySignatureModal = document.getElementById('displaySignatureModal');
|
||||||
|
displaySignatureModal.removeAttribute('style');
|
||||||
|
|
||||||
|
const signaturePad = new SignaturePad(document.getElementById('signature-pad'), {
|
||||||
|
backgroundColor: 'rgb(240,240,240)',
|
||||||
|
penColor: 'rgb(0, 0, 0)'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handle() {
|
||||||
|
document.querySelectorAll('.dropdown-gateway-button').forEach((element) => {
|
||||||
|
element.addEventListener('click', () => this.handleMethodSelect(element));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const signature = document.querySelector(
|
||||||
|
'meta[name="require-invoice-signature"]'
|
||||||
|
).content;
|
||||||
|
|
||||||
|
const terms = document.querySelector(
|
||||||
|
'meta[name="show-invoice-terms"]'
|
||||||
|
).content;
|
||||||
|
|
||||||
|
new Payment(Boolean(+signature), Boolean(+terms)).handle();
|
||||||
|
|
||||||
|
|
90
resources/js/clients/payment_methods/authorize-stripe-card.js
vendored
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://opensource.org/licenses/AAL
|
||||||
|
*/
|
||||||
|
|
||||||
|
class AuthorizeStripeCard {
|
||||||
|
constructor(key) {
|
||||||
|
this.key = key;
|
||||||
|
this.cardHolderName = document.getElementById("cardholder-name");
|
||||||
|
this.cardButton = document.getElementById("card-button");
|
||||||
|
this.clientSecret = this.cardButton.dataset.secret;
|
||||||
|
}
|
||||||
|
|
||||||
|
setupStripe() {
|
||||||
|
this.stripe = Stripe(this.key);
|
||||||
|
this.elements = this.stripe.elements();
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
createElement() {
|
||||||
|
this.cardElement = this.elements.create("card");
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
mountCardElement() {
|
||||||
|
this.cardElement.mount("#card-element");
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleStripe(stripe, cardHolderName) {
|
||||||
|
stripe
|
||||||
|
.handleCardSetup(this.clientSecret, this.cardElement, {
|
||||||
|
payment_method_data: {
|
||||||
|
billing_details: { name: cardHolderName.value }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(result => {
|
||||||
|
if (result.error) {
|
||||||
|
return this.handleFailure(result);
|
||||||
|
}
|
||||||
|
return this.handleSuccess(result);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleFailure(result) {
|
||||||
|
let errors = document.getElementById("errors");
|
||||||
|
|
||||||
|
errors.textContent = "";
|
||||||
|
errors.textContent = result.error.message;
|
||||||
|
errors.hidden = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSuccess(result) {
|
||||||
|
document.getElementById("gateway_response").value = JSON.stringify(
|
||||||
|
result.setupIntent
|
||||||
|
);
|
||||||
|
document.getElementById("is_default").value = document.getElementById(
|
||||||
|
"proxy_is_default"
|
||||||
|
).checked;
|
||||||
|
|
||||||
|
document.getElementById("server_response").submit();
|
||||||
|
}
|
||||||
|
|
||||||
|
handle() {
|
||||||
|
this.setupStripe()
|
||||||
|
.createElement()
|
||||||
|
.mountCardElement();
|
||||||
|
|
||||||
|
this.cardButton.addEventListener("click", () => {
|
||||||
|
this.handleStripe(this.stripe, this.cardHolderName);
|
||||||
|
});
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const publishableKey = document.querySelector(
|
||||||
|
'meta[name="stripe-publishable-key"]'
|
||||||
|
).content;
|
||||||
|
|
||||||
|
/** @handle */
|
||||||
|
new AuthorizeStripeCard(publishableKey).handle();
|
62
resources/js/clients/quotes/action-selectors.js
vendored
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://opensource.org/licenses/AAL
|
||||||
|
*/
|
||||||
|
|
||||||
|
class ActionSelectors {
|
||||||
|
constructor() {
|
||||||
|
this.parentElement = document.querySelector(".form-check-parent");
|
||||||
|
this.parentForm = document.getElementById("bulkActions");
|
||||||
|
}
|
||||||
|
|
||||||
|
watchCheckboxes(parentElement) {
|
||||||
|
document.querySelectorAll(".form-check-child").forEach(child => {
|
||||||
|
if (parentElement.checked) {
|
||||||
|
child.checked = parentElement.checked;
|
||||||
|
this.processChildItem(child, document.getElementById("bulkActions"));
|
||||||
|
} else {
|
||||||
|
child.checked = false;
|
||||||
|
document
|
||||||
|
.querySelectorAll(".child-hidden-input")
|
||||||
|
.forEach(element => element.remove());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
processChildItem(element, parent, options = {}) {
|
||||||
|
if (options.hasOwnProperty("single")) {
|
||||||
|
document
|
||||||
|
.querySelectorAll(".child-hidden-input")
|
||||||
|
.forEach(element => element.remove());
|
||||||
|
}
|
||||||
|
|
||||||
|
let _temp = document.createElement("INPUT");
|
||||||
|
|
||||||
|
_temp.setAttribute("name", "quotes[]");
|
||||||
|
_temp.setAttribute("value", element.dataset.value);
|
||||||
|
_temp.setAttribute("class", "child-hidden-input");
|
||||||
|
_temp.hidden = true;
|
||||||
|
|
||||||
|
parent.append(_temp);
|
||||||
|
}
|
||||||
|
|
||||||
|
handle() {
|
||||||
|
this.parentElement.addEventListener("click", () => {
|
||||||
|
this.watchCheckboxes(this.parentElement);
|
||||||
|
});
|
||||||
|
|
||||||
|
for (let child of document.querySelectorAll(".form-check-child")) {
|
||||||
|
child.addEventListener("click", () => {
|
||||||
|
this.processChildItem(child, this.parentForm);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @handle **/
|
||||||
|
new ActionSelectors().handle();
|
51
resources/js/clients/quotes/approve.js
vendored
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://opensource.org/licenses/AAL
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Approve {
|
||||||
|
constructor(displaySignature) {
|
||||||
|
this.shouldDisplaySignature = displaySignature;
|
||||||
|
}
|
||||||
|
|
||||||
|
submitForm() {
|
||||||
|
document.getElementById('approve-form').submit();
|
||||||
|
}
|
||||||
|
|
||||||
|
displaySignature() {
|
||||||
|
let displaySignatureModal = document.getElementById('displaySignatureModal');
|
||||||
|
displaySignatureModal.removeAttribute('style');
|
||||||
|
|
||||||
|
const signaturePad = new SignaturePad(document.getElementById('signature-pad'), {
|
||||||
|
backgroundColor: 'rgb(240,240,240)',
|
||||||
|
penColor: 'rgb(0, 0, 0)'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handle() {
|
||||||
|
document.getElementById('approve-button').addEventListener('click', () => {
|
||||||
|
if (this.shouldDisplaySignature) {
|
||||||
|
this.displaySignature();
|
||||||
|
|
||||||
|
document.getElementById('signature-next-step').addEventListener('click', () => {
|
||||||
|
this.submitForm();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.shouldDisplaySignature) this.submitForm();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const signature = document.querySelector(
|
||||||
|
'meta[name="require-quote-signature"]'
|
||||||
|
).content;
|
||||||
|
|
||||||
|
new Approve(Boolean(+signature)).handle();
|
||||||
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
return [
|
return [
|
||||||
'continue' => 'Continue',
|
'continue' => 'Continue',
|
||||||
|
'back' => 'Back',
|
||||||
'complete' => 'Complete',
|
'complete' => 'Complete',
|
||||||
'next' => 'Next',
|
'next' => 'Next',
|
||||||
'next_step' => 'Next step',
|
'next_step' => 'Next step',
|
||||||
@ -504,6 +505,7 @@ return [
|
|||||||
'api_tokens' => 'API Tokens',
|
'api_tokens' => 'API Tokens',
|
||||||
'users_and_tokens' => 'Users & Tokens',
|
'users_and_tokens' => 'Users & Tokens',
|
||||||
'account_login' => 'Account Login',
|
'account_login' => 'Account Login',
|
||||||
|
'account_login_text' => 'Welcome back! Glad to see you.',
|
||||||
'recover_password' => 'Recover your password',
|
'recover_password' => 'Recover your password',
|
||||||
'forgot_password' => 'Forgot your password?',
|
'forgot_password' => 'Forgot your password?',
|
||||||
'email_address' => 'Email address',
|
'email_address' => 'Email address',
|
||||||
@ -711,6 +713,7 @@ return [
|
|||||||
'reminder_subject' => 'Reminder: Invoice :invoice from :account',
|
'reminder_subject' => 'Reminder: Invoice :invoice from :account',
|
||||||
'reset' => 'Reset',
|
'reset' => 'Reset',
|
||||||
'invoice_not_found' => 'The requested invoice is not available',
|
'invoice_not_found' => 'The requested invoice is not available',
|
||||||
|
'request_cancellation' => 'Request cancellation',
|
||||||
'referral_program' => 'Referral Program',
|
'referral_program' => 'Referral Program',
|
||||||
'referral_code' => 'Referral URL',
|
'referral_code' => 'Referral URL',
|
||||||
'last_sent_on' => 'Sent Last: :date',
|
'last_sent_on' => 'Sent Last: :date',
|
||||||
@ -2196,6 +2199,7 @@ return [
|
|||||||
'create_expense_category' => 'Create category',
|
'create_expense_category' => 'Create category',
|
||||||
'pro_plan_reports' => ':link to enable reports by joining the Pro Plan',
|
'pro_plan_reports' => ':link to enable reports by joining the Pro Plan',
|
||||||
'mark_ready' => 'Mark Ready',
|
'mark_ready' => 'Mark Ready',
|
||||||
|
'profile_updated_successfully' => 'The profile has been updated successfully.',
|
||||||
|
|
||||||
'limits' => 'Limits',
|
'limits' => 'Limits',
|
||||||
'fees' => 'Fees',
|
'fees' => 'Fees',
|
||||||
@ -2705,6 +2709,7 @@ return [
|
|||||||
'amount_greater_than_balance' => 'The amount is greater than the invoice balance, a credit will be created with the remaining amount.',
|
'amount_greater_than_balance' => 'The amount is greater than the invoice balance, a credit will be created with the remaining amount.',
|
||||||
'custom_fields_tip' => 'Use <code>Label|Option1,Option2</code> to show a select box.',
|
'custom_fields_tip' => 'Use <code>Label|Option1,Option2</code> to show a select box.',
|
||||||
'client_information' => 'Client Information',
|
'client_information' => 'Client Information',
|
||||||
|
'client_information_text' => 'Use a permanent address where you can receive mail.',
|
||||||
'updated_client_details' => 'Successfully updated client details',
|
'updated_client_details' => 'Successfully updated client details',
|
||||||
'auto' => 'Auto',
|
'auto' => 'Auto',
|
||||||
'tax_amount' => 'Tax Amount',
|
'tax_amount' => 'Tax Amount',
|
||||||
|
5
resources/sass/app.scss
vendored
@ -7,6 +7,11 @@
|
|||||||
@import 'components/validation';
|
@import 'components/validation';
|
||||||
@import 'components/inputs';
|
@import 'components/inputs';
|
||||||
@import 'components/alerts';
|
@import 'components/alerts';
|
||||||
|
@import 'components/badge';
|
||||||
|
|
||||||
|
.active-page {
|
||||||
|
@apply bg-blue-900 #{!important};
|
||||||
|
}
|
||||||
|
|
||||||
// ..
|
// ..
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
31
resources/sass/components/badge.scss
vendored
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
.badge {
|
||||||
|
@apply inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium leading-4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-light {
|
||||||
|
@apply bg-gray-100 text-gray-800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-primary {
|
||||||
|
@apply bg-blue-200 text-blue-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-danger {
|
||||||
|
@apply bg-red-100 text-red-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-success {
|
||||||
|
@apply bg-green-100 text-green-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-secondary {
|
||||||
|
@apply bg-gray-800 text-gray-200;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-warning {
|
||||||
|
@apply bg-orange-100 text-orange-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-info {
|
||||||
|
@apply bg-blue-100 text-blue-500;
|
||||||
|
}
|
30
resources/sass/components/buttons.scss
vendored
@ -1,5 +1,5 @@
|
|||||||
.button {
|
.button {
|
||||||
@apply rounded py-3 px-4;
|
@apply rounded py-3 px-4 text-sm leading-4 transition duration-150 ease-in-out font-semibold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-primary {
|
.button-primary {
|
||||||
@ -13,3 +13,31 @@
|
|||||||
.button-block {
|
.button-block {
|
||||||
@apply block w-full;
|
@apply block w-full;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.button-danger {
|
||||||
|
@apply bg-red-500 text-white;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
@apply bg-red-600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-secondary {
|
||||||
|
@apply bg-gray-100;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
@apply bg-gray-200;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-link {
|
||||||
|
@apply text-blue-600;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
@apply text-blue-700 underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
@apply outline-none underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
6
resources/sass/components/inputs.scss
vendored
@ -1,5 +1,5 @@
|
|||||||
.input {
|
.input {
|
||||||
@apply items-center border border-gray-300 rounded mt-2 w-full py-3 px-4;
|
@apply items-center border border-gray-300 rounded mt-2 w-full py-2 px-4 text-sm;
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
@apply outline-none border-blue-500;
|
@apply outline-none border-blue-500;
|
||||||
@ -9,3 +9,7 @@
|
|||||||
.input-label {
|
.input-label {
|
||||||
@apply text-sm text-gray-600;
|
@apply text-sm text-gray-600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.input-slim {
|
||||||
|
@apply py-2;
|
||||||
|
}
|
||||||
|
59
resources/views/portal/ninja2020/auth/login.blade.php
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
@extends('portal.ninja2020.layout.clean')
|
||||||
|
@section('meta_title', ctrans('texts.login'))
|
||||||
|
|
||||||
|
@section('body')
|
||||||
|
<div class="grid lg:grid-cols-3">
|
||||||
|
<div class="hidden lg:block col-span-1 bg-red-100 h-screen">
|
||||||
|
<img src="https://www.invoiceninja.com/wp-content/uploads/2018/04/bg-home2018b.jpg"
|
||||||
|
class="w-full h-screen object-cover"
|
||||||
|
alt="Background image">
|
||||||
|
</div>
|
||||||
|
<div class="col-span-2 h-screen flex">
|
||||||
|
<div class="m-auto md:w-1/2 lg:w-1/4">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<h1 class="text-center text-3xl">{{ ctrans('texts.account_login') }}</h1>
|
||||||
|
<p class="text-center mt-1 text-gray-600">{{ ctrans('texts.account_login_text') }}</p>
|
||||||
|
<form action="{{ route('client.login') }}" method="post" class="mt-6">
|
||||||
|
@csrf
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<label for="email" class="input-label">{{ ctrans('texts.email_address') }}</label>
|
||||||
|
<input type="email" name="email" id="email"
|
||||||
|
class="input"
|
||||||
|
value="{{ old('email') }}"
|
||||||
|
autofocus>
|
||||||
|
@error('email')
|
||||||
|
<div class="validation validation-fail">
|
||||||
|
{{ $message }}
|
||||||
|
</div>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col mt-4">
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
<label for="password" class="input-label">{{ ctrans('texts.password') }}</label>
|
||||||
|
<a class="text-xs text-gray-600 hover:text-gray-800 ease-in duration-100"
|
||||||
|
href="{{ route('client.password.request') }}">{{ trans('texts.forgot_password') }}</a>
|
||||||
|
</div>
|
||||||
|
<input type="password" name="password" id="password"
|
||||||
|
class="input"
|
||||||
|
autofocus>
|
||||||
|
@error('password')
|
||||||
|
<div class="validation validation-fail">
|
||||||
|
{{ $message }}
|
||||||
|
</div>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
<div class="mt-5">
|
||||||
|
<button class="button button-primary button-block">
|
||||||
|
{{ trans('texts.login') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<a href="#" class="uppercase text-sm mt-4 text-center text-grey-600 hover:text-blue-600 ease-in duration-100">
|
||||||
|
{{ trans('texts.login_create_an_account') }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
@ -0,0 +1,44 @@
|
|||||||
|
@extends('portal.ninja2020.layout.clean')
|
||||||
|
@section('meta_title', $title)
|
||||||
|
|
||||||
|
@section('body')
|
||||||
|
<div class="grid lg:grid-cols-3">
|
||||||
|
<div class="hidden lg:block col-span-1 bg-red-100 h-screen">
|
||||||
|
<img src="https://www.invoiceninja.com/wp-content/uploads/2018/04/bg-home2018b.jpg"
|
||||||
|
class="w-full h-screen object-cover"
|
||||||
|
alt="Background image">
|
||||||
|
</div>
|
||||||
|
<div class="col-span-2 h-screen flex">
|
||||||
|
<div class="m-auto w-1/2 md:w-1/3 lg:w-1/4">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<h1 class="text-center text-3xl">{{ ctrans('texts.password_recovery') }}</h1>
|
||||||
|
<p class="text-center mt-1 text-gray-600">{{ ctrans('texts.reset_password_text') }}</p>
|
||||||
|
@if(session('status'))
|
||||||
|
<div class="alert alert-success mt-4">
|
||||||
|
{{ session('status') }}
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
<form action="{{ route($passwordEmailRoute) }}" method="post" class="mt-6">
|
||||||
|
@csrf
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<label for="email" class="input-label">{{ ctrans('texts.email_address') }}</label>
|
||||||
|
<input type="email" name="email" id="email"
|
||||||
|
class="input"
|
||||||
|
value="{{ old('email') }}"
|
||||||
|
autofocus>
|
||||||
|
@error('email')
|
||||||
|
<div class="validation validation-fail">
|
||||||
|
{{ $message }}
|
||||||
|
</div>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
<div class="mt-5">
|
||||||
|
<button class="button button-primary button-block">{{ ctrans('texts.next_step') }}</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
@ -0,0 +1,67 @@
|
|||||||
|
@extends('portal.ninja2020.layout.clean')
|
||||||
|
@section('meta_title', ctrans('texts.password_recovery'))
|
||||||
|
|
||||||
|
@section('body')
|
||||||
|
<div class="grid lg:grid-cols-3">
|
||||||
|
<div class="hidden lg:block col-span-1 bg-red-100 h-screen">
|
||||||
|
<img src="https://www.invoiceninja.com/wp-content/uploads/2018/04/bg-home2018b.jpg"
|
||||||
|
class="w-full h-screen object-cover"
|
||||||
|
alt="Background image">
|
||||||
|
</div>
|
||||||
|
<div class="col-span-2 h-screen flex">
|
||||||
|
<div class="m-auto w-1/2 md:w-1/3 lg:w-1/4">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<h1 class="text-center text-3xl">{{ ctrans('texts.password_recovery') }}</h1>
|
||||||
|
<p class="text-center mt-1 text-gray-600">{{ ctrans('texts.reset_password_text') }}</p>
|
||||||
|
@if(session('status'))
|
||||||
|
<div class="alert alert-success mt-4">
|
||||||
|
{{ session('status') }}
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
<form action="{{ route('client.password.update') }}" method="post" class="mt-6">
|
||||||
|
@csrf
|
||||||
|
<input type="hidden" name="token" value="{{ $token }}">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<label for="email" class="input-label">{{ ctrans('texts.email_address') }}</label>
|
||||||
|
<input type="email" name="email" id="email"
|
||||||
|
class="input"
|
||||||
|
value="{{ $email ?? old('email') }}"
|
||||||
|
autofocus>
|
||||||
|
@error('email')
|
||||||
|
<div class="validation validation-fail">
|
||||||
|
{{ $message }}
|
||||||
|
</div>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col mt-4">
|
||||||
|
<label for="password" class="input-label">{{ ctrans('texts.password') }}</label>
|
||||||
|
<input type="password" name="password" id="password"
|
||||||
|
class="input"
|
||||||
|
autofocus>
|
||||||
|
@error('password')
|
||||||
|
<div class="validation validation-fail">
|
||||||
|
{{ $message }}
|
||||||
|
</div>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col mt-4">
|
||||||
|
<label for="password" class="input-label">{{ ctrans('texts.password') }}</label>
|
||||||
|
<input type="password" name="password_confirmation" id="password_confirmation"
|
||||||
|
class="input"
|
||||||
|
autofocus>
|
||||||
|
@error('password_confirmation')
|
||||||
|
<div class="validation validation-fail">
|
||||||
|
{{ $message }}
|
||||||
|
</div>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
<div class="mt-5">
|
||||||
|
<button class="button button-primary button-block">{{ ctrans('texts.complete') }}</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
@ -0,0 +1,33 @@
|
|||||||
|
<nav class="sm:hidden mb-4">
|
||||||
|
<a href="{{ url()->previous() }}"
|
||||||
|
class="flex items-center text-sm leading-5 font-medium text-gray-500 hover:text-gray-700 focus:outline-none focus:underline transition duration-150 ease-in-out">
|
||||||
|
<svg class="flex-shrink-0 -ml-1 mr-1 h-5 w-5 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fill-rule="evenodd"
|
||||||
|
d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z"
|
||||||
|
clip-rule="evenodd"/>
|
||||||
|
</svg>
|
||||||
|
{{ ctrans('texts.back') }}
|
||||||
|
</a>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
@if (count($breadcrumbs))
|
||||||
|
|
||||||
|
<nav class="hidden sm:flex items-center text-sm leading-5 font-medium mb-4">
|
||||||
|
@foreach ($breadcrumbs as $breadcrumb)
|
||||||
|
|
||||||
|
@if ($breadcrumb->url && !$loop->last)
|
||||||
|
<a href="{{ $breadcrumb->url }}"
|
||||||
|
class="text-gray-500 hover:text-gray-700 focus:outline-none focus:underline transition duration-150 ease-in-out">{{ $breadcrumb->title }}</a>
|
||||||
|
<svg class="flex-shrink-0 mx-2 h-5 w-5 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fill-rule="evenodd"
|
||||||
|
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
|
||||||
|
clip-rule="evenodd"/>
|
||||||
|
</svg>
|
||||||
|
@else
|
||||||
|
<a class="text-gray-500 hover:text-gray-700 focus:outline-none focus:underline transition duration-150 ease-in-out">{{ $breadcrumb->title }}</a>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@endforeach
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
@endif
|
@ -0,0 +1,4 @@
|
|||||||
|
<div class="validation validation-pass mb-4">
|
||||||
|
{{ session('success') }}
|
||||||
|
</div>
|
||||||
|
|
@ -0,0 +1,23 @@
|
|||||||
|
<div class="hidden md:flex md:flex-shrink-0">
|
||||||
|
<div class="flex flex-col w-64">
|
||||||
|
<div class="flex items-center h-16 flex-shrink-0 px-4 bg-blue-900">
|
||||||
|
<a href="{{ route('client.dashboard') }}">
|
||||||
|
<img class="h-6 w-auto"
|
||||||
|
src="{!! $settings->company_logo ?: 'https://www.invoiceninja.com/wp-content/themes/invoice-ninja/images/logo.png' !!}"
|
||||||
|
alt="{{ config('app.name') }}"/>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="h-0 flex-1 flex flex-col overflow-y-auto">
|
||||||
|
<nav class="flex-1 py-4 bg-blue-800">
|
||||||
|
@foreach($sidebar as $row)
|
||||||
|
<a class="group flex items-center p-4 text-sm leading-5 font-medium text-white bg-blue-800 hover:bg-blue-900 focus:outline-none focus:bg-blue-900 transition ease-in-out duration-150 {{ isActive($row['url']) }}"
|
||||||
|
href="{{ route($row['url']) }}">
|
||||||
|
<img src="{{ asset('images/svg/' . $row['icon'] . '.svg') }}" class="w-5 h-5 fill-current text-white mr-3" alt=""/>
|
||||||
|
{{ $row['title'] }}
|
||||||
|
</a>
|
||||||
|
@endforeach
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
@ -0,0 +1,65 @@
|
|||||||
|
<div class="relative z-10 flex-shrink-0 flex h-16 bg-white shadow" xmlns:x-transition="http://www.w3.org/1999/xhtml">
|
||||||
|
<button @click.stop="sidebarOpen = true"
|
||||||
|
class="px-4 border-r border-gray-200 text-gray-500 focus:outline-none focus:bg-gray-100 focus:text-gray-600 md:hidden">
|
||||||
|
<svg class="h-6 w-6" stroke="currentColor" fill="none" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h7"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<div class="flex-1 px-4 flex justify-between">
|
||||||
|
<div class="flex-1 flex">
|
||||||
|
<div class="w-full flex md:ml-0">
|
||||||
|
<label for="search_field" class="sr-only">{{ ctrans('texts.search') }}</label>
|
||||||
|
<div class="relative w-full text-gray-400 focus-within:text-gray-600">
|
||||||
|
<div class="absolute inset-y-0 left-0 flex items-center pointer-events-none">
|
||||||
|
<svg class="h-5 w-5" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||||
|
d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<input id="search_field"
|
||||||
|
class="block w-full h-full pl-8 pr-3 py-2 rounded-md text-gray-900 placeholder-gray-500 focus:outline-none focus:placeholder-gray-400 sm:text-sm"
|
||||||
|
placeholder="Search"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="ml-4 flex items-center md:ml-6">
|
||||||
|
<button
|
||||||
|
class="p-1 text-gray-400 rounded-full hover:bg-gray-100 hover:text-gray-500 focus:outline-none focus:shadow-outline focus:text-gray-500">
|
||||||
|
<svg class="h-6 w-6" stroke="currentColor" fill="none" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<div @click.away="open = false" class="ml-3 relative" x-data="{ open: false }">
|
||||||
|
<div>
|
||||||
|
<button @click="open = !open"
|
||||||
|
class="max-w-xs flex items-center text-sm rounded-full focus:outline-none focus:shadow-outline">
|
||||||
|
<img class="h-8 w-8 rounded-full"
|
||||||
|
src="{{ auth()->user()->avatar() }}"
|
||||||
|
alt=""/>
|
||||||
|
<span class="ml-2">{{ auth()->user()->present()->name() }}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div x-show="open"
|
||||||
|
x-transition:enter="transition ease-out duration-100"
|
||||||
|
x-transition:enter-start="transform opacity-0 scale-95"
|
||||||
|
x-transition:enter-end="transform opacity-100 scale-100"
|
||||||
|
x-transition:leave="transition ease-in duration-75"
|
||||||
|
x-transition:leave-start="transform opacity-100 scale-100"
|
||||||
|
x-transition:leave-end="transform opacity-0 scale-95"
|
||||||
|
class="origin-top-right absolute right-0 mt-2 w-48 rounded-md shadow-lg">
|
||||||
|
<div class="py-1 rounded-md bg-white shadow-xs">
|
||||||
|
<a href="{{ route('client.profile.edit', ['client_contact' => auth()->user()->hashed_id]) }}"
|
||||||
|
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 transition ease-in-out duration-150">
|
||||||
|
{{ ctrans('texts.profile') }}
|
||||||
|
</a>
|
||||||
|
<a href="{{ route('client.logout') }}"
|
||||||
|
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 transition ease-in-out duration-150">
|
||||||
|
{{ ctrans('texts.logout') }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -0,0 +1,31 @@
|
|||||||
|
<div
|
||||||
|
class="h-screen flex overflow-hidden bg-gray-100"
|
||||||
|
x-data="{ sidebarOpen: false }"
|
||||||
|
@keydown.window.escape="sidebarOpen = false">
|
||||||
|
|
||||||
|
<!-- Off-canvas menu for mobile -->
|
||||||
|
@include('portal.ninja2020.components.general.sidebar.mobile')
|
||||||
|
|
||||||
|
<!-- Static sidebar for desktop -->
|
||||||
|
@include('portal.ninja2020.components.general.sidebar.desktop')
|
||||||
|
|
||||||
|
<div class="flex flex-col w-0 flex-1 overflow-hidden">
|
||||||
|
@include('portal.ninja2020.components.general.sidebar.header')
|
||||||
|
<main
|
||||||
|
class="flex-1 relative z-0 overflow-y-auto py-6 focus:outline-none"
|
||||||
|
tabindex="0" x-data
|
||||||
|
x-init="$el.focus()">
|
||||||
|
|
||||||
|
<div class="mx-auto px-4 sm:px-6 md:px-8">
|
||||||
|
@yield('header')
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mx-auto px-4 sm:px-6 md:px-8">
|
||||||
|
<div class="py-4">
|
||||||
|
@includeWhen(session()->has('success'), 'portal.ninja2020.components.general.messages.success')
|
||||||
|
{{ $slot }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -0,0 +1,34 @@
|
|||||||
|
<div class="md:hidden">
|
||||||
|
<div @click="sidebarOpen = false"
|
||||||
|
class="fixed inset-0 z-30 bg-gray-600 opacity-0 pointer-events-none transition-opacity ease-linear duration-300"
|
||||||
|
:class="{'opacity-75 pointer-events-auto': sidebarOpen, 'opacity-0 pointer-events-none': !sidebarOpen}"></div>
|
||||||
|
<div
|
||||||
|
class="fixed inset-y-0 left-0 flex flex-col z-40 max-w-xs w-full pt-5 pb-4 bg-blue-800 transform ease-in-out duration-300 -translate-x-full"
|
||||||
|
:class="{'translate-x-0': sidebarOpen, '-translate-x-full': !sidebarOpen}">
|
||||||
|
<div class="absolute top-0 right-0 -mr-14 p-1">
|
||||||
|
<button x-show="sidebarOpen" @click="sidebarOpen = false"
|
||||||
|
class="flex items-center justify-center h-12 w-12 rounded-full focus:outline-none focus:bg-gray-600">
|
||||||
|
<svg class="h-6 w-6 text-white" stroke="currentColor" fill="none" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="flex-shrink-0 flex items-center px-4">
|
||||||
|
<img class="h-6 w-auto"
|
||||||
|
src="{!! $settings->company_logo ?: 'https://www.invoiceninja.com/wp-content/themes/invoice-ninja/images/logo.png' !!}"
|
||||||
|
alt="{{ config('app.name') }}"/>
|
||||||
|
</div>
|
||||||
|
<div class="mt-5 flex-1 h-0 overflow-y-auto">
|
||||||
|
<nav class="flex-1 py-4 bg-blue-800">
|
||||||
|
@foreach($sidebar as $row)
|
||||||
|
<a class="group flex items-center p-4 text-sm leading-5 font-medium text-white bg-blue-800 hover:bg-blue-900 focus:outline-none focus:bg-blue-900 transition ease-in-out duration-150 {{ isActive($row['url']) }}"
|
||||||
|
href="{{ route($row['url']) }}">
|
||||||
|
<img src="{{ asset('images/svg/' . $row['icon'] . '.svg') }}"
|
||||||
|
class="w-5 h-5 fill-current text-white mr-3" alt=""/>
|
||||||
|
{{ $row['title'] }}
|
||||||
|
</a>
|
||||||
|
@endforeach
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
87
resources/views/portal/ninja2020/credits/index.blade.php
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
@extends('portal.ninja2020.layout.app')
|
||||||
|
@section('meta_title', ctrans('texts.credits'))
|
||||||
|
|
||||||
|
@section('header')
|
||||||
|
{{ Breadcrumbs::render('credits') }}
|
||||||
|
|
||||||
|
@if($errors->any())
|
||||||
|
<div class="alert alert-failure mb-4">
|
||||||
|
@foreach($errors->all() as $error)
|
||||||
|
<p>{{ $error }}</p>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<div class="bg-white shadow rounded mb-4" translate>
|
||||||
|
<div class="px-4 py-5 sm:p-6">
|
||||||
|
<div class="sm:flex sm:items-start sm:justify-between">
|
||||||
|
<div>
|
||||||
|
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||||
|
{{ ctrans('texts.credits') }}
|
||||||
|
</h3>
|
||||||
|
<div class="mt-2 max-w-xl text-sm leading-5 text-gray-500">
|
||||||
|
<p translate>
|
||||||
|
{{ ctrans('texts.list_of_credits') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('body')
|
||||||
|
<div class="flex flex-col mt-4">
|
||||||
|
<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 shadow overflow-hidden rounded border-b border-gray-200">
|
||||||
|
<table class="min-w-full">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
|
||||||
|
{{ ctrans('texts.amount') }}
|
||||||
|
</th>
|
||||||
|
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
|
||||||
|
{{ ctrans('texts.balance') }}
|
||||||
|
</th>
|
||||||
|
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
|
||||||
|
{{ ctrans('texts.credit_date') }}
|
||||||
|
</th>
|
||||||
|
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
|
||||||
|
{{ ctrans('texts.public_notes') }}
|
||||||
|
</th>
|
||||||
|
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach($credits as $credit)
|
||||||
|
<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">
|
||||||
|
{{ App\Utils\Number::formatMoney($credit->amount, $credit->client) }}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
|
||||||
|
{{ App\Utils\Number::formatMoney($credit->balance, $credit->client) }}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
|
||||||
|
{{ $credit->formatDate($credit->date, $credit->client->date_format()) }}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
|
||||||
|
{{ empty($credit->public_notes) ? '/' : $credit->public_notes }}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
|
||||||
|
<a href="{{ route('client.credits.show', $credit->hashed_id) }}"
|
||||||
|
class="button-link">
|
||||||
|
@lang('texts.view')
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="my-6">
|
||||||
|
{{ $credits->links('portal.ninja2020.vendor.pagination') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
57
resources/views/portal/ninja2020/credits/show.blade.php
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
@extends('portal.ninja2020.layout.app')
|
||||||
|
@section('meta_title', ctrans('texts.credit'))
|
||||||
|
|
||||||
|
@section('header')
|
||||||
|
{{ Breadcrumbs::render('credits.show', $credit) }}
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('body')
|
||||||
|
<div class="container mx-auto">
|
||||||
|
<div class="bg-white shadow overflow-hidden sm:rounded-lg">
|
||||||
|
<div class="px-4 py-5 border-b border-gray-200 sm:px-6">
|
||||||
|
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||||
|
{{ ctrans('texts.credit') }}
|
||||||
|
</h3>
|
||||||
|
<p class="mt-1 max-w-2xl text-sm leading-5 text-gray-500" translate>
|
||||||
|
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<dl>
|
||||||
|
<div class="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
||||||
|
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||||
|
{{ ctrans('texts.amount') }}
|
||||||
|
</dt>
|
||||||
|
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||||
|
{{ App\Utils\Number::formatMoney($credit->amount, $credit->client) }}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
<div class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
||||||
|
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||||
|
{{ ctrans('texts.balance') }}
|
||||||
|
</dt>
|
||||||
|
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||||
|
{{ App\Utils\Number::formatMoney($credit->balance, $credit->client) }}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
<div class="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
||||||
|
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||||
|
{{ ctrans('texts.credit_date') }}
|
||||||
|
</dt>
|
||||||
|
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||||
|
{{ $credit->formatDate($credit->date, $credit->client->date_format()) }}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
<div class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
||||||
|
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||||
|
{{ ctrans('texts.public_notes') }}
|
||||||
|
</dt>
|
||||||
|
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||||
|
{{ $credit->public_notes }}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
@ -1,7 +1,29 @@
|
|||||||
@extends('portal.ninja2020.layout.clean')
|
@extends('portal.ninja2020.layout.app')
|
||||||
|
@section('meta_title', ctrans('texts.dashboard'))
|
||||||
|
|
||||||
@section('body')
|
@section('header')
|
||||||
<div class="m-4">
|
{{ Breadcrumbs::render('dashboard') }}
|
||||||
<button class="button">Hello world</button>
|
|
||||||
|
<div class="bg-white shadow rounded mb-4" translate>
|
||||||
|
<div class="px-4 py-5 sm:p-6">
|
||||||
|
<div class="sm:flex sm:items-start sm:justify-between">
|
||||||
|
<div>
|
||||||
|
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||||
|
{{ ctrans('texts.dashboard') }}
|
||||||
|
</h3>
|
||||||
|
<div class="mt-2 max-w-xl text-sm leading-5 text-gray-500">
|
||||||
|
<p translate>
|
||||||
|
{{ ctrans('texts.quick_overview_statistics') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@endsection
|
@endsection
|
||||||
|
|
||||||
|
@section('body')
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Amet esse magnam nam numquam omnis optio, pariatur
|
||||||
|
perferendis quae quaerat quam, quas quos repellat sapiente sit soluta, tenetur totam ut vel veritatis voluptatibus?
|
||||||
|
Aut, dolor illo? Asperiores eum eveniet quae sed?
|
||||||
|
@endsection
|
||||||
|
@ -0,0 +1,79 @@
|
|||||||
|
@extends('portal.ninja2020.layout.app')
|
||||||
|
@section('meta_title', ctrans('texts.add_credit_card'))
|
||||||
|
|
||||||
|
@push('head')
|
||||||
|
<meta name="stripe-publishable-key" content="{{ $gateway->getPublishableKey() }}">
|
||||||
|
@endpush
|
||||||
|
|
||||||
|
@section('header')
|
||||||
|
{{ Breadcrumbs::render('payment_methods.add_credit_card') }}
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('body')
|
||||||
|
<form action="{{ route('client.payment_methods.store') }}" method="post" id="server_response">
|
||||||
|
@csrf
|
||||||
|
<input type="hidden" name="company_gateway_id" value="{{ $gateway->gateway_id }}">
|
||||||
|
<input type="hidden" name="gateway_type_id" value="1">
|
||||||
|
<input type="hidden" name="gateway_response" id="gateway_response">
|
||||||
|
<input type="hidden" name="is_default" id="is_default">
|
||||||
|
</form>
|
||||||
|
<div class="container mx-auto">
|
||||||
|
<div class="grid grid-cols-6 gap-4">
|
||||||
|
<div class="col-span-6 md:col-start-2 md:col-span-4">
|
||||||
|
<div class="alert alert-failure mb-4" hidden id="errors"></div>
|
||||||
|
<div class="bg-white shadow overflow-hidden sm:rounded-lg">
|
||||||
|
<div class="px-4 py-5 border-b border-gray-200 sm:px-6">
|
||||||
|
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||||
|
{{ ctrans('texts.add_credit_card') }}
|
||||||
|
</h3>
|
||||||
|
<p class="mt-1 max-w-2xl text-sm leading-5 text-gray-500" translate>
|
||||||
|
{{ ctrans('texts.authorize_for_future_use') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<dl>
|
||||||
|
<div class="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 flex items-center">
|
||||||
|
<dt class="text-sm leading-5 font-medium text-gray-500 mr-4">
|
||||||
|
{{ ctrans('texts.name') }}
|
||||||
|
</dt>
|
||||||
|
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||||
|
<input class="input" id="cardholder-name" type="text" placeholder="{{ ctrans('texts.name') }}">
|
||||||
|
</dd>
|
||||||
|
</div><div class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
||||||
|
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||||
|
{{ ctrans('texts.credit_card') }}
|
||||||
|
</dt>
|
||||||
|
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||||
|
<div id="card-element" class="form-control"></div>
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
<div class="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
||||||
|
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||||
|
{{ ctrans('texts.save_as_default') }}
|
||||||
|
</dt>
|
||||||
|
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||||
|
<input type="checkbox" class="form-check" name="proxy_is_default"
|
||||||
|
id="proxy_is_default"/>
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
<div class="bg-white px-4 py-5 flex justify-end">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
id="card-button"
|
||||||
|
data-secret="{{ $intent->client_secret }}"
|
||||||
|
class="button button-primary">
|
||||||
|
{{ ctrans('texts.save') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@push('footer')
|
||||||
|
<script src="https://js.stripe.com/v3/"></script>
|
||||||
|
<script src="{{ asset('js/clients/payment_methods/authorize-stripe-card.js') }}"></script>
|
||||||
|
@endpush
|
@ -0,0 +1,44 @@
|
|||||||
|
<div style="display: none;" id="displaySignatureModal"
|
||||||
|
class="fixed bottom-0 inset-x-0 px-4 pb-4 sm:inset-0 sm:flex sm:items-center sm:justify-center">
|
||||||
|
<div x-show="open" x-transition:enter="ease-out duration-300" x-transition:enter-start="opacity-0"
|
||||||
|
x-transition:enter-end="opacity-100" x-transition:leave="ease-in duration-200"
|
||||||
|
x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0"
|
||||||
|
class="fixed inset-0 transition-opacity">
|
||||||
|
<div class="absolute inset-0 bg-gray-500 opacity-75"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div x-show="open" x-transition:enter="ease-out duration-300"
|
||||||
|
x-transition:enter-start="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||||
|
x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100" x-transition:leave="ease-in duration-200"
|
||||||
|
x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100"
|
||||||
|
x-transition:leave-end="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||||
|
class="bg-white rounded-lg px-4 pt-5 pb-4 overflow-hidden shadow-xl transform transition-all sm:max-w-lg sm:w-full sm:p-6">
|
||||||
|
<div class="sm:flex sm:items-start">
|
||||||
|
<div
|
||||||
|
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
|
||||||
|
<svg class="h-6 w-6 text-red-600" stroke="currentColor" fill="none" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
|
||||||
|
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||||
|
{{ ctrans('texts.sign_here') }}
|
||||||
|
</h3>
|
||||||
|
<div class="mt-2">
|
||||||
|
<p class="text-sm leading-5 text-gray-500">
|
||||||
|
<canvas id="signature-pad" class="signature-pad" width=400 height=200></canvas>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
|
||||||
|
<div class="flex w-full rounded-md shadow-sm sm:ml-3 sm:w-auto">
|
||||||
|
<button type="button" class="button button-primary" id="signature-next-step"
|
||||||
|
@click="document.getElementById('displaySignatureModal').style.display = 'none';">
|
||||||
|
{{ ctrans('texts.next_step') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -0,0 +1,49 @@
|
|||||||
|
<div style="display: none;" id="displayTermsModal"
|
||||||
|
class="fixed bottom-0 inset-x-0 px-4 pb-4 sm:inset-0 sm:flex sm:items-center sm:justify-center">
|
||||||
|
<div x-show="open" x-transition:enter="ease-out duration-300" x-transition:enter-start="opacity-0"
|
||||||
|
x-transition:enter-end="opacity-100" x-transition:leave="ease-in duration-200"
|
||||||
|
x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0"
|
||||||
|
class="fixed inset-0 transition-opacity">
|
||||||
|
<div class="absolute inset-0 bg-gray-500 opacity-75"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div x-show="open" x-transition:enter="ease-out duration-300"
|
||||||
|
x-transition:enter-start="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||||
|
x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100" x-transition:leave="ease-in duration-200"
|
||||||
|
x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100"
|
||||||
|
x-transition:leave-end="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||||
|
class="bg-white rounded-lg px-4 pt-5 pb-4 overflow-hidden shadow-xl transform transition-all sm:max-w-lg sm:w-full sm:p-6">
|
||||||
|
<div class="sm:flex sm:items-start">
|
||||||
|
<div
|
||||||
|
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
|
||||||
|
<svg class="h-6 w-6 text-red-600" stroke="currentColor" fill="none" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
|
||||||
|
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||||
|
{{ ctrans('texts.terms') }}
|
||||||
|
</h3>
|
||||||
|
<div class="mt-2">
|
||||||
|
<p class="text-sm leading-5 text-gray-500">
|
||||||
|
{!! $invoice->terms !!}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
|
||||||
|
<div class="flex w-full rounded-md shadow-sm sm:ml-3 sm:w-auto">
|
||||||
|
<button type="button" id="accept-terms-button" class="button button-primary">
|
||||||
|
{{ ctrans('texts.agree_to_terms', ['terms' => trans('texts.invoice_terms')]) }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="mt-3 flex w-full rounded-md shadow-sm sm:mt-0 sm:w-auto">
|
||||||
|
<button @click="document.getElementById('displayTermsModal').style.display = 'none';" type="button"
|
||||||
|
class="button button-secondary">
|
||||||
|
{{ ctrans('texts.close') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
125
resources/views/portal/ninja2020/invoices/index.blade.php
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
@extends('portal.ninja2020.layout.app')
|
||||||
|
@section('meta_title', ctrans('texts.invoices'))
|
||||||
|
|
||||||
|
@section('header')
|
||||||
|
{{ Breadcrumbs::render('invoices') }}
|
||||||
|
|
||||||
|
@if($errors->any())
|
||||||
|
<div class="alert alert-failure mb-4">
|
||||||
|
@foreach($errors->all() as $error)
|
||||||
|
<p>{{ $error }}</p>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<div class="bg-white shadow rounded mb-4" translate>
|
||||||
|
<div class="px-4 py-5 sm:p-6">
|
||||||
|
<div class="sm:flex sm:items-start sm:justify-between">
|
||||||
|
<div>
|
||||||
|
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||||
|
{{ ctrans('texts.invoices') }}
|
||||||
|
</h3>
|
||||||
|
<div class="mt-2 max-w-xl text-sm leading-5 text-gray-500">
|
||||||
|
<p translate>
|
||||||
|
{{ ctrans('texts.list_of_invoices') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('body')
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
<span>{{ ctrans('texts.with_selected') }}</span>
|
||||||
|
<form action="{{ route('client.invoices.bulk') }}" method="post" id="bulkActions">
|
||||||
|
@csrf
|
||||||
|
<button type="submit" class="button button-primary" name="action"
|
||||||
|
value="download">{{ ctrans('texts.download') }}</button>
|
||||||
|
<button type="submit" class="button button-primary" name="action"
|
||||||
|
value="payment">{{ ctrans('texts.pay_now') }}</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col mt-4">
|
||||||
|
<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 shadow overflow-hidden rounded border-b border-gray-200">
|
||||||
|
<table class="min-w-full">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" class="form-check form-check-parent">
|
||||||
|
</label>
|
||||||
|
</th>
|
||||||
|
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
|
||||||
|
{{ ctrans('texts.invoice_number') }}
|
||||||
|
</th>
|
||||||
|
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
|
||||||
|
{{ ctrans('texts.invoice_date') }}
|
||||||
|
</th>
|
||||||
|
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
|
||||||
|
{{ ctrans('texts.balance') }}
|
||||||
|
</th>
|
||||||
|
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
|
||||||
|
{{ ctrans('texts.due_date') }}
|
||||||
|
</th>
|
||||||
|
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
|
||||||
|
{{ ctrans('texts.status') }}
|
||||||
|
</th>
|
||||||
|
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach($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 font-medium text-gray-900">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" class="form-check form-check-child"
|
||||||
|
data-value="{{ $invoice->hashed_id }}">
|
||||||
|
</label>
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
|
||||||
|
{{ $invoice->number }}
|
||||||
|
</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>
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
|
||||||
|
{{ App\Utils\Number::formatMoney($invoice->balance, $invoice->client) }}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
|
||||||
|
{{ $invoice->formatDate($invoice->due_date, $invoice->client->date_format()) }}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
|
||||||
|
{!! App\Models\Invoice::badgeForStatus($invoice->status) !!}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap flex items-center justify-end text-sm leading-5 font-medium">
|
||||||
|
@if($invoice->isPayable())
|
||||||
|
<button
|
||||||
|
class="button button-primary py-1 px-2 text-xs uppercase mr-3 pay-now-button"
|
||||||
|
data-value="{{ $invoice->hashed_id }}">
|
||||||
|
@lang('texts.pay_now')
|
||||||
|
</button>
|
||||||
|
@endif
|
||||||
|
<a href="{{ route('client.invoice.show', $invoice->hashed_id) }}"
|
||||||
|
class="button-link">
|
||||||
|
@lang('texts.view')
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="my-6">
|
||||||
|
{{ $invoices->links('portal.ninja2020.vendor.pagination') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@push('footer')
|
||||||
|
<script src="{{ asset('js/clients/invoices/action-selectors.js') }}"></script>
|
||||||
|
@endpush
|
113
resources/views/portal/ninja2020/invoices/payment.blade.php
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
@extends('portal.ninja2020.layout.app')
|
||||||
|
@section('meta_title', ctrans('texts.pay_now'))
|
||||||
|
|
||||||
|
@push('head')
|
||||||
|
<meta name="show-invoice-terms" content="{{ $settings->show_accept_invoice_terms ? true : false }}">
|
||||||
|
<meta name="require-invoice-signature" content="{{ $settings->require_invoice_signature ? true : false }}">
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/signature_pad@2.3.2/dist/signature_pad.min.js"></script>
|
||||||
|
@endpush
|
||||||
|
|
||||||
|
@section('body')
|
||||||
|
<form action="{{ route('client.payments.process') }}" method="post" id="payment-form">
|
||||||
|
@csrf
|
||||||
|
<input type="hidden" name="hashed_ids" value="{{ $hashed_ids }}" id="hashed_ids">
|
||||||
|
<input type="hidden" name="company_gateway_id" id="company_gateway_id">
|
||||||
|
<input type="hidden" name="payment_method_id" id="payment_method_id">
|
||||||
|
</form>
|
||||||
|
<div class="container mx-auto">
|
||||||
|
<div class="grid grid-cols-6 gap-4">
|
||||||
|
<div class="col-span-6 md:col-start-2 md:col-span-4">
|
||||||
|
<div class="flex justify-end">
|
||||||
|
<div class="flex justify-end mb-2">
|
||||||
|
<div x-data="{ open: false }" @keydown.window.escape="open = false" @click.away="open = false"
|
||||||
|
class="relative inline-block text-left">
|
||||||
|
<div>
|
||||||
|
<div class="rounded-md shadow-sm">
|
||||||
|
<button @click="open = !open" type="button"
|
||||||
|
class="inline-flex justify-center w-full rounded-md border border-gray-300 px-4 py-2 bg-white text-sm leading-5 font-medium text-gray-700 hover:text-gray-500 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-50 active:text-gray-800 transition ease-in-out duration-150">
|
||||||
|
{{ ctrans('texts.pay_now') }}
|
||||||
|
<svg class="-mr-1 ml-2 h-5 w-5" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fill-rule="evenodd"
|
||||||
|
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
|
||||||
|
clip-rule="evenodd"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div x-show="open" class="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg">
|
||||||
|
<div class="rounded-md bg-white shadow-xs">
|
||||||
|
<div class="py-1">
|
||||||
|
@foreach($payment_methods as $payment_method)
|
||||||
|
<a href="#" @click="{ open = false }"
|
||||||
|
data-company-gateway-id="{{ $payment_method['company_gateway_id'] }}"
|
||||||
|
data-gateway-type-id="{{ $payment_method['gateway_type_id'] }}"
|
||||||
|
class="dropdown-gateway-button block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900">
|
||||||
|
{{ $payment_method['label'] }}
|
||||||
|
</a>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@foreach($invoices as $invoice)
|
||||||
|
<div class="bg-white shadow overflow-hidden sm:rounded-lg mb-4">
|
||||||
|
<div class="px-4 py-5 border-b border-gray-200 sm:px-6">
|
||||||
|
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||||
|
{{ ctrans('texts.invoice') }}
|
||||||
|
<a class="button-link"
|
||||||
|
href="{{ route('client.invoice.show', $invoice->hashed_id) }}">
|
||||||
|
(#{{ $invoice->number }})
|
||||||
|
</a>
|
||||||
|
</h3>
|
||||||
|
<p class="mt-1 max-w-2xl text-sm leading-5 text-gray-500" translate>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<dl>
|
||||||
|
<div class="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
||||||
|
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||||
|
{{ ctrans('texts.invoice_number') }}
|
||||||
|
</dt>
|
||||||
|
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||||
|
{{ $invoice->number }}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
<div class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
||||||
|
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||||
|
{{ ctrans('texts.due_date') }}
|
||||||
|
</dt>
|
||||||
|
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||||
|
{{ $invoice->due_date }}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
<div class="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
||||||
|
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||||
|
{{ ctrans('texts.additional_info') }}
|
||||||
|
</dt>
|
||||||
|
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||||
|
@if($invoice->po_number)
|
||||||
|
{{ $invoice->po_number }}
|
||||||
|
@elseif($invoice->public_notes)
|
||||||
|
{{ $invoice->public_notes }}
|
||||||
|
@else
|
||||||
|
{{ $invoice->invoice_date}}
|
||||||
|
@endif
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@include('portal.ninja2020.invoices.includes.terms')
|
||||||
|
@include('portal.ninja2020.invoices.includes.signature')
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@push('footer')
|
||||||
|
<script src="{{ asset('js/clients/invoices/payment.js') }}"></script>
|
||||||
|
@endpush
|
43
resources/views/portal/ninja2020/invoices/show.blade.php
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
@extends('portal.ninja2020.layout.app')
|
||||||
|
@section('meta_title', ctrans('texts.view_invoice'))
|
||||||
|
|
||||||
|
@section('header')
|
||||||
|
{{ Breadcrumbs::render('invoices.show', $invoice) }}
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('body')
|
||||||
|
|
||||||
|
@if($invoice->isPayable())
|
||||||
|
<form action="{{ route('client.invoices.bulk') }}" method="post">
|
||||||
|
@csrf
|
||||||
|
<div class="bg-white shadow sm:rounded-lg mb-4" translate>
|
||||||
|
<div class="px-4 py-5 sm:p-6">
|
||||||
|
<div class="sm:flex sm:items-start sm:justify-between">
|
||||||
|
<div>
|
||||||
|
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||||
|
{{ ctrans('texts.unpaid') }}
|
||||||
|
</h3>
|
||||||
|
<div class="mt-2 max-w-xl text-sm leading-5 text-gray-500">
|
||||||
|
<p translate>
|
||||||
|
{{ ctrans('texts.invoice_unpaid') }}
|
||||||
|
<!-- This invoice is still not paid. Click the button to complete the payment. -->
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<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">
|
||||||
|
<input type="hidden" name="invoices[]" value="{{ $invoice->hashed_id }}">
|
||||||
|
<input type="hidden" name="action" value="payment">
|
||||||
|
<button class="button button-primary">@lang('texts.pay_now')</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<embed src="{{ asset($invoice->pdf_url()) }}#toolbar=1&navpanes=1&scrollbar=1" type="application/pdf" width="100%"
|
||||||
|
height="1180px"/>
|
||||||
|
|
||||||
|
@endsection
|
76
resources/views/portal/ninja2020/layout/app.blade.php
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
|
||||||
|
<!-- Source: https://github.com/invoiceninja/invoiceninja -->
|
||||||
|
<!-- Error: {{ session('error') }} -->
|
||||||
|
|
||||||
|
@if (config('services.analytics.tracking_id'))
|
||||||
|
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-122229484-1"></script>
|
||||||
|
<script>
|
||||||
|
window.dataLayer = window.dataLayer || [];
|
||||||
|
|
||||||
|
function gtag() {
|
||||||
|
dataLayer.push(arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
gtag('js', new Date());
|
||||||
|
gtag('config', '{{ config('services.analytics.tracking_id') }}', {'anonymize_ip': true});
|
||||||
|
|
||||||
|
function trackEvent(category, action) {
|
||||||
|
ga('send', 'event', category, action, this.src);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
Vue.config.devtools = true;
|
||||||
|
</script>
|
||||||
|
@else
|
||||||
|
<script>
|
||||||
|
function gtag() {
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<!-- Title -->
|
||||||
|
<title>@yield('meta_title', 'Invoice Ninja') — {{ config('app.name') }}</title>
|
||||||
|
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<meta name="description" content="@yield('meta_description')"/>
|
||||||
|
|
||||||
|
<!-- CSRF Token -->
|
||||||
|
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||||
|
|
||||||
|
<!-- Scripts -->
|
||||||
|
<script src="{{ mix('js/app.js') }}" defer></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.x.x/dist/alpine.js" defer></script>
|
||||||
|
<script src="https://kit.fontawesome.com/8a87eb8352.js" crossorigin="anonymous"></script>
|
||||||
|
|
||||||
|
<!-- Fonts -->
|
||||||
|
<link rel="dns-prefetch" href="https://fonts.gstatic.com">
|
||||||
|
<link href="https://fonts.googleapis.com/css?family=Open+Sans&display=swap" rel="stylesheet" type="text/css">
|
||||||
|
|
||||||
|
<!-- Styles -->
|
||||||
|
<link href="{{ mix('css/app.css') }}" rel="stylesheet">
|
||||||
|
{{-- <link href="{{ mix('favicon.png') }}" rel="shortcut icon" type="image/png"> --}}
|
||||||
|
|
||||||
|
<link rel="canonical" href="{{ config('ninja.site_url') }}/{{ request()->path() }}"/>
|
||||||
|
|
||||||
|
{{-- Feel free to push anything to header using @push('header') --}}
|
||||||
|
@stack('head')
|
||||||
|
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body class="antialiased">
|
||||||
|
@component('portal.ninja2020.components.general.sidebar.main')
|
||||||
|
@yield('body')
|
||||||
|
@endcomponent
|
||||||
|
</body>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
@yield('footer')
|
||||||
|
@stack('footer')
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
</html>
|
@ -44,6 +44,7 @@
|
|||||||
|
|
||||||
<!-- Scripts -->
|
<!-- Scripts -->
|
||||||
<script src="{{ mix('js/app.js') }}" defer></script>
|
<script src="{{ mix('js/app.js') }}" defer></script>
|
||||||
|
<script src="https://kit.fontawesome.com/8a87eb8352.js" crossorigin="anonymous"></script>
|
||||||
|
|
||||||
<!-- Fonts -->
|
<!-- Fonts -->
|
||||||
<link rel="dns-prefetch" href="https://fonts.gstatic.com">
|
<link rel="dns-prefetch" href="https://fonts.gstatic.com">
|
||||||
|
@ -0,0 +1,52 @@
|
|||||||
|
<div x-show="open" class="fixed bottom-0 inset-x-0 px-4 pb-4 sm:inset-0 sm:flex sm:items-center sm:justify-center">
|
||||||
|
<div x-show="open" x-transition:enter="ease-out duration-300" x-transition:enter-start="opacity-0"
|
||||||
|
x-transition:enter-end="opacity-100" x-transition:leave="ease-in duration-200"
|
||||||
|
x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0"
|
||||||
|
class="fixed inset-0 transition-opacity">
|
||||||
|
<div class="absolute inset-0 bg-gray-500 opacity-75"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div x-show="open" x-transition:enter="ease-out duration-300"
|
||||||
|
x-transition:enter-start="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||||
|
x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100" x-transition:leave="ease-in duration-200"
|
||||||
|
x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100"
|
||||||
|
x-transition:leave-end="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||||
|
class="bg-white rounded-lg px-4 pt-5 pb-4 overflow-hidden shadow-xl transform transition-all sm:max-w-lg sm:w-full sm:p-6">
|
||||||
|
<div class="sm:flex sm:items-start">
|
||||||
|
<div
|
||||||
|
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
|
||||||
|
<svg class="h-6 w-6 text-red-600" stroke="currentColor" fill="none" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
|
||||||
|
<h3 class="text-lg leading-6 font-medium text-gray-900" translate>
|
||||||
|
Are you sure?
|
||||||
|
</h3>
|
||||||
|
<div class="mt-2">
|
||||||
|
<p class="text-sm leading-5 text-gray-500">
|
||||||
|
Warning! This action can't be reversed.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
|
||||||
|
<div class="flex w-full rounded-md shadow-sm sm:ml-3 sm:w-auto">
|
||||||
|
<form action="{{ route('client.payment_methods.destroy', $payment_method->hashed_id) }}" method="post">
|
||||||
|
@csrf
|
||||||
|
@method('DELETE')
|
||||||
|
<button type="submit" class="button button-danger button-block">
|
||||||
|
{{ ctrans('texts.remove') }}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="mt-3 flex w-full rounded-md shadow-sm sm:mt-0 sm:w-auto">
|
||||||
|
|
||||||
|
<button @click="open = false" type="button" class="button button-secondary button-block">
|
||||||
|
{{ ctrans('texts.cancel') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
109
resources/views/portal/ninja2020/payment_methods/index.blade.php
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
@extends('portal.ninja2020.layout.app')
|
||||||
|
@section('meta_title', ctrans('texts.payment_methods'))
|
||||||
|
|
||||||
|
@section('header')
|
||||||
|
{{ Breadcrumbs::render('payment_methods') }}
|
||||||
|
|
||||||
|
<div class="bg-white shadow rounded mb-4" translate>
|
||||||
|
<div class="px-4 py-5 sm:p-6">
|
||||||
|
<div class="sm:flex sm:items-start sm:justify-between">
|
||||||
|
<div>
|
||||||
|
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||||
|
{{ ctrans('texts.payment_methods') }}
|
||||||
|
</h3>
|
||||||
|
<div class="mt-2 max-w-xl text-sm leading-5 text-gray-500">
|
||||||
|
<p translate>
|
||||||
|
{{ ctrans('texts.list_of_payment_methods') }}
|
||||||
|
<!-- List of methods available for payments. -->
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<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">
|
||||||
|
<input type="hidden" name="hashed_ids">
|
||||||
|
<input type="hidden" name="action" value="payment">
|
||||||
|
<a href="{{ route('client.payment_methods.create') }}" class="button button-primary">@lang('texts.add_payment_method')</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('body')
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<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 shadow overflow-hidden rounded border-b border-gray-200">
|
||||||
|
<table class="min-w-full">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
|
||||||
|
{{ ctrans('texts.created_at') }}
|
||||||
|
</th>
|
||||||
|
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
|
||||||
|
{{ ctrans('texts.payment_type_id') }}
|
||||||
|
</th>
|
||||||
|
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
|
||||||
|
{{ ctrans('texts.type') }}
|
||||||
|
</th>
|
||||||
|
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
|
||||||
|
{{ ctrans('texts.expires') }}
|
||||||
|
</th>
|
||||||
|
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
|
||||||
|
{{ ctrans('texts.card_number') }}
|
||||||
|
</th>
|
||||||
|
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
|
||||||
|
{{ ctrans('texts.default') }}
|
||||||
|
</th>
|
||||||
|
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach($payment_methods as $payment_method)
|
||||||
|
<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">
|
||||||
|
{{ $payment_method->formatDateTimestamp($payment_method->created_at, auth()->user()->client->date_format()) }}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
|
||||||
|
{{ ctrans("texts.{$payment_method->gateway_type->alias}") }}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
|
||||||
|
{{ ucfirst(optional($payment_method->meta)->brand) }}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
|
||||||
|
@if(isset($payment_method->meta->exp_month) && isset($payment_method->meta->exp_year))
|
||||||
|
{{ $payment_method->meta->exp_month}} / {{ $payment_method->meta->exp_year }}
|
||||||
|
@endif
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
|
||||||
|
@isset($payment_method->meta->last4)
|
||||||
|
**** {{ $payment_method->meta->last4 }}
|
||||||
|
@endisset
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
|
||||||
|
@if($payment_method->is_default)
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none"
|
||||||
|
stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||||
|
stroke-linejoin="round" class="feather feather-check">
|
||||||
|
<path d="M20 6L9 17l-5-5"/>
|
||||||
|
</svg>
|
||||||
|
@endif
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap flex items-center justify-end text-sm leading-5 font-medium">
|
||||||
|
<a href="{{ route('client.payment_methods.show', $payment_method->hashed_id) }}"
|
||||||
|
class="text-blue-600 hover:text-indigo-900 focus:outline-none focus:underline">
|
||||||
|
@lang('texts.view')
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="my-6">
|
||||||
|
{{ $payment_methods->links('portal.ninja2020.vendor.pagination') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
100
resources/views/portal/ninja2020/payment_methods/show.blade.php
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
@extends('portal.ninja2020.layout.app')
|
||||||
|
@section('meta_title', ucfirst($payment_method->gateway_type->name))
|
||||||
|
|
||||||
|
@section('header')
|
||||||
|
{{ Breadcrumbs::render('payment_methods.show', $payment_method) }}
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('body')
|
||||||
|
<div class="container mx-auto">
|
||||||
|
<div class="bg-white shadow overflow-hidden sm:rounded-lg">
|
||||||
|
<div class="px-4 py-5 border-b border-gray-200 sm:px-6">
|
||||||
|
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||||
|
{{ ctrans("texts.{$payment_method->gateway_type->alias}") }}
|
||||||
|
</h3>
|
||||||
|
<p class="mt-1 max-w-2xl text-sm leading-5 text-gray-500" translate>
|
||||||
|
<!-- Details of the payment method. -->
|
||||||
|
{{ ctrans('texts.details_of_method') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<dl>
|
||||||
|
<div class="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
||||||
|
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||||
|
{{ ctrans('texts.payment_type') }}
|
||||||
|
</dt>
|
||||||
|
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||||
|
{{ ucfirst($payment_method->gateway_type->name) }}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
<div class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
||||||
|
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||||
|
{{ ctrans('texts.type') }}
|
||||||
|
</dt>
|
||||||
|
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||||
|
{{ ucfirst($payment_method->meta->brand) }}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
<div class="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
||||||
|
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||||
|
{{ ctrans('texts.card_number') }}
|
||||||
|
</dt>
|
||||||
|
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||||
|
**** {{ ucfirst($payment_method->meta->last4) }}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
<div class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
||||||
|
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||||
|
{{ ctrans('texts.date_created') }}
|
||||||
|
</dt>
|
||||||
|
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||||
|
{{ $payment_method->formatDateTimestamp($payment_method->created_at, auth()->user()->client->date_format()) }}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
<div class="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
||||||
|
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||||
|
{{ ctrans('texts.default') }}
|
||||||
|
</dt>
|
||||||
|
<div class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||||
|
{{ $payment_method->is_default ? ctrans('texts.yes') : ctrans('texts.no') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@isset($payment_method->meta->exp_month)
|
||||||
|
<div class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
||||||
|
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||||
|
{{ ctrans('texts.expires') }}
|
||||||
|
</dt>
|
||||||
|
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||||
|
{{ $payment_method->meta->exp_month }} / {{ $payment_method->meta->exp_year }}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
@endisset
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="bg-white shadow sm:rounded-lg mb-4 mt-4" translate>
|
||||||
|
<div class="px-4 py-5 sm:p-6">
|
||||||
|
<div class="sm:flex sm:items-start sm:justify-between">
|
||||||
|
<div>
|
||||||
|
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||||
|
Remove
|
||||||
|
</h3>
|
||||||
|
<div class="mt-2 max-w-xl text-sm leading-5 text-gray-500">
|
||||||
|
<p>
|
||||||
|
Permanently remove this payment method.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<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 }">
|
||||||
|
<button class="button button-danger" translate @click="open = true">
|
||||||
|
Remove payment method
|
||||||
|
</button>
|
||||||
|
@include('portal.ninja2020.payment_methods.includes.modals.removal')
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
85
resources/views/portal/ninja2020/payments/index.blade.php
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
@extends('portal.ninja2020.layout.app')
|
||||||
|
@section('meta_title', ctrans('texts.payments'))
|
||||||
|
|
||||||
|
@section('header')
|
||||||
|
{{ Breadcrumbs::render('payments') }}
|
||||||
|
|
||||||
|
<div class="bg-white shadow rounded mb-4" translate>
|
||||||
|
<div class="px-4 py-5 sm:p-6">
|
||||||
|
<div class="sm:flex sm:items-start sm:justify-between">
|
||||||
|
<div>
|
||||||
|
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||||
|
{{ ctrans('texts.payments') }}
|
||||||
|
</h3>
|
||||||
|
<div class="mt-2 max-w-xl text-sm leading-5 text-gray-500">
|
||||||
|
<p translate>
|
||||||
|
{{ ctrans('texts.List of your payments.') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('body')
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<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 shadow overflow-hidden rounded border-b border-gray-200">
|
||||||
|
<table class="min-w-full">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
|
||||||
|
{{ ctrans('texts.payment_date') }}
|
||||||
|
</th>
|
||||||
|
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
|
||||||
|
{{ ctrans('texts.payment_type_id') }}
|
||||||
|
</th>
|
||||||
|
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
|
||||||
|
{{ ctrans('texts.amount') }}
|
||||||
|
</th>
|
||||||
|
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
|
||||||
|
{{ ctrans('texts.transaction_reference') }}
|
||||||
|
</th>
|
||||||
|
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
|
||||||
|
{{ ctrans('texts.status') }}
|
||||||
|
</th>
|
||||||
|
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach($payments as $payment)
|
||||||
|
<tr class="cursor-pointer bg-white group hover:bg-gray-100" @click="window.location = '{{ route('client.payments.show', $payment->hashed_id) }}'">
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
|
||||||
|
{{ $payment->formatDate($payment->date, $payment->client->date_format()) }}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
|
||||||
|
{{ $payment->type->name }}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
|
||||||
|
{{ \App\Utils\Number::formatMoney($payment->amount, $payment->client) }}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
|
||||||
|
{{ $payment->transaction_reference }}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
|
||||||
|
{!! \App\Models\Payment::badgeForStatus($payment->status_id) !!}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap flex items-center justify-end text-sm leading-5 font-medium">
|
||||||
|
<a href="{{ route('client.payments.show', $payment->hashed_id) }}"
|
||||||
|
class="text-blue-600 hover:text-indigo-900 focus:outline-none focus:underline">
|
||||||
|
@lang('texts.view')
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="my-6">
|
||||||
|
{{ $payments->links('portal.ninja2020.vendor.pagination') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
92
resources/views/portal/ninja2020/payments/show.blade.php
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
@extends('portal.ninja2020.layout.app')
|
||||||
|
@section('meta_title', ctrans('texts.payment'))
|
||||||
|
|
||||||
|
@section('header')
|
||||||
|
{{ Breadcrumbs::render('payments.show', $payment) }}
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('body')
|
||||||
|
<div class="container mx-auto">
|
||||||
|
<div class="bg-white shadow overflow-hidden sm:rounded-lg">
|
||||||
|
<div class="px-4 py-5 border-b border-gray-200 sm:px-6">
|
||||||
|
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||||
|
{{ ctrans('texts.payment') }}
|
||||||
|
</h3>
|
||||||
|
<p class="mt-1 max-w-2xl text-sm leading-5 text-gray-500" translate>
|
||||||
|
{{ ctrans('texts.Details of the payment.') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<dl>
|
||||||
|
<div class="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
||||||
|
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||||
|
{{ ctrans('texts.payment_date') }}
|
||||||
|
</dt>
|
||||||
|
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||||
|
{{ $payment->clientPaymentDate() }}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
<div class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
||||||
|
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||||
|
{{ ctrans('texts.transaction_reference') }}
|
||||||
|
</dt>
|
||||||
|
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||||
|
{{ $payment->transaction_reference }}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
<div class="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
||||||
|
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||||
|
{{ ctrans('texts.method') }}
|
||||||
|
</dt>
|
||||||
|
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||||
|
{{ $payment->type->name }}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
<div class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
||||||
|
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||||
|
{{ ctrans('texts.amount') }}
|
||||||
|
</dt>
|
||||||
|
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||||
|
{{ $payment->formattedAmount() }}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
<div class="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
||||||
|
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||||
|
{{ ctrans('texts.status') }}
|
||||||
|
</dt>
|
||||||
|
<div class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||||
|
{!! \App\Models\Payment::badgeForStatus($payment->status_id) !!}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="bg-white shadow overflow-hidden sm:rounded-lg mt-4">
|
||||||
|
<div class="px-4 py-5 border-b border-gray-200 sm:px-6">
|
||||||
|
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||||
|
{{ ctrans('texts.invoices') }}
|
||||||
|
</h3>
|
||||||
|
<p class="mt-1 max-w-2xl text-sm leading-5 text-gray-500" translate>
|
||||||
|
{{ ctrans('texts.List of invoices affected by payment.') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<dl>
|
||||||
|
@foreach($payment->invoices as $invoice)
|
||||||
|
<div class="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
||||||
|
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||||
|
{{ ctrans('texts.invoice_number') }}
|
||||||
|
</dt>
|
||||||
|
<div class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||||
|
<a class="button-link"
|
||||||
|
href="{{ route('client.invoice.show', ['invoice' => $invoice->hashed_id])}}">
|
||||||
|
{{ $invoice->number }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
356
resources/views/portal/ninja2020/profile/index.blade.php
Normal file
@ -0,0 +1,356 @@
|
|||||||
|
@extends('portal.ninja2020.layout.app')
|
||||||
|
|
||||||
|
@section('meta_title', ctrans('texts.client_information'))
|
||||||
|
|
||||||
|
@section('header')
|
||||||
|
<p class="leading-5 text-gray-500" translate>{{ ctrans('texts.Update your personal information.') }}</p>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('body')
|
||||||
|
|
||||||
|
<!-- Basic information: first & last name, e-mail address etc. -->
|
||||||
|
<div class="mt-2 sm:mt-6">
|
||||||
|
<div class="md:grid md:grid-cols-3 md:gap-6">
|
||||||
|
<div class="md:col-span-1">
|
||||||
|
<div class="sm:px-0">
|
||||||
|
<h3 class="text-lg font-medium leading-6 text-gray-900" translate>{{ ctrans('texts.profile') }}</h3>
|
||||||
|
<p class="mt-1 text-sm leading-5 text-gray-500">
|
||||||
|
@lang('texts.client_information_text')
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-5 md:mt-0 md:col-span-2">
|
||||||
|
<form action="{{ route('client.profile.update', auth()->user()->hashed_id) }}" method="POST"
|
||||||
|
id="update_contact">
|
||||||
|
@csrf
|
||||||
|
@method('PUT')
|
||||||
|
<div class="shadow overflow-hidden rounded">
|
||||||
|
<div class="px-4 py-5 bg-white sm:p-6">
|
||||||
|
<div class="grid grid-cols-6 gap-6">
|
||||||
|
<div class="col-span-6 sm:col-span-3">
|
||||||
|
<label for="first_name" class="input-label">@lang('texts.first_name')</label>
|
||||||
|
<input id="first_name" class="input" name="first_name"
|
||||||
|
value="{{ auth()->user()->first_name }}"/>
|
||||||
|
@error('first_name')
|
||||||
|
<div class="validation validation-fail">
|
||||||
|
{{ $message }}
|
||||||
|
</div>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-span-6 sm:col-span-3">
|
||||||
|
<label for="last_name" class="input-label">@lang('texts.last_name')</label>
|
||||||
|
<input id="last_name" class="input" name="last_name"
|
||||||
|
value="{{ auth()->user()->last_name }}"/>
|
||||||
|
@error('last_name')
|
||||||
|
<div class="validation validation-fail">
|
||||||
|
{{ $message }}
|
||||||
|
</div>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-span-6 sm:col-span-4">
|
||||||
|
<label for="email_address" class="input-label">@lang('texts.email_address')</label>
|
||||||
|
<input id="email_address" class="input" type="email" name="email"
|
||||||
|
value="{{ auth()->user()->email }}"/>
|
||||||
|
@error('email')
|
||||||
|
<div class="validation validation-fail">
|
||||||
|
{{ $message }}
|
||||||
|
</div>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-span-6 sm:col-span-4">
|
||||||
|
<label for="phone" class="input-label">@lang('texts.phone')</label>
|
||||||
|
<input id="phone" class="input" name="phone" value="{{ auth()->user()->phone }}"/>
|
||||||
|
@error('phone')
|
||||||
|
<div class="validation validation-fail">
|
||||||
|
{{ $message }}
|
||||||
|
</div>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-span-6 sm:col-span-6 lg:col-span-3">
|
||||||
|
<label for="password" class="input-label">@lang('texts.password')</label>
|
||||||
|
<input id="password" class="input" name="password" type="password"/>
|
||||||
|
@error('password')
|
||||||
|
<div class="validation validation-fail">
|
||||||
|
{{ $message }}
|
||||||
|
</div>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-span-6 sm:col-span-3 lg:col-span-3">
|
||||||
|
<label for="state" class="input-label">@lang('texts.confirm_password')</label>
|
||||||
|
<input id="state" class="input" name="password_confirmation" type="password"/>
|
||||||
|
@error('password_confirmation')
|
||||||
|
<div class="validation validation-fail">
|
||||||
|
{{ $message }}
|
||||||
|
</div>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="px-4 py-3 bg-gray-50 text-right sm:px-6">
|
||||||
|
<button class="button button-primary">
|
||||||
|
@lang('texts.save')
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Name, website & logo -->
|
||||||
|
<div class="mt-10 sm:mt-6">
|
||||||
|
<div class="md:grid md:grid-cols-3 md:gap-6">
|
||||||
|
<div class="md:col-span-1">
|
||||||
|
<div class="sm:px-0">
|
||||||
|
<h3 class="text-lg font-medium leading-6 text-gray-900">{{ ctrans('texts.name_website_logo') }}</h3>
|
||||||
|
<p class="mt-1 text-sm leading-5 text-gray-500" translate>
|
||||||
|
{{ ctrans('texts. Make sure you use full link to your site.') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-5 md:mt-0 md:col-span-2">
|
||||||
|
<form action="{{ route('client.profile.edit_client', auth()->user()->hashed_id) }}" method="POST"
|
||||||
|
id="update_contact">
|
||||||
|
@csrf
|
||||||
|
@method('PUT')
|
||||||
|
<div class="shadow overflow-hidden rounded">
|
||||||
|
<div class="px-4 py-5 bg-white sm:p-6">
|
||||||
|
<div class="grid grid-cols-6 gap-6">
|
||||||
|
<div class="col-span-6 sm:col-span-3">
|
||||||
|
<label for="street" class="input-label">@lang('texts.name')</label>
|
||||||
|
<input id="name" class="input" name="name"
|
||||||
|
value="{{ auth()->user()->client->name }}"/>
|
||||||
|
@error('name')
|
||||||
|
<div class="validation validation-fail">
|
||||||
|
{{ $message }}
|
||||||
|
</div>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
<div class="col-span-6 sm:col-span-3">
|
||||||
|
<label for="website" class="input-label">@lang('texts.website')</label>
|
||||||
|
<input id="website" class="input" name="last_name"
|
||||||
|
value="{{ auth()->user()->client->website }}"/>
|
||||||
|
@error('website')
|
||||||
|
<div class="validation validation-fail">
|
||||||
|
{{ $message }}
|
||||||
|
</div>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="px-4 py-3 bg-gray-50 text-right sm:px-6">
|
||||||
|
<button class="button button-primary">
|
||||||
|
@lang('texts.save')
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Client personal address -->
|
||||||
|
<div class="mt-10 sm:mt-6">
|
||||||
|
<div class="md:grid md:grid-cols-3 md:gap-6">
|
||||||
|
<div class="md:col-span-1">
|
||||||
|
<div class="sm:px-0">
|
||||||
|
<h3 class="text-lg font-medium leading-6 text-gray-900">{{ ctrans('texts.personal_address') }}</h3>
|
||||||
|
<p class="mt-1 text-sm leading-5 text-gray-500" translate>
|
||||||
|
{{ ctrans('texts.your_personal_address') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-5 md:mt-0 md:col-span-2">
|
||||||
|
<form action="{{ route('client.profile.edit_client', auth()->user()->hashed_id) }}" method="POST"
|
||||||
|
id="update_contact">
|
||||||
|
@csrf
|
||||||
|
@method('PUT')
|
||||||
|
<div class="shadow overflow-hidden rounded">
|
||||||
|
<div class="px-4 py-5 bg-white sm:p-6">
|
||||||
|
<div class="grid grid-cols-6 gap-6">
|
||||||
|
<div class="col-span-6 sm:col-span-4">
|
||||||
|
<label for="address1" class="input-label">@lang('texts.address1')</label>
|
||||||
|
<input id="address1" class="input" name="address1"
|
||||||
|
value="{{ auth()->user()->client->address1 }}"/>
|
||||||
|
@error('address1')
|
||||||
|
<div class="validation validation-fail">
|
||||||
|
{{ $message }}
|
||||||
|
</div>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
<div class="col-span-6 sm:col-span-3">
|
||||||
|
<label for="address2" class="input-label">@lang('texts.address2')</label>
|
||||||
|
<input id="address2" class="input" name="address2"
|
||||||
|
value="{{ auth()->user()->client->address2 }}"/>
|
||||||
|
@error('address2')
|
||||||
|
<div class="validation validation-fail">
|
||||||
|
{{ $message }}
|
||||||
|
</div>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
<div class="col-span-6 sm:col-span-3">
|
||||||
|
<label for="city" class="input-label">@lang('texts.city')</label>
|
||||||
|
<input id="city" class="input" name="city"
|
||||||
|
value="{{ auth()->user()->client->city }}"/>
|
||||||
|
@error('city')
|
||||||
|
<div class="validation validation-fail">
|
||||||
|
{{ $message }}
|
||||||
|
</div>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
<div class="col-span-6 sm:col-span-2">
|
||||||
|
<label for="state" class="input-label">@lang('texts.state')</label>
|
||||||
|
<input id="state" class="input" name="state"
|
||||||
|
value="{{ auth()->user()->client->state }}"/>
|
||||||
|
@error('state')
|
||||||
|
<div class="validation validation-fail">
|
||||||
|
{{ $message }}
|
||||||
|
</div>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
<div class="col-span-6 sm:col-span-2">
|
||||||
|
<label for="postal_code" class="input-label">@lang('texts.postal_code')</label>
|
||||||
|
<input id="postal_code" class="input" name="postal_code"
|
||||||
|
value="{{ auth()->user()->client->postal_code }}"/>
|
||||||
|
@error('postal_code')
|
||||||
|
<div class="validation validation-fail">
|
||||||
|
{{ $message }}
|
||||||
|
</div>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
<div class="col-span-6 sm:col-span-2">
|
||||||
|
<label for="country" class="input-label">@lang('texts.country')</label>
|
||||||
|
<select id="country" class="input form-select" name="country">
|
||||||
|
@foreach($countries as $country)
|
||||||
|
<option
|
||||||
|
{{ $country == auth()->user()->client->country->id ? 'selected' : null }} value="{{ $country->id }}">{{ $country->full_name }}</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
@error('country')
|
||||||
|
<div class="validation validation-fail">
|
||||||
|
{{ $message }}
|
||||||
|
</div>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="px-4 py-3 bg-gray-50 text-right sm:px-6">
|
||||||
|
<button class="button button-primary">
|
||||||
|
@lang('texts.save')
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Client shipping address -->
|
||||||
|
<div class="mt-10 sm:mt-6">
|
||||||
|
<div class="md:grid md:grid-cols-3 md:gap-6">
|
||||||
|
<div class="md:col-span-1">
|
||||||
|
<div class="sm:px-0">
|
||||||
|
<h3 class="text-lg font-medium leading-6 text-gray-900">{{ ctrans('texts.shipping_address') }}</h3>
|
||||||
|
<p class="mt-1 text-sm leading-5 text-gray-500" translate>
|
||||||
|
{{ ctrans('texts.your_shipping_address') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-5 md:mt-0 md:col-span-2">
|
||||||
|
<form action="{{ route('client.profile.edit_client', auth()->user()->hashed_id) }}" method="POST"
|
||||||
|
id="update_contact">
|
||||||
|
@csrf
|
||||||
|
@method('PUT')
|
||||||
|
<div class="shadow overflow-hidden rounded">
|
||||||
|
<div class="px-4 py-5 bg-white sm:p-6">
|
||||||
|
<div class="grid grid-cols-6 gap-6">
|
||||||
|
<div class="col-span-6 sm:col-span-4">
|
||||||
|
<label for="shipping_address1"
|
||||||
|
class="input-label">@lang('texts.shipping_address1')</label>
|
||||||
|
<input id="shipping_address1" class="input" name="shipping_address1"
|
||||||
|
value="{{ auth()->user()->client->shipping_address1 }}"/>
|
||||||
|
@error('shipping_address1')
|
||||||
|
<div class="validation validation-fail">
|
||||||
|
{{ $message }}
|
||||||
|
</div>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
<div class="col-span-6 sm:col-span-3">
|
||||||
|
<label for="shipping_address2"
|
||||||
|
class="input-label">@lang('texts.shipping_address2')</label>
|
||||||
|
<input id="shipping_address2" class="input" name="shipping_address2"
|
||||||
|
value="{{ auth()->user()->client->shipping_address2 }}"/>
|
||||||
|
@error('shipping_address2')
|
||||||
|
<div class="validation validation-fail">
|
||||||
|
{{ $message }}
|
||||||
|
</div>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
<div class="col-span-6 sm:col-span-3">
|
||||||
|
<label for="shipping_city" class="input-label">@lang('texts.shipping_city')</label>
|
||||||
|
<input id="shipping_city" class="input" name="shipping_city"
|
||||||
|
value="{{ auth()->user()->client->shipping_city }}"/>
|
||||||
|
@error('shipping_city')
|
||||||
|
<div class="validation validation-fail">
|
||||||
|
{{ $message }}
|
||||||
|
</div>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
<div class="col-span-6 sm:col-span-2">
|
||||||
|
<label for="shipping_state"
|
||||||
|
class="input-label">@lang('texts.shipping_state')</label>
|
||||||
|
<input id="shipping_state" class="input" name="shipping_state"
|
||||||
|
value="{{ auth()->user()->client->shipping_state }}"/>
|
||||||
|
@error('shipping_state')
|
||||||
|
<div class="validation validation-fail">
|
||||||
|
{{ $message }}
|
||||||
|
</div>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
<div class="col-span-6 sm:col-span-2">
|
||||||
|
<label for="shipping_postal_code"
|
||||||
|
class="input-label">@lang('texts.shipping_postal_code')</label>
|
||||||
|
<input id="shipping_postal_code" class="input" name="shipping_postal_code"
|
||||||
|
value="{{ auth()->user()->client->shipping_postal_code }}"/>
|
||||||
|
@error('shipping_postal_code')
|
||||||
|
<div class="validation validation-fail">
|
||||||
|
{{ $message }}
|
||||||
|
</div>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
<div class="col-span-6 sm:col-span-2">
|
||||||
|
<label for="shipping_country"
|
||||||
|
class="input-label">@lang('texts.shipping_country')</label>
|
||||||
|
<select id="shipping_country" class="input form-select" name="shipping_country">
|
||||||
|
@foreach($countries as $country)
|
||||||
|
<option
|
||||||
|
{{ $country == auth()->user()->client->shipping_country->id ? 'selected' : null }} value="{{ $country->id }}">{{ $country->full_name }}
|
||||||
|
</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
@error('country')
|
||||||
|
<div class="validation validation-fail">
|
||||||
|
{{ $message }}
|
||||||
|
</div>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="px-4 py-3 bg-gray-50 text-right sm:px-6">
|
||||||
|
<button class="button button-primary">
|
||||||
|
@lang('texts.save')
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@endsection
|
90
resources/views/portal/ninja2020/quotes/approve.blade.php
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
@extends('portal.ninja2020.layout.app')
|
||||||
|
@section('meta_title', ctrans('texts.approve'))
|
||||||
|
|
||||||
|
@push('head')
|
||||||
|
<meta name="require-quote-signature" content="{{ $settings->require_invoice_signature ? true : false }}">
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/signature_pad@2.3.2/dist/signature_pad.min.js"></script>
|
||||||
|
@endpush
|
||||||
|
|
||||||
|
@section('header')
|
||||||
|
{{ Breadcrumbs::render('quotes.approve') }}
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('body')
|
||||||
|
<form action="{{ route('client.quotes.bulk') }}" method="post" id="approve-form">
|
||||||
|
@csrf
|
||||||
|
<input type="hidden" name="action" value="approve">
|
||||||
|
<input type="hidden" name="process" value="true">
|
||||||
|
@foreach($quotes as $quote)
|
||||||
|
<input type="hidden" name="quotes[]" value="{{ $quote->hashed_id }}">
|
||||||
|
@endforeach
|
||||||
|
</form>
|
||||||
|
<div class="container mx-auto">
|
||||||
|
<div class="grid grid-cols-6 gap-4">
|
||||||
|
<div class="col-span-6 md:col-start-2 md:col-span-4">
|
||||||
|
<div class="flex justify-end">
|
||||||
|
<div class="flex justify-end mb-2">
|
||||||
|
<div class="relative inline-block text-left">
|
||||||
|
<div>
|
||||||
|
<div class="rounded-md shadow-sm">
|
||||||
|
<button type="button" id="approve-button"
|
||||||
|
class="inline-flex justify-center w-full rounded-md border border-gray-300 px-4 py-2 bg-white text-sm leading-5 font-medium text-gray-700 hover:text-gray-500 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-50 active:text-gray-800 transition ease-in-out duration-150">
|
||||||
|
{{ ctrans('texts.approve') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@foreach($quotes as $quote)
|
||||||
|
<div class="bg-white shadow overflow-hidden sm:rounded-lg mb-4">
|
||||||
|
<div class="px-4 py-5 border-b border-gray-200 sm:px-6">
|
||||||
|
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||||
|
{{ ctrans('texts.invoice') }}
|
||||||
|
<a class="button-link" href="{{ route('client.quotes.show', $quote->hashed_id) }}">
|
||||||
|
({{ $quote->number }})
|
||||||
|
</a>
|
||||||
|
</h3>
|
||||||
|
<p class="mt-1 max-w-2xl text-sm leading-5 text-gray-500" translate>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<dl>
|
||||||
|
<div class="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
||||||
|
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||||
|
{{ ctrans('texts.quote_number') }}
|
||||||
|
</dt>
|
||||||
|
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||||
|
{{ $quote->number }}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
<div class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
||||||
|
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||||
|
{{ ctrans('texts.quote_date') }}
|
||||||
|
</dt>
|
||||||
|
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||||
|
{{ $quote->formatDate($quote->date, $quote->client->date_format()) }}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
<div class="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
||||||
|
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||||
|
{{ ctrans('texts.balance') }}
|
||||||
|
</dt>
|
||||||
|
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||||
|
{{ App\Utils\Number::formatMoney($quote->balance, $quote->client) }}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@include('portal.ninja2020.invoices.includes.signature')
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@push('footer')
|
||||||
|
<script src="{{ asset('js/clients/quotes/approve.js') }}"></script>
|
||||||
|
@endpush
|
@ -0,0 +1,44 @@
|
|||||||
|
<div style="display: none;" id="displaySignatureModal"
|
||||||
|
class="fixed bottom-0 inset-x-0 px-4 pb-4 sm:inset-0 sm:flex sm:items-center sm:justify-center">
|
||||||
|
<div x-show="open" x-transition:enter="ease-out duration-300" x-transition:enter-start="opacity-0"
|
||||||
|
x-transition:enter-end="opacity-100" x-transition:leave="ease-in duration-200"
|
||||||
|
x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0"
|
||||||
|
class="fixed inset-0 transition-opacity">
|
||||||
|
<div class="absolute inset-0 bg-gray-500 opacity-75"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div x-show="open" x-transition:enter="ease-out duration-300"
|
||||||
|
x-transition:enter-start="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||||
|
x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100" x-transition:leave="ease-in duration-200"
|
||||||
|
x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100"
|
||||||
|
x-transition:leave-end="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||||
|
class="bg-white rounded-lg px-4 pt-5 pb-4 overflow-hidden shadow-xl transform transition-all sm:max-w-lg sm:w-full sm:p-6">
|
||||||
|
<div class="sm:flex sm:items-start">
|
||||||
|
<div
|
||||||
|
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
|
||||||
|
<svg class="h-6 w-6 text-red-600" stroke="currentColor" fill="none" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
|
||||||
|
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||||
|
{{ ctrans('texts.sign_here') }}
|
||||||
|
</h3>
|
||||||
|
<div class="mt-2">
|
||||||
|
<p class="text-sm leading-5 text-gray-500">
|
||||||
|
<canvas id="signature-pad" class="signature-pad" width=400 height=200></canvas>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
|
||||||
|
<div class="flex w-full rounded-md shadow-sm sm:ml-3 sm:w-auto">
|
||||||
|
<button type="button" class="button button-primary" id="signature-next-step"
|
||||||
|
@click="document.getElementById('displaySignatureModal').style.display = 'none';">
|
||||||
|
{{ ctrans('texts.next_step') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
117
resources/views/portal/ninja2020/quotes/index.blade.php
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
@extends('portal.ninja2020.layout.app')
|
||||||
|
@section('meta_title', ctrans('texts.quotes'))
|
||||||
|
|
||||||
|
@section('header')
|
||||||
|
{{ Breadcrumbs::render('quotes') }}
|
||||||
|
|
||||||
|
@if($errors->any())
|
||||||
|
<div class="alert alert-failure mb-4">
|
||||||
|
@foreach($errors->all() as $error)
|
||||||
|
<p>{{ $error }}</p>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<div class="bg-white shadow rounded mb-4" translate>
|
||||||
|
<div class="px-4 py-5 sm:p-6">
|
||||||
|
<div class="sm:flex sm:items-start sm:justify-between">
|
||||||
|
<div>
|
||||||
|
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||||
|
{{ ctrans('texts.quotes') }}
|
||||||
|
</h3>
|
||||||
|
<div class="mt-2 max-w-xl text-sm leading-5 text-gray-500">
|
||||||
|
<p translate>
|
||||||
|
{{ ctrans('texts.list_of_quotes') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('body')
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
<span>{{ ctrans('texts.with_selected') }}</span>
|
||||||
|
<form action="{{ route('client.quotes.bulk') }}" method="post" id="bulkActions">
|
||||||
|
@csrf
|
||||||
|
<button type="submit" class="button button-primary" name="action"
|
||||||
|
value="download">{{ ctrans('texts.download') }}</button>
|
||||||
|
<button type="submit" class="button button-primary" name="action"
|
||||||
|
value="approve">{{ ctrans('texts.approve') }}</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col mt-4">
|
||||||
|
<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 shadow overflow-hidden rounded border-b border-gray-200">
|
||||||
|
<table class="min-w-full">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" class="form-check form-check-parent">
|
||||||
|
</label>
|
||||||
|
</th>
|
||||||
|
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
|
||||||
|
{{ ctrans('texts.quote_number') }}
|
||||||
|
</th>
|
||||||
|
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
|
||||||
|
{{ ctrans('texts.quote_date') }}
|
||||||
|
</th>
|
||||||
|
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
|
||||||
|
{{ ctrans('texts.balance') }}
|
||||||
|
</th>
|
||||||
|
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
|
||||||
|
{{ ctrans('texts.valid_until') }}
|
||||||
|
</th>
|
||||||
|
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
|
||||||
|
{{ ctrans('texts.status') }}
|
||||||
|
</th>
|
||||||
|
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach($quotes as $quote)
|
||||||
|
<tr class="bg-white group hover:bg-gray-100">
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 font-medium text-gray-900">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" class="form-check form-check-child"
|
||||||
|
data-value="{{ $quote->hashed_id }}">
|
||||||
|
</label>
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
|
||||||
|
{{ $quote->number }}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
|
||||||
|
{{ $quote->formatDate($quote->date, $quote->client->date_format()) }}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
|
||||||
|
{{ App\Utils\Number::formatMoney($quote->balance, $quote->client) }}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
|
||||||
|
{{ $quote->formatDate($quote->date, $quote->client->date_format()) }}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
|
||||||
|
{!! App\Models\Quote::badgeForStatus($quote->status_id) !!}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap flex items-center justify-end text-sm leading-5 font-medium">
|
||||||
|
<a href="{{ route('client.quotes.show', $quote->hashed_id) }}"
|
||||||
|
class="button-link">
|
||||||
|
@lang('texts.view')
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="my-6">
|
||||||
|
{{ $quotes->links('portal.ninja2020.vendor.pagination') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@push('footer')
|
||||||
|
<script src="{{ asset('js/clients/quotes/action-selectors.js') }}"></script>
|
||||||
|
@endpush
|
44
resources/views/portal/ninja2020/quotes/show.blade.php
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
@extends('portal.ninja2020.layout.app')
|
||||||
|
@section('meta_title', ctrans('texts.view_quote'))
|
||||||
|
|
||||||
|
@section('header')
|
||||||
|
{{ Breadcrumbs::render('quotes.show', $quote) }}
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('body')
|
||||||
|
|
||||||
|
@if(!$quote->isApproved())
|
||||||
|
<form action="{{ route('client.quotes.bulk') }}" method="post">
|
||||||
|
@csrf
|
||||||
|
<input type="hidden" name="action" value="approve">
|
||||||
|
<input type="hidden" name="quotes[]" value="{{ $quote->hashed_id }}">
|
||||||
|
<div class="bg-white shadow sm:rounded-lg mb-4" translate>
|
||||||
|
<div class="px-4 py-5 sm:p-6">
|
||||||
|
<div class="sm:flex sm:items-start sm:justify-between">
|
||||||
|
<div>
|
||||||
|
<h3 class="text-lg leading-6 font-medium text-gray-900" translate>
|
||||||
|
{{ ctrans('texts.waitin_for_approval') }}
|
||||||
|
</h3>
|
||||||
|
<div class="mt-2 max-w-xl text-sm leading-5 text-gray-500">
|
||||||
|
<p translate>
|
||||||
|
{{ ctrans('texts.quote_still_not_approved') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<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">
|
||||||
|
<input type="hidden" name="action" value="payment">
|
||||||
|
<button class="button button-primary">@lang('texts.approve')</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<embed src="{{ asset($quote->pdf_file_path()) }}#toolbar=1&navpanes=1&scrollbar=1" type="application/pdf"
|
||||||
|
width="100%"
|
||||||
|
height="1180px"/>
|
||||||
|
|
||||||
|
@endsection
|
@ -0,0 +1,74 @@
|
|||||||
|
@extends('portal.ninja2020.layout.app')
|
||||||
|
|
||||||
|
@section('header')
|
||||||
|
{{ Breadcrumbs::render('recurring_invoices.request_cancellation', $invoice) }}
|
||||||
|
@stop
|
||||||
|
|
||||||
|
@section('body')
|
||||||
|
<div class="container mx-auto">
|
||||||
|
<div class="bg-white shadow overflow-hidden rounded">
|
||||||
|
<div class="px-4 py-5 border-b border-gray-200 sm:px-6">
|
||||||
|
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||||
|
{{ ctrans('texts.recurring_invoices') }}
|
||||||
|
</h3>
|
||||||
|
<p class="mt-1 max-w-2xl text-sm leading-5 text-gray-500" translate>
|
||||||
|
Details of the recurring invoice.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<dl>
|
||||||
|
<div class="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
||||||
|
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||||
|
{{ ctrans('texts.start_date') }}
|
||||||
|
</dt>
|
||||||
|
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||||
|
{{ $invoice->formatDate($invoice->start_date, $invoice->client->date_format()) }}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
<div class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
||||||
|
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||||
|
{{ ctrans('texts.next_send_date') }}
|
||||||
|
</dt>
|
||||||
|
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||||
|
{{ $invoice->formatDate($invoice->next_send_date, $invoice->client->date_format()) }}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
<div class="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
||||||
|
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||||
|
{{ ctrans('texts.frequency') }}
|
||||||
|
</dt>
|
||||||
|
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||||
|
{{ \App\Models\RecurringInvoice::frequencyForKey($invoice->frequency_id) }}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
<div class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
||||||
|
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||||
|
{{ ctrans('texts.cycles_remaining') }}
|
||||||
|
</dt>
|
||||||
|
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||||
|
{{ $invoice->remaining_cycles }}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
<div class="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
||||||
|
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||||
|
{{ ctrans('texts.amount') }}
|
||||||
|
</dt>
|
||||||
|
<div class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||||
|
{{ \App\Utils\Number::formatMoney($invoice->amount, $invoice->client) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="bg-white shadow rounded-sm mb-4 mt-4 border-l-2 border-green-500" translate>
|
||||||
|
<div class="px-4 py-5 sm:p-6">
|
||||||
|
<div class="sm:flex sm:items-start sm:justify-between">
|
||||||
|
<div>
|
||||||
|
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||||
|
Cancellation pending, we'll be in touch!
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
@ -0,0 +1,49 @@
|
|||||||
|
<div x-show="open" class="fixed bottom-0 inset-x-0 px-4 pb-4 sm:inset-0 sm:flex sm:items-center sm:justify-center">
|
||||||
|
<div x-show="open" x-transition:enter="ease-out duration-300" x-transition:enter-start="opacity-0"
|
||||||
|
x-transition:enter-end="opacity-100" x-transition:leave="ease-in duration-200"
|
||||||
|
x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0"
|
||||||
|
class="fixed inset-0 transition-opacity">
|
||||||
|
<div class="absolute inset-0 bg-gray-500 opacity-75"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div x-show="open" x-transition:enter="ease-out duration-300"
|
||||||
|
x-transition:enter-start="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||||
|
x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100" x-transition:leave="ease-in duration-200"
|
||||||
|
x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100"
|
||||||
|
x-transition:leave-end="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||||
|
class="bg-white rounded-lg px-4 pt-5 pb-4 overflow-hidden shadow-xl transform transition-all sm:max-w-lg sm:w-full sm:p-6">
|
||||||
|
<div class="sm:flex sm:items-start">
|
||||||
|
<div
|
||||||
|
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
|
||||||
|
<svg class="h-6 w-6 text-red-600" stroke="currentColor" fill="none" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
|
||||||
|
<h3 class="text-lg leading-6 font-medium text-gray-900" translate>
|
||||||
|
Request Cancellation
|
||||||
|
</h3>
|
||||||
|
<div class="mt-2">
|
||||||
|
<p class="text-sm leading-5 text-gray-500">
|
||||||
|
Warning! You are requesting a cancellation of this service.
|
||||||
|
Your service may be cancelled with no further notification to you.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
|
||||||
|
<div class="flex w-full rounded-md shadow-sm sm:ml-3 sm:w-auto">
|
||||||
|
<a href="{{ route('client.recurring_invoices.request_cancellation',['recurring_invoice' => $invoice->hashed_id]) }}"
|
||||||
|
class="button button-danger button-block">
|
||||||
|
Confirm cancellation
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="mt-3 flex w-full rounded-md shadow-sm sm:mt-0 sm:w-auto">
|
||||||
|
<button @click="open = false" type="button" class="button button-secondary button-block">
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -0,0 +1,85 @@
|
|||||||
|
@extends('portal.ninja2020.layout.app')
|
||||||
|
@section('meta_title', ctrans('texts.recurring_invoices'))
|
||||||
|
|
||||||
|
@section('header')
|
||||||
|
{{ Breadcrumbs::render('recurring_invoices') }}
|
||||||
|
|
||||||
|
<div class="bg-white shadow rounded mb-4" translate>
|
||||||
|
<div class="px-4 py-5 sm:p-6">
|
||||||
|
<div class="sm:flex sm:items-start sm:justify-between">
|
||||||
|
<div>
|
||||||
|
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||||
|
{{ ctrans('texts.recurring_invoices') }}
|
||||||
|
</h3>
|
||||||
|
<div class="mt-2 max-w-xl text-sm leading-5 text-gray-500">
|
||||||
|
<p>
|
||||||
|
{{ ctrans('texts.list_of_recurring_invoices') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('body')
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<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 shadow overflow-hidden rounded border-b border-gray-200">
|
||||||
|
<table class="min-w-full">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
|
||||||
|
{{ ctrans('texts.frequency') }}
|
||||||
|
</th>
|
||||||
|
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
|
||||||
|
{{ ctrans('texts.start_date') }}
|
||||||
|
</th>
|
||||||
|
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
|
||||||
|
{{ ctrans('texts.next_send_date') }}
|
||||||
|
</th>
|
||||||
|
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
|
||||||
|
{{ ctrans('texts.cycles_remaining') }}
|
||||||
|
</th>
|
||||||
|
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
|
||||||
|
{{ ctrans('texts.amount') }}
|
||||||
|
</th>
|
||||||
|
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach($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">
|
||||||
|
{{ \App\Models\RecurringInvoice::frequencyForKey($invoice->frequency_id) }}
|
||||||
|
</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>
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
|
||||||
|
{{ $invoice->formatDate($invoice->next_send_date, $invoice->client->date_format()) }}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
|
||||||
|
{{ $invoice->remaining_cycles }}
|
||||||
|
</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 flex items-center justify-end text-sm leading-5 font-medium">
|
||||||
|
<a href="{{ route('client.recurring_invoices.show', $invoice->hashed_id) }}"
|
||||||
|
class="text-blue-600 hover:text-indigo-900 focus:outline-none focus:underline">
|
||||||
|
@lang('texts.view')
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="my-6">
|
||||||
|
{{ $invoices->links('portal.ninja2020.vendor.pagination') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
@ -0,0 +1,87 @@
|
|||||||
|
@extends('portal.ninja2020.layout.app')
|
||||||
|
@section('meta_title', ctrans('texts.recurring_invoice'))
|
||||||
|
|
||||||
|
@section('header')
|
||||||
|
{{ Breadcrumbs::render('recurring_invoices.show', $invoice) }}
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('body')
|
||||||
|
<div class="container mx-auto">
|
||||||
|
<div class="bg-white shadow overflow-hidden sm:rounded-lg">
|
||||||
|
<div class="px-4 py-5 border-b border-gray-200 sm:px-6">
|
||||||
|
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||||
|
{{ ctrans('texts.recurring_invoices') }}
|
||||||
|
</h3>
|
||||||
|
<p class="mt-1 max-w-2xl text-sm leading-5 text-gray-500" translate>
|
||||||
|
{{ ctrans('texts.details_of_recurring_invoice') }}.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<dl>
|
||||||
|
<div class="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
||||||
|
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||||
|
{{ ctrans('texts.start_date') }}
|
||||||
|
</dt>
|
||||||
|
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||||
|
{{ $invoice->formatDate($invoice->start_date, $invoice->client->date_format()) }}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
<div class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
||||||
|
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||||
|
{{ ctrans('texts.next_send_date') }}
|
||||||
|
</dt>
|
||||||
|
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||||
|
{{ $invoice->formatDate($invoice->next_send_date, $invoice->client->date_format()) }}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
<div class="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
||||||
|
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||||
|
{{ ctrans('texts.frequency') }}
|
||||||
|
</dt>
|
||||||
|
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||||
|
{{ \App\Models\RecurringInvoice::frequencyForKey($invoice->frequency_id) }}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
<div class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
||||||
|
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||||
|
{{ ctrans('texts.cycles_remaining') }}
|
||||||
|
</dt>
|
||||||
|
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||||
|
{{ $invoice->remaining_cycles }}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
<div class="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
||||||
|
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||||
|
{{ ctrans('texts.amount') }}
|
||||||
|
</dt>
|
||||||
|
<div class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||||
|
{{ \App\Utils\Number::formatMoney($invoice->amount, $invoice->client) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="bg-white shadow sm:rounded-lg mb-4 mt-4" translate>
|
||||||
|
<div class="px-4 py-5 sm:p-6">
|
||||||
|
<div class="sm:flex sm:items-start sm:justify-between">
|
||||||
|
<div>
|
||||||
|
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||||
|
{{ ctrans('texts.cancellation') }}
|
||||||
|
</h3>
|
||||||
|
<div class="mt-2 max-w-xl text-sm leading-5 text-gray-500">
|
||||||
|
<p translate>
|
||||||
|
{{ ctrans('texts.In case you want to stop the recurring invoice, please click the request the
|
||||||
|
cancellation.') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<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 }">
|
||||||
|
<button class="button button-danger" translate @click="open = true">Request Cancellation</button>
|
||||||
|
@include('portal.ninja2020.recurring_invoices.includes.modals.cancellation')
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
67
resources/views/portal/ninja2020/vendor/pagination.blade.php
vendored
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
<div class="border-t border-gray-200 px-4 flex items-center justify-between sm:px-0">
|
||||||
|
<div class="w-0 flex-1 flex">
|
||||||
|
<a href="{{ $paginator->previousPageUrl() }}"
|
||||||
|
class="-mt-px border-t-2 border-transparent pt-4 pr-1 inline-flex items-center text-sm leading-5 font-medium text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:outline-none focus:text-gray-700 focus:border-gray-400 transition ease-in-out duration-150">
|
||||||
|
<svg class="mr-3 h-5 w-5 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fill-rule="evenodd"
|
||||||
|
d="M7.707 14.707a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 1.414L5.414 9H17a1 1 0 110 2H5.414l2.293 2.293a1 1 0 010 1.414z"
|
||||||
|
clip-rule="evenodd"/>
|
||||||
|
</svg>
|
||||||
|
@lang('texts.previous')
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="hidden md:flex">
|
||||||
|
@foreach ($elements as $element)
|
||||||
|
@if (is_string($element))
|
||||||
|
<span
|
||||||
|
class="-mt-px border-t-2 border-transparent pt-4 px-4 inline-flex items-center text-sm leading-5 font-medium text-gray-500">
|
||||||
|
...
|
||||||
|
</span>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if (is_array($element))
|
||||||
|
@foreach ($element as $page => $url)
|
||||||
|
@if ($page == $paginator->currentPage())
|
||||||
|
<a href="#" disabled
|
||||||
|
class="-mt-px border-t-2 border-blue-600 pt-4 px-4 inline-flex items-center text-sm leading-5 font-medium text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:outline-none focus:text-gray-700 focus:border-gray-400 transition ease-in-out duration-150"
|
||||||
|
aria-current="page">
|
||||||
|
{{ $page }}
|
||||||
|
</a>
|
||||||
|
@else
|
||||||
|
<a href="{{ $url }}"
|
||||||
|
class="-mt-px border-t-2 border-transparent pt-4 px-4 inline-flex items-center text-sm leading-5 font-medium text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:outline-none focus:text-gray-700 focus:border-gray-400 transition ease-in-out duration-150">
|
||||||
|
{{ $page }}
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
@endforeach
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if ($paginator->hasMorePages())
|
||||||
|
<div class="w-0 flex-1 flex justify-end">
|
||||||
|
<a href="{{ $paginator->nextPageUrl() }}"
|
||||||
|
class="-mt-px border-t-2 border-transparent pt-4 pl-1 inline-flex items-center text-sm leading-5 font-medium text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:outline-none focus:text-gray-700 focus:border-gray-400 transition ease-in-out duration-150">
|
||||||
|
@lang('texts.next')
|
||||||
|
<svg class="ml-3 h-5 w-5 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fill-rule="evenodd"
|
||||||
|
d="M12.293 5.293a1 1 0 011.414 0l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-2.293-2.293a1 1 0 010-1.414z"
|
||||||
|
clip-rule="evenodd"/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
<div class="w-0 flex-1 flex justify-end">
|
||||||
|
<a href="#"
|
||||||
|
class="-mt-px border-t-2 border-transparent pt-4 pl-1 inline-flex items-center text-sm leading-5 font-medium text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:outline-none focus:text-gray-700 focus:border-gray-400 transition ease-in-out duration-150">
|
||||||
|
@lang('texts.next')
|
||||||
|
<svg class="ml-3 h-5 w-5 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fill-rule="evenodd"
|
||||||
|
d="M12.293 5.293a1 1 0 011.414 0l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-2.293-2.293a1 1 0 010-1.414z"
|
||||||
|
clip-rule="evenodd"/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
@ -2,7 +2,91 @@
|
|||||||
|
|
||||||
// Dashboard
|
// Dashboard
|
||||||
Breadcrumbs::for('dashboard', function ($trail) {
|
Breadcrumbs::for('dashboard', function ($trail) {
|
||||||
$trail->push(trans('texts.dashboard'), route('dashboard.index'));
|
$trail->push(trans('texts.dashboard'), route('client.dashboard'));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Invoices
|
||||||
|
Breadcrumbs::for('invoices', function ($trail) {
|
||||||
|
$trail->push(ctrans('texts.invoices'), route('client.invoices.index'));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Invoices > Show invoice
|
||||||
|
Breadcrumbs::for('invoices.show', function ($trail, $invoice) {
|
||||||
|
$trail->parent('invoices');
|
||||||
|
$trail->push(sprintf('%s: %s', ctrans('texts.invoice'), $invoice->number), route('client.invoices.index', $invoice->hashed_id));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Recurring invoices
|
||||||
|
Breadcrumbs::for('recurring_invoices', function ($trail) {
|
||||||
|
$trail->push(ctrans('texts.recurring_invoices'), route('client.recurring_invoices.index'));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Recurring invoices > Show recurring invoice
|
||||||
|
Breadcrumbs::for('recurring_invoices.show', function ($trail, $invoice) {
|
||||||
|
$trail->parent('recurring_invoices');
|
||||||
|
$trail->push(sprintf('%s: %s', ctrans('texts.recurring_invoice'), $invoice->hashed_id), route('client.recurring_invoices.index', $invoice->hashed_id));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Recurring invoices > Show recurring invoice
|
||||||
|
Breadcrumbs::for('recurring_invoices.request_cancellation', function ($trail, $invoice) {
|
||||||
|
$trail->parent('recurring_invoices.show', $invoice);
|
||||||
|
$trail->push(ctrans('texts.request_cancellation'), route('client.recurring_invoices.request_cancellation', $invoice->hashed_id));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Payments
|
||||||
|
Breadcrumbs::for('payments', function ($trail) {
|
||||||
|
$trail->push(ctrans('texts.payments'), route('client.payments.index'));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Payments > Show payment
|
||||||
|
Breadcrumbs::for('payments.show', function ($trail, $invoice) {
|
||||||
|
$trail->parent('payments');
|
||||||
|
$trail->push(sprintf('%s: %s', ctrans('texts.payment'), $invoice->hashed_id), route('client.payments.index', $invoice->hashed_id));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Payment methods
|
||||||
|
Breadcrumbs::for('payment_methods', function ($trail) {
|
||||||
|
$trail->push(ctrans('texts.payment_methods'), route('client.payment_methods.index'));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Payment methods > Show payment method
|
||||||
|
Breadcrumbs::for('payment_methods.show', function ($trail, $invoice) {
|
||||||
|
$trail->parent('payment_methods');
|
||||||
|
$trail->push(sprintf('%s: %s', ctrans('texts.payment_methods'), $invoice->hashed_id), route('client.payment_methods.index', $invoice->hashed_id));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Payment methods > Create method
|
||||||
|
Breadcrumbs::for('payment_methods.add_credit_card', function ($trail) {
|
||||||
|
$trail->parent('payment_methods');
|
||||||
|
$trail->push(ctrans('texts.add_credit_card'));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Quotes
|
||||||
|
Breadcrumbs::for('quotes', function ($trail) {
|
||||||
|
$trail->push(ctrans('texts.quotes'), route('client.quotes.index'));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Quotes > Show quote
|
||||||
|
Breadcrumbs::for('quotes.show', function ($trail, $quote) {
|
||||||
|
$trail->parent('quotes');
|
||||||
|
$trail->push(sprintf('%s: %s', ctrans('texts.quotes'), $quote->hashed_id), route('client.quotes.index', $quote->hashed_id));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Quotes > Approve
|
||||||
|
Breadcrumbs::for('quotes.approve', function ($trail) {
|
||||||
|
$trail->parent('quotes');
|
||||||
|
$trail->push(ctrans('texts.approve'));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Quotes
|
||||||
|
Breadcrumbs::for('credits', function ($trail) {
|
||||||
|
$trail->push(ctrans('texts.credits'), route('client.credits.index'));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Quotes > Show quote
|
||||||
|
Breadcrumbs::for('credits.show', function ($trail, $credit) {
|
||||||
|
$trail->parent('credits');
|
||||||
|
$trail->push(sprintf('%s: %s', ctrans('texts.credits'), $credit->hashed_id), route('client.credits.index', $credit->hashed_id));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Dashboard > Client
|
// Dashboard > Client
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
Route::get('client', 'Auth\ContactLoginController@showLoginForm')->name('client.login'); //catch all
|
Route::get('client', 'Auth\ContactLoginController@showLoginForm')->name('client.login'); //catch all
|
||||||
|
|
||||||
Route::get('client/login', 'Auth\ContactLoginController@showLoginForm')->name('client.login')->middleware('locale');
|
Route::get('client/login', 'Auth\ContactLoginController@showLoginForm')->name('client.login')->middleware('locale');
|
||||||
@ -12,6 +14,7 @@ Route::post('client/password/reset', 'Auth\ContactResetPasswordController@reset'
|
|||||||
|
|
||||||
//todo implement domain DB
|
//todo implement domain DB
|
||||||
Route::group(['middleware' => ['auth:contact','locale'], 'prefix' => 'client', 'as' => 'client.'], function () {
|
Route::group(['middleware' => ['auth:contact','locale'], 'prefix' => 'client', 'as' => 'client.'], function () {
|
||||||
|
|
||||||
Route::get('dashboard', 'ClientPortal\DashboardController@index')->name('dashboard'); // name = (dashboard. index / create / show / update / destroy / edit
|
Route::get('dashboard', 'ClientPortal\DashboardController@index')->name('dashboard'); // name = (dashboard. index / create / show / update / destroy / edit
|
||||||
|
|
||||||
Route::get('invoices', 'ClientPortal\InvoiceController@index')->name('invoices.index')->middleware('portal_enabled');
|
Route::get('invoices', 'ClientPortal\InvoiceController@index')->name('invoices.index')->middleware('portal_enabled');
|
||||||
@ -36,6 +39,11 @@ Route::group(['middleware' => ['auth:contact','locale'], 'prefix' => 'client', '
|
|||||||
|
|
||||||
Route::resource('payment_methods', 'ClientPortal\PaymentMethodController');// name = (payment_methods. index / create / show / update / destroy / edit
|
Route::resource('payment_methods', 'ClientPortal\PaymentMethodController');// name = (payment_methods. index / create / show / update / destroy / edit
|
||||||
|
|
||||||
|
Route::match(['GET', 'POST'], 'quotes/approve', 'ClientPortal\QuoteController@bulk')->name('quotes.bulk');
|
||||||
|
Route::resource('quotes', 'ClientPortal\QuoteController')->only('index', 'show');
|
||||||
|
|
||||||
|
Route::resource('credits', 'ClientPortal\CreditController')->only('index', 'show');
|
||||||
|
|
||||||
Route::post('document', 'ClientPortal\DocumentController@store')->name('document.store');
|
Route::post('document', 'ClientPortal\DocumentController@store')->name('document.store');
|
||||||
Route::delete('document', 'ClientPortal\DocumentController@destroy')->name('document.destroy');
|
Route::delete('document', 'ClientPortal\DocumentController@destroy')->name('document.destroy');
|
||||||
|
|
||||||
@ -43,6 +51,7 @@ Route::group(['middleware' => ['auth:contact','locale'], 'prefix' => 'client', '
|
|||||||
});
|
});
|
||||||
|
|
||||||
Route::group(['middleware' => ['invite_db'], 'prefix' => 'client', 'as' => 'client.'], function () {
|
Route::group(['middleware' => ['invite_db'], 'prefix' => 'client', 'as' => 'client.'], function () {
|
||||||
|
|
||||||
Route::get('invoice/{invitation_key}/download_pdf', 'InvoiceController@downloadPdf');
|
Route::get('invoice/{invitation_key}/download_pdf', 'InvoiceController@downloadPdf');
|
||||||
Route::get('quote/{invitation_key}/download_pdf', 'QuoteController@downloadPdf');
|
Route::get('quote/{invitation_key}/download_pdf', 'QuoteController@downloadPdf');
|
||||||
Route::get('credit/{invitation_key}/download_pdf', 'CreditController@downloadPdf');
|
Route::get('credit/{invitation_key}/download_pdf', 'CreditController@downloadPdf');
|
||||||
@ -52,6 +61,7 @@ Route::group(['middleware' => ['invite_db'], 'prefix' => 'client', 'as' => 'clie
|
|||||||
Route::get('{entity}/{invitation_key}','ClientPortal\InvitationController@router');
|
Route::get('{entity}/{invitation_key}','ClientPortal\InvitationController@router');
|
||||||
Route::get('{entity}/{client_hash}/{invitation_key}','ClientPortal\InvitationController@routerForIframe'); //should never need this
|
Route::get('{entity}/{client_hash}/{invitation_key}','ClientPortal\InvitationController@routerForIframe'); //should never need this
|
||||||
Route::get('payment_hook/{company_gateway_id}/{gateway_type_id}','ClientPortal\PaymentHookController@process');
|
Route::get('payment_hook/{company_gateway_id}/{gateway_type_id}','ClientPortal\PaymentHookController@process');
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::fallback('BaseController@notFoundClient');
|
Route::fallback('BaseController@notFoundClient');
|
||||||
|
19
webpack.mix.js
vendored
@ -1,19 +1,14 @@
|
|||||||
const mix = require("laravel-mix");
|
const mix = require("laravel-mix");
|
||||||
const tailwindcss = require("tailwindcss");
|
const tailwindcss = require("tailwindcss");
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Mix Asset Management
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Mix provides a clean, fluent API for defining some Webpack build steps
|
|
||||||
| for your Laravel application. By default, we are compiling the Sass
|
|
||||||
| file for the application as well as bundling up all the JS files.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
mix.js("resources/js/app.js", "public/js")
|
mix.js("resources/js/app.js", "public/js")
|
||||||
.sass("resources/sass/app.scss", "public/css")
|
.js("resources/js/clients/payment_methods/authorize-stripe-card.js", "public/js/clients/payment_methods/authorize-stripe-card.js")
|
||||||
|
.js("resources/js/clients/invoices/action-selectors.js", "public/js/clients/invoices/action-selectors.js")
|
||||||
|
.js("resources/js/clients/invoices/payment.js", "public/js/clients/invoices/payment.js")
|
||||||
|
.js("resources/js/clients/quotes/action-selectors.js", "public/js/clients/quotes/action-selectors.js")
|
||||||
|
.js("resources/js/clients/quotes/approve.js", "public/js/clients/quotes/approve.js");
|
||||||
|
|
||||||
|
mix.sass("resources/sass/app.scss", "public/css")
|
||||||
.options({
|
.options({
|
||||||
processCssUrls: false,
|
processCssUrls: false,
|
||||||
postCss: [tailwindcss("./tailwind.config.js")]
|
postCss: [tailwindcss("./tailwind.config.js")]
|
||||||
|