Compare commits

...

5 Commits

Author SHA1 Message Date
2e9f87550c Move traffic mismatch mail tempaltes to admin/service
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 36s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2024-08-03 11:49:34 +10:00
78a8f63ac9 Remove social link items and update test email 2024-08-03 11:40:37 +10:00
df3f7e31be Update password reset email 2024-08-03 11:33:23 +10:00
0469d64577 Move email/ resources to mail/, added invoice generated email to admin, updated email template 2024-08-03 11:33:23 +10:00
f8453ae391 More work on moving service updates to use components, move 'host' to 'hosting', move some redundant views 2024-08-01 17:34:31 +10:00
64 changed files with 583 additions and 910 deletions

View File

@ -6,17 +6,20 @@ use Illuminate\Console\Command;
use Illuminate\Support\Facades\Config; use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Mail;
use App\Mail\TestEmail as MailTest; use App\Mail\Test;
use App\Models\{Site,User}; use App\Models\{Site,User};
class TestEmail extends Command class EmailTest extends Command
{ {
/** /**
* The name and signature of the console command. * The name and signature of the console command.
* *
* @var string * @var string
*/ */
protected $signature = 'test:email {site : Site ID} {id : User ID} {email? : Alternative Email}'; protected $signature = 'test-email'
.' {--s|site : Site ID}'
.' {id : User ID}'
.' {email? : Alternative Email}';
/** /**
* The console command description. * The console command description.
@ -25,16 +28,6 @@ class TestEmail extends Command
*/ */
protected $description = 'Send a test email'; protected $description = 'Send a test email';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/** /**
* Execute the console command. * Execute the console command.
* *
@ -42,11 +35,20 @@ class TestEmail extends Command
*/ */
public function handle() public function handle()
{ {
Config::set('site',Site::findOrFail($this->argument('site'))); Config::set(
'site',
$this->option('site')
? Site::findOrFail($this->option('site'))
: Site::where('url',config('app.url'))->sole()
);
$uo = User::find($this->argument('id')); $uo = User::find($this->argument('id'));
Mail::to($this->argument('email') ?? $uo->email) $result = Mail::to($this->argument('email') ?? $uo->email)
->send(new MailTest($uo)); ->send(new Test($uo));
$this->info($result->getMessageId());
return self::SUCCESS;
} }
} }

View File

@ -3,7 +3,6 @@
namespace App\Console\Commands; namespace App\Console\Commands;
use Illuminate\Support\Facades\Config; use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Mail;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use App\Models\{Invoice,Site}; use App\Models\{Invoice,Site};
@ -15,7 +14,9 @@ class InvoiceEmail extends Command
* *
* @var string * @var string
*/ */
protected $signature = 'invoice:email {site} {id}'; protected $signature = 'invoice:email'
.' {--s|site : Site ID}'
.' {id?}';
/** /**
* The console command description. * The console command description.
@ -31,21 +32,23 @@ class InvoiceEmail extends Command
*/ */
public function handle() public function handle()
{ {
Config::set('site',Site::findOrFail($this->argument('site'))); Config::set(
'site',
$this->option('site')
? Site::findOrFail($this->option('site'))
: Site::where('url',config('app.url'))->sole()
);
$o = Invoice::findOrFail($this->argument('id')); $o = Invoice::findOrFail($this->argument('id'));
$result = Mail::to($o->account->user->email)->send(new \App\Mail\InvoiceEmail($o));
try { try {
$o->print_status = TRUE; $o->send();
//$o->reminders = $o->reminders('send');
$o->save(); $o->save();
} catch (\Exception $e) { } catch (\Exception $e) {
dd($e); dd($e);
} }
dump($result->getDebug()); return self::SUCCESS;
} }
} }

View File

@ -2,6 +2,7 @@
namespace App\Console\Commands; namespace App\Console\Commands;
use Carbon\Carbon;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Illuminate\Support\Facades\Config; use Illuminate\Support\Facades\Config;
@ -14,7 +15,11 @@ class InvoiceGenerate extends Command
* *
* @var string * @var string
*/ */
protected $signature = 'invoice:generate {site} {account?} {--p|preview : Preview} {--l|list : List Items}'; protected $signature = 'invoice:generate'
.' {--l|list : List Items}'
.' {--p|preview : Preview}'
.' {--s|site : Site ID}'
.' {id?}';
/** /**
* The console command description. * The console command description.
@ -30,37 +35,50 @@ class InvoiceGenerate extends Command
*/ */
public function handle() public function handle()
{ {
Config::set('site',Site::findOrFail($this->argument('site'))); Config::set(
'site',
$this->option('site')
? Site::findOrFail($this->option('site'))
: Site::where('url',config('app.url'))->sole()
);
if ($this->argument('account')) if ($this->argument('id'))
$accounts = collect()->push(Account::find($this->argument('account'))); $accounts = collect()->push(Account::find($this->argument('id')));
else else
$accounts = Account::active()->get(); $accounts = Account::active()->get();
foreach ($accounts as $o) { foreach ($accounts as $o) {
$items = $o->invoice_next(Carbon::now());
if (! $items->count()) {
$this->warn(sprintf('No items for account (%s) [%d]',$o->name,$o->id));
continue;
}
$this->info(sprintf('Account: %s [%d]',$o->name,$o->lid));
$io = new Invoice; $io = new Invoice;
$io->account_id = $o->id; $io->account_id = $o->id;
foreach ($o->services(TRUE)->get() as $so) { foreach ($items as $oo)
foreach ($so->next_invoice_items(FALSE) as $ooo) $io->items_active->push($oo);
$io->items->push($ooo);
}
// If there are no items, no reason to do anything // If there are no items, no reason to do anything
if (! $io->items->count() OR $io->total < 0) if ($io->total < 0) {
$this->warn(sprintf(' - Invoice totals [%3.2f] - skipping',$io->total));
continue; continue;
}
$io->account_id = $o->id; $io->account_id = $o->id;
if ($this->option('list')) { if ($this->option('list')) {
$this->warn(sprintf('|%4s|%4s|%-50s|%8s|', $this->line(sprintf('|%4s|%4s|%-50s|%8s|',
'SID', 'SID',
'PID', 'PID',
'Name', 'Name',
'Amount', 'Amount',
)); ));
foreach ($io->items as $oo) { foreach ($io->items_active as $oo) {
$this->info(sprintf('|%4s|%4s|%-50s|%8.2f|', $this->info(sprintf('|%4s|%4s|%-50s|%8.2f|',
$oo->service_id, $oo->service_id,
$oo->product_id, $oo->product_id,
@ -70,8 +88,9 @@ class InvoiceGenerate extends Command
} }
} }
//dump($io);
if ($this->option('preview')) { if ($this->option('preview')) {
$this->info(sprintf('Invoice for Account [%d] - [%d] items totalling [%3.2f]',$o->id,$io->items->count(),$io->total)); $this->info(sprintf('=> Invoice for Account [%d] - [%d] items totalling [%3.2f]',$o->id,$io->items_active->count(),$io->total));
continue; continue;
} }
@ -81,5 +100,7 @@ class InvoiceGenerate extends Command
$io->pushNew(); $io->pushNew();
} }
return self::SUCCESS;
} }
} }

View File

@ -3,14 +3,10 @@
namespace App\Http\Controllers\Auth; namespace App\Http\Controllers\Auth;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Mail;
use Laravel\Socialite\Facades\Socialite; use Laravel\Socialite\Facades\Socialite;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Mail\SocialLink;
use App\Models\{ProviderOauth,ProviderToken,User,UserOauth}; use App\Models\{ProviderOauth,ProviderToken,User,UserOauth};
class SocialLoginController extends Controller class SocialLoginController extends Controller
@ -105,46 +101,4 @@ class SocialLoginController extends Controller
->intended('/home') ->intended('/home')
->with('success','Token refreshed.'); ->with('success','Token refreshed.');
} }
/**
* We have identified the user and oauth, just need them to confirm the link
*
* @param $provider
* @param UserOauth $ao
* @param User $uo
* @return \Illuminate\View\View
*/
public function link($provider,UserOauth $ao,User $uo): \Illuminate\View\View
{
// @note If this is sent now (send()), it results in the caller to be executed a second time (handleProviderCallback()).
Mail::to($uo->email)->queue(new SocialLink($ao));
return view('auth.social_link')
->with('oauthid',$ao->id)
->with('provider',$provider);
}
public function linkcomplete(Request $request,$provider)
{
// Load our oauth id
$aoo = UserOauth::findOrFail($request->post('oauthid'));
// Check our email matches
if (Arr::get($aoo->oauth_data,'email','invalid') !== $request->post('email'))
return redirect('/login')->with('error','Account details didnt match to make link.');
// Check our token matches
if ($aoo->link_token !== $request->post('token'))
return redirect('/login')->with('error','Token details didnt match to make link.');
// Load our email.
$uo = User::where('email',$request->post('email'))->firstOrFail();
$aoo->user_id = $uo->id;
$aoo->save();
Auth::login($uo);
return redirect()
->intended('/home');
}
} }

View File

