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\Mail;
use App\Mail\TestEmail as MailTest;
use App\Mail\Test;
use App\Models\{Site,User};
class TestEmail extends Command
class EmailTest extends Command
{
/**
* The name and signature of the console command.
*
* @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.
@ -25,16 +28,6 @@ class TestEmail extends Command
*/
protected $description = 'Send a test email';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
@ -42,11 +35,20 @@ class TestEmail extends Command
*/
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'));
Mail::to($this->argument('email') ?? $uo->email)
->send(new MailTest($uo));
$result = Mail::to($this->argument('email') ?? $uo->email)
->send(new Test($uo));
$this->info($result->getMessageId());
return self::SUCCESS;
}
}

View File

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

View File

@ -2,6 +2,7 @@
namespace App\Console\Commands;
use Carbon\Carbon;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Config;
@ -14,7 +15,11 @@ class InvoiceGenerate extends Command
*
* @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.
@ -30,37 +35,50 @@ class InvoiceGenerate extends Command
*/
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'))
$accounts = collect()->push(Account::find($this->argument('account')));
if ($this->argument('id'))
$accounts = collect()->push(Account::find($this->argument('id')));
else
$accounts = Account::active()->get();
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->account_id = $o->id;
foreach ($o->services(TRUE)->get() as $so) {
foreach ($so->next_invoice_items(FALSE) as $ooo)
$io->items->push($ooo);
}
foreach ($items as $oo)
$io->items_active->push($oo);
// 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;
}
$io->account_id = $o->id;
if ($this->option('list')) {
$this->warn(sprintf('|%4s|%4s|%-50s|%8s|',
$this->line(sprintf('|%4s|%4s|%-50s|%8s|',
'SID',
'PID',
'Name',
'Amount',
));
foreach ($io->items as $oo) {
foreach ($io->items_active as $oo) {
$this->info(sprintf('|%4s|%4s|%-50s|%8.2f|',
$oo->service_id,
$oo->product_id,
@ -70,8 +88,9 @@ class InvoiceGenerate extends Command
}
}
//dump($io);
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;
}
@ -81,5 +100,7 @@ class InvoiceGenerate extends Command
$io->pushNew();
}
return self::SUCCESS;
}
}

View File

@ -3,14 +3,10 @@
namespace App\Http\Controllers\Auth;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Mail;
use Laravel\Socialite\Facades\Socialite;
use App\Http\Controllers\Controller;
use App\Mail\SocialLink;
use App\Models\{ProviderOauth,ProviderToken,User,UserOauth};
class SocialLoginController extends Controller
@ -105,46 +101,4 @@ class SocialLoginController extends Controller
->intended('/home')
->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\Collection;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Session;
@ -18,7 +17,7 @@ use Illuminate\Validation\ValidationException;
use Illuminate\View\View;
use Symfony\Component\HttpKernel\Exception\HttpException;
use App\Http\Requests\{ChargeAdd,ServiceChangeRequest};
use App\Http\Requests\ServiceChangeRequest;
use App\Mail\{CancelRequest,ChangeRequest};
use App\Models\{Charge,Invoice,Product,Service};
@ -274,7 +273,7 @@ class ServiceController extends Controller
*/
public function domain_list(): View
{
$o = Service\Domain::serviceActive()
$o = Service\Domain::ServiceActive()
->serviceUserAuthorised(Auth::user())
->select('service_domain.*')
->join('services',['services.id'=>'service_domain.service_id'])
@ -287,8 +286,7 @@ class ServiceController extends Controller
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())
->select('service_email.*')
->join('services',['services.id'=>'service_email.service_id'])
@ -314,15 +312,14 @@ class ServiceController extends Controller
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())
->select('service_host.*')
->join('services',['services.id'=>'service_host.service_id'])
->with(['service.account','service.product.type.supplied.supplier_detail.supplier','tld'])
->get();
return view('theme.backend.adminlte.service.host.list')
return view('theme.backend.adminlte.service.hosting.list')
->with('o',$o);
}
@ -471,7 +468,7 @@ class ServiceController extends Controller
$o->type->save();
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'))
$o->recur_schedule = $validated->get('recur_schedule');

View File

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

View File

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

View File

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

View File

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

View File

