Added email and password reset

This commit is contained in:
Deon George 2018-08-07 14:26:33 +10:00
parent 46db6537d6
commit a99834a6d1
No known key found for this signature in database
GPG Key ID: 7670E8DC27415254
21 changed files with 2273 additions and 4096 deletions

View File

@ -3,6 +3,8 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Http\UploadedFile;
use App\Models\SiteDetails; use App\Models\SiteDetails;
@ -40,7 +42,18 @@ class AdminHomeController extends Controller
} else { } else {
try { try {
if ($key == 'site_logo' AND $value instanceof UploadedFile)
{
$path = $value->storePubliclyAs('site/'.config('SITE_SETUP')->id,$value->getClientOriginalName());
SiteDetails::updateOrCreate([
'site_id'=>config('SITE_SETUP')->id,
'key'=>$key,
],[
'value'=>$path,
]);
} else {
// Update or create our config record. // Update or create our config record.
SiteDetails::updateOrCreate([ SiteDetails::updateOrCreate([
'site_id'=>config('SITE_SETUP')->id, 'site_id'=>config('SITE_SETUP')->id,
@ -48,8 +61,10 @@ class AdminHomeController extends Controller
],[ ],[
'value'=>$value, 'value'=>$value,
]); ]);
}
} catch (\Exception $e) { } catch (\Exception $e) {
dd($e); Log::debug($e->getMessage(),['k'=>$key,'v'=>$value]);
} }
} }
} }

View File

@ -29,4 +29,9 @@ class ForgotPasswordController extends Controller
{ {
$this->middleware('guest'); $this->middleware('guest');
} }
public function showLinkRequestForm()
{
return view('adminlte::auth.passwords.email');
}
} }

View File

@ -4,6 +4,7 @@ namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ResetsPasswords; use Illuminate\Foundation\Auth\ResetsPasswords;
use Illuminate\Http\Request;
class ResetPasswordController extends Controller class ResetPasswordController extends Controller
{ {
@ -36,4 +37,11 @@ class ResetPasswordController extends Controller
{ {
$this->middleware('guest'); $this->middleware('guest');
} }
public function showResetForm(Request $request, $token = null)
{
return view('adminlte::auth.passwords.reset')->with(
['token' => $token, 'email' => $request->email]
);
}
} }

View File

