From e7df1f5e7ce577293d8678a6d1c8e8357fb36a72 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Mon, 14 Mar 2016 09:25:58 +0200 Subject: [PATCH 01/23] Bug fix to show/track errors on install/update --- app/Http/Controllers/AppController.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/AppController.php b/app/Http/Controllers/AppController.php index a5e787168357..394323d7420a 100644 --- a/app/Http/Controllers/AppController.php +++ b/app/Http/Controllers/AppController.php @@ -231,7 +231,8 @@ class AppController extends BaseController } Artisan::call('optimize', array('--force' => true)); } catch (Exception $e) { - Response::make($e->getMessage(), 500); + Utils::logError($e); + return Response::make($e->getMessage(), 500); } } @@ -262,7 +263,8 @@ class AppController extends BaseController Event::fire(new UserSettingsChanged()); Session::flash('message', trans('texts.processed_updates')); } catch (Exception $e) { - Response::make($e->getMessage(), 500); + Utils::logError($e); + return Response::make($e->getMessage(), 500); } } From ed7cbf1d2b3fd9fa8ad5a4285d72ff753977c8b5 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Mon, 14 Mar 2016 10:09:18 +0200 Subject: [PATCH 02/23] Support searching by custom client fields --- app/Http/Controllers/AccountController.php | 3 +- app/Ninja/Repositories/AccountRepository.php | 37 +++++++++++++++++--- resources/views/header.blade.php | 22 +++++++++++- 3 files changed, 56 insertions(+), 6 deletions(-) diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index a0fb51b74654..abefe0b5f745 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -123,7 +123,8 @@ class AccountController extends BaseController public function getSearchData() { - $data = $this->accountRepo->getSearchData(); + $account = Auth::user()->account; + $data = $this->accountRepo->getSearchData($account); return Response::json($data); } diff --git a/app/Ninja/Repositories/AccountRepository.php b/app/Ninja/Repositories/AccountRepository.php index ff85efb76eba..0e08fbba5d06 100644 --- a/app/Ninja/Repositories/AccountRepository.php +++ b/app/Ninja/Repositories/AccountRepository.php @@ -70,16 +70,16 @@ class AccountRepository return $account; } - public function getSearchData() + public function getSearchData($account) { - $data = $this->getAccountSearchData(); + $data = $this->getAccountSearchData($account); $data['navigation'] = $this->getNavigationSearchData(); return $data; } - private function getAccountSearchData() + private function getAccountSearchData($account) { $data = [ 'clients' => [], @@ -88,6 +88,14 @@ class AccountRepository 'quotes' => [], ]; + // include custom client fields in search + if ($account->custom_client_label1) { + $data[$account->custom_client_label1] = []; + } + if ($account->custom_client_label2) { + $data[$account->custom_client_label2] = []; + } + $clients = Client::scope() ->with('contacts', 'invoices') ->get(); @@ -96,20 +104,38 @@ class AccountRepository if ($client->name) { $data['clients'][] = [ 'value' => $client->name, + 'tokens' => $client->name, 'url' => $client->present()->url, ]; + } + + if ($client->custom_value1) { + $data[$account->custom_client_label1][] = [ + 'value' => "{$client->custom_value1}: " . $client->getDisplayName(), + 'tokens' => $client->custom_value1, + 'url' => $client->present()->url, + ]; + } + if ($client->custom_value2) { + $data[$account->custom_client_label2][] = [ + 'value' => "{$client->custom_value2}: " . $client->getDisplayName(), + 'tokens' => $client->custom_value2, + 'url' => $client->present()->url, + ]; } foreach ($client->contacts as $contact) { if ($contact->getFullName()) { $data['contacts'][] = [ 'value' => $contact->getDisplayName(), + 'tokens' => $contact->getDisplayName(), 'url' => $client->present()->url, ]; } if ($contact->email) { - $data[trans('texts.contacts')][] = [ + $data['contacts'][] = [ 'value' => $contact->email, + 'tokens' => $contact->email, 'url' => $client->present()->url, ]; } @@ -119,6 +145,7 @@ class AccountRepository $entityType = $invoice->getEntityType(); $data["{$entityType}s"][] = [ 'value' => $invoice->getDisplayName() . ': ' . $client->getDisplayName(), + 'tokens' => $invoice->getDisplayName() . ': ' . $client->getDisplayName(), 'url' => $invoice->present()->url, ]; } @@ -156,6 +183,7 @@ class AccountRepository $features[] = ['new_tax_rate', '/tax_rates/create']; $features[] = ['new_product', '/products/create']; $features[] = ['new_user', '/users/create']; + $features[] = ['custom_fields', '/settings/invoice_settings']; $settings = array_merge(Account::$basicSettings, Account::$advancedSettings); @@ -169,6 +197,7 @@ class AccountRepository foreach ($features as $feature) { $data[] = [ 'value' => trans('texts.' . $feature[0]), + 'tokens' => trans('texts.' . $feature[0]), 'url' => URL::to($feature[1]) ]; } diff --git a/resources/views/header.blade.php b/resources/views/header.blade.php index de405212392c..09525079e371 100644 --- a/resources/views/header.blade.php +++ b/resources/views/header.blade.php @@ -274,11 +274,31 @@ hint: true, highlight: true, } + @if (Auth::check() && Auth::user()->account->custom_client_label1) + ,{ + name: 'data', + display: 'value', + source: searchData(data['{{ Auth::user()->account->custom_client_label1 }}'], 'tokens'), + templates: { + header: ' {{ Auth::user()->account->custom_client_label1 }}' + } + } + @endif + @if (Auth::check() && Auth::user()->account->custom_client_label2) + ,{ + name: 'data', + display: 'value', + source: searchData(data['{{ Auth::user()->account->custom_client_label2 }}'], 'tokens'), + templates: { + header: ' {{ Auth::user()->account->custom_client_label2 }}' + } + } + @endif @foreach (['clients', 'contacts', 'invoices', 'quotes', 'navigation'] as $type) ,{ name: 'data', display: 'value', - source: searchData(data['{{ $type }}'], 'value', true), + source: searchData(data['{{ $type }}'], 'tokens', true), templates: { header: ' {{ trans("texts.{$type}") }}' } From 9c0eaac6b85862a0377520a823b95643dd59dc16 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Mon, 14 Mar 2016 10:25:08 +0200 Subject: [PATCH 03/23] Enable entering search text before results are loaded --- resources/views/header.blade.php | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/resources/views/header.blade.php b/resources/views/header.blade.php index 09525079e371..4a795e0e8a1a 100644 --- a/resources/views/header.blade.php +++ b/resources/views/header.blade.php @@ -258,18 +258,16 @@ localStorage.setItem('auth_provider', provider); } + window.loadedSearchData = false; function showSearch() { $('#search').typeahead('val', ''); $('#navbar-options').hide(); $('#search-form').show(); - - if (window.hasOwnProperty('loadedSearchData')) { - $('#search').focus(); - } else { + $('#search').focus(); + + if (!window.loadedSearchData) { trackEvent('/activity', '/search'); $.get('{{ URL::route('getSearchData') }}', function(data) { - window.loadedSearchData = true; - $('#search').typeahead({ hint: true, highlight: true, @@ -307,6 +305,7 @@ ).on('typeahead:selected', function(element, datum, name) { window.location = datum.url; }).focus(); + window.loadedSearchData = true; }); } } @@ -322,7 +321,9 @@ }, 3000); $('#search').blur(function(event){ - hideSearch(); + if (window.loadedSearchData) { + hideSearch(); + } }); if (isStorageSupported()) { From d6697171f619b11718c266fbcd494aa4efdcfdcf Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Mon, 14 Mar 2016 10:40:14 +0200 Subject: [PATCH 04/23] =?UTF-8?q?Show=20=E2=80=98Expired=E2=80=99=20for=20?= =?UTF-8?q?overdue=20quotes=20in=20list=20view?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Services/InvoiceService.php | 9 +++++---- resources/lang/en/texts.php | 3 +++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/app/Services/InvoiceService.php b/app/Services/InvoiceService.php index c5d6f258e02c..522c1a5dffc3 100644 --- a/app/Services/InvoiceService.php +++ b/app/Services/InvoiceService.php @@ -160,8 +160,8 @@ class InvoiceService extends BaseService ], [ 'invoice_status_name', - function ($model) { - return $model->quote_invoice_id ? link_to("invoices/{$model->quote_invoice_id}/edit", trans('texts.converted'))->toHtml() : self::getStatusLabel($model); + function ($model) use ($entityType) { + return $model->quote_invoice_id ? link_to("invoices/{$model->quote_invoice_id}/edit", trans('texts.converted'))->toHtml() : self::getStatusLabel($entityType, $model); } ] ]; @@ -237,12 +237,13 @@ class InvoiceService extends BaseService ]; } - private function getStatusLabel($model) + private function getStatusLabel($entityType, $model) { // check if invoice is overdue if (Utils::parseFloat($model->balance) && $model->due_date && $model->due_date != '0000-00-00') { if (\DateTime::createFromFormat('Y-m-d', $model->due_date) < new \DateTime("now")) { - return "

".trans('texts.overdue')."

"; + $label = $entityType == ENTITY_INVOICE ? trans('texts.overdue') : trans('texts.expired'); + return "

" . $label . "

"; } } diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index 1e83a7821bed..84a6c70f95d0 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -1057,6 +1057,9 @@ $LANG = array( 'enable_portal_password_help'=>'Allows you to set a password for each contact. If a password is set, the contact will be required to enter a password before viewing invoices.', 'send_portal_password'=>'Generate password automatically', 'send_portal_password_help'=>'If no password is set, one will be generated and sent with the first invoice.', + + 'expired' => 'Expired', + ); return $LANG; From 0621817660597d353bcaa6a7468abcc08febd298 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Mon, 14 Mar 2016 12:12:09 +0200 Subject: [PATCH 05/23] Added client side card check with StripeJS --- resources/lang/en/texts.php | 3 ++ resources/views/payments/payment.blade.php | 43 +++++++++++++++++----- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index 84a6c70f95d0..e587699b927f 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -1059,6 +1059,9 @@ $LANG = array( 'send_portal_password_help'=>'If no password is set, one will be generated and sent with the first invoice.', 'expired' => 'Expired', + 'invalid_card_number' => 'The credit card number is not valid.', + 'invalid_expiry' => 'The expiration date is not valid.', + 'invalid_cvv' => 'The CVV is not valid.', ); diff --git a/resources/views/payments/payment.blade.php b/resources/views/payments/payment.blade.php index 822075fdc5c1..48ff913c4ea3 100644 --- a/resources/views/payments/payment.blade.php +++ b/resources/views/payments/payment.blade.php @@ -11,9 +11,6 @@ $('.payment-form').submit(function(event) { var $form = $(this); - // Disable the submit button to prevent repeated clicks - $form.find('button').prop('disabled', true); - var data = { name: $('#first_name').val() + ' ' + $('#last_name').val(), address_line1: $('#address1').val(), @@ -28,6 +25,24 @@ exp_year: $('#expiration_year').val() }; + // Validate the card details + if (!Stripe.card.validateCardNumber(data.number)) { + $('#js-error-message').html('{{ trans('texts.invalid_card_number') }}').fadeIn(); + return false; + } + if (!Stripe.card.validateExpiry(data.exp_month, data.exp_year)) { + $('#js-error-message').html('{{ trans('texts.invalid_expiry') }}').fadeIn(); + return false; + } + if (!Stripe.card.validateCVC(data.cvc)) { + $('#js-error-message').html('{{ trans('texts.invalid_cvv') }}').fadeIn(); + return false; + } + + // Disable the submit button to prevent repeated clicks + $form.find('button').prop('disabled', true); + $('#js-error-message').hide(); + Stripe.card.createToken(data, stripeResponseHandler); // Prevent the form from submitting with the default action @@ -102,11 +117,20 @@ @endif @endif +@if (Utils::isNinjaDev()) + {{ Former::populateField('first_name', 'Test') }} + {{ Former::populateField('last_name', 'Test') }} + {{ Former::populateField('address1', '350 5th Ave') }} + {{ Former::populateField('city', 'New York') }} + {{ Former::populateField('state', 'NY') }} + {{ Former::populateField('postal_code', '10118') }} + {{ Former::populateField('country_id', 840) }} +@endif + +

 

- -
@@ -290,14 +314,15 @@
-

 
 

- +

 

{!! Button::success(strtoupper(trans('texts.pay_now') . ' - ' . $account->formatMoney($amount, $client, true) )) ->submit() ->large() !!} -
- + +

 

+ +
From 528ab0090116e01f56486182a3a82290c7f77c9c Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Mon, 14 Mar 2016 17:32:29 +0200 Subject: [PATCH 06/23] Fix for Zapier triggers --- app/Listeners/SubscriptionListener.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/app/Listeners/SubscriptionListener.php b/app/Listeners/SubscriptionListener.php index cac483b4d309..fd8c39dba303 100644 --- a/app/Listeners/SubscriptionListener.php +++ b/app/Listeners/SubscriptionListener.php @@ -25,45 +25,45 @@ class SubscriptionListener public function createdClient(ClientWasCreated $event) { $transformer = new ClientTransformer($event->client->account); - $this->checkSubscriptions(ACTIVITY_TYPE_CREATE_CLIENT, $event->client, $transformer); + $this->checkSubscriptions(EVENT_CREATE_CLIENT, $event->client, $transformer); } public function createdQuote(QuoteWasCreated $event) { $transformer = new InvoiceTransformer($event->quote->account); - $this->checkSubscriptions(ACTIVITY_TYPE_CREATE_QUOTE, $event->quote, $transformer, ENTITY_CLIENT); + $this->checkSubscriptions(EVENT_CREATE_QUOTE, $event->quote, $transformer, ENTITY_CLIENT); } public function createdPayment(PaymentWasCreated $event) { $transformer = new PaymentTransformer($event->payment->account); - $this->checkSubscriptions(ACTIVITY_TYPE_CREATE_PAYMENT, $event->payment, $transformer, [ENTITY_CLIENT, ENTITY_INVOICE]); + $this->checkSubscriptions(EVENT_CREATE_PAYMENT, $event->payment, $transformer, [ENTITY_CLIENT, ENTITY_INVOICE]); } public function createdInvoice(InvoiceWasCreated $event) { $transformer = new InvoiceTransformer($event->invoice->account); - $this->checkSubscriptions(ACTIVITY_TYPE_CREATE_INVOICE, $event->invoice, $transformer, ENTITY_CLIENT); + $this->checkSubscriptions(EVENT_CREATE_INVOICE, $event->invoice, $transformer, ENTITY_CLIENT); } public function createdCredit(CreditWasCreated $event) { - //$this->checkSubscriptions(ACTIVITY_TYPE_CREATE_CREDIT, $event->credit); + } public function createdVendor(VendorWasCreated $event) { - //$this->checkSubscriptions(ACTIVITY_TYPE_CREATE_VENDOR, $event->vendor); + } public function createdExpense(ExpenseWasCreated $event) { - //$this->checkSubscriptions(ACTIVITY_TYPE_CREATE_EXPENSE, $event->expense); + } - private function checkSubscriptions($activityTypeId, $entity, $transformer, $include = '') + private function checkSubscriptions($eventId, $entity, $transformer, $include = '') { - $subscription = $entity->account->getSubscription($activityTypeId); + $subscription = $entity->account->getSubscription($eventId); if ($subscription) { $manager = new Manager(); From 5a6445f808c28451162be6d278bd91e68bd08d29 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Mon, 14 Mar 2016 20:23:20 +0200 Subject: [PATCH 07/23] Added silent flag to test invoices --- app/Http/Controllers/PublicClientController.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/PublicClientController.php b/app/Http/Controllers/PublicClientController.php index 93384037bf3b..d067ff38a740 100644 --- a/app/Http/Controllers/PublicClientController.php +++ b/app/Http/Controllers/PublicClientController.php @@ -50,7 +50,8 @@ class PublicClientController extends BaseController ]); } - if (!Input::has('phantomjs') && !Session::has($invitationKey) && (!Auth::check() || Auth::user()->account_id != $invoice->account_id)) { + if (!Input::has('phantomjs') && !Input::has('silent') && !Session::has($invitationKey) + && (!Auth::check() || Auth::user()->account_id != $invoice->account_id)) { if ($invoice->is_quote) { event(new QuoteInvitationWasViewed($invoice, $invitation)); } else { From c49f5fb52c82072b4deddc2ffbfed6ee4f415630 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Mon, 14 Mar 2016 20:49:38 +0200 Subject: [PATCH 08/23] Ensure archived invoices are viewable --- app/Http/Middleware/Authenticate.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Middleware/Authenticate.php b/app/Http/Middleware/Authenticate.php index 9d5d8549442f..a6d1363e4cdd 100644 --- a/app/Http/Middleware/Authenticate.php +++ b/app/Http/Middleware/Authenticate.php @@ -70,7 +70,7 @@ class Authenticate { } protected function getInvitation($key){ - $invitation = Invitation::where('invitation_key', '=', $key)->first(); + $invitation = Invitation::withTrashed()->where('invitation_key', '=', $key)->first(); if ($invitation && !$invitation->is_deleted) { return $invitation; } From 25b0957a7f0cdc7bbec54bd71ebacca2200d8239 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Mon, 14 Mar 2016 23:37:13 +0200 Subject: [PATCH 09/23] Fix navigation when not signed up and browser is narrower --- public/css/built.css | 20 ++++++++++++++++++-- public/css/style.css | 20 ++++++++++++++++++-- resources/views/header.blade.php | 6 ++++-- resources/views/invoices/edit.blade.php | 2 +- 4 files changed, 41 insertions(+), 7 deletions(-) diff --git a/public/css/built.css b/public/css/built.css index d4b3a8204e6b..4a7c3612187f 100644 --- a/public/css/built.css +++ b/public/css/built.css @@ -2910,8 +2910,24 @@ box-shadow: 0px 0px 15px 0px rgba(0, 5, 5, 0.2); .large-dialog { width: 960px; } - .hide-desktop - { + .hide-desktop { + display: none; + } +} + +/* Style to fix navigation by show icon instead of name */ +@media only screen and (min-width : 1200px) { + .nav-account-icon { + display: none; + } +} +@media only screen and (max-width : 992px) { + .nav-account-icon { + display: none; + } +} +@media only screen and (max-width : 1200px) and (min-width: 992px) { + .nav-account-name { display: none; } } diff --git a/public/css/style.css b/public/css/style.css index e469e9418d83..97f2bbf79edf 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -783,8 +783,24 @@ box-shadow: 0px 0px 15px 0px rgba(0, 5, 5, 0.2); .large-dialog { width: 960px; } - .hide-desktop - { + .hide-desktop { + display: none; + } +} + +/* Style to fix navigation by show icon instead of name */ +@media only screen and (min-width : 1200px) { + .nav-account-icon { + display: none; + } +} +@media only screen and (max-width : 992px) { + .nav-account-icon { + display: none; + } +} +@media only screen and (max-width : 1200px) and (min-width: 992px) { + .nav-account-name { display: none; } } diff --git a/resources/views/header.blade.php b/resources/views/header.blade.php index 4a795e0e8a1a..46bebbd68732 100644 --- a/resources/views/header.blade.php +++ b/resources/views/header.blade.php @@ -427,14 +427,16 @@