@ -8,7 +8,6 @@ use Illuminate\Http\Request;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Session; use Illuminate\Support\Facades\Session;
@ -18,7 +17,7 @@ use Illuminate\Validation\ValidationException;
use Illuminate\View\View; use Illuminate\View\View;
use Symfony\Component\HttpKernel\Exception\HttpException; use Symfony\Component\HttpKernel\Exception\HttpException;
use App\Http\Requests\{ChargeAdd,ServiceChangeRequest}; use App\Http\Requests\ServiceChangeRequest;
use App\Mail\{CancelRequest,ChangeRequest}; use App\Mail\{CancelRequest,ChangeRequest};
use App\Models\{Charge,Invoice,Product,Service}; use App\Models\{Charge,Invoice,Product,Service};
@ -274,7 +273,7 @@ class ServiceController extends Controller
*/ */
public function domain_list(): View public function domain_list(): View
{ {
$o = Service\Domain::serviceActive() $o = Service\Domain::ServiceActive()
->serviceUserAuthorised(Auth::user()) ->serviceUserAuthorised(Auth::user())
->select('service_domain.*') ->select('service_domain.*')
->join('services',['services.id'=>'service_domain.service_id']) ->join('services',['services.id'=>'service_domain.service_id'])
@ -287,8 +286,7 @@ class ServiceController extends Controller
public function email_list(): View public function email_list(): View
{ {
// @todo Need to add the with path when calculating next_billed and price $o = Service\Email::ServiceActive()
$o = Service\Email::serviceActive()
->serviceUserAuthorised(Auth::user()) ->serviceUserAuthorised(Auth::user())
->select('service_email.*') ->select('service_email.*')
->join('services',['services.id'=>'service_email.service_id']) ->join('services',['services.id'=>'service_email.service_id'])
@ -314,15 +312,14 @@ class ServiceController extends Controller
public function hosting_list(): View public function hosting_list(): View
{ {
// @todo Need to add the with path when calculating next_billed and price $o = Service\Host::ServiceActive()
$o = Service\Host::serviceActive()
->serviceUserAuthorised(Auth::user()) ->serviceUserAuthorised(Auth::user())
->select('service_host.*') ->select('service_host.*')
->join('services',['services.id'=>'service_host.service_id']) ->join('services',['services.id'=>'service_host.service_id'])
->with(['service.account','service.product.type.supplied.supplier_detail.supplier','tld']) ->with(['service.account','service.product.type.supplied.supplier_detail.supplier','tld'])
->get(); ->get();
return view('theme.backend.adminlte.service.host.list') return view('theme.backend.adminlte.service.hosting.list')
->with('o',$o); ->with('o',$o);
} }
@ -471,7 +468,7 @@ class ServiceController extends Controller
$o->type->save(); $o->type->save();
if ($validated->has('invoice_next_at')) if ($validated->has('invoice_next_at'))
$o->invoice_next_at = $validated->get('invoice_next_at'); $o->invoice_next_at = $validated?->get('invoice_next_at');
if ($validated->has('recur_schedule')) if ($validated->has('recur_schedule'))
$o->recur_schedule = $validated->get('recur_schedule'); $o->recur_schedule = $validated->get('recur_schedule');

View File

@ -52,7 +52,7 @@ class CancelRequest extends Mailable
} }
return $this return $this
->markdown('email.admin.service.cancel') ->markdown('mail.admin.service.cancel')
->subject($subject) ->subject($subject)
->with(['site'=>$this->service->site]); ->with(['site'=>$this->service->site]);
} }

View File

@ -52,7 +52,7 @@ class ChangeRequest extends Mailable
} }
return $this return $this
->markdown('email.admin.service.change') ->markdown('mail.admin.service.change')
->subject($subject) ->subject($subject)
->with(['site'=>$this->service->site]); ->with(['site'=>$this->service->site]);
} }

View File

@ -2,21 +2,20 @@
namespace App\Mail; namespace App\Mail;
use App\Models\Site;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable; use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Config;
use App\Models\Invoice; use App\Models\Invoice;
class InvoiceEmail extends Mailable class InvoiceEmail extends Mailable implements ShouldQueue
{ {
use Queueable, SerializesModels; use Queueable, SerializesModels;
public $invoice; protected Invoice $io;
public $site;
/** /**
* Create a new message instance. * Create a new message instance.
@ -25,28 +24,33 @@ class InvoiceEmail extends Mailable
*/ */
public function __construct(Invoice $o) public function __construct(Invoice $o)
{ {
$this->invoice = $o; $this->io = $o;
$this->queue = 'user';
} }
/** /**
* Build the message. * Get the message envelope.
*
* @return $this
*/ */
public function build() public function envelope(): Envelope
{ {
Config::set('site',Site::findOrFail($this->invoice->site_id)); return new Envelope(
$this->site = config('site'); subject: sprintf('Invoice %d for services, due %s',
$this->io->lid,
$this->io->due_at->format('Y-m-d')),
);
}
return $this /**
->markdown('email.user.invoice',['site'=>config('site')]) * Get the message content definition.
->subject(sprintf( 'Invoice: %s - Total: $%s - Due: %s', */
$this->invoice->id, public function content(): Content
number_format($this->invoice->total,2), {
$this->invoice->due_at->format('Y-m-d'))) return new Content(
->with([ markdown: 'mail.invoice',
'user'=>$this->invoice->account->user, with: [
'site'=>$this->invoice->account->user->site, 'io'=>$this->io,
]); 'site'=>$this->io->site,
]
);
} }
} }

View File

@ -0,0 +1,57 @@
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;
use App\Models\Invoice;
class InvoiceGeneratedAdmin extends Mailable implements ShouldQueue
{
use Queueable, SerializesModels;
protected Invoice $io;
/**
* Create a new message instance.
*
* @param Invoice $o
*/
public function __construct(Invoice $o)
{
$this->io = $o;
$this->queue = 'admin';
}
/**
* Get the message envelope.
*/
public function envelope(): Envelope
{
return new Envelope(
subject: sprintf('Invoice %d generated for %s, due %s',
$this->io->lid,
$this->io->account->name,
$this->io->due_at->format('Y-m-d')),
);
}
/**
* Get the message content definition.
*/
public function content(): Content
{
return new Content(
markdown: 'mail.admin.invoice.generated',
with: [
'io'=>$this->io,
'site'=>$this->io->site,
]
);
}
}

View File

@ -52,7 +52,7 @@ class OrderRequest extends Mailable
} }
return $this return $this
->markdown('email.admin.order.approve') ->markdown('mail.admin.order.approve')
->subject($subject) ->subject($subject)
->with(['site'=>$this->service->site]); ->with(['site'=>$this->service->site]);
} }

View File

@ -51,7 +51,7 @@ class OrderRequestApprove extends Mailable
} }
return $this return $this
->markdown('email.admin.order.approve') ->markdown('mail.admin.order.approve')
->subject($subject) ->subject($subject)
->with(['site'=>$this->so->site]); ->with(['site'=>$this->so->site]);
} }

View File

@ -38,7 +38,7 @@ class OrderRequestReject extends Mailable
Config::set('site',$this->service->site); Config::set('site',$this->service->site);
return $this return $this
->markdown('email.admin.order.reject') ->markdown('mail.admin.order.reject')
->subject(sprintf('Your order: #%s was rejected',$this->service->id)) ->subject(sprintf('Your order: #%s was rejected',$this->service->id))
->with(['site'=>$this->service->site]); ->with(['site'=>$this->service->site]);
} }

View File

@ -1,48 +0,0 @@
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Config;
use App\Models\{Site,User,UserOauth};
class SocialLink extends Mailable
{
use Queueable, SerializesModels;
public string $token;
public Site $site;
public ?User $user;
/**
* Create a new message instance.
*
* @param UserOauth $o
*/
public function __construct(UserOauth $o)
{
$this->site = $o->site;
$this->token = $o->link_token;
$this->user = $o->user;
}
/**
* Build the message.
*
* @return $this
*/
public function build()
{
Config::set('site',$this->site);
return $this
->markdown('email.system.social_link')
->subject('Link your Account')
->with([
'site'=>$this->site,
]);
}
}

View File

