1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-09-27 11:47:11 +02:00

Merge pull request #6548 from turbo124/v5-stable

v5.3.4
This commit is contained in:
David Bomba 2021-09-02 16:18:56 +10:00 committed by GitHub
commit 7c0787e7e1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 311 additions and 51 deletions

View File

@ -1 +1 @@
5.3.3 5.3.4

View File

@ -13,6 +13,7 @@ namespace App\Http\Controllers\Auth;
use App\Events\Contact\ContactLoggedIn; use App\Events\Contact\ContactLoggedIn;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Libraries\MultiDB;
use App\Models\Account; use App\Models\Account;
use App\Models\ClientContact; use App\Models\ClientContact;
use App\Models\Company; use App\Models\Company;
@ -40,8 +41,16 @@ class ContactLoginController extends Controller
$company = null; $company = null;
}elseif (strpos($request->getHost(), 'invoicing.co') !== false) { }elseif (strpos($request->getHost(), 'invoicing.co') !== false) {
$subdomain = explode('.', $request->getHost())[0]; $subdomain = explode('.', $request->getHost())[0];
MultiDB::findAndSetDbByDomain(['subdomain' => $subdomain]);
$company = Company::where('subdomain', $subdomain)->first(); $company = Company::where('subdomain', $subdomain)->first();
} elseif(Ninja::isHosted() && $company = Company::where('portal_domain', $request->getSchemeAndHttpHost())->first()){
} elseif(Ninja::isHosted()){
MultiDB::findAndSetDbByDomain(['portal_domain' => $request->getSchemeAndHttpHost()]);
$company = Company::where('portal_domain', $request->getSchemeAndHttpHost())->first();
} }
elseif (Ninja::isSelfHost()) { elseif (Ninja::isSelfHost()) {
@ -61,6 +70,9 @@ class ContactLoginController extends Controller
{ {
Auth::shouldUse('contact'); Auth::shouldUse('contact');
if(Ninja::isHosted() && $request->has('db'))
MultiDB::setDb($request->input('db'));
$this->validateLogin($request); $this->validateLogin($request);
// If the class is using the ThrottlesLogins trait, we can automatically throttle // If the class is using the ThrottlesLogins trait, we can automatically throttle
// the login attempts for this application. We'll key this by the username and // the login attempts for this application. We'll key this by the username and

View File

@ -16,28 +16,28 @@ use App\Http\Controllers\Controller;
use App\Http\Requests\ClientPortal\Uploads\StoreUploadRequest; use App\Http\Requests\ClientPortal\Uploads\StoreUploadRequest;
use App\Libraries\MultiDB; use App\Libraries\MultiDB;
use App\Models\ClientContact; use App\Models\ClientContact;
use App\Models\Company;
use App\Utils\Ninja; use App\Utils\Ninja;
use Auth;
use Illuminate\Contracts\Routing\ResponseFactory; use Illuminate\Contracts\Routing\ResponseFactory;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\Response; use Illuminate\Http\Response;
use Auth;
class NinjaPlanController extends Controller class NinjaPlanController extends Controller
{ {
public function index(string $contact_key) public function index(string $contact_key, string $company_key)
{ {
MultiDB::findAndSetDbByCompanyKey($company_key);
$company = Company::where('company_key', $company_key)->first();
$account = $company->account;
if (Ninja::isHosted() && MultiDB::findAndSetDbByContactKey(request()->segment(3)) && $client_contact = ClientContact::where('contact_key', request()->segment(3))->first()) if (Ninja::isHosted() && MultiDB::findAndSetDbByContactKey(request()->segment(3)) && $client_contact = ClientContact::where('contact_key', request()->segment(3))->first())
{ {
// auth()->guard('contact')->login($client_contact, true); Auth::guard('contact')->login($client_contact,true);
Auth::guard('contact')->login($client_contact);
/* Harvest user account*/
$account = $client_contact->company->account;
/* Current paid users get pushed straight to subscription overview page*/ /* Current paid users get pushed straight to subscription overview page*/
if($account->isPaid()) if($account->isPaidHostedClient())
return redirect('/client/subscriptions'); return redirect('/client/subscriptions');
/* Users that are not paid get pushed to a custom purchase page */ /* Users that are not paid get pushed to a custom purchase page */

View File

@ -14,6 +14,7 @@ namespace App\Http\Controllers;
use App\Jobs\Util\ImportStripeCustomers; use App\Jobs\Util\ImportStripeCustomers;
use App\Jobs\Util\StripeUpdatePaymentMethods; use App\Jobs\Util\StripeUpdatePaymentMethods;
use App\Libraries\MultiDB;
use App\Models\Client; use App\Models\Client;
use App\Models\CompanyGateway; use App\Models\CompanyGateway;
@ -60,6 +61,8 @@ class StripeController extends BaseController
if(auth()->user()->isAdmin()) if(auth()->user()->isAdmin())
{ {
MultiDB::findAndSetDbByCompanyKey(auth()->user()->company()->company_key);
$company_gateway = CompanyGateway::where('company_id', auth()->user()->company()->id) $company_gateway = CompanyGateway::where('company_id', auth()->user()->company()->id)
->where('is_deleted',0) ->where('is_deleted',0)
->whereIn('gateway_key', $this->stripe_keys) ->whereIn('gateway_key', $this->stripe_keys)

View File

@ -79,7 +79,7 @@ class PortalComposer
$data['currencies'] = TranslationHelper::getCurrencies(); $data['currencies'] = TranslationHelper::getCurrencies();
$data['contact'] = auth('contact')->user(); $data['contact'] = auth('contact')->user();
$data['multiple_contacts'] = session()->get('multiple_contacts'); $data['multiple_contacts'] = session()->get('multiple_contacts') ?: collect();
return $data; return $data;
} }

View File

@ -875,7 +875,7 @@ class CompanyImport implements ShouldQueue
{ {
$this->genericImport(Design::class, $this->genericImport(Design::class,
['company_id', 'user_id'], ['company_id', 'user_id', 'hashed_id'],
[ [
['users' => 'user_id'], ['users' => 'user_id'],
], ],

View File

@ -51,8 +51,12 @@ class AutoBillCron
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL]) ->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
->where('auto_bill_enabled', true) ->where('auto_bill_enabled', true)
->where('balance', '>', 0) ->where('balance', '>', 0)
->with('company') ->where('is_deleted', false)
->cursor()->each(function ($invoice){ ->with('company');
nlog($auto_bill_partial_invoices->count(). " partial invoices to auto bill");
$auto_bill_partial_invoices->cursor()->each(function ($invoice){
$this->runAutoBiller($invoice); $this->runAutoBiller($invoice);
}); });
@ -60,8 +64,12 @@ class AutoBillCron
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL]) ->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
->where('auto_bill_enabled', true) ->where('auto_bill_enabled', true)
->where('balance', '>', 0) ->where('balance', '>', 0)
->with('company') ->where('is_deleted', false)
->cursor()->each(function ($invoice){ ->with('company');
nlog($auto_bill_invoices->count(). " full invoices to auto bill");
$auto_bill_invoices->cursor()->each(function ($invoice){
$this->runAutoBiller($invoice); $this->runAutoBiller($invoice);
}); });
@ -69,14 +77,19 @@ class AutoBillCron
} else { } else {
//multiDB environment, need to //multiDB environment, need to
foreach (MultiDB::$dbs as $db) { foreach (MultiDB::$dbs as $db) {
MultiDB::setDB($db); MultiDB::setDB($db);
$auto_bill_partial_invoices = Invoice::whereDate('partial_due_date', '<=', now()) $auto_bill_partial_invoices = Invoice::whereDate('partial_due_date', '<=', now())
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL]) ->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
->where('auto_bill_enabled', true) ->where('auto_bill_enabled', true)
->where('balance', '>', 0) ->where('balance', '>', 0)
->with('company') ->where('is_deleted', false)
->cursor()->each(function ($invoice){ ->with('company');
nlog($auto_bill_partial_invoices->count(). " partial invoices to auto bill db = {$db}");
$auto_bill_partial_invoices->cursor()->each(function ($invoice){
$this->runAutoBiller($invoice); $this->runAutoBiller($invoice);
}); });
@ -84,8 +97,12 @@ class AutoBillCron
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL]) ->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
->where('auto_bill_enabled', true) ->where('auto_bill_enabled', true)
->where('balance', '>', 0) ->where('balance', '>', 0)
->with('company') ->where('is_deleted', false)
->cursor()->each(function ($invoice){ ->with('company');
nlog($auto_bill_invoices->count(). " full invoices to auto bill db = {$db}");
$auto_bill_invoices->cursor()->each(function ($invoice){
$this->runAutoBiller($invoice); $this->runAutoBiller($invoice);
}); });
@ -96,6 +113,12 @@ class AutoBillCron
private function runAutoBiller(Invoice $invoice) private function runAutoBiller(Invoice $invoice)
{ {
info("Firing autobill for {$invoice->company_id} - {$invoice->number}"); info("Firing autobill for {$invoice->company_id} - {$invoice->number}");
$invoice->service()->autoBill()->save();
try{
$invoice->service()->autoBill()->save();
}
catch(\Exception $e) {
nlog("Failed to capture payment for {$invoice->company_id} - {$invoice->number} ->" . $e->getMessage());
}
} }
} }

View File

@ -46,6 +46,7 @@ class RecurringInvoicesCron
$recurring_invoices = RecurringInvoice::where('next_send_date', '<=', now()->toDateTimeString()) $recurring_invoices = RecurringInvoice::where('next_send_date', '<=', now()->toDateTimeString())
->whereNotNull('next_send_date') ->whereNotNull('next_send_date')
->whereNull('deleted_at') ->whereNull('deleted_at')
->where('is_deleted', false)
->where('status_id', RecurringInvoice::STATUS_ACTIVE) ->where('status_id', RecurringInvoice::STATUS_ACTIVE)
->where('remaining_cycles', '!=', '0') ->where('remaining_cycles', '!=', '0')
->whereHas('client', function ($query) { ->whereHas('client', function ($query) {
@ -61,7 +62,13 @@ class RecurringInvoicesCron
nlog("Current date = " . now()->format("Y-m-d") . " Recurring date = " .$recurring_invoice->next_send_date); nlog("Current date = " . now()->format("Y-m-d") . " Recurring date = " .$recurring_invoice->next_send_date);
if (!$recurring_invoice->company->is_disabled) { if (!$recurring_invoice->company->is_disabled) {
SendRecurring::dispatchNow($recurring_invoice, $recurring_invoice->company->db);
try{
SendRecurring::dispatchNow($recurring_invoice, $recurring_invoice->company->db);
}
catch(\Exception $e){
nlog("Unable to sending recurring invoice {$recurring_invoice->id}");
}
} }
}); });
} else { } else {
@ -72,6 +79,7 @@ class RecurringInvoicesCron
$recurring_invoices = RecurringInvoice::where('next_send_date', '<=', now()->toDateTimeString()) $recurring_invoices = RecurringInvoice::where('next_send_date', '<=', now()->toDateTimeString())
->whereNotNull('next_send_date') ->whereNotNull('next_send_date')
->whereNull('deleted_at') ->whereNull('deleted_at')
->where('is_deleted', false)
->where('status_id', RecurringInvoice::STATUS_ACTIVE) ->where('status_id', RecurringInvoice::STATUS_ACTIVE)
->where('remaining_cycles', '!=', '0') ->where('remaining_cycles', '!=', '0')
->whereHas('client', function ($query) { ->whereHas('client', function ($query) {
@ -87,7 +95,13 @@ class RecurringInvoicesCron
nlog("Current date = " . now()->format("Y-m-d") . " Recurring date = " .$recurring_invoice->next_send_date ." Recurring #id = ". $recurring_invoice->id); nlog("Current date = " . now()->format("Y-m-d") . " Recurring date = " .$recurring_invoice->next_send_date ." Recurring #id = ". $recurring_invoice->id);
if (!$recurring_invoice->company->is_disabled) { if (!$recurring_invoice->company->is_disabled) {
SendRecurring::dispatchNow($recurring_invoice, $recurring_invoice->company->db);
try{
SendRecurring::dispatchNow($recurring_invoice, $recurring_invoice->company->db);
}
catch(\Exception $e){
nlog("Unable to sending recurring invoice {$recurring_invoice->id} on db {$db}");
}
} }
}); });
} }

View File

@ -129,9 +129,21 @@ class SendRecurring implements ShouldQueue
} }
}); });
if ($invoice->client->getSetting('auto_bill_date') == 'on_send_date' && $this->recurring_invoice->auto_bill_enabled) { if ($invoice->client->getSetting('auto_bill_date') == 'on_send_date' && $invoice->auto_bill_enabled) {
nlog("attempting to autobill {$invoice->number}"); nlog("attempting to autobill {$invoice->number}");
$invoice->service()->autoBill()->save(); $invoice->service()->autoBill()->save();
}
elseif($invoice->client->getSetting('auto_bill_date') == 'on_due_date' && $invoice->auto_bill_enabled) {
if($invoice->due_date && Carbon\Carbon::parse($invoice->due_date)->startOfDay()->lte(now()->startOfDay())) {
nlog("attempting to autobill {$invoice->number}");
$invoice->service()->autoBill()->save();
}
} }