@ -38,7 +38,7 @@ class OrderRequestReject extends Mailable
Config::set('site',$this->service->site);
return $this
->markdown('email.admin.order.reject')
->markdown('mail.admin.order.reject')
->subject(sprintf('Your order: #%s was rejected',$this->service->id))
->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;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Config;
use App\Models\User;
class TestEmail extends Mailable
class Test extends Mailable
{
use Queueable, SerializesModels;
@ -33,10 +31,8 @@ class TestEmail extends Mailable
*/
public function build()
{
Config::set('site',$this->user->site);
return $this
->markdown('email.system.test_email')
->markdown('mail.test')
->subject('Just a test...')
->with([
'site'=>$this->user->site,

View File

@ -4,7 +4,6 @@ namespace App\Mail;
use Carbon\Carbon;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Config;
@ -36,10 +35,10 @@ class TrafficMismatch extends Mailable
*/
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
->markdown('email.system.broadband_traffic_mismatch')
->markdown('mail.admin.service.traffic_mismatch')
->subject('Traffic Mismatch for '.$this->date)
->with([
'site'=>$x,

View File

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

View File

@ -75,7 +75,7 @@ class Charge extends Model
return sprintf('%s %s',
$this->description,
$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\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Mail;
use Leenooks\Casts\LeenooksCarbon;
use Leenooks\Traits\ScopeActive;
use App\Casts\CollectionOrNull;
use App\Interfaces\IDs;
use App\Traits\PushNew;
use App\Mail\{InvoiceEmail,InvoiceGeneratedAdmin};
use App\Traits\{PushNew,SiteID};
/**
* Class Invoice
@ -36,7 +38,7 @@ use App\Traits\PushNew;
*/
class Invoice extends Model implements IDs
{
use PushNew,ScopeActive;
use PushNew,ScopeActive,SiteID;
protected $casts = [
'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
protected $pushable = ['items'];
// Our related items that need to be updated when we call pushNew()
protected $pushable = ['items_active'];
protected $with = [
'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 */
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
*
@ -565,6 +582,28 @@ class Invoice extends Model implements IDs
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
*
@ -574,12 +613,20 @@ class Invoice extends Model implements IDs
{
$return = collect();
foreach ($this->items_active->groupBy('product_id') as $o) {
$po = $o->first()->product;
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->count = count($o->pluck('service_id')->unique());
$return->push([
'product' => $o->first()->product,
'product' => $po,
'services' => $o->pluck('service_id')->unique(),
'sub_total' => $o->sum('sub_total'),
'tax_total' => $o->sum('tax'),
@ -589,4 +636,21 @@ class Invoice extends Model implements IDs
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
];
/* 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 */
public function invoice()

View File

@ -64,12 +64,14 @@ abstract class Type extends Model implements ServiceItem
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
{
return $this->expire_at && $this->expire_at->isFuture();
return $this->expire_at
&& $this->expire_at->isFuture();
}
/* ATTRIBUTES */

View File

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

View File

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

View File

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

6
composer.lock generated
View File

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

View File

@ -4,5 +4,6 @@ return [
'language_id' => 1,
'invoice_text' => 'Thank you for using our Internet Services.',
'invoice_days' => 30, // Days in Advance to invoice
'invoice_review' => 3, // Days to review an invoice before it is emailed
'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])
# Your order was rejected.
@component('mail::panel')
@component('mail::table')
| Service | Details |
| :---------- | :---------------- |
@ -22,8 +20,6 @@
**REASON:** {{ $reason }}
@endcomponent
Thanks,<br>
{{ config('app.name') }}
@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'])
Hi {{ isset($user) ? $user->name_full.',' : '' }}
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.
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::panel',['url'=>$reset_link])
Reset Password
@component('mail::button',['url'=>$reset_link])
Reset Password
@endcomponent
@component('mail::subcopy')
Reset password: {{ $reset_link }}
@component('mail::subcontent')
If the button above doesnt work, you can copy and paste this link into your browser, and take it from there:
```
{{ $reset_link }}
```
@endcomponent
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
@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>
</table>
</div>

View File

@ -46,7 +46,7 @@
<td>{{ $oo->registrar->name }}</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->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>
</tr>
@endforeach
@ -58,15 +58,9 @@
</div>
@endsection
@section('page-scripts')
@css(datatables,bootstrap4|rowgroup)
@js(datatables,bootstrap4|rowgroup)
@pa(datatables,rowgroup)
<style>
.strike {
text-decoration: line-through;
}
</style>
@section('page-scripts')
<script type="text/javascript">
$(document).ready(function() {
$('#services').DataTable({
@ -81,10 +75,6 @@
}
],
});
$('tbody').on('click','tr', function () {
$(this).toggleClass('selected');
});
});
</script>
@append

View File

@ -50,7 +50,7 @@
<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>@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>
</tr>
@endforeach
@ -62,15 +62,9 @@
</div>
@endsection
@section('page-scripts')
@css(datatables,bootstrap4|rowgroup)
@js(datatables,bootstrap4|rowgroup)
@pa(datatables,rowgroup)
<style>
.strike {
text-decoration: line-through;
}
</style>
@section('page-scripts')
<script type="text/javascript">
$(document).ready(function() {
$('#services').DataTable({
@ -85,10 +79,6 @@
}
],
});
$('tbody').on('click','tr', function () {
$(this).toggleClass('selected');
});
});
</script>
@append

View File

@ -44,7 +44,7 @@
<td>{{ $oo->service_expire ? $oo->service_expire->format('Y-m-d') : '-' }}</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->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>
</tr>
@endforeach
@ -56,15 +56,9 @@
</div>
@endsection
@section('page-scripts')
@css(datatables,bootstrap4|rowgroup)
@js(datatables,bootstrap4|rowgroup)
@pa(datatables,rowgroup)
<style>
.strike {
text-decoration: line-through;
}
</style>
@section('page-scripts')
<script type="text/javascript">
$(document).ready(function() {
$('#services').DataTable({
@ -79,10 +73,6 @@
}
],
});
$('tbody').on('click','tr', function () {
$(this).toggleClass('selected');
});
});
</script>
@append

View File

@ -72,10 +72,6 @@
},
orderFixed: [1, 'asc']
});
$('#service_movements tbody').on('click','tr', function () {
$(this).toggleClass('selected');
});
});
</script>
@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.
</div>
</div>
@section('page-scripts')
<script>
$(document).ready(function() {

View File

@ -35,7 +35,7 @@
</div>
<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>

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="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="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>
<x-form.domain name="domain" 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">
@include('adminlte::widget.form_date',[
'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') : ''),
])
<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')"/>
</div>
</div>
@ -47,77 +18,27 @@
<div class="row">
<div class="col-12 col-sm-9 col-md-6 col-xl-5">
@include('adminlte::widget.form_select',[
'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 ?? '',
])
<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])"/>
</div>
<div class="col-12 col-sm-9 col-md-12 col-xl-6">
@include('adminlte::widget.form_text',[
'label'=>'Registrar Account',
'icon'=>'fas fa-user-circle',
'id'=>'registrar_account',
'old'=>'domain.registrar_account',
'name'=>'domain[registrar_account]',
'value'=>$o->registrar_account ?? '',
])
<x-leenooks::form.text id="registrar_account" name="domain[registrar_account]" icon="fa-user-circle" label="Registrar Username" :value="$o->registrar_account"/>
</div>
</div>
<div class="row">
<div class="col-12 col-sm-9 col-md-6 col-xl-5">
@include('adminlte::widget.form_select',[
'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,
])
<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])"/>
</div>
<div class="col-12 col-sm-9 col-md-12 col-xl-6">
@include('adminlte::widget.form_text',[
'label'=>'Registrar Username',
'icon'=>'fas fa-user',
'id'=>'registrar_username',
'old'=>'domain.registrar_username',
'name'=>'domain[registrar_username]',
'value'=>$o->registrar_username ?? '',
])
<x-leenooks::form.text id="registrar_username" name="domain[registrar_username]" icon="fa-user" label="Registrar Username" :value="$o->registrar_username"/>
</div>
</div>
<hr>
<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>
@pa(select2,autofocus)
@section('page-scripts')
@css(select2)
@js(select2,autofocus)
<script type="text/javascript">
$(document).ready(function() {
$('#tld_id').select2({

View File

@ -1,156 +1,36 @@
<!-- o = App\Models\Service\Email::class -->
<!-- o=Service\Email::class -->
<div class="row">
<!-- DOMAIN NAME -->
<div class="col-6">
<div class="form-group has-validation">
<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 class="col-12 col-sm-9 col-md-12 col-xl-7">
<x-form.domain name="email" label="Email Domain Name" :name_value="$o->domain_name" :tld_value="$o->tld_id"/>
</div>
<!-- EXPIRY -->
<div class="col-3">
<div class="form-group has-validation">
<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 class="col-12 col-sm-9 col-md-6 col-xl-5">
<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')"/>
</div>
</div>
<div class="row">
<!-- ADMIN URL -->
<div class="col-9">
<div class="form-group has-validation">
<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>
<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"/>
</div>
<!-- ACCOUNTS -->
<div class="col-3">
<div class="form-group has-validation">
<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>
<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"/>
</div>
</div>
<div class="row">
<!-- ADMIN USER -->
<div class="col-6">
<div class="form-group has-validation">
<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>
<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"/>
</div>
<!-- ADMIN PASS -->
<div class="col-6">
<div class="form-group has-validation">
<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>
<x-leenooks::form.text id="admin_pass" name="email[admin_pass]" icon="fa-lock" label="Admin Password" :value="$o->admin_pass"/>
</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
</div>

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) }}
</div>
{{ Illuminate\Mail\Markdown::parse($slot) }}

View File

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

View File

@ -1,25 +1,32 @@
<!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" />
<meta name="color-scheme" 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=Roboto:400,300,100,500,700,900,400italic,300italic" rel="stylesheet" type="text/css">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="color-scheme" 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=Roboto:400,300,100,500,700,900,400italic,300italic" rel="stylesheet" type="text/css">
</head>
<body>
{{ $header ?? '' }}
<section class="header">
<div class="fixedw main-header">
{{ $header ?? '' }}
</div>
</section>
<section class="content">
<div class="fixedw main-body">
{{ Illuminate\Mail\Markdown::parse($slot) }}
</div>
<section class="content">
<div class="fixedw main-body">
{{ Illuminate\Mail\Markdown::parse($slot) }}
</div>
{{ $subcopy ?? '' }}
</section>
{{ $footer ?? '' }}
{{ $subcontent ?? '' }}
</section>
<section class="footer">
<div class="fixedw main-footer">
{{ $footer ?? '' }}
</div>
</section>
</body>
</html>

View File

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

View File

@ -1,88 +1,104 @@
body{
margin: 0 auto;
font-family: 'Bree Serif';
background: #f4f4f4;
color: #6c7584;
font-size: 1.2em;
margin:0 auto;
font-family:'Roboto', serif;
color:#121212;
font-size:1.0em;
padding-top:20px;
}
.header{
background: #232323;
border-bottom: 5px solid #454d59;
padding: 20px 0px 10px 0px;
margin-bottom: 30px;
color: #f4f4f4;
font-weight: 300;
margin-bottom:0;
}
.footer{
background: #232323;
border-top: 5px solid #454d59;
padding: 10px 0px 20px 0px;
margin-top: 30px;
color: #f4f4f4;
font-weight: 100;
}
.subject{
font-weight: 300;
font-family: 'Bree Serif',serif;
font-size: 1.5em;
/* text-align: right; */
.main-header{
background:#fafafa;
border-top-left-radius:10px;
border-top-right-radius:10px;
color:#121212;
font-weight:400;
padding:10px 20px;
border-top:1px solid #dbdbdb;
border-right:1px solid #dbdbdb;
border-left:1px solid #dbdbdb;
}
.panel{
background:#454d59;
border-radius: 10px;
margin-top: 20px;
padding: 20px;
font-weight: 300;
color: #f4f4f4;
font-size: 1.4em;
display: inline-block
.main-header img{
width:250px;
}
.light-box{
background: #f9f9f9;
border-radius: 10px;
padding: 10px;
font-weight: 300;
margin-top: 10px;
font-size: 0.8em;
margin-bottom: 10px;
.main-header .heading{
font-weight:bold;
font-size:1.4em;
padding:5px 0;
}
.main-header .subheading{
font-size:0.8em;
padding:5px 0;
}
.main-body{
background: #ffffff;
border-radius: 10px;
color:#6c7584;
font-weight: 400;
background:#ffffff;
padding:10px 20px;
border-top:1px solid #dbdbdb;
border-left: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{
font-weight: 300;
border-bottom: 1px solid #dbdbdb;
color: #ccc
font-weight:300;
border-bottom:1px solid #dbdbdb;
}
.main-body table td.title{
font-size: 1.1em;
line-height: 20px;
font-size:1.1em;
line-height:20px;
color:#6c7584
}
.main-body table td.title small{
font-weight: 300;
font-size: 0.9em;
color: #6c7584
font-weight:300;
font-size:0.9em;
color:#6c7584
}
.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-weight: 300;
font-style: normal;
text-align: right;
}
.panel a{
text-decoration: underline;
color: #f4f4f4;
.button{
background:#2f2f2f;
border-radius:5px;
padding:10px;
color:#fafafa;
font-size:1em;
display:inline-block
}
.panel a:hover{
text-decoration: none;
color: #ffffff;
.button a{
text-decoration:none;
color:#fafafa;
}
.button a:hover{
text-decoration:none;
color:#ffffff;
}
/* -- TO VALIDATE -- */
@ -114,15 +130,17 @@ h3{
color: #333;
font-size:18px;
}
.links table td span, .links table td a{font-weight: 400}
.border-l{border-left:1px solid #ccc}
.links table td span, .links table td a{
font-weight: 400
}
.apikey{font-size: 18px; color:#333}
.apikey p{border-bottom: 1px solid #dbdbdb; padding: 10px 0 10px 0;margin: 0 0;}
.apikey p.last{border-bottom: none}
.apikey small{font-size: 80%; font-weight: 300}
.twitter{padding: 20px; font-weight: 300;font-size:16px;}
.fixedw{width: 80%; margin: 0 auto;}
.right{float:right}
.left{float:left}
.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}/callback',[SocialLoginController::class,'handleProviderCallback']);
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
Route::get('admin/switch/stop',[SwitchUserController::class,'switch_stop'])