@ -3,14 +3,12 @@
namespace App\Mail; namespace App\Mail;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable; use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Config;
use App\Models\User; use App\Models\User;
class TestEmail extends Mailable class Test extends Mailable
{ {
use Queueable, SerializesModels; use Queueable, SerializesModels;
@ -33,10 +31,8 @@ class TestEmail extends Mailable
*/ */
public function build() public function build()
{ {
Config::set('site',$this->user->site);
return $this return $this
->markdown('email.system.test_email') ->markdown('mail.test')
->subject('Just a test...') ->subject('Just a test...')
->with([ ->with([
'site'=>$this->user->site, 'site'=>$this->user->site,

View File

@ -4,7 +4,6 @@ namespace App\Mail;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable; use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Config; use Illuminate\Support\Facades\Config;
@ -36,10 +35,10 @@ class TrafficMismatch extends Mailable
*/ */
public function build() public function build()
{ {
Config::set('site',$x=Site::find(1)); // @todo To auto determine; Config::set('site',$x=Site::where('url',config('app.url'))->sole());
return $this return $this
->markdown('email.system.broadband_traffic_mismatch') ->markdown('mail.admin.service.traffic_mismatch')
->subject('Traffic Mismatch for '.$this->date) ->subject('Traffic Mismatch for '.$this->date)
->with([ ->with([
'site'=>$x, 'site'=>$x,

View File

@ -253,13 +253,15 @@ class Account extends Model implements IDs
/* METHODS */ /* METHODS */
public function invoice_next(): Collection public function invoice_next(Carbon $date=NULL): Collection
{ {
// Collect all the invoice items for our active services $svs = $this
$nextdate = ($x=$this
->services_active ->services_active
->filter(fn($item)=>$item->isBilled() && $item->invoice_next) ->filter(fn($item)=>$item->isBilled() && $item->invoice_next)
->sortBy(fn($item)=>(string)$item->invoice_next)) ->sortBy(fn($item)=>(string)$item->invoice_next);
// Collect all the invoice items for our active services
$nextdate = $date ?: $svs
->first() ->first()
?->invoice_next ?->invoice_next
->clone(); ->clone();
@ -271,7 +273,7 @@ class Account extends Model implements IDs
->subDay() ->subDay()
->endOfday(); ->endOfday();
$items = $x $items = $svs
->filter(fn($item)=>$item->invoice_next->lessThan($nextitemsdate)) ->filter(fn($item)=>$item->invoice_next->lessThan($nextitemsdate))
->sortBy(fn($item)=>$item->invoice_next.$item->name) ->sortBy(fn($item)=>$item->invoice_next.$item->name)
->map(fn($item)=>$item->next_invoice_items($nextitemsdate)) ->map(fn($item)=>$item->next_invoice_items($nextitemsdate))

View File

@ -75,7 +75,7 @@ class Charge extends Model
return sprintf('%s %s', return sprintf('%s %s',
$this->description, $this->description,
$this->getAttribute('attributes') $this->getAttribute('attributes')
? join('|',unserialize($this->getAttribute('attributes'))) ? $this->getAttribute('attributes')->join('|')
: ''); : '');
} }

View File

@ -8,12 +8,14 @@ use Clarkeash\Doorman\Models\Invite;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Mail;
use Leenooks\Casts\LeenooksCarbon; use Leenooks\Casts\LeenooksCarbon;
use Leenooks\Traits\ScopeActive; use Leenooks\Traits\ScopeActive;
use App\Casts\CollectionOrNull; use App\Casts\CollectionOrNull;
use App\Interfaces\IDs; use App\Interfaces\IDs;
use App\Traits\PushNew; use App\Mail\{InvoiceEmail,InvoiceGeneratedAdmin};
use App\Traits\{PushNew,SiteID};
/** /**
* Class Invoice * Class Invoice
@ -36,7 +38,7 @@ use App\Traits\PushNew;
*/ */
class Invoice extends Model implements IDs class Invoice extends Model implements IDs
{ {
use PushNew,ScopeActive; use PushNew,ScopeActive,SiteID;
protected $casts = [ protected $casts = [
'created_at' => 'datetime:Y-m-d', 'created_at' => 'datetime:Y-m-d',
@ -95,8 +97,8 @@ class Invoice extends Model implements IDs
], ],
]; ];
// Array of items that can be updated with PushNew // Our related items that need to be updated when we call pushNew()
protected $pushable = ['items']; protected $pushable = ['items_active'];
protected $with = [ protected $with = [
'items_active:id,start_at,stop_at,quantity,price_base,discount_amt,item_type,product_id,service_id,invoice_id', 'items_active:id,start_at,stop_at,quantity,price_base,discount_amt,item_type,product_id,service_id,invoice_id',
@ -108,6 +110,21 @@ class Invoice extends Model implements IDs
/* STATIC METHODS */ /* STATIC METHODS */
public static function boot()
{
parent::boot();
static::created(function($model) {
// Send an email to an admin that the invoice was created
$uo = User::where('email',config('osb.admin'))->sole();
Mail::to($uo->email)
->send(new InvoiceGeneratedAdmin($model));
// @todo Queue an email to the user
});
}
/** /**
* This works out what multiplier to use to change billing periods * This works out what multiplier to use to change billing periods
* *
@ -565,6 +582,28 @@ class Invoice extends Model implements IDs
return parent::save($options); return parent::save($options);
} }
/**
* Record the invoice being sent
*
* @return int
*/
public function send(): int
{
$result = Mail::to($this->account->user->email)
->send(new InvoiceEmail($this));
$this->print_status = TRUE;
if ($this->reminders->has('sent'))
$this->reminders->put('sent',collect($this->reminders->get('sent')));
else
$this->reminders->put('sent',collect());
$this->reminders->get('sent')->push(Carbon::now());
return $result;
}
/** /**
* Group the invoice items by product ID, returning the number of products and total * Group the invoice items by product ID, returning the number of products and total
* *
@ -574,12 +613,20 @@ class Invoice extends Model implements IDs
{ {
$return = collect(); $return = collect();
foreach ($this->items_active->groupBy('product_id') as $o) { foreach ($this->items_active->groupBy('product_id') as $id => $o) {
if (! $id) {
$po = new Product;
$po->translate = new ProductTranslate;
$po->translate->name_detail = 'Miscellanious';
} else {
$po = $o->first()->product; $po = $o->first()->product;
}
$po->count = count($o->pluck('service_id')->unique()); $po->count = count($o->pluck('service_id')->unique());
$return->push([ $return->push([
'product' => $o->first()->product, 'product' => $po,
'services' => $o->pluck('service_id')->unique(), 'services' => $o->pluck('service_id')->unique(),
'sub_total' => $o->sum('sub_total'), 'sub_total' => $o->sum('sub_total'),
'tax_total' => $o->sum('tax'), 'tax_total' => $o->sum('tax'),
@ -589,4 +636,21 @@ class Invoice extends Model implements IDs
return $return->sortBy('product.name'); return $return->sortBy('product.name');
} }
public function summary_other(): Collection
{
$result = collect();
foreach ($this->items_active->whereNull('service_id') as $o) {
dd($o);
$result->push([
'description' => 'Account Items',
'sub_total' => $o->sub_total,
'tax_total' => $o->tax,
'total' => $o->total,
]);
}
return $result;
}
} }

View File

@ -52,6 +52,22 @@ class InvoiceItem extends Model
127 => 'Rounding', // * SERVICE_ID is NULL, MODULE_ID is NULL, MODULE_REF is NULL 127 => 'Rounding', // * SERVICE_ID is NULL, MODULE_ID is NULL, MODULE_REF is NULL
]; ];
/* STATIC */
public static function boot()
{
parent::boot();
static::created(function($model) {
// If this items were a charge, we'll update the charge to processed
if (($model->module_id === 30) && $model->module_ref) {
$o = Charge::findOrfail($model->module_ref);
$o->processed = TRUE;
$o->save();
}
});
}
/* RELATIONS */ /* RELATIONS */
public function invoice() public function invoice()

View File

@ -64,12 +64,14 @@ abstract class Type extends Model implements ServiceItem
public function hasExpired(): bool public function hasExpired(): bool
{ {
return (! $this->inContract()) && ($this->service->invoice_next_at && $this->service->invoice_next_at->isPast()); return (! $this->inContract())
&& ($this->service->invoice_next && $this->service->invoice_next->isPast());
} }
public function inContract(): bool public function inContract(): bool
{ {
return $this->expire_at && $this->expire_at->isFuture(); return $this->expire_at
&& $this->expire_at->isFuture();
} }
/* ATTRIBUTES */ /* ATTRIBUTES */

View File

@ -72,7 +72,8 @@ class User extends Authenticatable implements IDs
*/ */
public function sendPasswordResetNotification($token) public function sendPasswordResetNotification($token)
{ {
$this->notify((new ResetPasswordNotification($token))->onQueue('high')); $this->notify((new ResetPasswordNotification($token))
->onQueue('user'));
} }
/* INTERFACES */ /* INTERFACES */

View File

@ -25,7 +25,7 @@ class ResetPassword extends ResetPasswordNotification implements ShouldQueue
} }
return (new MailMessage) return (new MailMessage)
->markdown('email.user.passwordreset',[ ->markdown('mail.password.reset',[
'site'=>$notifiable->site, 'site'=>$notifiable->site,
'user'=>$notifiable, 'user'=>$notifiable,
'reset_link'=>route('password.reset',$this->token,true), 'reset_link'=>route('password.reset',$this->token,true),

View File

@ -8,6 +8,7 @@
namespace App\Traits; namespace App\Traits;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\Log;
trait PushNew trait PushNew
{ {
@ -22,7 +23,7 @@ trait PushNew
// us to recurse into all of these nested relations for the model instance. // us to recurse into all of these nested relations for the model instance.
foreach ($this->relations as $key => $models) { foreach ($this->relations as $key => $models) {
// If we are not pushable, jump to the next item. // If we are not pushable, jump to the next item.
if (! is_array($this->pushable) OR ! in_array($key,$this->pushable)) if ((! is_array($this->pushable)) || (! in_array($key,$this->pushable)))
continue; continue;
$models = $models instanceof Collection $models = $models instanceof Collection
@ -32,6 +33,8 @@ trait PushNew
$model->setAttribute($this->getForeignKey(),$this->{$this->getKeyName()}); $model->setAttribute($this->getForeignKey(),$this->{$this->getKeyName()});
if (! $model->pushNew()) { if (! $model->pushNew()) {
Log::alert('Failed to save model',['attrs'=>$model->getAttributes()]);
return false; return false;
} }
} }

6
composer.lock generated
View File

@ -3056,11 +3056,11 @@
}, },
{ {
"name": "leenooks/laravel", "name": "leenooks/laravel",
"version": "11.1.8", "version": "11.1.9",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://gitea.dege.au/laravel/leenooks.git", "url": "https://gitea.dege.au/laravel/leenooks.git",
"reference": "8acc3a91af7e4ce622aec60934a2f942b4a19eda" "reference": "293d9913c65143f00e3051bff32d15c97644bd15"
}, },
"type": "library", "type": "library",
"extra": { "extra": {
@ -3093,7 +3093,7 @@
"laravel", "laravel",
"leenooks" "leenooks"
], ],
"time": "2024-07-31T12:40:35+00:00" "time": "2024-08-01T07:32:20+00:00"
}, },
{ {
"name": "leenooks/passkey", "name": "leenooks/passkey",

View File

@ -4,5 +4,6 @@ return [
'language_id' => 1, 'language_id' => 1,
'invoice_text' => 'Thank you for using our Internet Services.', 'invoice_text' => 'Thank you for using our Internet Services.',
'invoice_days' => 30, // Days in Advance to invoice 'invoice_days' => 30, // Days in Advance to invoice
'invoice_review' => 3, // Days to review an invoice before it is emailed
'admin' => env('APP_ADMIN'), 'admin' => env('APP_ADMIN'),
]; ];

View File

@ -0,0 +1,30 @@
@use(App\Models\TLD)
<div class="form-group has-validation">
@if(isset($label))
<label for="domain_name">{{ $label }}</label>
@endif
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-fw fa-globe-asia"></i></span>
</div>
<input type="text" class="form-control col-9 text-right @error($name.'.domain_name')is-invalid @enderror" id="domain_name" name="{{ $name }}[domain_name]" placeholder="Domain Name..." value="{{ old($name.'.domain_name',$nameValue ?? '') }}" required>
<div class="input-group-append">
<span class="input-group-text">.</span>
</div>
<select class="form-control col-3" name="{{ $name }}[tld_id]">
@foreach(TLD::orderBy('name')->get() as $oo)
<option value="{{ $oo->id }}" @selected($oo->id == old($name.'.tld_id',$tldValue ?? ''))>{{ $oo->name }}</option>
@endforeach
</select>
<span class="invalid-feedback" role="alert">
@error($name.'.domain_name')
{{ $message }}
@else
Domain Name is required.
@enderror
</span>
</div>
<span class="input-helper">Domain Name</span>
</div>

View File

@ -1,21 +0,0 @@
@component('mail::message',['site'=>$site,'heading'=>'Link Your Account'])
Hi {{ isset($user) ? $user->name_full.',' : '' }}
A request was made to link your account to a social login.
If you didnt make this request, you can ignore this, and the request will be ignored.
If you did make the request, then please enter the code displayed below.
@component('mail::panel')
{{ $token }}
@endcomponent
Once you've keyed in this code, you'll be able to login to your account using your social login instead of a username and a password.
Thanks,
{{ config('mail.from.name') }}
@component('mail::subcopy')
If you didnt make this request, you can safely ignore this email - no change was made to your account, nor was it accessed by an unauthorised person.
@endcomponent
@endcomponent

View File

@ -1,27 +0,0 @@
@component('mail::message',['site'=>$site,'heading'=>'Invoice: '.$invoice->id])
Hi {{ isset($user) ? $user->name_full.',' : '' }}
A new invoice has been generated on your account. A summary of that invoice is below.
@component('mail::table')
| # | ID | Name | Amount |
| -: | - |:-----| ------:|
@foreach ($invoice->summary_products() as $item)
| {{ $item['services']->count() }} | {{ $item['product']->lid }} | {{ $item['product']->name }} | ${{ number_format($item['total'],2) }} |
@endforeach
||| Sub Total | ${{ number_format($invoice->sub_total,2) }} |
||| Tax | ${{ number_format($invoice->tax_total,2) }} |
||| Total | ${{ number_format($invoice->total,2) }} |
||| Payments | ${{ number_format($invoice->paid,2) }} |
||| Still Due | ${{ number_format($invoice->due,2) }} |
@endcomponent
If you would like a PDF copy of that invoice, please click on the link below:
@component('mail::panel',['url'=>$invoice->download_link()])
Download PDF
@endcomponent
Thanks,<br>
{{ config('mail.from.name') }}
@endcomponent

View File

@ -0,0 +1,25 @@
@component('mail::message',['site'=>$site,'heading'=>'Invoice: #'.$io->id,'subheading'=>sprintf('Due: <strong>%s</strong>',$io->due_at->format('Y-m-d'))])
Hi {{ isset($user) ? $user->name_full.',' : '' }}
A new invoice has been generated for <strong>{{ $io->account->name }}</strong>. A summary of that invoice is below:
@component('mail::table')
| ID | # | Name | Amount |
| -: | -: |:-----| ------:|
@foreach ($io->summary_products() as $item)
| {{ $item['product']->lid }} | {{ $item['services']->count() }} | {{ $item['product']->name }} | ${{ number_format($item['total'],2) }} |
@endforeach
||| Sub Total | ${{ number_format($io->sub_total,2) }} |
||| Tax | ${{ number_format($io->tax_total,2) }} |
||| **Total** | **${{ number_format($io->total,2) }}** |
@endcomponent
This invoice will be automatically emailed in {{ config('osb.invoice_review') }} days time.
@component('mail::button',['url'=>url('u/invoice',$io->id)])
Review
@endcomponent
Thanks,<br>
{{ config('mail.from.name') }}
@endcomponent

View File

@ -1,8 +1,6 @@
@component('mail::message',['site'=>$site]) @component('mail::message',['site'=>$site])
# Your order was rejected. # Your order was rejected.
@component('mail::panel')
@component('mail::table') @component('mail::table')
| Service | Details | | Service | Details |
| :---------- | :---------------- | | :---------- | :---------------- |
@ -22,8 +20,6 @@
**REASON:** {{ $reason }} **REASON:** {{ $reason }}
@endcomponent
Thanks,<br> Thanks,<br>
{{ config('app.name') }} {{ config('app.name') }}
@endcomponent @endcomponent

View File

@ -0,0 +1,29 @@
@component('mail::message',['site'=>$site,'heading'=>'Invoice: #'.$io->lid,'subheading'=>sprintf('Due: <strong>%s</strong>',$io->due_at->format('Y-m-d'))])
Hi {{ isset($user) ? $user->name_full.',' : '' }}
A new invoice has been generated on your account. A summary of that invoice is below:
@component('mail::table')
| ID | # | Name | Amount |
| -: | -: |:-----| ------:|
@foreach ($io->summary_products() as $item)
| {{ $item['product']->lid }} | {{ $item['services']->count() }} | {{ $item['product']->name }} | ${{ number_format($item['total'],2) }} |
@endforeach
||| Sub Total | ${{ number_format($io->sub_total,2) }} |
||| Tax | ${{ number_format($io->tax_total,2) }} |
||| **Total** | **${{ number_format($io->total,2) }}** |
@if($io->paid)
||| Payments | ${{ number_format($io->paid,2) }} |
||| Still Due | ${{ number_format($io->due,2) }} |
@endif
@endcomponent
If you would like a PDF copy of that invoice, please click on the link below:
@component('mail::button',['url'=>$io->download_link()])
Download
@endcomponent
Thanks,<br>
{{ config('mail.from.name') }}
@endcomponent

View File

@ -1,17 +1,19 @@
@component('mail::message',['site'=>$site,'heading'=>'Password Reset']) @component('mail::message',['site'=>$site,'heading'=>'Password Reset'])
Hi {{ isset($user) ? $user->name_full.',' : '' }} Hi {{ isset($user) ? $user->name_full.',' : '' }}
You are receiving this email because we received a password reset request for your account. 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.
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: To reset your password, please follow this link, or click on the URL below:
@component('mail::panel',['url'=>$reset_link]) @component('mail::button',['url'=>$reset_link])
Reset Password Reset Password
@endcomponent @endcomponent
@component('mail::subcopy') @component('mail::subcontent')
Reset password: {{ $reset_link }} If the button above doesnt work, you can copy and paste this link into your browser, and take it from there:
```
{{ $reset_link }}
```
@endcomponent @endcomponent
Thanks,<br> Thanks,<br>

View File

@ -1,37 +0,0 @@
@if(($x=$list)->count())
<div class="card card-light">
<div class="card-header">
<h1 class="card-title">Pending Charges</h1>
</div>
<div class="card-body">
<table class="table table-hover">
<thead>
<tr>
<th>ID</th>
<th>Date Created</th>
<th>Date Charge</th>
<th>Service</th>
<th>Type</th>
<th>Description</th>
<th class="text-right">Total</th>
</tr>
</thead>
<tbody>
@foreach ($x as $co)
<tr>
<td><a href="{{ url('a/charge/addedit',[$co->id]) }}">{{ $co->id }}</a></td>
<td>{{ $co->created_at->format('Y-m-d') }}</td>
<td>{{ $co->charge_at ? $co->charge_at->format('Y-m-d') : '-' }}</td>
<td>{{ $co->service->sid }}</td>
<td>{{ $co->type }}</td>
<td>{{ $co->description }}</td>
<td class="text-right">{{ number_format($co->quantity*$co->amount,2) }}</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
@endif

View File

@ -1,80 +0,0 @@
@extends('adminlte::layouts.auth')
@section('htmlheader_title')
Link Account
@endsection
@section('content')
<div class="login-box">
<div class="login-logo">
<a>{!! config('app.name_html_long') !!}</a>
</div>
<div class="alert alert-success">
<strong>NOTE:</strong> Link your account.<br><br>
<ul>
<li>An email has been sent to you with a token, please use those details here:</li>
</ul>
</div>
@if (count($errors) > 0)
<div class="alert alert-danger">
<strong>Whoops!</strong> {{ trans('adminlte_lang::message.someproblems') }}<br><br>
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<!-- /.login-logo -->
<div class="card">
<div class="card-body login-card-body">
<p class="login-box-msg">Link your account</p>
<form method="post" action="{{ url('/auth/'.$provider.'/linkcomplete') }}">
{{ csrf_field() }}
<input type="hidden" name="oauthid" value="{{ $oauthid }}">
<div class="input-group mb-3">
<input type="email" name="email" class="form-control" placeholder="Email">
<div class="input-group-append">
<span class="fa fa-envelope input-group-text"></span>
</div>
</div>
<div class="input-group mb-3">
<input type="password" name="token" class="form-control" placeholder="Token">
<div class="input-group-append">
<span class="fa fa-lock input-group-text"></span>
</div>
</div>
<div class="row">
<div class="col-8">
&nbsp;
</div>
<!-- /.col -->
<div class="col-4">
<button type="submit" name="submit" class="btn btn-primary btn-block btn-flat">Link</button>
</div>
<!-- /.col -->
</div>
</form>
<p class="mb-1">
<a name="reset" href="{{ url('/password/reset') }}">{{ trans('adminlte_lang::message.forgotpassword') }}</a>
</p>
@isset($register)
<p class="mb-0">
<a href="register.html" class="text-center">Register a new account</a>
</p>
@endisset
</div>
<!-- /.login-card-body -->
</div>
</div>
<!-- /.login-box -->
@endsection

View File

@ -1,19 +0,0 @@
<!-- Terms and conditions modal -->
<div class="modal fade" id="termsModal" tabindex="-1" role="dialog" aria-labelledby="Terms and conditions" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h3 class="modal-title">Terms and conditions</h3>
</div>
<div class="modal-body">
<p>Lorem ipsum dolor sit amet, veniam numquam has te. No suas nonumes recusabo mea, est ut graeci definitiones. His ne melius vituperata scriptorem, cum paulo copiosae conclusionemque at. Facer inermis ius in, ad brute nominati referrentur vis. Dicat erant sit ex. Phaedrum imperdiet scribentur vix no, ad latine similique forensibus vel.</p>
<p>Dolore populo vivendum vis eu, mei quaestio liberavisse ex. Electram necessitatibus ut vel, quo at probatus oportere, molestie conclusionemque pri cu. Brute augue tincidunt vim id, ne munere fierent rationibus mei. Ut pro volutpat praesent qualisque, an iisque scripta intellegebat eam.</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" data-dismiss="modal">OK</button>
</div>
</div>
</div>
</div>

View File

@ -1,43 +0,0 @@
<table class="table table-bordered w-100" id="invoices">
<thead>
<tr>
<th class="text-right">#</th>
<th class="text-right">Issued</th>
<th class="text-right">Due</th>
<th class="text-right">Total</th>
<th class="text-right">Payments</th>
<th class="text-right">Outstanding</th>
</tr>
</thead>
<tbody>
@foreach ($o->invoices as $io)
<tr>
<td class="text-right"><a href="{{ url('u/invoice',$io->id) }}">{{ $io->id }}</a></td>
<td class="text-right">{{ $io->created_at->format('Y-m-d') }}</td>
<td class="text-right">{{ $io->due_at->format('Y-m-d') }}</td>
<td class="text-right">${{ number_format($io->total,2) }}</td>
<td class="text-right">${{ number_format($io->paid,2) }}</td>
<td class="text-right">${{ number_format($io->due,2) }}</td>
</tr>
@endforeach
</tbody>
</table>
@section('page-scripts')
@css(datatables,bootstrap4)
@js(datatables,bootstrap4|responsive)
<script type="text/javascript">
$(document).ready(function() {
$('#invoices').DataTable( {
responsive: true,
order: [1, 'desc']
});
$('#invoices tbody').on('click','tr', function () {
$(this).toggleClass('selected');
});
});
</script>
@append

View File

@ -128,6 +128,19 @@
@endforeach @endforeach
@endforeach @endforeach
@endforeach @endforeach
@if($o->summary_other()->count())
<tr>
<td colspan="7">{{ $item['description'] }}</td>
</tr>
@foreach($o->summary_other() as $item)
<tr>
<td colspan="2">&nbsp;</td>
<td>{{ $item['description'] }}</td>
</tr>
@endforeach
@endif
</tbody> </tbody>
</table> </table>
</div> </div>

View File

@ -46,7 +46,7 @@
<td>{{ $oo->registrar->name }}</td> <td>{{ $oo->registrar->name }}</td>
<td>{{ $oo->registrar_ns }}</td> <td>{{ $oo->registrar_ns }}</td>
<td>@if ($oo->service->isBilled()) {{ $oo->service->invoice_next->format('Y-m-d') }}@else - @endif</td> <td>@if ($oo->service->isBilled()) {{ $oo->service->invoice_next->format('Y-m-d') }}@else - @endif</td>
<td>@if (! $oo->service->external_billing)${{ number_format($oo->service->next_invoice_items(TRUE)->sum('total'),2) }}@else - @endif</td> <td>@if (! $oo->service->external_billing)${{ number_format($oo->service->next_invoice_items()->sum('total'),2) }}@else - @endif</td>
<td>{{ $oo->service->billing_interval_string }}</td> <td>{{ $oo->service->billing_interval_string }}</td>
</tr> </tr>
@endforeach @endforeach
@ -58,15 +58,9 @@
</div> </div>
@endsection @endsection
@section('page-scripts') @pa(datatables,rowgroup)
@css(datatables,bootstrap4|rowgroup)
@js(datatables,bootstrap4|rowgroup)
<style> @section('page-scripts')
.strike {
text-decoration: line-through;
}
</style>
<script type="text/javascript"> <script type="text/javascript">
$(document).ready(function() { $(document).ready(function() {
$('#services').DataTable({ $('#services').DataTable({
@ -81,10 +75,6 @@
} }
], ],
}); });
$('tbody').on('click','tr', function () {
$(this).toggleClass('selected');
});
}); });
</script> </script>
@append @append

View File

@ -50,7 +50,7 @@
<td>@if($oo->admin_user){{ $oo->admin_user }}/{{ $oo->admin_pass }}@else &nbsp; @endif</td> <td>@if($oo->admin_user){{ $oo->admin_user }}/{{ $oo->admin_pass }}@else &nbsp; @endif</td>
<td class="text-right">{{ number_format($oo->accounts ?: 0) }}</td> <td class="text-right">{{ number_format($oo->accounts ?: 0) }}</td>
<td>@if ($oo->service->isBilled()) {{ $oo->service->invoice_next->format('Y-m-d') }} @else - @endif</td> <td>@if ($oo->service->isBilled()) {{ $oo->service->invoice_next->format('Y-m-d') }} @else - @endif</td>
<td>@if (! $oo->service->external_billing)${{ number_format($oo->service->next_invoice_items(TRUE)->sum('total'),2) }}@else - @endif</td> <td>@if (! $oo->service->external_billing)${{ number_format($oo->service->next_invoice_items()->sum('total'),2) }}@else - @endif</td>
<td>{{ $oo->service->billing_interval_string }}</td> <td>{{ $oo->service->billing_interval_string }}</td>
</tr> </tr>
@endforeach @endforeach
@ -62,15 +62,9 @@
</div> </div>
@endsection @endsection
@section('page-scripts') @pa(datatables,rowgroup)
@css(datatables,bootstrap4|rowgroup)
@js(datatables,bootstrap4|rowgroup)
<style> @section('page-scripts')
.strike {
text-decoration: line-through;
}
</style>
<script type="text/javascript"> <script type="text/javascript">
$(document).ready(function() { $(document).ready(function() {
$('#services').DataTable({ $('#services').DataTable({
@ -85,10 +79,6 @@
} }
], ],
}); });
$('tbody').on('click','tr', function () {
$(this).toggleClass('selected');
});
}); });
</script> </script>
@append @append

View File

@ -44,7 +44,7 @@
<td>{{ $oo->service_expire ? $oo->service_expire->format('Y-m-d') : '-' }}</td> <td>{{ $oo->service_expire ? $oo->service_expire->format('Y-m-d') : '-' }}</td>
<td>{{ $oo->service->product->supplier->name }}</td> <td>{{ $oo->service->product->supplier->name }}</td>
<td>@if ($oo->service->isBilled()) {{ $oo->service->invoice_next->format('Y-m-d') }}@else - @endif</td> <td>@if ($oo->service->isBilled()) {{ $oo->service->invoice_next->format('Y-m-d') }}@else - @endif</td>
<td>@if (! $oo->service->external_billing)${{ number_format($oo->service->next_invoice_items(TRUE)->sum('total'),2) }}@else - @endif</td> <td>@if (! $oo->service->external_billing)${{ number_format($oo->service->next_invoice_items()->sum('total'),2) }}@else - @endif</td>
<td>{{ $oo->service->billing_interval_string }}</td> <td>{{ $oo->service->billing_interval_string }}</td>
</tr> </tr>
@endforeach @endforeach
@ -56,15 +56,9 @@
</div> </div>
@endsection @endsection
@section('page-scripts') @pa(datatables,rowgroup)
@css(datatables,bootstrap4|rowgroup)
@js(datatables,bootstrap4|rowgroup)
<style> @section('page-scripts')
.strike {
text-decoration: line-through;
}
</style>
<script type="text/javascript"> <script type="text/javascript">
$(document).ready(function() { $(document).ready(function() {
$('#services').DataTable({ $('#services').DataTable({
@ -79,10 +73,6 @@
} }
], ],
}); });
$('tbody').on('click','tr', function () {
$(this).toggleClass('selected');
});
}); });
</script> </script>
@append @append

View File

@ -72,10 +72,6 @@
}, },
orderFixed: [1, 'asc'] orderFixed: [1, 'asc']
}); });
$('#service_movements tbody').on('click','tr', function () {
$(this).toggleClass('selected');
});
}); });
</script> </script>
@append @append