View File

@ -64,7 +64,7 @@ class SupportMessageSent extends Mailable
$db = str_replace("db-ninja-", "", $company->db); $db = str_replace("db-ninja-", "", $company->db);
if(Ninja::isHosted()) if(Ninja::isHosted())
$subject = "{$priority}Hosted-{$db} :: {$plan} :: ".date('M jS, g:ia'); $subject = "{$priority}Hosted-{$db}-[{$company->is_large}] :: {$plan} :: ".date('M jS, g:ia');
else else
$subject = "{$priority}Self Hosted :: {$plan} :: ".date('M jS, g:ia'); $subject = "{$priority}Self Hosted :: {$plan} :: ".date('M jS, g:ia');

View File

@ -29,6 +29,7 @@ class TestMailServer extends Mailable
$this->from_email = $from_email; $this->from_email = $from_email;
} }
/** /**
* Test Server mail. * Test Server mail.
* *
@ -36,12 +37,18 @@ class TestMailServer extends Mailable
*/ */
public function build() public function build()
{ {
$settings = new \stdClass;
$settings->primary_color = "#4caf50";
$settings->email_style = 'dark';
return $this->from(config('mail.from.address'), config('mail.from.name')) return $this->from(config('mail.from.address'), config('mail.from.name'))
->subject(ctrans('texts.email')) ->subject(ctrans('texts.email'))
->markdown('email.support.message', [ ->markdown('email.support.message', [
'support_message' => $this->support_messages, 'support_message' => $this->support_messages,
'system_info' => '', 'system_info' => '',
'laravel_log' => [], 'laravel_log' => [],
'settings' => $settings,
]); ]);
} }
} }

