Finished - Client auth + password reset + mailables

This commit is contained in:
David Bomba 2019-07-18 09:45:18 +10:00
parent 7e4294fcc5
commit f63803fe7b
18 changed files with 284 additions and 27 deletions

View File

@ -13,6 +13,8 @@ namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Password;
class ContactForgotPasswordController extends Controller
{
@ -36,11 +38,28 @@ class ContactForgotPasswordController extends Controller
*/
public function __construct()
{
$this->middleware('guest');
$this->middleware('guest:contact');
}
/**
* Show the reset email form.
*
* @return \Illuminate\Http\Response
*/
public function showLinkRequestForm(){
return view('portal.default.auth.passwords.email',[
'title' => 'Client Password Reset',
'passwordEmailRoute' => 'client.password.email'
]);
}
protected function guard()
{
return Auth::guard('contact');
}
public function broker()
{
return Password::broker('contacts');
}
}

View File

@ -13,6 +13,9 @@ namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ResetsPasswords;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Password;
class ContactResetPasswordController extends Controller
{
@ -34,7 +37,7 @@ class ContactResetPasswordController extends Controller
*
* @var string
*/
protected $redirectTo = '/client';
protected $redirectTo = '/client/dashboard';
/**
* Create a new controller instance.
@ -43,11 +46,32 @@ class ContactResetPasswordController extends Controller
*/
public function __construct()
{
$this->middleware('guest');
$this->middleware('guest:contact');
}
/**
* Display the password reset view for the given token.
*
* If no token is present, display the link request form.
*
* @param \Illuminate\Http\Request $request
* @param string|null $token
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function showResetForm(Request $request, $token = null)
{
return view('portal.default.auth.passwords.reset')->with(
['token' => $token, 'email' => $request->email]
);
}
protected function guard()
{
return Auth::guard('contact');
}
protected function broker()
{
return Password::broker('contacts');
}
}

View File

@ -13,6 +13,8 @@ namespace App\Models;
use App\Models\Company;
use App\Models\User;
use App\Notifications\ClientContactResetPassword as ResetPasswordNotification;
use App\Notifications\ClientContactResetPassword;
use App\Utils\Traits\MakesHash;
use Hashids\Hashids;
use Illuminate\Contracts\Auth\MustVerifyEmail;
@ -89,4 +91,8 @@ class ClientContact extends Authenticatable
return $this->belongsTo(User::class);
}
public function sendPasswordResetNotification($token)
{
$this->notify(new ClientContactResetPassword($token));
}
}

View File

@ -14,6 +14,7 @@
"@coreui/coreui": "^2.1.12",
"@coreui/icons": "^0.3.0",
"bootstrap": "^4.3.1",
"cross-env": "^5.2.0",
"font-awesome": "^4.7.0",
"laravel-mix": "^4.1.2",
"puppeteer": "^1.18.1"

View File

@ -30,27 +30,6 @@
<meta name="description" content="@yield('meta_description')"/>
<link href="{{ asset('favicon.png') }}" rel="shortcut icon" type="image/png">
<!--
TODO Setup social sharing info
<meta property="og:site_name" content="Invoice Ninja"/>
<meta property="og:url" content="{{ config('ninja.site_url') }}"/>
<meta property="og:title" content="Invoice Ninja"/>
<meta property="og:image" content="{{ config('ninja.site_url') }}/images/logo.png"/>
<meta property="og:description" content="Create. Send. Get Paid."/>
--/>
<!-- http://realfavicongenerator.net -->
<!--
TODO Setup favicon
<link rel="apple-touch-icon" sizes="180x180" href="{{ url('apple-touch-icon.png') }}">
<link rel="icon" type="image/png" href="{{ url('favicon-32x32.png') }}" sizes="32x32">
<link rel="icon" type="image/png" href="{{ url('favicon-16x16.png') }}" sizes="16x16">
<link rel="manifest" href="{{ url('manifest.json') }}">
<link rel="mask-icon" href="{{ url('safari-pinned-tab.svg') }}" color="#3bc65c">
<link rel="shortcut icon" href="{{ url('favicon.ico') }}">
<meta name="apple-mobile-web-app-title" content="Invoice Ninja">
<meta name="application-name" content="Invoice Ninja">
<meta name="theme-color" content="#ffffff">
-->
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<meta name="csrf-token" content="{{ csrf_token() }}">
@ -58,12 +37,10 @@
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/simple-line-icons/2.4.1/css/simple-line-icons.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.css">
<link rel="canonical" href="{{ config('ninja.site_url') }}/{{ request()->path() }}"/>
<link rel="stylesheet" href="{{ mix('/css/ninja.min.css') }}">
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
<script src=" {{ mix('/js/coreui.min.js') }}"></script>
<script defer src=" {{ mix('/js/ninja.min.js') }}"></script>
@yield('head')

View File

@ -0,0 +1,51 @@
@extends('portal.default.layouts.guest')
@section('body')
<body class="app flex-row align-items-center">
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card-group">
<div class="card p-4">
<div class="card-body">
@if (session('status'))
<div class="alert alert-success" role="alert">
{{ session('status') }}
</div>
@endif
<form method="POST" action="{{ route($passwordEmailRoute) }}">
@csrf
<h1>@lang('texts.password_recovery')</h1>
<p class="text-muted"></p>
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text">
<i class="icon-user"></i>
</span>
</div>
<input id="email" type="email" class="form-control{{ $errors->has('email') ? ' is-invalid' : '' }}" name="email" value="{{ old('email') }}" placeholder="@lang('texts.email')" required autofocus>
@if ($errors->has('email'))
<span class="invalid-feedback" role="alert">
<strong>{{ $errors->first('email') }}</strong>
</span>
@endif
</div>
<div class="row">
<div class="col-6">
<button class="btn btn-primary px-4" type="submit">@lang('texts.send_email')</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
@endsection

View File

@ -0,0 +1,71 @@
@extends('portal.default.layouts.guest')
@section('body')
<body class="app flex-row align-items-center">
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card-group">
<div class="card p-4">
<div class="card-body">
<div class="card-body">
<form method="POST" action="{{ route('client.password.update') }}">
@csrf
<input type="hidden" name="token" value="{{ $token }}">
<h1>@lang('texts.change_password')</h1>
<p class="text-muted"></p>
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text">
<i class="icon-user"></i>
</span>
</div>
<input id="email" type="email" class="form-control{{ $errors->has('email') ? ' is-invalid' : '' }}" name="email" value="{{ old('email') }}" placeholder="@lang('texts.email')" required autofocus>
@if ($errors->has('email'))
<span class="invalid-feedback" role="alert">
<strong>{{ $errors->first('email') }}</strong>
</span>
@endif
</div>
<div class="input-group mb-4">
<div class="input-group-prepend">
<span class="input-group-text">
<i class="icon-lock"></i>
</span>
</div>
<input id="password" type="password" class="form-control{{ $errors->has('password') ? ' is-invalid' : '' }}" name="password" placeholder="@lang('texts.password')" required>
@if ($errors->has('password'))
<span class="invalid-feedback" role="alert">
<strong>{{ $errors->first('password') }}</strong>
</span>
@endif
</div>
<div class="input-group mb-4">
<div class="input-group-prepend">
<span class="input-group-text">
<i class="icon-lock"></i>
</span>
</div>
<input id="password-confirm" type="password" class="form-control" name="password_confirmation" placeholder="@lang('texts.confirm_password')" required>
</div>
<div class="row">
<div class="col-6">
<button class="btn btn-primary px-4" type="submit">@lang('texts.change_password')</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
@endsection

View File

@ -0,0 +1 @@
{{ $slot }}: {{ $url }}

View File

@ -0,0 +1 @@
{{ $slot }}

View File

@ -0,0 +1 @@
[{{ $slot }}]({{ $url }})

View File

@ -0,0 +1,11 @@
@if(!is_array($header))
{!! strip_tags($header) !!}
@endisset
{!! strip_tags($slot) !!}
@isset($subcopy)
{!! strip_tags($subcopy) !!}
@endisset
{!! strip_tags($footer) !!}

View File

@ -0,0 +1,27 @@
@component('mail::layout')
{{-- Header --}}
@slot('header')
@component('mail::header', ['url' => config('app.url')])
{{ config('app.name') }}
@endcomponent
@endslot
{{-- Body --}}
{{ $slot }}
{{-- Subcopy --}}
@isset($subcopy)
@slot('subcopy')
@component('mail::subcopy')
{{ $subcopy }}
@endcomponent
@endslot
@endisset
{{-- Footer --}}
@slot('footer')
@component('mail::footer')
© {{ date('Y') }} {{ config('app.name') }}. @lang('All rights reserved.')
@endcomponent
@endslot
@endcomponent

View File

@ -0,0 +1 @@
{{ $slot }}

View File

@ -0,0 +1 @@
{{ $slot }}

View File

@ -0,0 +1 @@
[{{ $slot }}]({{ $url }})

View File

@ -0,0 +1 @@
{{ $slot }}

View File

@ -0,0 +1 @@
{{ $slot }}

View File

@ -0,0 +1,62 @@
@component('mail::message')
{{-- Greeting --}}
@if (! empty($greeting))
# {{ $greeting }}
@else
@if ($level === 'error')
# @lang('Whoops!')
@else
# @lang('Hello!')
@endif
@endif
{{-- Intro Lines --}}
@foreach ($introLines as $line)
{{ $line }}
@endforeach
{{-- Action Button --}}
@isset($actionText)
<?php
switch ($level) {
case 'success':
case 'error':
$color = $level;
break;
default:
$color = 'primary';
}
?>
@component('mail::button', ['url' => $actionUrl, 'color' => $color])
{{ $actionText }}
@endcomponent
@endisset
{{-- Outro Lines --}}
@foreach ($outroLines as $line)
{{ $line }}
@endforeach
{{-- Salutation --}}
@if (! empty($salutation))
{{ $salutation }}
@else
@lang('Regards'),<br>{{ config('app.name') }}
@endif
{{-- Subcopy --}}
@isset($actionText)
@slot('subcopy')
@lang(
"If youre having trouble clicking the \":actionText\" button, copy and paste the URL below\n".
'into your web browser: [:actionURL](:actionURL)',
[
'actionText' => $actionText,
'actionURL' => $actionUrl,
]
)
@endslot
@endisset
@endcomponent