@ -8,7 +8,7 @@ class Invoice extends Model
{ {
protected $table = 'ab_invoice'; protected $table = 'ab_invoice';
protected $dates = ['date_orig','due_date']; protected $dates = ['date_orig','due_date'];
protected $with = ['items.taxes','items.product','items.service','account.country.currency','paymentitems']; protected $with = ['account.country.currency','items.product','items.service','items.taxes','paymentitems'];
protected $appends = [ protected $appends = [
'date_due', 'date_due',

View File

@ -12,16 +12,16 @@ class InvoiceItem extends Model
private $_tax = 0; private $_tax = 0;
public function product()
{
return $this->belongsTo(Product::class);
}
public function invoice() public function invoice()
{ {
return $this->belongsTo(Invoice::class); return $this->belongsTo(Invoice::class);
} }
public function product()
{
return $this->belongsTo(Product::class);
}
public function service() public function service()
{ {
return $this->belongsTo(Service::class); return $this->belongsTo(Service::class);

View File

@ -39,31 +39,54 @@ class Site extends Model
return NULL; return NULL;
} }
$detail = $this->details->where('key',$key)->first(); $detail = $this->getSiteDetailValue($key);
if ($detail) { return $detail->exists ? $detail->value : $this->getDefaultValue($key);
return $detail->value;
} }
// Suppress some default values private function getDefaultValue($key)
$optional = [ {
'block_quotes', $okblank = [
'clients',
'page_tabs',
'services',
'site_description',
'site_fax',
'site_address2', 'site_address2',
'site_slider', 'site_fax',
'steps', 'social',
'testimonials', 'top_menu'
]; ];
if (in_array($key,$optional)) if (! in_array($key,$okblank))
return ''; Log::alert('Returning Default Value for Key:',['key'=>$key]);
Log::alert('Calling unknown Site Key:',['key'=>$key]); // Suppress some default values
return array_get($this->_sampledata(),$key); $default = [
'block_quotes' => '',
'clients' => '',
'page_tabs' => '',
'services' => '',
'site_description' => '',
'site_fax' => '',
'site_address2' => '',
'site_slider' => '',
'social' => [],
'steps' => '',
'testimonials' => '',
'top_menu' => [],
];
return array_get($default,$key);
}
public function getSiteLogoAttribute()
{
$return = $this->getSiteDetailValue('site_logo')->value;
return $return ? 'storage/'.$return : '/image/generic/150/20/fff';
}
private function getSiteDetailValue($key)
{
$return = $this->details->where('key',$key)->first();
return $return ?: (new SiteDetails);
} }
/** /**
@ -169,10 +192,7 @@ class Site extends Model
//'button'=>['text'=>'Purchase Now','url'=>'#'], //'button'=>['text'=>'Purchase Now','url'=>'#'],
], ],
], ],
'site'=>[ 'site_logo'=>route('image',['width'=>128,'height'=>32,'color'=>'eee']),
'id'=>NULL,
'logo'=>route('image',['width'=>128,'height'=>32,'color'=>'eee']),
],
'site_address1'=>'Building Name', 'site_address1'=>'Building Name',
'site_address2'=>NULL, 'site_address2'=>NULL,
'site_city'=>'City', 'site_city'=>'City',
@ -237,7 +257,7 @@ class Site extends Model
public function sample() public function sample()
{ {
return $this->forceFill(array_get($this->_sampledata(),'site')); return $this->forceFill($this->_sampledata());
} }
public function aboutus() public function aboutus()
@ -279,9 +299,4 @@ class Site extends Model
return join("\n",$this->_address()); return join("\n",$this->_address());
} }
} }
public function logo_url()
{
return url($this->logo ? $this->logo : '/image/generic/150/20/fff');
}
} }

View File

@ -0,0 +1,52 @@
<?php
namespace App\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
class ResetPasswordNotification extends Notification
{
use Queueable;
public $token;
/**
* Create a new notification instance.
*
* @return void
*/
public function __construct($token)
{
$this->token = $token;
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @return array
*/
public function via($notifiable)
{
return ['mail'];
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
* @return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
$logo = config('SITE_SETUP')->site_logo;
$site_name = config('SITE_SETUP')->site_name;
$reset_link = route('password.reset', $this->token, true);
return (new MailMessage)
->markdown('email.user.passwordreset',compact('logo','site_name','reset_link'));
}
}

View File

@ -8,6 +8,7 @@ use Laravel\Passport\HasApiTokens;
use Leenooks\Carbon; use Leenooks\Carbon;
use Leenooks\Traits\UserSwitch; use Leenooks\Traits\UserSwitch;
use App\Notifications\ResetPasswordNotification;
class User extends Authenticatable class User extends Authenticatable
{ {
@ -164,14 +165,19 @@ class User extends Authenticatable
return sprintf('<a href="/u/account/view/%s">%s</a>',$this->id,$this->user_id); return sprintf('<a href="/u/account/view/%s">%s</a>',$this->id,$this->user_id);
} }
public function scopeActive()
{
return $this->where('active',TRUE);
}
public function isAdmin($id) public function isAdmin($id)
{ {
return $id AND in_array($this->role(),['wholesaler','reseller']) AND in_array($id,$this->all_accounts()->pluck('id')->toArray()); return $id AND in_array($this->role(),['wholesaler','reseller']) AND in_array($id,$this->all_accounts()->pluck('id')->toArray());
} }
public function scopeActive() public function sendPasswordResetNotification($token)
{ {
return $this->where('active',TRUE); $this->notify(new ResetPasswordNotification($token));
} }
// List all the agents, including agents of agents // List all the agents, including agents of agents

View File

@ -99,7 +99,7 @@ return [
| |
*/ */
'sendmail' => '/usr/sbin/sendmail -bs', 'sendmail' => '/usr/sbin/sendmail -t',
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------

View File

@ -0,0 +1,32 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreatePasswordResetsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('password_resets', function (Blueprint $table) {
$table->string('email')->index();
$table->string('token');
$table->timestamp('created_at')->nullable();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('password_resets');
}
}

5407
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -40,6 +40,6 @@
"dependencies": { "dependencies": {
"datatables.net": "^1.10.16", "datatables.net": "^1.10.16",
"datatables.net-dt": "^1.10.16", "datatables.net-dt": "^1.10.16",
"npm": "^6.1.0" "npm": "^6.3.0"
} }
} }

View File

@ -18,7 +18,7 @@
<h3 class="box-title">Setup Configuration</h3> <h3 class="box-title">Setup Configuration</h3>
</div> </div>
<form role="form" method="POST"> <form role="form" method="POST" enctype="multipart/form-data">
{{ csrf_field() }} {{ csrf_field() }}
@if(session()->has('success')) @if(session()->has('success'))

View File

@ -4,11 +4,16 @@
<span class="help-block">{{ $errors->first('site_name') }}</span> <span class="help-block">{{ $errors->first('site_name') }}</span>
</div> </div>
<div class="form-group col-sm-12 {{ $errors->has('site_description') ? 'has-error' : '' }}"> <div class="form-group col-sm-9 {{ $errors->has('site_description') ? 'has-error' : '' }}">
<label for="site_description">Site Description</label> <label for="site_description">Site Description</label>
<textarea class="form-control" id="site_description" name="site_description" placeholder="Site Description" rows="3">{{ old('site_description',$so->site_description) }}</textarea> <textarea class="form-control" id="site_description" name="site_description" placeholder="Site Description" rows="3">{{ old('site_description',$so->site_description) }}</textarea>
<span class="help-block">{{ $errors->first('site_description') }}</span> <span class="help-block">{{ $errors->first('site_description') }}</span>
</div> </div>
<div class="form-group col-sm-3 {{ $errors->has('site_logo') ? 'has-error' : '' }}">
<label for="site_logo">Site Logo</label>
<input type="file" class="form-control" id="site_logo" name="site_logo"><img class="col-sm-12" src="{{ asset($so->site_logo) }}">
<span class="help-block">{{ $errors->first('site_logo') }}</span>
</div>
<fieldset class="form-group col-sm-12"> <fieldset class="form-group col-sm-12">
<label>Site Address</label> <label>Site Address</label>

View File

@ -27,7 +27,7 @@
<!-- BEGIN HEADER --> <!-- BEGIN HEADER -->
<div class="header"> <div class="header">
<div class="container"> <div class="container">
<a class="site-logo" href="{{ url('/') }}"><img src="{{ $so->logo_url() }}" alt="{{ $so->site_description }}" height="32"></a> <a class="site-logo" href="{{ url('/') }}"><img src="{{ $so->site_logo }}" alt="{{ $so->site_description }}" height="32"></a>
<a href="javascript:void(0);" class="mobi-toggler"><i class="fa fa-bars"></i></a> <a href="javascript:void(0);" class="mobi-toggler"><i class="fa fa-bars"></i></a>
<!-- BEGIN NAVIGATION --> <!-- BEGIN NAVIGATION -->

View File

@ -0,0 +1,19 @@
@component('mail::message',['site_name'=>$site_name,'logo'=>$logo])
# Password Reset
You are receiving this email because we received a password reset request for your account.
If you did not request a password reset, no further action is required.
To reset your password, please follow this link, or click on the URL below:
@component('mail::button', ['url' => $reset_link])
Reset Password
@endcomponent
@component('mail::panel')
Reset password: {{ $reset_link }}
@endcomponent
Thanks,<br>
{{ $site_name }}
@endcomponent

View File

@ -0,0 +1,7 @@
<tr>
<td class="header">
<a href="{{ $url }}">
<img class="pull-left" src="{{ $logo }}"> <div class="pull-right">{{ $slot }}</div>
</a>
</td>
</tr>

View File

@ -0,0 +1,56 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<style>
@media only screen and (max-width: 600px) {
.inner-body {
width: 100% !important;
}
.footer {
width: 100% !important;
}
}
@media only screen and (max-width: 500px) {
.button {
width: 100% !important;
}
}
</style>
<table class="wrapper" width="100%" cellpadding="0" cellspacing="0">
<tr>
<td width="15%">&nbsp;</td>
<td align="center">
<table class="content" width="100%" cellpadding="0" cellspacing="0">
{{ $header ?? '' }}
<!-- Email Body -->
<tr>
<td class="body" width="100%" cellpadding="0" cellspacing="0">
<table class="inner-body" align="center" width="570" cellpadding="0" cellspacing="0">
<!-- Body content -->
<tr>
<td class="content-cell">
{{ Illuminate\Mail\Markdown::parse($slot) }}
{{ $subcopy ?? '' }}
</td>
</tr>
</table>
</td>
</tr>
{{ $footer ?? '' }}
</table>
</td>
<td width="15%">&nbsp;</td>
</tr>
</table>
</body>
</html>

View File

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

View File

@ -0,0 +1,299 @@
/* Base */
body, body *:not(html):not(style):not(br):not(tr):not(code) {
font-family: Avenir, Helvetica, sans-serif;
box-sizing: border-box;
}
body {
background-color: #f5f8fa;
color: #74787E;
height: 100%;
hyphens: auto;
line-height: 1.4;
margin: 0;
-moz-hyphens: auto;
-ms-word-break: break-all;
width: 100% !important;
-webkit-hyphens: auto;
-webkit-text-size-adjust: none;
word-break: break-all;
word-break: break-word;
}
p,
ul,
ol,
blockquote {
line-height: 1.4;
text-align: left;
}
a {
color: #3869D4;
}
a img {
border: none;
}
/* Typography */
h1 {
color: #2F3133;
font-size: 19px;
font-weight: bold;
margin-top: 0;
text-align: left;
}
h2 {
color: #2F3133;
font-size: 16px;
font-weight: bold;
margin-top: 0;
text-align: left;
}
h3 {
color: #2F3133;
font-size: 14px;
font-weight: bold;
margin-top: 0;
text-align: left;
}
p {
color: #74787E;
font-size: 16px;
line-height: 1.5em;
margin-top: 0;
text-align: left;
}
p.sub {
font-size: 12px;
}
img {
max-width: 100%;
}
/* Layout */
.wrapper {
background-color: #f5f8fa;
margin: 0;
padding: 0;
width: 100%;
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
-premailer-width: 100%;
}
.content {
margin: 0;
padding: 0;
width: 100%;
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
-premailer-width: 100%;
}
/* Header */
.header {
padding: 25px 0;
text-align: center;
}
.header a {
color: #bbbfc3;
font-size: 19px;
font-weight: bold;
text-decoration: none;
text-shadow: 0 1px 0 white;
}
.header img {
height: 35px;
}
/* Body */
.body {
background-color: #FFFFFF;
border-bottom: 1px solid #EDEFF2;
border-top: 1px solid #EDEFF2;
margin: 0;
padding: 0;
width: 580px;
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
-premailer-width: 100%;
}
.inner-body {
background-color: #FFFFFF;
margin: 0 auto;
padding: 0;
width: 570px;
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
-premailer-width: 570px;
}
.pull-right {
float: right;
}
.pull-left {
float: left;
}
/* Subcopy */
.subcopy {
border-top: 1px solid #EDEFF2;
margin-top: 25px;
padding-top: 25px;
}
.subcopy p {
font-size: 12px;
}
/* Footer */
.footer {
margin: 0 auto;
padding: 0;
text-align: center;
width: 570px;
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
-premailer-width: 570px;
}
.footer p {
color: #AEAEAE;
font-size: 12px;
text-align: center;
}
/* Tables */
.table table {
margin: 30px auto;
width: 100%;
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
-premailer-width: 100%;
}
.table th {
border-bottom: 1px solid #EDEFF2;
padding-bottom: 8px;
margin: 0;
}
.table td {
color: #74787E;
font-size: 15px;
line-height: 18px;
padding: 10px 0;
margin: 0;
}
.content-cell {
padding: 15px;
}
/* Buttons */
.action {
margin: 30px auto;
padding: 0;
text-align: center;
width: 100%;
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
-premailer-width: 100%;
}
.button {
border-radius: 3px;
box-shadow: 0 2px 3px rgba(0, 0, 0, 0.16);
color: #FFF;
display: inline-block;
text-decoration: none;
-webkit-text-size-adjust: none;
}
.button-blue {
background-color: #3097D1;
border-top: 10px solid #3097D1;
border-right: 18px solid #3097D1;
border-bottom: 10px solid #3097D1;
border-left: 18px solid #3097D1;
}
.button-green {
background-color: #2ab27b;
border-top: 10px solid #2ab27b;
border-right: 18px solid #2ab27b;
border-bottom: 10px solid #2ab27b;
border-left: 18px solid #2ab27b;
}
.button-red {
background-color: #bf5329;
border-top: 10px solid #bf5329;
border-right: 18px solid #bf5329;
border-bottom: 10px solid #bf5329;
border-left: 18px solid #bf5329;
}
/* Panels */
.panel {
margin: 0 0 21px;
}
.panel-content {
background-color: #EDEFF2;
padding: 16px;
}
.panel-item {
padding: 0;
}
.panel-item p:last-of-type {
margin-bottom: 0;
padding-bottom: 0;
}
/* Promotions */
.promotion {
background-color: #FFFFFF;
border: 2px dashed #9BA2AB;
margin: 0;
margin-bottom: 25px;
margin-top: 25px;
padding: 24px;
width: 100%;
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
-premailer-width: 100%;
}
.promotion h1 {
text-align: center;
}
.promotion p {
font-size: 15px;
text-align: center;
}

View File

@ -23,7 +23,7 @@ Route::group(['middleware'=>['theme:adminlte-be','auth','role:wholesaler'],'pref
Route::post('setup','AdminHomeController@setup_update'); Route::post('setup','AdminHomeController@setup_update');
Route::get('switch/start/{id}','\Leenooks\Controllers\AdminController@user_switch_start')->name('switch.user.stop'); Route::get('switch/start/{id}','\Leenooks\Controllers\AdminController@user_switch_start')->name('switch.user.stop');
Route::get('accounting/connect', 'AccountingController@connect'); //Route::get('accounting/connect', 'AccountingController@connect');
}); });
Route::get('admin/switch/stop','\Leenooks\Controllers\AdminController@user_switch_stop')->name('switch.user.start')->middleware('auth'); Route::get('admin/switch/stop','\Leenooks\Controllers\AdminController@user_switch_stop')->name('switch.user.start')->middleware('auth');