View File

@ -45,7 +45,7 @@ class ClientGatewayToken extends BaseModel
public function client() public function client()
{ {
return $this->hasOne(Client::class)->withTrashed(); return $this->belongsTo(Client::class)->withTrashed();
} }
public function gateway() public function gateway()
@ -60,12 +60,12 @@ class ClientGatewayToken extends BaseModel
public function company() public function company()
{ {
return $this->hasOne(Company::class); return $this->belongsTo(Company::class);
} }
public function user() public function user()
{ {
return $this->hasOne(User::class)->withTrashed(); return $this->belongsTo(User::class)->withTrashed();
} }
/** /**

View File

@ -63,7 +63,7 @@ class CreditCard
'amount' => $this->stripe->convertToStripeAmount($data['total']['amount_with_fee'], $this->stripe->client->currency()->precision, $this->stripe->client->currency()), 'amount' => $this->stripe->convertToStripeAmount($data['total']['amount_with_fee'], $this->stripe->client->currency()->precision, $this->stripe->client->currency()),
'currency' => $this->stripe->client->getCurrencyCode(), 'currency' => $this->stripe->client->getCurrencyCode(),
'customer' => $this->stripe->findOrCreateCustomer(), 'customer' => $this->stripe->findOrCreateCustomer(),
'description' => ctrans('texts.invoices') . ': ' . collect($data['invoices'])->pluck('invoice_number'), // TODO: More meaningful description. 'description' => $this->decodeUnicodeString(ctrans('texts.invoices') . ': ' . collect($data['invoices'])->pluck('invoice_number')),
]; ];
$payment_intent_data['setup_future_usage'] = 'off_session'; $payment_intent_data['setup_future_usage'] = 'off_session';
@ -74,6 +74,15 @@ class CreditCard
return render('gateways.stripe.credit_card.pay', $data); return render('gateways.stripe.credit_card.pay', $data);
} }
private function decodeUnicodeString($string)
{
return iconv("UTF-8", "ISO-8859-1//TRANSLIT", $this->decode_encoded_utf8($string));
}
private function decode_encoded_utf8($string){
return preg_replace_callback('#\\\\u([0-9a-f]{4})#ism', function($matches) { return mb_convert_encoding(pack("H*", $matches[1]), "UTF-8", "UCS-2BE"); }, $string);
}
public function paymentResponse(PaymentResponseRequest $request) public function paymentResponse(PaymentResponseRequest $request)
{ {
$this->stripe->init(); $this->stripe->init();

View File

@ -23,6 +23,7 @@ use App\Models\Currency;
use App\Models\GatewayType; use App\Models\GatewayType;
use App\PaymentDrivers\StripePaymentDriver; use App\PaymentDrivers\StripePaymentDriver;
use App\PaymentDrivers\Stripe\UpdatePaymentMethods; use App\PaymentDrivers\Stripe\UpdatePaymentMethods;
use App\Utils\Ninja;
use App\Utils\Traits\GeneratesCounter; use App\Utils\Traits\GeneratesCounter;
use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesHash;
use Stripe\Customer; use Stripe\Customer;
@ -51,8 +52,8 @@ class ImportCustomers
$this->update_payment_methods = new UpdatePaymentMethods($this->stripe); $this->update_payment_methods = new UpdatePaymentMethods($this->stripe);
if(strlen($this->stripe->company_gateway->getConfigField('account_id')) < 1) if(Ninja::isHosted() && strlen($this->stripe->company_gateway->getConfigField('account_id')) < 1)
throw new StripeConnectFailure('Stripe Connect has not been configured'); throw new StripeConnectFailure('Stripe Connect has not been configured');
$customers = Customer::all([], $this->stripe->stripe_connect_auth); $customers = Customer::all([], $this->stripe->stripe_connect_auth);
@ -61,9 +62,6 @@ class ImportCustomers
$this->addCustomer($customer); $this->addCustomer($customer);
} }
/* Now call the update payment methods handler*/
// $this->stripe->updateAllPaymentMethods();
} }
private function addCustomer(Customer $customer) private function addCustomer(Customer $customer)
@ -76,14 +74,21 @@ class ImportCustomers
nlog("search Stripe for {$customer->id}"); nlog("search Stripe for {$customer->id}");
$existing_customer = $this->stripe $existing_customer_token = $this->stripe
->company_gateway ->company_gateway
->client_gateway_tokens() ->client_gateway_tokens()
->where('gateway_customer_reference', $customer->id) ->where('gateway_customer_reference', $customer->id)
->exists(); ->first();
if($existing_customer){ if($existing_customer_token){
nlog("Skipping - Customer exists: {$customer->email}"); nlog("Skipping - Customer exists: {$customer->email} just updating payment methods");
$this->update_payment_methods->updateMethods($customer, $existing_customer_token->client);
return;
}
if($customer->email && $contact = $this->stripe->company_gateway->company->client_contacts()->where('email', $customer->email)->first()){
nlog("Customer exists: {$customer->email} just updating payment methods");
$this->update_payment_methods->updateMethods($customer, $contact->client);
return; return;
} }
@ -92,15 +97,15 @@ class ImportCustomers
$client = ClientFactory::create($this->stripe->company_gateway->company_id, $this->stripe->company_gateway->user_id); $client = ClientFactory::create($this->stripe->company_gateway->company_id, $this->stripe->company_gateway->user_id);
if(property_exists($customer, 'address')) if($customer->address)
{ {
$client->address1 = property_exists($customer->address, 'line1') ? $customer->address->line1 : ''; $client->address1 = $customer->address->line1 ? $customer->address->line1 : '';
$client->address2 = property_exists($customer->address, 'line2') ? $customer->address->line2 : ''; $client->address2 = $customer->address->line2 ? $customer->address->line2 : '';
$client->city = property_exists($customer->address, 'city') ? $customer->address->city : ''; $client->city = $customer->address->city ? $customer->address->city : '';
$client->state = property_exists($customer->address, 'state') ? $customer->address->state : ''; $client->state = $customer->address->state ? $customer->address->state : '';
$client->phone = property_exists($customer->address, 'phone') ? $customer->phone : ''; $client->phone = $customer->address->phone ? $customer->phone : '';
if(property_exists($customer->address, 'country')){ if($customer->address->country){
$country = Country::where('iso_3166_2', $customer->address->country)->first(); $country = Country::where('iso_3166_2', $customer->address->country)->first();
@ -124,7 +129,7 @@ class ImportCustomers
} }
$client->name = property_exists($customer, 'name') ? $customer->name : $customer->email; $client->name = $customer->name ? $customer->name : $customer->email;
if (!isset($client->number) || empty($client->number)) { if (!isset($client->number) || empty($client->number)) {
$client->number = $this->getNextClientNumber($client); $client->number = $this->getNextClientNumber($client);

View File

@ -130,6 +130,7 @@ class UpdatePaymentMethods
$token_exists = ClientGatewayToken::where([ $token_exists = ClientGatewayToken::where([
'gateway_customer_reference' => $customer_reference, 'gateway_customer_reference' => $customer_reference,
'token' => $method->id, 'token' => $method->id,
'company_id' => $client->company_id,
])->exists(); ])->exists();
/* Already exists return */ /* Already exists return */

View File

@ -14,8 +14,8 @@ return [
'require_https' => env('REQUIRE_HTTPS', true), 'require_https' => env('REQUIRE_HTTPS', true),
'app_url' => rtrim(env('APP_URL', ''), '/'), 'app_url' => rtrim(env('APP_URL', ''), '/'),
'app_domain' => env('APP_DOMAIN', 'invoicing.co'), 'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
'app_version' => '5.3.3', 'app_version' => '5.3.4',
'app_tag' => '5.3.3', 'app_tag' => '5.3.4',
'minimum_client_version' => '5.0.16', 'minimum_client_version' => '5.0.16',
'terms_version' => '1.0.1', 'terms_version' => '1.0.1',
'api_secret' => env('API_SECRET', ''), 'api_secret' => env('API_SECRET', ''),

View File

@ -52,6 +52,9 @@
<a class="text-xs text-gray-600 hover:text-gray-800 ease-in duration-100" <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> href="{{ route('client.password.request') }}">{{ trans('texts.forgot_password') }}</a>
</div> </div>
@if($company)
<input type="hidden" name="db" value="{{$company->db}}">
@endif
<input type="password" name="password" id="password" <input type="password" name="password" id="password"
class="input" class="input"
autofocus> autofocus>

View File

@ -0,0 +1,171 @@
@extends('portal.ninja2020.layout.app')
@section('meta_title', ctrans('texts.pro_plan_call_to_action'))
@section('body')
<style>
/* Toggle A */
input:checked ~ .dot {
transform: translateX(100%);
background-color: #48bb78;
}
/* Toggle B */
input:checked ~ .dot {
transform: translateX(100%);
background-color: #48bb78;
}
</style>
<div class="container flex flex-wrap pt-4 pb-10 m-auto mt-6 md:mt-15 lg:px-12 xl:px-16" x-data="{show: true}">
<div class="w-full px-0 lg:px-4">
<h2 class="px-12 text-base font-bold text-center md:text-2xl text-blue-700">
Choose your plan
</h2>
<p class="py-1 text-sm text-center text-blue-700 mb-10">
<!-- Toggle B -->
<div class="flex items-center justify-center w-full mb-12"">
<label for="toggleB" class="flex items-center cursor-pointer">
<!-- toggle -->
<div class="relative">
<!-- input -->
<input type="checkbox" id="toggleB" class="sr-only" @click="show = !show">
<!-- line -->
<div class="block bg-gray-600 w-14 h-8 rounded-full"></div>
<!-- dot -->
<div class="dot absolute left-1 top-1 bg-white w-6 h-6 rounded-full transition"></div>
</div>
<!-- label -->
<div class="ml-3 text-gray-700 font-medium">
Monthly vs Annual
</div>
</label>
</div>
</p>
<!-- monthly Plans -->
<div class="flex flex-wrap items-center justify-center py-4 pt-0" x-show=" show ">
<div class="w-full p-4 md:w-1/2 lg:w-1/2">
<label class="flex flex-col rounded-lg shadow-lg relative cursor-pointer hover:shadow-2xl">
<div class="w-full px-4 py-8 rounded-t-lg bg-blue-500">
<h3 class="mx-auto text-base font-semibold text-center underline text-white group-hover:text-white">
Pro Plan
</h3>
<p class="text-5xl font-bold text-center text-white">
$10
</p>
<p class="text-xs text-center uppercase text-white">
monthly
</p>
</div>
<div class="flex flex-col items-center justify-center w-full h-full py-6 rounded-b-lg bg-blue-700">
<p class="text-xl text-white">
Sign up!
</p>
<a type="button" class="w-5/6 py-2 mt-2 font-semibold text-center uppercase bg-white border border-transparent rounded text-blue-500" href="https://invoiceninja.invoicing.co/client/subscriptions/WJxbojagwO/purchase">
Purchase
</a>
</div>
</label>
</div>
<div class="w-full p-4 md:w-1/2 lg:w-1/2">
<label class="flex flex-col rounded-lg shadow-lg relative cursor-pointer hover:shadow-2xl">
<div class="w-full px-4 py-8 rounded-t-lg bg-blue-500">
<h3 class="mx-auto text-base font-semibold text-center underline text-white group-hover:text-white">
Enterprise (1-2 Users)
</h3>
<p class="text-5xl font-bold text-center text-white">
$14
</p>
<p class="text-xs text-center uppercase text-white">
monthly
</p>
</div>
<div class="flex flex-col items-center justify-center w-full h-full py-6 rounded-b-lg bg-blue-700">
<p class="text-xl text-white">
Sign up!
</p>
<a type="button" class="w-5/6 py-2 mt-2 font-semibold text-center uppercase bg-white border border-transparent rounded text-blue-500" href="https://invoiceninja.invoicing.co/client/subscriptions/7LDdwRb1YK/purchase">
Purchase
</a>
</div>
</label>
</div>
</div>
<!-- Annual Plans -->
<div class="flex flex-wrap items-center justify-center py-4 pt-0" x-show=" !show ">
<div class="w-full p-4 md:w-1/2 lg:w-1/2">
<label class="flex flex-col rounded-lg shadow-lg relative cursor-pointer hover:shadow-2xl">
<div class="w-full px-4 py-8 rounded-t-lg bg-blue-500">
<h3 class="mx-auto text-base font-semibold text-center underline text-white group-hover:text-white">
Pro Plan
</h3>
<p class="text-5xl font-bold text-center text-white">
$100
</p>
<p class="text-xs text-center uppercase text-white">
yearly
</p>
</div>
<div
class="flex flex-col items-center justify-center w-full h-full py-6 rounded-b-lg bg-blue-700"
>
<p class="text-xl text-white">
Buy 10 months get 2 free!
</p>
<a type="button" class="w-5/6 py-2 mt-2 font-semibold text-center uppercase bg-white border border-transparent rounded text-blue-500" href="https://invoiceninja.invoicing.co/client/subscriptions/q9wdL9wejP/purchase">
Purchase
</a>
</div>
</label>
</div>
<div class="w-full p-4 md:w-1/2 lg:w-1/2">
<label class="flex flex-col rounded-lg shadow-lg relative cursor-pointer hover:shadow-2xl">
<div class="w-full px-4 py-8 rounded-t-lg bg-blue-500">
<h3 class="mx-auto text-base font-semibold text-center underline text-white group-hover:text-white">
Enterprise (1-2 Users)
</h3>
<p class="text-5xl font-bold text-center text-white">
$140
</p>
<p class="text-xs text-center uppercase text-white">
yearly
</p>
</div>
<div
class="flex flex-col items-center justify-center w-full h-full py-6 rounded-b-lg bg-blue-700"
>
<p class="text-xl text-white">
Buy 10 months get 2 free!
</p>
<a type="button" class="w-5/6 py-2 mt-2 font-semibold text-center uppercase bg-white border border-transparent rounded text-blue-500" href="https://invoiceninja.invoicing.co/client/subscriptions/LYqaQWldnj/purchase">
Purchase
</a>
</div>
</label>
</div>
</div>
</div>
</div>
@endsection
@push('footer')
@endpush

View File

@ -26,7 +26,7 @@ Route::get('client/magic_link/{magic_link}', 'ClientPortal\ContactHashLoginContr
Route::get('documents/{document_hash}', 'ClientPortal\DocumentController@publicDownload')->name('documents.public_download')->middleware(['document_db']); Route::get('documents/{document_hash}', 'ClientPortal\DocumentController@publicDownload')->name('documents.public_download')->middleware(['document_db']);
Route::get('error', 'ClientPortal\ContactHashLoginController@errorPage')->name('client.error'); Route::get('error', 'ClientPortal\ContactHashLoginController@errorPage')->name('client.error');
Route::get('client/payment/{contact_key}/{payment_id}', 'ClientPortal\InvitationController@paymentRouter')->middleware(['domain_db','contact_key_login']); Route::get('client/payment/{contact_key}/{payment_id}', 'ClientPortal\InvitationController@paymentRouter')->middleware(['domain_db','contact_key_login']);
Route::get('client/ninja/{contact_key}', 'ClientPortal\NinjaPlanController@index')->name('client.ninja_contact_login')->middleware(['domain_db']); Route::get('client/ninja/{contact_key}/{company_key}', 'ClientPortal\NinjaPlanController@index')->name('client.ninja_contact_login')->middleware(['domain_db']);
Route::group(['middleware' => ['auth:contact', 'locale', 'check_client_existence','domain_db'], 'prefix' => 'client', 'as' => 'client.'], function () { Route::group(['middleware' => ['auth:contact', 'locale', 'check_client_existence','domain_db'], '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