mirror of
				https://github.com/invoiceninja/invoiceninja.git
				synced 2025-10-31 11:17:34 -04:00 
			
		
		
		
	Quick login with client contacts (#3680)
- New dropdown in navigation bar - New switch_company route - New $multiple_contacts variable in PortalComposer
This commit is contained in:
		
							parent
							
								
									6fc1d0f607
								
							
						
					
					
						commit
						7f9abbf96b
					
				| @ -0,0 +1,34 @@ | |||||||
|  | <?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 | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | namespace App\Http\Controllers\ClientPortal; | ||||||
|  | 
 | ||||||
|  | use App\Http\Controllers\Controller; | ||||||
|  | use App\Models\ClientContact; | ||||||
|  | use App\Utils\Traits\MakesHash; | ||||||
|  | 
 | ||||||
|  | class SwitchCompanyController extends Controller | ||||||
|  | { | ||||||
|  |     use MakesHash; | ||||||
|  | 
 | ||||||
|  |     public function __invoke(string $contact) | ||||||
|  |     { | ||||||
|  |         $client_contact = ClientContact::query() | ||||||
|  |             ->where('user_id', auth()->user()->id) | ||||||
|  |             ->where('id', $this->transformKeys($contact)) | ||||||
|  |             ->first(); | ||||||
|  | 
 | ||||||
|  |         auth('contact')->login($client_contact, true); | ||||||
|  | 
 | ||||||
|  |         return back(); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -11,6 +11,7 @@ | |||||||
| 
 | 
 | ||||||
| namespace App\Http\ViewComposers; | namespace App\Http\ViewComposers; | ||||||
| 
 | 
 | ||||||
|  | use App\Models\ClientContact; | ||||||
| use App\Utils\TranslationHelper; | use App\Utils\TranslationHelper; | ||||||
| use Illuminate\View\View; | use Illuminate\View\View; | ||||||
| 
 | 
 | ||||||
| @ -48,6 +49,7 @@ class PortalComposer | |||||||
|         $data['company'] = auth()->user()->company; |         $data['company'] = auth()->user()->company; | ||||||
|         $data['client'] = auth()->user()->client; |         $data['client'] = auth()->user()->client; | ||||||
|         $data['settings'] = auth()->user()->client->getMergedSettings(); |         $data['settings'] = auth()->user()->client->getMergedSettings(); | ||||||
|  |         $data['multiple_contacts'] = ClientContact::where('email', auth('contact')->user()->email)->get(); | ||||||
| 
 | 
 | ||||||
|         return $data; |         return $data; | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -3204,5 +3204,5 @@ return [ | |||||||
|     'view_credit' => 'View Credit', |     'view_credit' => 'View Credit', | ||||||
|     'to_view_entity_password' => 'To view the :entity you need to enter password.', |     'to_view_entity_password' => 'To view the :entity you need to enter password.', | ||||||
|     'showing_x_of' => 'Showing :first to :last out of :total results', |     'showing_x_of' => 'Showing :first to :last out of :total results', | ||||||
|     'no_results' => 'No results found.' |     'no_results' => 'No results found.', | ||||||
| ]; | ]; | ||||||
|  | |||||||
| @ -1,8 +1,7 @@ | |||||||
| <div class="relative z-10 flex-shrink-0 flex h-16 bg-white shadow" xmlns:x-transition="http://www.w3.org/1999/xhtml"> | <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" |     <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"> | ||||||
|             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"> |         <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"/> |             <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h7" /> | ||||||
|         </svg> |         </svg> | ||||||
|     </button> |     </button> | ||||||
|     <div class="flex-1 px-4 flex justify-between"> |     <div class="flex-1 px-4 flex justify-between"> | ||||||
| @ -12,49 +11,51 @@ | |||||||
|                 <div class="relative w-full text-gray-400 focus-within:text-gray-600"> |                 <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"> |                     <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"> |                         <svg class="h-5 w-5" fill="currentColor" viewBox="0 0 20 20"> | ||||||
|                             <path fill-rule="evenodd" clip-rule="evenodd" |                             <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" /> | ||||||
|                                   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> |                         </svg> | ||||||
|                     </div> |                     </div> | ||||||
|                     <input id="search_field" |                     <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" /> | ||||||
|                            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> | ||||||
|         </div> |         </div> | ||||||
|         <div class="ml-4 flex items-center md:ml-6"> |         <div class="ml-4 flex items-center md:ml-6"> | ||||||
|             <button |             @if($multiple_contacts) | ||||||
|                 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"> |             <div class="relative inline-block text-left" x-data="{ open: false }"> | ||||||
|                 <svg class="h-6 w-6" stroke="currentColor" fill="none" viewBox="0 0 24 24"> |                 <div> | ||||||
|                     <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" |                     <span class="rounded shadow-sm"> | ||||||
|                           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"/> |                         <button x-on:click="open = !open" x-on:click.away="open = false" 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"> | ||||||
|                 </svg> |                             <span class="hidden md:block mr-1">{{ auth('contact')->user()->company->present()->name }}</span> | ||||||
|             </button> |                             <svg class="md:-mr-1 md: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> | ||||||
|  |                     </span> | ||||||
|  |                 </div> | ||||||
|  |                 <div class="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg" x-show="open"> | ||||||
|  |                     <div class="rounded bg-white shadow-xs"> | ||||||
|  |                         <div class="py-1"> | ||||||
|  |                             @foreach($multiple_contacts as $contact) | ||||||
|  |                                 <a data-turbolinks="false" href="{{ route('client.switch_company', $contact->hashed_id) }}" class="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">{{ $contact->company->present()->name }}</a> | ||||||
|  |                             @endforeach | ||||||
|  |                         </div> | ||||||
|  |                     </div> | ||||||
|  |                 </div> | ||||||
|  |             </div> | ||||||
|  |             @endif | ||||||
|             <div @click.away="open = false" class="ml-3 relative" x-data="{ open: false }"> |             <div @click.away="open = false" class="ml-3 relative" x-data="{ open: false }"> | ||||||
|                 <div> |                 <div> | ||||||
|                     <button @click="open = !open" |                     <button @click="open = !open" class="max-w-xs flex items-center text-sm rounded-full focus:outline-none focus:shadow-outline"> | ||||||
|                             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="" /> | ||||||
|                         <img class="h-8 w-8 rounded-full" |  | ||||||
|                              src="{{ auth()->user()->avatar() }}" |  | ||||||
|                              alt=""/> |  | ||||||
|                         <span class="ml-2">{{ auth()->user()->present()->name() }}</span> |                         <span class="ml-2">{{ auth()->user()->present()->name() }}</span> | ||||||
|                     </button> |                     </button> | ||||||
|                 </div> |                 </div> | ||||||
|                 <div x-show="open" |                 <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"> | ||||||
|                      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"> |                     <div class="py-1 rounded-md bg-white shadow-xs"> | ||||||
|                         <a href="{{ route('client.profile.edit', ['client_contact' => auth()->user()->hashed_id]) }}" |                         <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"> | ||||||
|                            class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 transition ease-in-out duration-150"> |  | ||||||
|                             {{ ctrans('texts.profile') }} |                             {{ ctrans('texts.profile') }} | ||||||
|                         </a> |                         </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"> |                         <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') }} |                             {{ ctrans('texts.logout') }} | ||||||
|                         </a> |                         </a> | ||||||
|                     </div> |                     </div> | ||||||
|  | |||||||
| @ -50,6 +50,8 @@ Route::group(['middleware' => ['auth:contact','locale'], 'prefix' => 'client', ' | |||||||
|     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'); | ||||||
| 
 | 
 | ||||||
|  |     Route::get('client/switch_company/{contact}', 'ClientPortal\SwitchCompanyController')->name('switch_company'); | ||||||
|  | 
 | ||||||
|     Route::get('logout', 'Auth\ContactLoginController@logout')->name('logout'); |     Route::get('logout', 'Auth\ContactLoginController@logout')->name('logout'); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user