View File

@ -26,6 +26,7 @@
<strong>NOTE</strong>: A plan setup fee is normally not applicable to Broadband changes, but a plan change fee normally is. <strong>NOTE</strong>: A plan setup fee is normally not applicable to Broadband changes, but a plan change fee normally is.
</div> </div>
</div> </div>
@section('page-scripts') @section('page-scripts')
<script> <script>
$(document).ready(function() { $(document).ready(function() {

View File

@ -35,7 +35,7 @@
</div> </div>
<div class="col-12 col-sm-9 col-md-6 col-xl-5"> <div class="col-12 col-sm-9 col-md-6 col-xl-5">
<x-leenooks::form.date id="expire_at" name="broadband[expire_at]" icon="fa-calendar" label="Contract End" old="broadband.expire_at" :value="$o->expire_at?->format('Y-m-d') ?: ($o->connect_at?->addMonths($o->contract_term)->format('Y-m-d'))"/> <x-leenooks::form.date id="expire_at" name="broadband[expire_at]" icon="fa-calendar" label="Contract End" old="broadband.expire_at" :value="($o->expire_at ?: $o->connect_at?->addMonths($o->contract_term))?->format('Y-m-d')"/>
</div> </div>
</div> </div>

View File

@ -1,44 +1,15 @@
@use(App\Models\Domain)
@use(App\Models\DomainRegistrar)
@use(App\Models\Service\Domain,ServiceDomain)
<!-- o=Service\Domain::class -->
<div class="row"> <div class="row">
<div class="col-12 col-sm-9 col-md-12 col-xl-7"> <div class="col-12 col-sm-9 col-md-12 col-xl-7">
<div class="form-group"> <x-form.domain name="domain" label="Domain Name" :name_value="$o->domain_name" :tld_value="$o->tld_id"/>
<label for="domain_name">Domain Name</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-fw fa-globe-asia"></i></span>
</div>
<input type="text" style="width:55%;" class="form-control text-right @error('domain_name') is-invalid @enderror" id="domain_name" name="domain[domain_name]" placeholder="Domain Name..." value="{{ old('domain.domain_name',$o->domain_name) }}" required>
<div class="input-group-append">
<span class="input-group-text">.</span>
</div>
<select style="width:25%;" class="form-control @error('tld_id') is-invalid @enderror" id="tld_id" name="domain[tld_id]">
@foreach(\App\Models\TLD::orderBy('name')->get() as $oo)
<option value="{{ $oo->id }}" @if($oo->id == old('domain.tld_id',$o->tld_id))selected @endif>{{ $oo->name }}</option>
@endforeach
</select>
<span class="invalid-feedback" role="alert">
@error('domain_name')
{{ $message }}
@enderror
@error('tld_id')
{{ $message }}
@enderror
</span>
</div>
</div>
</div> </div>
<div class="col-12 col-sm-9 col-md-6 col-xl-5"> <div class="col-12 col-sm-9 col-md-6 col-xl-5">
@include('adminlte::widget.form_date',[ <x-leenooks::form.date id="expire_at" name="domain[expire_at]" icon="fa-calendar" label="Expiry" old="domain.expire_at" :value="$o->expire_at?->format('Y-m-d')"/>
'label'=>'Expiry',
'icon'=>'fas fa-calendar',
'id'=>'expire_at',
'old'=>'domain.expire_at',
'name'=>'domain[expire_at]',
'value'=>$o->expire_at ? $o->expire_at->format('Y-m-d') : ($o->connect_at ? $o->connect_at->addMonths($o->contract_term)->format('Y-m-d') : ''),
])
</div> </div>
</div> </div>
@ -47,77 +18,27 @@
<div class="row"> <div class="row">
<div class="col-12 col-sm-9 col-md-6 col-xl-5"> <div class="col-12 col-sm-9 col-md-6 col-xl-5">
@include('adminlte::widget.form_select',[ <x-leenooks::form.select id="domain_registrar_id" name="domain[domain_registrar_id]" icon="fa-handshake" label="Registrar" :value="$o->domain_registrar_id" :options="DomainRegistrar::active()->orderBy('name')->get()->transform(fn($item)=>['id'=>$item->id,'value'=>$item->name])"/>
'label'=>'Registrar',
'icon'=>'fas fa-handshake',
'id'=>'domain_registrar_id',
'old'=>'domain.domain_registrar_id',
'name'=>'domain[domain_registrar_id]',
'options'=>\App\Models\DomainRegistrar::active()->orderBy('name')->get()->transform(function($item) { return ['id'=>$item->id,'value'=>$item->name]; }),
'value'=>$o->domain_registrar_id ?? '',
])
</div> </div>
<div class="col-12 col-sm-9 col-md-12 col-xl-6"> <div class="col-12 col-sm-9 col-md-12 col-xl-6">
@include('adminlte::widget.form_text',[ <x-leenooks::form.text id="registrar_account" name="domain[registrar_account]" icon="fa-user-circle" label="Registrar Username" :value="$o->registrar_account"/>
'label'=>'Registrar Account',
'icon'=>'fas fa-user-circle',
'id'=>'registrar_account',
'old'=>'domain.registrar_account',
'name'=>'domain[registrar_account]',
'value'=>$o->registrar_account ?? '',
])
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-12 col-sm-9 col-md-6 col-xl-5"> <div class="col-12 col-sm-9 col-md-6 col-xl-5">
@include('adminlte::widget.form_select',[ <x-leenooks::form.select id="registrar_ns" name="domain[registrar_ns]" icon="fa-project-diagram" label="DNS Location" :value="$o->registrar_ns" :options="ServiceDomain::select('registrar_ns')->distinct()->orderBy('registrar_ns')->get()->transform(fn($item)=>['id'=>$item->registrar_ns,'value'=>$item->registrar_ns])"/>
'label'=>'DNS Location',
'icon'=>'fas fa-project-diagram',
'id'=>'registrar_ns',
'old'=>'domain.registrar_ns',
'name'=>'domain[registrar_ns]',
'options'=>\App\Models\Service\Domain::select('registrar_ns')->distinct()->orderBy('registrar_ns')->get()->transform(function($item) { return ['id'=>$item->registrar_ns,'value'=>$item->registrar_ns]; }),
'value'=>$o->registrar_ns ?? '',
'addvalues'=>TRUE,
])
</div> </div>
<div class="col-12 col-sm-9 col-md-12 col-xl-6"> <div class="col-12 col-sm-9 col-md-12 col-xl-6">
@include('adminlte::widget.form_text',[ <x-leenooks::form.text id="registrar_username" name="domain[registrar_username]" icon="fa-user" label="Registrar Username" :value="$o->registrar_username"/>
'label'=>'Registrar Username',
'icon'=>'fas fa-user',
'id'=>'registrar_username',
'old'=>'domain.registrar_username',
'name'=>'domain[registrar_username]',
'value'=>$o->registrar_username ?? '',
])
</div> </div>
</div> </div>
<hr> @pa(select2,autofocus)
<p class="h6">Service Dates</p>
<div class="row">
<div class="col-12 col-sm-9 col-md-6 col-xl-5">
@include('adminlte::widget.form_date',[
'label'=>'Registered Date',
'icon'=>'fas fa-calendar',
'id'=>'start_at',
'old'=>'start_at',
'name'=>'start_at',
'value'=>$o->service->start_at ? $o->service->start_at->format('Y-m-d') : '',
])
</div>
</div>
@section('page-scripts') @section('page-scripts')
@css(select2)
@js(select2,autofocus)
<script type="text/javascript"> <script type="text/javascript">
$(document).ready(function() { $(document).ready(function() {
$('#tld_id').select2({ $('#tld_id').select2({

View File

@ -1,156 +1,36 @@
<!-- o = App\Models\Service\Email::class --> <!-- o=Service\Email::class -->
<div class="row"> <div class="row">
<!-- DOMAIN NAME --> <!-- DOMAIN NAME -->
<div class="col-6"> <div class="col-12 col-sm-9 col-md-12 col-xl-7">
<div class="form-group has-validation"> <x-form.domain name="email" label="Email Domain Name" :name_value="$o->domain_name" :tld_value="$o->tld_id"/>
<label for="domain_name">Email Domain Name</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-fw fa-globe-asia"></i></span>
</div>
<input type="text" class="form-control col-9 text-right @error('email.domain_name') is-invalid @enderror" id="domain_name" name="email[domain_name]" placeholder="Domain Name..." value="{{ old('email.domain_name',$o->domain_name) }}" required>
<div class="input-group-append">
<span class="input-group-text">.</span>
</div>
<select class="form-control col-3" name="email[tld_id]">
@foreach(\App\Models\TLD::orderBy('name')->get() as $oo)
<option value="{{ $oo->id }}" @if($oo->id == old('email.tld_id',$o->tld_id))selected @endif>{{ $oo->name }}</option>
@endforeach
</select>
<span class="invalid-feedback" role="alert">
@error('email.domain_name')
{{ $message }}
@else
Domain Name is required.
@enderror
</span>
</div>
<span class="input-helper">Domain Name</span>
</div>
</div> </div>
<!-- EXPIRY --> <!-- EXPIRY -->
<div class="col-3"> <div class="col-12 col-sm-9 col-md-6 col-xl-5">
<div class="form-group has-validation"> <x-leenooks::form.date id="expire_at" name="email[expire_at]" icon="fa-calendar" label="Expiry" old="email.expire_at" :value="($o->expire_at ?: $o->connect_at?->addMonths($o->contract_term))?->format('Y-m-d')"/>
<label for="expire_at">Expiry</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-fw fa-calendar"></i></span>
</div>
<input type="date" class="form-control @error('email.expire_at') is-invalid @enderror" id="expire_at" name="email[expire_at]" value="{{ old('email.expire_at',($o->expire_at ? $o->expire_at->format('Y-m-d') : NULL)) }}">
<span class="invalid-feedback" role="alert">
@error('email.expire_at')
{{ $message }}
@enderror
</span>
</div>
<span class="input-helper">Email Hosting Expires</span>
</div>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<!-- ADMIN URL --> <!-- ADMIN URL -->
<div class="col-9"> <div class="col-9">
<div class="form-group has-validation"> <x-leenooks::form.text id="admin_url" name="email[admin_url]" icon="fa-user" label="Hosting Admin URL" placeholder="Admin URL..." old="email.admin_url" :value="$o->admin_url"/>
<label for="admin_url">Hosting Admin URL</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fab fa-fw fa-safari"></i></span>
</div>
<input type="text" class="form-control @error('email.admin_url') is-invalid @enderror" id="admin_url" name="email[admin_url]" placeholder="Admin URL..." value="{{ old('email.admin_url',$o->admin_url) }}">
<span class="invalid-feedback" role="alert">
@error('email.admin_url')
{{ $message }}
@enderror
</span>
</div>
<span class="input-helper">Admin URL</span>
</div>
</div> </div>
<!-- ACCOUNTS --> <!-- ACCOUNTS -->
<div class="col-3"> <div class="col-3">
<div class="form-group has-validation"> <x-leenooks::form.text id="accounts" name="email[accounts]" icon="fa-user" label="Accounts" placeholder="Admin URL..." helper="# Accounts" old="email.accounts" :value="$o->accounts"/>
<label for="accounts">Accounts</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-fw fa-hashtag"></i></span>
</div>
<input type="text" class="form-control @error('email.accounts') is-invalid @enderror" id="accounts" name="email[accounts]" placeholder="Accounts" value="{{ old('email.accounts',$o->accounts) }}">
<span class="invalid-feedback" role="alert">
@error('email.accounts')
{{ $message }}
@enderror
</span>
</div>
<span class="input-helper"># Accounts</span>
</div>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<!-- ADMIN USER --> <!-- ADMIN USER -->
<div class="col-6"> <div class="col-6">
<div class="form-group has-validation"> <x-leenooks::form.text id="admin_user" name="email[admin_user]" icon="fa-user" label="Admin Username" old="email.admin_user" :value="$o->admin_user"/>
<label for="admin_user">Admin User</label>
<div class="input-group flex-nowrap">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-fw fa-user"></i></span>
</div>
<input type="text" class="form-control @error('email.admin_user') is-invalid @enderror" id="admin_user" name="email[admin_user]" placeholder="Admin USER" value="{{ old('email.admin_user',$o->admin_user) }}">
<span class="invalid-feedback" role="alert">
@error('email.admin_user')
{{ $message }}
@enderror
</span>
</div>
<span class="input-helper">Admin USER</span>
</div>
</div> </div>
<!-- ADMIN PASS --> <!-- ADMIN PASS -->
<div class="col-6"> <div class="col-6">
<div class="form-group has-validation"> <x-leenooks::form.text id="admin_pass" name="email[admin_pass]" icon="fa-lock" label="Admin Password" :value="$o->admin_pass"/>
<label for="admin_pass">Admin Pass</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-fw fa-lock"></i></span>
</div>
<input type="text" class="form-control @error('email.admin_pass') is-invalid @enderror" id="admin_pass" name="email[admin_pass]" value="{{ old('email.admin_pass',$o->admin_pass) }}">
<span class="invalid-feedback" role="alert">
@error('email.admin_pass')
{{ $message }}
@enderror
</span>
</div>
<span class="input-helper">Admin PASSWORD</span>
</div>
</div> </div>
</div> </div>
@section('page-scripts')
@css(select2)
@js(select2,autofocus)
<style>
.select2-selection.select2-selection--single {
height: calc(2.25rem + 2px) !important;
}
.select2.select2-container.select2-container--default {
display: flex;
flex: 1 1 auto;
}
.select2.select2-container.select2-container--default .selection {
width: 100%;
}
</style>
<script type="text/javascript">
$(document).ready(function() {
// @todo This is taking up too much width
//$('#tld_id').select2();
});
</script>
@append

View File

@ -1,98 +0,0 @@
<!-- o = App\Models\Service\Host::class -->
<div class="row">
<div class="col-12 col-sm-9 col-md-12 col-xl-7">
<div class="form-group">
<label for="domain_name">Domain Name</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-fw fa-globe-asia"></i></span>
</div>
<input type="text" style="width:55%;" class="form-control text-right @error('domain_name') is-invalid @enderror" id="domain_name" name="host[domain_name]" placeholder="Domain Name..." value="{{ old('host.domain_name',$o->domain_name) }}" required>
<div class="input-group-append">
<span class="input-group-text">.</span>
</div>
<select style="width:25%;" class="form-control @error('tld_id') is-invalid @enderror" id="tld_id" name="host[tld_id]">
@foreach(\App\Models\TLD::orderBy('name')->get() as $oo)
<option value="{{ $oo->id }}" @if($oo->id == old('host.tld_id',$o->tld_id))selected @endif>{{ $oo->name }}</option>
@endforeach
</select>
<span class="invalid-feedback" role="alert">
@error('domain_name')
{{ $message }}
@enderror
@error('tld_id')
{{ $message }}
@enderror
</span>
</div>
</div>
</div>
<div class="col-12 col-sm-9 col-md-6 col-xl-5">
@include('adminlte::widget.form_date',[
'label'=>'Expiry',
'id'=>'expire_at',
'old'=>'host.expire_at',
'name'=>'host[expire_at]',
'value'=>$o->expire_at ? $o->expire_at->format('Y-m-d') : ($o->connect_at ? $o->connect_at->addMonths($o->contract_term)->format('Y-m-d') : ''),
])
</div>
</div>
<hr>
<p class="h6">Hosting Details</p>
<div class="row">
<div class="col-12 col-sm-9 col-md-6 col-xl-5">
@include('adminlte::widget.form_select',[
'label'=>'Hosting Server',
'icon'=>'fas fa-handshake',
'id'=>'supplier_host_server_id',
'old'=>'host.supplier_host_server_id',
'name'=>'host[supplier_host_server_id]',
'options'=>\App\Models\SupplierHostServer::active()->orderBy('name')->get()->transform(function($item) { return ['id'=>$item->id,'value'=>$item->name]; }),
'value'=>$o->supplier_host_server_id ?? '',
])
</div>
</div>
<div class="row">
<div class="col-12 col-sm-9 col-md-12 col-xl-7">
@include('adminlte::widget.form_text',[
'label'=>'Admin User',
'icon'=>'fas fa-user',
'id'=>'host_username',
'old'=>'host.host_username',
'name'=>'host[host_username]',
'value'=>$o->host_username ?? '',
])
</div>
<div class="col-12 col-sm-9 col-md-5 col-xl-5">
@include('adminlte::widget.form_text',[
'label'=>'Admin Password',
'icon'=>'fas fa-lock',
'id'=>'host_password',
'old'=>'host.host_password',
'name'=>'host[host_password]',
'value'=>$o->host_password ?? '',
])
</div>
</div>
@section('page-scripts')
@css(select2)
@js(select2,autofocus)
<script type="text/javascript">
$(document).ready(function() {
$('#tld_id').select2({
dropdownAutoWidth: true,
width: 'style',
});
});
</script>
@append

View File

@ -0,0 +1,44 @@
@use(App\Models\SupplierHostServer)
<!-- o=Service\Host::class -->
<div class="row">
<div class="col-12 col-sm-9 col-md-12 col-xl-7">
<x-form.domain name="hosting" label="Domain Name" :name_value="$o->domain_name" :tld_value="$o->tld_id"/>
</div>
<div class="col-12 col-sm-9 col-md-6 col-xl-5">
<x-leenooks::form.date id="expire_at" name="hosting[expire_at]" icon="fa-calendar" label="Expiry" old="hosting.expire_at" :value="($o->expire_at ?: $o->connect_at?->addMonths($o->contract_term))?->format('Y-m-d')"/>
</div>
</div>
<hr>
<p class="h6">Hosting Details</p>
<div class="row">
<div class="col-12 col-sm-9 col-md-6 col-xl-5">
<x-leenooks::form.select id="supplier_host_server_id" name="hosting[supplier_host_server_id]" icon="fa-handshake" label="Registrar" :value="$o->supplier_host_server_id" :options="SupplierHostServer::active()->orderBy('name')->get()->transform(fn($item)=>['id'=>$item->id,'value'=>$item->name])"/>
</div>
</div>
<div class="row">
<div class="col-12 col-sm-9 col-md-12 col-xl-7">
<x-leenooks::form.text id="host_username" name="hosting[host_username]" icon="fa-user" label="Admin Username" :value="$o->host_username"/>
</div>
<div class="col-12 col-sm-9 col-md-5 col-xl-5">
<x-leenooks::form.text id="host_password" name="hosting[host_password]" icon="fa-lock" label="Admin Password" :value="$o->host_password"/>
</div>
</div>
@pa(select2,autofocus)
@section('page-scripts')
<script type="text/javascript">
$(document).ready(function() {
$('#tld_id').select2({
dropdownAutoWidth: true,
width: 'style',
});
});
</script>
@append

View File

@ -0,0 +1,9 @@
<div style="margin: auto; text-align: center; padding-top: 20px;padding-bottom: 20px;">
<div class="button">
@isset($url)
<a href="{{ $url }}">{{ $slot }}</a>
@else
{{ $slot }}
@endisset
</div>
</div>

View File

@ -1,3 +1 @@
<div class="footer"> {{ Illuminate\Mail\Markdown::parse($slot) }}
{{ Illuminate\Mail\Markdown::parse($slot) }}
</div>

View File

@ -1,6 +1,5 @@
<div class="header"> <img class="right" src="{{ url($site->email_logo) }}"><br>
<div class="fixedw"> <div class="heading">{{ $slot }}</div>
<img src="{{ url($site->email_logo) }}"><br> @if($subheading)
<div class="subject">{{ $slot }}</div> <div class="subheading">{!! $subheading !!}</div>
</div> @endif
</div>

View File

@ -5,21 +5,28 @@
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="color-scheme" content="light only"> <meta name="color-scheme" content="light only">
<meta name="supported-color-schemes" content="light only"> <meta name="supported-color-schemes" content="light only">
{{--
<link href='http://fonts.googleapis.com/css?family=Bree+Serif' rel='stylesheet' type='text/css'> <link href='http://fonts.googleapis.com/css?family=Bree+Serif' rel='stylesheet' type='text/css'>
--}}
<link href="http://fonts.googleapis.com/css?family=Roboto:400,300,100,500,700,900,400italic,300italic" rel="stylesheet" type="text/css"> <link href="http://fonts.googleapis.com/css?family=Roboto:400,300,100,500,700,900,400italic,300italic" rel="stylesheet" type="text/css">
</head> </head>
<body> <body>
<section class="header">
<div class="fixedw main-header">
{{ $header ?? '' }} {{ $header ?? '' }}
</div>
</section>
<section class="content"> <section class="content">
<div class="fixedw main-body"> <div class="fixedw main-body">
{{ Illuminate\Mail\Markdown::parse($slot) }} {{ Illuminate\Mail\Markdown::parse($slot) }}
</div> </div>
{{ $subcopy ?? '' }} {{ $subcontent ?? '' }}
</section> </section>
<section class="footer">
<div class="fixedw main-footer">
{{ $footer ?? '' }} {{ $footer ?? '' }}
</div>
</section>
</body> </body>
</html> </html>

View File

@ -1,7 +1,7 @@
@component('mail::layout') @component('mail::layout')
{{-- Header --}} {{-- Header --}}
@slot('header') @slot('header')
@component('mail::header',['site'=>$site]) @component('mail::header',['site'=>$site,'subheading'=>$subheading ?? NULL])
{{ $heading }} {{ $heading }}
@endcomponent @endcomponent
@endslot @endslot
@ -9,11 +9,11 @@
{{-- Body --}} {{-- Body --}}
{{ $slot }} {{ $slot }}
{{-- Subcopy --}} {{-- Sub Content --}}
@isset($subcopy) @isset($subcontent)
@slot('subcopy') @slot('subcontent')
@component('mail::subcopy') @component('mail::subcontent')
{{ $subcopy }} {{ $subcontent }}
@endcomponent @endcomponent
@endslot @endslot
@endisset @endisset
@ -21,11 +21,9 @@
{{-- Footer --}} {{-- Footer --}}
@slot('footer') @slot('footer')
@component('mail::footer') @component('mail::footer')
<div class="fixedw" style="text-align: right; font-size: 0.8em;">
{{ config('mail.from.name') }}<br> {{ config('mail.from.name') }}<br>
{!! $site->address->join('<br>') !!}<br> {!! $site->address->join('<br>') !!}<br>
{{ $site->site_email }} {{ $site->site_email }}
</div>
@endcomponent @endcomponent
@endslot @endslot
@endcomponent @endcomponent

View File

@ -1,9 +0,0 @@
<div style="margin: auto; text-align: center; padding-bottom: 20px;">
<div class="panel">
@isset($url)
<a href="{{ $url }}">{{ $slot }}</a>
@else
{{ $slot }}
@endisset
</div>
</div>

View File

@ -1,3 +1 @@
<div class="light-box">
{{ Illuminate\Mail\Markdown::parse($slot) }} {{ Illuminate\Mail\Markdown::parse($slot) }}
</div>

View File

@ -1,88 +1,104 @@
body{ body{
margin: 0 auto; margin:0 auto;
font-family: 'Bree Serif'; font-family:'Roboto', serif;
background: #f4f4f4; color:#121212;
color: #6c7584; font-size:1.0em;
font-size: 1.2em; padding-top:20px;
} }
.header{ .header{
background: #232323; margin-bottom:0;
border-bottom: 5px solid #454d59;
padding: 20px 0px 10px 0px;
margin-bottom: 30px;
color: #f4f4f4;
font-weight: 300;
} }
.footer{ .footer{
background: #232323;
border-top: 5px solid #454d59;
padding: 10px 0px 20px 0px;
margin-top: 30px;
color: #f4f4f4;
font-weight: 100;
} }
.subject{ .main-header{
font-weight: 300; background:#fafafa;
font-family: 'Bree Serif',serif; border-top-left-radius:10px;
font-size: 1.5em; border-top-right-radius:10px;
/* text-align: right; */ color:#121212;
font-weight:400;
padding:10px 20px;
border-top:1px solid #dbdbdb;
border-right:1px solid #dbdbdb;
border-left:1px solid #dbdbdb;
} }
.panel{ .main-header img{
background:#454d59; width:250px;
border-radius: 10px;
margin-top: 20px;
padding: 20px;
font-weight: 300;
color: #f4f4f4;
font-size: 1.4em;
display: inline-block
} }
.light-box{ .main-header .heading{
background: #f9f9f9; font-weight:bold;
border-radius: 10px; font-size:1.4em;
padding: 10px; padding:5px 0;
font-weight: 300; }
margin-top: 10px; .main-header .subheading{
font-size: 0.8em; font-size:0.8em;
margin-bottom: 10px; padding:5px 0;
} }
.main-body{ .main-body{
background: #ffffff; background:#ffffff;
border-radius: 10px;
color:#6c7584;
font-weight: 400;
padding:10px 20px; padding:10px 20px;
border-top:1px solid #dbdbdb; border-top:1px solid #dbdbdb;
border-left:1px solid #dbdbdb; border-left:1px solid #dbdbdb;
border-right:1px solid #dbdbdb; border-right:1px solid #dbdbdb;
border-bottom:3px solid #dbdbdb; border-bottom:1px solid #dbdbdb;
}
.main-body table{
width: 100%;
background:#fdfdfd;
border-radius:10px;
padding:10px;
font-weight:300;
margin-top:10px;
font-size:0.8em;
margin-bottom:10px;
border: 1px dashed #dbdbdb
} }
.main-body table thead td{ .main-body table thead td{
font-weight: 300; font-weight:300;
border-bottom: 1px solid #dbdbdb; border-bottom:1px solid #dbdbdb;
color: #ccc
} }
.main-body table td.title{ .main-body table td.title{
font-size: 1.1em; font-size:1.1em;
line-height: 20px; line-height:20px;
color:#6c7584
} }
.main-body table td.title small{ .main-body table td.title small{
font-weight: 300; font-weight:300;
font-size: 0.9em; font-size:0.9em;
color: #6c7584 color:#6c7584
} }
.main-body .note{ .main-body .note{
font-size:0.8em;
font-weight:300;
font-style:normal;
}
.main-footer{
background:#2f2f2f;
border-bottom-left-radius:10px;
border-bottom-right-radius:10px;
color:#fefefe;
padding:10px 20px;
margin: 0 0 0 auto;
border-bottom:1px solid #dbdbdb;
border-right:1px solid #dbdbdb;
border-left:1px solid #dbdbdb;
font-size: 0.8em; font-size: 0.8em;
font-weight: 300; text-align: right;
font-style: normal;
} }
.panel a{ .button{
text-decoration: underline; background:#2f2f2f;
color: #f4f4f4; border-radius:5px;
padding:10px;
color:#fafafa;
font-size:1em;
display:inline-block
} }
.panel a:hover{ .button a{
text-decoration: none; text-decoration:none;
color: #ffffff; color:#fafafa;
}
.button a:hover{
text-decoration:none;
color:#ffffff;
} }
/* -- TO VALIDATE -- */ /* -- TO VALIDATE -- */
@ -114,15 +130,17 @@ h3{
color: #333; color: #333;
font-size:18px; font-size:18px;
} }
.links table td span, .links table td a{font-weight: 400} .links table td span, .links table td a{
.border-l{border-left:1px solid #ccc} font-weight: 400
}
.apikey{font-size: 18px; color:#333} .apikey{font-size: 18px; color:#333}
.apikey p{border-bottom: 1px solid #dbdbdb; padding: 10px 0 10px 0;margin: 0 0;} .apikey p{border-bottom: 1px solid #dbdbdb; padding: 10px 0 10px 0;margin: 0 0;}
.apikey p.last{border-bottom: none} .apikey p.last{border-bottom: none}
.apikey small{font-size: 80%; font-weight: 300} .apikey small{font-size: 80%; font-weight: 300}
.twitter{padding: 20px; font-weight: 300;font-size:16px;}
.fixedw{width: 80%; margin: 0 auto;} .fixedw{width: 80%; margin: 0 auto;}
.right{float:right} .right{float:right}
.left{float:left} .left{float:left}
.clear{clear: both;} .clear{clear: both;}
table thead td {font-size: 16px;}
pre {white-space:pre-wrap;}

View File

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

View File

@ -61,8 +61,6 @@ Route::get('pay/paypal/capture',[PaypalController::class,'capture']);
Route::get('auth/{socialProvider}',[SocialLoginController::class,'redirectToProvider']); Route::get('auth/{socialProvider}',[SocialLoginController::class,'redirectToProvider']);
Route::get('auth/{socialProvider}/callback',[SocialLoginController::class,'handleProviderCallback']); Route::get('auth/{socialProvider}/callback',[SocialLoginController::class,'handleProviderCallback']);
Route::get('auth/{socialProvider}/token',[SocialLoginController::class,'handleBearerTokenCallback']); Route::get('auth/{socialProvider}/token',[SocialLoginController::class,'handleBearerTokenCallback']);
Route::get('auth/{socialProvider}/link',[SocialLoginController::class,'link']);
Route::post('auth/{socialProvider}/linkcomplete',[SocialLoginController::class,'linkcomplete']);
// Return from user switch // Return from user switch
Route::get('admin/switch/stop',[SwitchUserController::class,'switch_stop']) Route::get('admin/switch/stop',[SwitchUserController::class,'switch_stop'])