WIP: Road to 1.0.0 #1
|
@ -4,11 +4,15 @@
|
||||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"configurations": [
|
"configurations": [
|
||||||
|
|
||||||
{
|
{
|
||||||
"name": "Listen for XDebug",
|
"name": "Listen for XDebug",
|
||||||
"type": "php",
|
"type": "php",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"port": 9000
|
"port": 9000,
|
||||||
|
"ignore": [
|
||||||
|
"**/vendor/**/*.php"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Launch currently open script",
|
"name": "Launch currently open script",
|
||||||
|
|
|
@ -8,9 +8,11 @@ use App\Http\Requests\SendInviteRequest;
|
||||||
use App\Mail\InviteToTeam;
|
use App\Mail\InviteToTeam;
|
||||||
use App\Team;
|
use App\Team;
|
||||||
use App\User;
|
use App\User;
|
||||||
|
use App\Vacancy;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Facades\Mail;
|
use Illuminate\Support\Facades\Mail;
|
||||||
|
use Mpociot\Teamwork\Exceptions\UserNotInTeamException;
|
||||||
use Mpociot\Teamwork\Facades\Teamwork;
|
use Mpociot\Teamwork\Facades\Teamwork;
|
||||||
use Mpociot\Teamwork\TeamInvite;
|
use Mpociot\Teamwork\TeamInvite;
|
||||||
|
|
||||||
|
@ -23,8 +25,10 @@ class TeamController extends Controller
|
||||||
*/
|
*/
|
||||||
public function index()
|
public function index()
|
||||||
{
|
{
|
||||||
|
$teams = Team::with('users.roles')->get();
|
||||||
|
|
||||||
return view('dashboard.teams.teams')
|
return view('dashboard.teams.teams')
|
||||||
->with('teams', Team::all());
|
->with('teams', $teams);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -45,11 +49,13 @@ class TeamController extends Controller
|
||||||
*/
|
*/
|
||||||
public function store(NewTeamRequest $request)
|
public function store(NewTeamRequest $request)
|
||||||
{
|
{
|
||||||
Team::create([
|
$team = Team::create([
|
||||||
'name' => $request->teamName,
|
'name' => $request->teamName,
|
||||||
'owner_id' => Auth::user()->id
|
'owner_id' => Auth::user()->id
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
Auth::user()->teams()->attach($team->id);
|
||||||
|
|
||||||
$request->session()->flash('success', 'Team successfully created.');
|
$request->session()->flash('success', 'Team successfully created.');
|
||||||
return redirect()->back();
|
return redirect()->back();
|
||||||
}
|
}
|
||||||
|
@ -75,7 +81,8 @@ class TeamController extends Controller
|
||||||
{
|
{
|
||||||
return view('dashboard.teams.edit-team')
|
return view('dashboard.teams.edit-team')
|
||||||
->with('team', $team)
|
->with('team', $team)
|
||||||
->with('users', User::all());
|
->with('users', User::all())
|
||||||
|
->with('vacancies', Vacancy::all());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -186,4 +193,20 @@ class TeamController extends Controller
|
||||||
return redirect()->to(route('teams.index'));
|
return redirect()->to(route('teams.index'));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function switchTeam(Request $request, Team $team)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Auth::user()->switchTeam($team);
|
||||||
|
|
||||||
|
$request->session()->flash('success', 'Switched teams! Your team dashboard will now use this context.');
|
||||||
|
}
|
||||||
|
catch(UserNotInTeamException $ex)
|
||||||
|
{
|
||||||
|
$request->session()->flash('error', 'You can\'t switch to a team you don\'t belong to.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->back();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,11 +23,13 @@ use App\Notifications\EmailChanged;
|
||||||
use App\Notifications\ChangedPassword;
|
use App\Notifications\ChangedPassword;
|
||||||
use Spatie\Permission\Models\Role;
|
use Spatie\Permission\Models\Role;
|
||||||
|
|
||||||
|
use App\Traits\ReceivesAccountTokens;
|
||||||
use Google2FA;
|
use Google2FA;
|
||||||
|
|
||||||
class UserController extends Controller
|
class UserController extends Controller
|
||||||
{
|
{
|
||||||
|
|
||||||
|
use ReceivesAccountTokens;
|
||||||
|
|
||||||
public function showStaffMembers()
|
public function showStaffMembers()
|
||||||
{
|
{
|
||||||
|
@ -220,7 +222,7 @@ class UserController extends Controller
|
||||||
|
|
||||||
if ($request->confirmPrompt == 'DELETE ACCOUNT')
|
if ($request->confirmPrompt == 'DELETE ACCOUNT')
|
||||||
{
|
{
|
||||||
$user->delete();
|
$user->forceDelete();
|
||||||
$request->session()->flash('success','User deleted successfully. PII has been erased.');
|
$request->session()->flash('success','User deleted successfully. PII has been erased.');
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -232,6 +234,7 @@ class UserController extends Controller
|
||||||
return redirect()->route('registeredPlayerList');
|
return redirect()->route('registeredPlayerList');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function update(UpdateUserRequest $request, User $user)
|
public function update(UpdateUserRequest $request, User $user)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -356,4 +359,6 @@ class UserController extends Controller
|
||||||
//TODO: Dispatch event
|
//TODO: Dispatch event
|
||||||
return redirect()->back();
|
return redirect()->back();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
|
||||||
|
class UserDeleteRequest extends FormRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine if the user is authorized to make this request.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function authorize()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function rules()
|
||||||
|
{
|
||||||
|
if (Auth::user()->has2FA())
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'currentPassword' => 'required|password:web',
|
||||||
|
'otp' => 'required|integer|max:6'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'currentPassword' => 'required|password:web'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Mail;
|
||||||
|
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Mail\Mailable;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
use App\User;
|
||||||
|
|
||||||
|
class UserAccountDeleteConfirmation extends Mailable
|
||||||
|
{
|
||||||
|
use Queueable, SerializesModels;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public $deleteToken;
|
||||||
|
|
||||||
|
|
||||||
|
public $cancelToken;
|
||||||
|
|
||||||
|
|
||||||
|
public $originalIP;
|
||||||
|
|
||||||
|
|
||||||
|
public $name;
|
||||||
|
|
||||||
|
|
||||||
|
public $userID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new message instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct(User $user, array $tokens, string $originalIP)
|
||||||
|
{
|
||||||
|
$this->deleteToken = $tokens['delete'];
|
||||||
|
$this->cancelToken = $tokens['cancel'];
|
||||||
|
|
||||||
|
$this->originalIP = $originalIP;
|
||||||
|
$this->name = $user->name;
|
||||||
|
$this->userID = $user->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the message.
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function build()
|
||||||
|
{
|
||||||
|
return $this->view('mail.deleted-account');
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,6 +8,12 @@ use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
class UserObserver
|
class UserObserver
|
||||||
{
|
{
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
Log::debug('User observer has been initialised and ready for use!');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle the user "created" event.
|
* Handle the user "created" event.
|
||||||
*
|
*
|
||||||
|
@ -39,20 +45,28 @@ class UserObserver
|
||||||
|
|
||||||
public function deleting(User $user)
|
public function deleting(User $user)
|
||||||
{
|
{
|
||||||
$user->profile()->delete();
|
if ($user->isForceDeleting())
|
||||||
Log::debug('Referential integrity cleanup: Deleted profile!');
|
|
||||||
$applications = $user->applications;
|
|
||||||
|
|
||||||
if (!$applications->isEmpty())
|
|
||||||
{
|
{
|
||||||
Log::debug('RIC: Now trying to delete applications and responses...');
|
$user->profile->delete();
|
||||||
foreach($applications as $application)
|
Log::debug('Referential integrity cleanup: Deleted profile!');
|
||||||
|
$applications = $user->applications;
|
||||||
|
|
||||||
|
if (!$applications->isEmpty())
|
||||||
{
|
{
|
||||||
// code moved to Application observer, where it gets rid of attached elements individually
|
Log::debug('RIC: Now trying to delete applications and responses...');
|
||||||
Log::debug('RIC: Deleting application ' . $application->id);
|
foreach($applications as $application)
|
||||||
$application->delete();
|
{
|
||||||
|
// code moved to Application observer, where it gets rid of attached elements individually
|
||||||
|
Log::debug('RIC: Deleting application ' . $application->id);
|
||||||
|
$application->delete();
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log::debug('RIC: Not cleaning up soft deleted models!');
|
||||||
}
|
}
|
||||||
|
|
||||||
Log::debug('RIC: Cleanup done!');
|
Log::debug('RIC: Cleanup done!');
|
||||||
|
@ -66,7 +80,6 @@ class UserObserver
|
||||||
*/
|
*/
|
||||||
public function deleted(User $user)
|
public function deleted(User $user)
|
||||||
{
|
{
|
||||||
//
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -88,6 +101,8 @@ class UserObserver
|
||||||
*/
|
*/
|
||||||
public function forceDeleted(User $user)
|
public function forceDeleted(User $user)
|
||||||
{
|
{
|
||||||
//
|
Log::info('Model has been force deleted', [
|
||||||
|
'modelID' => $user->id
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services;
|
||||||
|
|
||||||
|
use App\Vacancy;
|
||||||
|
use App\Application;
|
||||||
|
|
||||||
|
class VacancyApplicationService
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds all applications associated with $model.
|
||||||
|
*
|
||||||
|
* @param Vacancy $model The model you want to search through.
|
||||||
|
* @return Illuminate\Support\Collection A collection of applications
|
||||||
|
*/
|
||||||
|
public function findApplications(Vacancy $model)
|
||||||
|
{
|
||||||
|
|
||||||
|
$applications = collect([]);
|
||||||
|
|
||||||
|
foreach(Application::all() as $application)
|
||||||
|
{
|
||||||
|
if ($application->response->vacancy->id == $model->id)
|
||||||
|
{
|
||||||
|
$applications->push($application);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $applications;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -13,4 +13,10 @@ class Team extends TeamworkTeam
|
||||||
'description',
|
'description',
|
||||||
'openJoin'
|
'openJoin'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
|
public function vacancies()
|
||||||
|
{
|
||||||
|
return $this->belongsToMany('App\Vacancy', 'team_has_vacancy');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Traits;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
|
||||||
|
trait HandlesAccountTokens
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
public function generateAccountTokens()
|
||||||
|
{
|
||||||
|
$deleteToken = bin2hex(openssl_random_pseudo_bytes(32));
|
||||||
|
$cancelToken = bin2hex(openssl_random_pseudo_bytes(32));
|
||||||
|
|
||||||
|
$tokens = [
|
||||||
|
|
||||||
|
'delete' => Hash::make($deleteToken),
|
||||||
|
'cancel' => Hash::make($cancelToken)
|
||||||
|
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->account_tokens = json_encode($tokens);
|
||||||
|
$this->save();
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
'delete' => $deleteToken,
|
||||||
|
'cancel' => $cancelToken
|
||||||
|
];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function verifyAccountToken(string $token, string $type): bool
|
||||||
|
{
|
||||||
|
$tokens = json_decode($this->account_tokens);
|
||||||
|
|
||||||
|
if ($type == 'deleteToken')
|
||||||
|
{
|
||||||
|
return Hash::check($token, $tokens->delete);
|
||||||
|
}
|
||||||
|
elseif ($type == 'cancelToken')
|
||||||
|
{
|
||||||
|
return Hash::check($token, $tokens->cancel);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,84 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Traits;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Illuminate\Support\Facades\Mail;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
|
||||||
|
use App\Http\Requests\UserDeleteRequest;
|
||||||
|
use App\Mail\UserAccountDeleteConfirmation;
|
||||||
|
use App\User;
|
||||||
|
|
||||||
|
|
||||||
|
trait ReceivesAccountTokens
|
||||||
|
{
|
||||||
|
public function userDelete(UserDeleteRequest $request)
|
||||||
|
{
|
||||||
|
// a little verbose
|
||||||
|
$user = User::find(Auth::user()->id);
|
||||||
|
$tokens = $user->generateAccountTokens();
|
||||||
|
|
||||||
|
Mail::to($user)->send(new UserAccountDeleteConfirmation($user, $tokens, $request->ip()));
|
||||||
|
|
||||||
|
$user->delete();
|
||||||
|
Auth::logout();
|
||||||
|
|
||||||
|
$request->session()->flash('success', 'Please check your email to finish deleting your account.');
|
||||||
|
return redirect()->to('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function processDeleteConfirmation(Request $request, $ID, $action, $token)
|
||||||
|
{
|
||||||
|
// We can't rely on Laravel's route model injection, because it'll ignore soft-deleted models,
|
||||||
|
// so we have to use a special scope to find them ourselves.
|
||||||
|
$user = User::withTrashed()->findOrFail($ID);
|
||||||
|
$email = $user->email;
|
||||||
|
|
||||||
|
switch($action)
|
||||||
|
{
|
||||||
|
case 'confirm':
|
||||||
|
|
||||||
|
if ($user->verifyAccountToken($token, 'deleteToken'))
|
||||||
|
{
|
||||||
|
Log::info('SECURITY: User deleted account!', [
|
||||||
|
|
||||||
|
'confirmDeleteToken' => $token,
|
||||||
|
'ipAddress' => $request->ip(),
|
||||||
|
'email' => $user->email
|
||||||
|
|
||||||
|
]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
$user->forceDelete();
|
||||||
|
|
||||||
|
$request->session()->flash('success', 'Account permanently deleted. Thank you for using our service.');
|
||||||
|
return redirect()->to('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'cancel':
|
||||||
|
|
||||||
|
if ($user->verifyAccountToken($token, 'cancelToken'))
|
||||||
|
{
|
||||||
|
$user->restore();
|
||||||
|
$request->session()->flash('success', 'Account deletion cancelled! You may now login.');
|
||||||
|
|
||||||
|
return redirect()->to(route('login'));
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
|
||||||
|
abort(404, 'The page you were trying to access may not exist or may be expired.');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,15 +2,18 @@
|
||||||
|
|
||||||
namespace App;
|
namespace App;
|
||||||
|
|
||||||
|
use App\Traits\HandlesAccountTokens;
|
||||||
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||||
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||||
use Illuminate\Notifications\Notifiable;
|
use Illuminate\Notifications\Notifiable;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
use Mpociot\Teamwork\Traits\UserHasTeams;
|
use Mpociot\Teamwork\Traits\UserHasTeams;
|
||||||
use Spatie\Permission\Traits\HasRoles;
|
use Spatie\Permission\Traits\HasRoles;
|
||||||
|
|
||||||
class User extends Authenticatable implements MustVerifyEmail
|
class User extends Authenticatable implements MustVerifyEmail
|
||||||
{
|
{
|
||||||
use UserHasTeams, Notifiable, HasRoles;
|
use UserHasTeams, Notifiable, HasRoles, SoftDeletes, HandlesAccountTokens;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The attributes that are mass assignable.
|
* The attributes that are mass assignable.
|
||||||
|
@ -74,7 +77,6 @@ class User extends Authenticatable implements MustVerifyEmail
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public function isStaffMember()
|
public function isStaffMember()
|
||||||
{
|
{
|
||||||
return $this->hasAnyRole('reviewer', 'admin', 'hiringManager');
|
return $this->hasAnyRole('reviewer', 'admin', 'hiringManager');
|
||||||
|
@ -85,8 +87,7 @@ class User extends Authenticatable implements MustVerifyEmail
|
||||||
return !is_null($this->twofa_secret);
|
return !is_null($this->twofa_secret);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public function routeNotificationForSlack($notification)
|
public function routeNotificationForSlack($notification)
|
||||||
{
|
{
|
||||||
return config('slack.webhook.integrationURL');
|
return config('slack.webhook.integrationURL');
|
||||||
|
|
|
@ -13,7 +13,7 @@ use GrahamCampbell\Markdown\Facades\Markdown;
|
||||||
|
|
||||||
class Vacancy extends Model
|
class Vacancy extends Model
|
||||||
{
|
{
|
||||||
use UsedByTeams;
|
//use UsedByTeams;
|
||||||
|
|
||||||
public $fillable = [
|
public $fillable = [
|
||||||
|
|
||||||
|
@ -25,7 +25,8 @@ class Vacancy extends Model
|
||||||
'vacancyFormID',
|
'vacancyFormID',
|
||||||
'vacancyCount',
|
'vacancyCount',
|
||||||
'vacancyStatus',
|
'vacancyStatus',
|
||||||
'vacancySlug'
|
'vacancySlug',
|
||||||
|
'team_id'
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -49,6 +50,12 @@ class Vacancy extends Model
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function teams()
|
||||||
|
{
|
||||||
|
return $this->belongsToMany('App\Team', 'team_has_vacancy');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public function forms()
|
public function forms()
|
||||||
{
|
{
|
||||||
return $this->belongsTo('App\Form', 'vacancyFormID', 'id');
|
return $this->belongsTo('App\Form', 'vacancyFormID', 'id');
|
||||||
|
|
|
@ -572,6 +572,22 @@ return [
|
||||||
'location' => 'https://gitcdn.github.io/bootstrap-toggle/2.2.2/js/bootstrap-toggle.min.js'
|
'location' => 'https://gitcdn.github.io/bootstrap-toggle/2.2.2/js/bootstrap-toggle.min.js'
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => 'BootstrapMultiselectDropdown',
|
||||||
|
'active' => true,
|
||||||
|
'files' => [
|
||||||
|
[
|
||||||
|
'type' => 'js',
|
||||||
|
'asset' => 'false',
|
||||||
|
'location' => 'https://cdnjs.cloudflare.com/ajax/libs/bootstrap-multiselect/0.9.15/js/bootstrap-multiselect.min.js'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'type' => 'css',
|
||||||
|
'asset' => false,
|
||||||
|
'location' => 'https://cdnjs.cloudflare.com/ajax/libs/bootstrap-multiselect/0.9.15/css/bootstrap-multiselect.css'
|
||||||
|
]
|
||||||
|
]
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
class VacancyNullableTeamId extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::table('vacancies', function(Blueprint $table){
|
||||||
|
|
||||||
|
$table->dropForeign('vacancies_ownerteamid_foreign');
|
||||||
|
$table->dropColumn('ownerTeamID');
|
||||||
|
$table->bigInteger('team_id')->nullable()->unsigned();
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::table('vacancies', function(Blueprint $table){
|
||||||
|
|
||||||
|
$table->dropColumn('team_id');
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
class TeamHasVacancy extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::create('team_has_vacancy', function(Blueprint $table){
|
||||||
|
|
||||||
|
$table->id();
|
||||||
|
$table->integer('team_id')->unsigned();
|
||||||
|
$table->bigInteger('vacancy_id')->unsigned();
|
||||||
|
$table->timestamps();
|
||||||
|
|
||||||
|
$table->foreign('team_id')
|
||||||
|
->references('id')
|
||||||
|
->on(config('teamwork.teams_table'));
|
||||||
|
|
||||||
|
$table->foreign('vacancy_id')
|
||||||
|
->references('id')
|
||||||
|
->on('vacancies');
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,7 +4,7 @@ use Illuminate\Database\Migrations\Migration;
|
||||||
use Illuminate\Database\Schema\Blueprint;
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
use Illuminate\Support\Facades\Schema;
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
class AddTeamAssocToVacancies extends Migration
|
class AddAccountTokensToUser extends Migration
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Run the migrations.
|
* Run the migrations.
|
||||||
|
@ -13,13 +13,8 @@ class AddTeamAssocToVacancies extends Migration
|
||||||
*/
|
*/
|
||||||
public function up()
|
public function up()
|
||||||
{
|
{
|
||||||
Schema::table('vacancies', function (Blueprint $table) {
|
Schema::table('users', function (Blueprint $table) {
|
||||||
|
$table->string('account_tokens')->after('password')->nullable();
|
||||||
$table->integer('ownerTeamID')->unsigned()->after('vacancyFormID');
|
|
||||||
|
|
||||||
$table->foreign('ownerTeamID')
|
|
||||||
->references('id')
|
|
||||||
->on('teams');
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,8 +25,8 @@ class AddTeamAssocToVacancies extends Migration
|
||||||
*/
|
*/
|
||||||
public function down()
|
public function down()
|
||||||
{
|
{
|
||||||
Schema::table('vacancies', function (Blueprint $table) {
|
Schema::table('users', function (Blueprint $table) {
|
||||||
//
|
$table->dropColumn('account_tokens');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -4,7 +4,7 @@ use Illuminate\Database\Migrations\Migration;
|
||||||
use Illuminate\Database\Schema\Blueprint;
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
use Illuminate\Support\Facades\Schema;
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
class AddTeamIDToVacancy extends Migration
|
class AddSoftDeletesToUsers extends Migration
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Run the migrations.
|
* Run the migrations.
|
||||||
|
@ -13,8 +13,8 @@ class AddTeamIDToVacancy extends Migration
|
||||||
*/
|
*/
|
||||||
public function up()
|
public function up()
|
||||||
{
|
{
|
||||||
Schema::table('vacancy', function (Blueprint $table) {
|
Schema::table('users', function (Blueprint $table) {
|
||||||
//
|
$table->softDeletes()->after('account_tokens');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,8 +25,8 @@ class AddTeamIDToVacancy extends Migration
|
||||||
*/
|
*/
|
||||||
public function down()
|
public function down()
|
||||||
{
|
{
|
||||||
Schema::table('vacancy', function (Blueprint $table) {
|
Schema::table('users', function (Blueprint $table) {
|
||||||
//
|
$table->dropSoftDeletes();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -77279,6 +77279,7 @@ $("#submitComment").on('click', function () {
|
||||||
$("#newComment").submit();
|
$("#newComment").submit();
|
||||||
});
|
});
|
||||||
$("#jointype").bootstrapToggle();
|
$("#jointype").bootstrapToggle();
|
||||||
|
$("#associatedVacancies").multiselect();
|
||||||
|
|
||||||
/***/ }),
|
/***/ }),
|
||||||
|
|
||||||
|
@ -77408,10 +77409,10 @@ window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
|
||||||
/*! no static exports found */
|
/*! no static exports found */
|
||||||
/***/ (function(module, exports, __webpack_require__) {
|
/***/ (function(module, exports, __webpack_require__) {
|
||||||
|
|
||||||
__webpack_require__(/*! /home/miguel456/staffmanager/resources/js/app.js */"./resources/js/app.js");
|
__webpack_require__(/*! /home/miguel456/Desktop/Projects/staffmanager/resources/js/app.js */"./resources/js/app.js");
|
||||||
__webpack_require__(/*! /home/miguel456/staffmanager/resources/js/application_charts.js */"./resources/js/application_charts.js");
|
__webpack_require__(/*! /home/miguel456/Desktop/Projects/staffmanager/resources/js/application_charts.js */"./resources/js/application_charts.js");
|
||||||
__webpack_require__(/*! /home/miguel456/staffmanager/resources/js/calendar.js */"./resources/js/calendar.js");
|
__webpack_require__(/*! /home/miguel456/Desktop/Projects/staffmanager/resources/js/calendar.js */"./resources/js/calendar.js");
|
||||||
module.exports = __webpack_require__(/*! /home/miguel456/staffmanager/resources/sass/app.scss */"./resources/sass/app.scss");
|
module.exports = __webpack_require__(/*! /home/miguel456/Desktop/Projects/staffmanager/resources/sass/app.scss */"./resources/sass/app.scss");
|
||||||
|
|
||||||
|
|
||||||
/***/ })
|
/***/ })
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
$('input[rel="txtTooltip"]').tooltip();
|
$('input[rel="txtTooltip"]').tooltip();
|
||||||
$('span[rel="spanTxtTooltip"]').tooltip(); // Also allow span tooltips
|
$('span[rel="spanTxtTooltip"]').tooltip(); // Also allow span tooltips
|
||||||
|
$('button[rel="buttonTxtTooltip"]').tooltip(); // button tooltip
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
$(function() {
|
||||||
|
$('#assocVacancies').multiselect({
|
||||||
|
disableIfEmpty: true,
|
||||||
|
nonSelectedText: 'Choose vacancies...'
|
||||||
|
});
|
||||||
|
});
|
|
@ -45,3 +45,4 @@ $("#submitComment").on('click', function(){
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#jointype").bootstrapToggle();
|
$("#jointype").bootstrapToggle();
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
@section('js')
|
@section('js')
|
||||||
|
|
||||||
<x-global-errors></x-global-errors>
|
<x-global-errors></x-global-errors>
|
||||||
|
<script src="/js/team-editor.js"></script>
|
||||||
|
|
||||||
@stop
|
@stop
|
||||||
|
|
||||||
|
@ -39,6 +40,69 @@
|
||||||
</x-modal>
|
</x-modal>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
|
<x-modal id="userlist_modal" modal-label="UserListModalLabel" modal-title="Team Members and Invites" include-close-button="true">
|
||||||
|
|
||||||
|
<p><i class="fas fa-info-circle"></i> Team members and pending invites will appear here.</p>
|
||||||
|
|
||||||
|
@if (!$team->users->isEmpty())
|
||||||
|
<table class="table table-borderless">
|
||||||
|
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>#</th>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Roles</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
|
||||||
|
@foreach ($team->users as $teammate)
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>{{ $teammate->id }}</td>
|
||||||
|
<td><a target="_blank" href="{{ route('showSingleProfile', ['user' => $teammate->id]) }}">{{ $teammate->name }}</a></td>
|
||||||
|
<td>
|
||||||
|
@foreach ($teammate->roles as $teammate_role)
|
||||||
|
|
||||||
|
<span class="badge badge-secondary">{{ $teammate_role->name }}</span>
|
||||||
|
|
||||||
|
@endforeach
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
@if ($teammate->isOwnerOfTeam($team))
|
||||||
|
<span class="badge badge-success">Team Owner</span>
|
||||||
|
@else
|
||||||
|
<span class="badge badge-primary">Team Member</span>
|
||||||
|
@endif
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<button rel="buttonTxtTooltip" data-toggle="tooltip" data-placement="top" title="Kick User" type="button" class="btn btn-danger btn-sm"><i class="fas fa-bolt"></i></button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
@endforeach
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
|
||||||
|
</table>
|
||||||
|
@else
|
||||||
|
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
|
||||||
|
<span><i class="fas fa-exclamation-triangle"></i> <b>There don't seem to be any teammates here!</b></span>
|
||||||
|
<p>Start inviting some people and grow your team.</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<x-slot name="modalFooter"></x-slot>
|
||||||
|
|
||||||
|
</x-modal>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|
||||||
<div class="col text-center">
|
<div class="col text-center">
|
||||||
|
@ -91,8 +155,42 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-footer">
|
<div class="card-footer">
|
||||||
<button type="button" class="btn btn-success" onclick="$('#editTeam').submit()"><i class="fas fa-save"></i> Save and Close</button>
|
<button type="button" class="btn btn-success ml-2" onclick="$('#editTeam').submit()"><i class="fas fa-save"></i> Save and Close</button>
|
||||||
<button type="button" class="btn btn-danger" onclick="location.href='{{ route('teams.index') }}'"><i class="far fa-trash-alt"></i> Cancel</button>
|
<button type="button" class="btn btn-warning ml-2" onclick="$('#userlist_modal').modal('show')"><i class="fas fa-users"></i> Team Members</button>
|
||||||
|
<button type="button" class="btn btn-danger ml-2" onclick="location.href='{{ route('teams.index') }}'"><i class="far fa-trash-alt"></i> Cancel</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="col">
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
|
||||||
|
<div class="card-header">
|
||||||
|
<div class="card-title"><h4>Team Vacancies</h4></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body">
|
||||||
|
|
||||||
|
<span class="text-muted"><i class="fas fa-info-circle"></i> The vacancies you select determine what applications your team members see. All applications under the vacancies you choose will be displayed.</span>
|
||||||
|
|
||||||
|
<form>
|
||||||
|
|
||||||
|
<select id="assocVacancies" name="assocVacancies" multiple="multiple">
|
||||||
|
|
||||||
|
@foreach($vacancies as $vacancy)
|
||||||
|
|
||||||
|
<option value="{{ $vacancy->id }}">{{ $vacancy->vacancyName }}</option>
|
||||||
|
|
||||||
|
@endforeach
|
||||||
|
|
||||||
|
</select>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -66,7 +66,16 @@
|
||||||
|
|
||||||
<div class="card-header bg-indigo">
|
<div class="card-header bg-indigo">
|
||||||
|
|
||||||
<div class="card-title"><h4>{{ __('messages.teams.m_teams_page') }}</h4></div>
|
|
||||||
|
<div class="row">
|
||||||
|
|
||||||
|
<div class="col">
|
||||||
|
|
||||||
|
<div class="card-title"><h4>{{ __('messages.teams.m_teams_page') }} <span class="badge badge-warning"><i class="fas fa-check-circle"></i> {{ (Auth::user()->currentTeam) ? Auth::user()->currentTeam->name : 'Select a team' }}</span></h4></div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -96,7 +105,7 @@
|
||||||
<td>{{ $team->name }}</td>
|
<td>{{ $team->name }}</td>
|
||||||
<td>
|
<td>
|
||||||
<button type="button" class="btn btn-success btn-sm ml-2" onclick="location.href='{{ route('teams.edit', ['team' => $team->id]) }}'"><i class="fas fa-cogs"></i> Settings</button>
|
<button type="button" class="btn btn-success btn-sm ml-2" onclick="location.href='{{ route('teams.edit', ['team' => $team->id]) }}'"><i class="fas fa-cogs"></i> Settings</button>
|
||||||
<button type="button" class="btn btn-warning btn-sm ml-2"><i class="fas fas fa-long-arrow-alt-right"></i> Team Dashboard</button>
|
<button onclick="location.href='{{ route('switchTeam', ['team' => $team]) }}'" rel="buttonTxtTooltip" data-placement="top" data-toggle="tooltip" title="Select your active team (for dasboard context, etc)" type="button" class="btn btn-warning btn-sm ml-2"><i class="fas fa-random"></i> Switch To</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
|
@ -122,6 +131,7 @@
|
||||||
<div class="card-footer">
|
<div class="card-footer">
|
||||||
|
|
||||||
<button type="button" class="btn btn-success btn-sm ml-3" onclick="$('#newTeamModal').modal('show')"><i class="fas fa-plus-circle"></i> New team</button>
|
<button type="button" class="btn btn-success btn-sm ml-3" onclick="$('#newTeamModal').modal('show')"><i class="fas fa-plus-circle"></i> New team</button>
|
||||||
|
<button type="button" class="btn btn-warning btn-sm ml-3"><i class="fas fas fa-long-arrow-alt-right"></i> Team Dashboard</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -20,8 +20,61 @@
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
|
|
||||||
|
<x-modal id="deleteAccountModal" modal-label="deleteAccountModalLabel" modal-title="Close account" include-close-button="true">
|
||||||
|
|
||||||
|
<p>Deleting your account is an irreversible process. The following data will be deleted (including personally identifiable data):</p>
|
||||||
|
<ul>
|
||||||
|
<li>Last IP address</li>
|
||||||
|
<li>Name, Email and MC Username</li>
|
||||||
|
<li>Your previous applications</li>
|
||||||
|
<li>Your profile data and preferences</li>
|
||||||
|
<li>If you were a staff member:</li>
|
||||||
|
<ul>
|
||||||
|
<li>Your comments</li>
|
||||||
|
<li>Any votes</li>
|
||||||
|
<li>Your roles</li>
|
||||||
|
</ul>
|
||||||
|
</ul>
|
||||||
|
<p>What is not deleted:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Server logs of your visits, including IP addresses</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
|
<form id="deleteAccountForm" method="POST" action="{{ route('userDelete') }}">
|
||||||
|
|
||||||
|
@csrf
|
||||||
|
@method('PATCH')
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="currentPassword">Re-enter your password</label>
|
||||||
|
<input class="form-control" autocomplete="current-password" type="password" name="currentPassword" id="currentPassword" required>
|
||||||
|
<p class="text-muted text-sm"><i class="fas fa-info-circle"></i> For your security, your password is always required for sensitive operations. <a href="{{ route('password.request') }}">Forgot your password?</a></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (Auth::user()->has2FA())
|
||||||
|
<div class="form-group mt-5">
|
||||||
|
|
||||||
|
<label for="otp">Two-factor authentication code</label>
|
||||||
|
<input type="text" id="otp" name="otp" class="form-control">
|
||||||
|
<p class="text-muted text-sm"><i class="fas fa-info-circle"></i> You cannot recover lost 2FA secrets.</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<x-slot name="modalFooter">
|
||||||
|
|
||||||
|
<button onclick="$('#deleteAccountForm').submit()" type="button" class="btn btn-warning"><i class="fas fa-exclamation-triangle"></i> Continue</button>
|
||||||
|
|
||||||
|
</x-slot>
|
||||||
|
|
||||||
|
</x-modal>
|
||||||
|
|
||||||
@if (!Auth::user()->has2FA())
|
@if (!Auth::user()->has2FA())
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<x-modal id="twoFactorAuthModal" modal-label="2faLabel" modal-title="{{__('messages.2fa_txt')}}" include-close-button="true">
|
<x-modal id="twoFactorAuthModal" modal-label="2faLabel" modal-title="{{__('messages.2fa_txt')}}" include-close-button="true">
|
||||||
|
|
||||||
|
@ -170,6 +223,9 @@
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" id="contactSettingsTab" data-toggle="tab" href="#contactSettings" role="tab" aria-controls="ContactSettings" aria-selected="false">{{__('messages.profile.contact_settings')}}</a>
|
<a class="nav-link" id="contactSettingsTab" data-toggle="tab" href="#contactSettings" role="tab" aria-controls="ContactSettings" aria-selected="false">{{__('messages.profile.contact_settings')}}</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" id="dangerZoneTab" data-toggle="tab" href="#dangerZone" role="tab" aria-controls="DangerZone" aria-selected="false">Danger Zone</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -250,6 +306,15 @@
|
||||||
<button class="btn btn-success" type="button" onclick="document.getElementById('changeEmail').submit()">{{__('messages.profile.change_email')}}</button>
|
<button class="btn btn-success" type="button" onclick="document.getElementById('changeEmail').submit()">{{__('messages.profile.change_email')}}</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="tab-pane fade p-3" id="dangerZone" role="tabpanel" aria-labelledby="dangerZoneTab">
|
||||||
|
<h5 class="card-title">Danger Zone</h5>
|
||||||
|
<p class="card-text text-bold"><i class="fas fa-radiation"></i> Careful! Actions in these tab might result in irreversible loss of data.</p>
|
||||||
|
|
||||||
|
<button onclick="$('#deleteAccountModal').modal('show')" rel="buttonTxtTooltip" data-toggle="tooltip" data-placement="top" title="This action will delete your account permanently." class="btn btn-danger" type="button"><i class="fas fa-user-slash"></i> Close Account</button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -0,0 +1,167 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width">
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||||
|
<title>Your account has been deleted</title>
|
||||||
|
<style>
|
||||||
|
/* -------------------------------------
|
||||||
|
INLINED WITH htmlemail.io/inline
|
||||||
|
------------------------------------- */
|
||||||
|
/* -------------------------------------
|
||||||
|
RESPONSIVE AND MOBILE FRIENDLY STYLES
|
||||||
|
------------------------------------- */
|
||||||
|
@media only screen and (max-width: 620px) {
|
||||||
|
table[class=body] h1 {
|
||||||
|
font-size: 28px !important;
|
||||||
|
margin-bottom: 10px !important;
|
||||||
|
}
|
||||||
|
table[class=body] p,
|
||||||
|
table[class=body] ul,
|
||||||
|
table[class=body] ol,
|
||||||
|
table[class=body] td,
|
||||||
|
table[class=body] span,
|
||||||
|
table[class=body] a {
|
||||||
|
font-size: 16px !important;
|
||||||
|
}
|
||||||
|
table[class=body] .wrapper,
|
||||||
|
table[class=body] .article {
|
||||||
|
padding: 10px !important;
|
||||||
|
}
|
||||||
|
table[class=body] .content {
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
table[class=body] .container {
|
||||||
|
padding: 0 !important;
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
table[class=body] .main {
|
||||||
|
border-left-width: 0 !important;
|
||||||
|
border-radius: 0 !important;
|
||||||
|
border-right-width: 0 !important;
|
||||||
|
}
|
||||||
|
table[class=body] .btn table {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
table[class=body] .btn a {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
table[class=body] .img-responsive {
|
||||||
|
height: auto !important;
|
||||||
|
max-width: 100% !important;
|
||||||
|
width: auto !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------
|
||||||
|
PRESERVE THESE STYLES IN THE HEAD
|
||||||
|
------------------------------------- */
|
||||||
|
@media all {
|
||||||
|
.ExternalClass {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.ExternalClass,
|
||||||
|
.ExternalClass p,
|
||||||
|
.ExternalClass span,
|
||||||
|
.ExternalClass font,
|
||||||
|
.ExternalClass td,
|
||||||
|
.ExternalClass div {
|
||||||
|
line-height: 100%;
|
||||||
|
}
|
||||||
|
.apple-link a {
|
||||||
|
color: inherit !important;
|
||||||
|
font-family: inherit !important;
|
||||||
|
font-size: inherit !important;
|
||||||
|
font-weight: inherit !important;
|
||||||
|
line-height: inherit !important;
|
||||||
|
text-decoration: none !important;
|
||||||
|
}
|
||||||
|
#MessageViewBody a {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: inherit;
|
||||||
|
font-family: inherit;
|
||||||
|
font-weight: inherit;
|
||||||
|
line-height: inherit;
|
||||||
|
}
|
||||||
|
.btn-primary table td:hover {
|
||||||
|
background-color: #34495e !important;
|
||||||
|
}
|
||||||
|
.btn-primary a:hover {
|
||||||
|
background-color: #34495e !important;
|
||||||
|
border-color: #34495e !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body class="" style="background-color: #f6f6f6; font-family: sans-serif; -webkit-font-smoothing: antialiased; font-size: 14px; line-height: 1.4; margin: 0; padding: 0; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;">
|
||||||
|
<span class="preheader" style="color: transparent; display: none; height: 0; max-height: 0; max-width: 0; opacity: 0; overflow: hidden; mso-hide: all; visibility: hidden; width: 0;">Your account has just been deleted!</span>
|
||||||
|
<table border="0" cellpadding="0" cellspacing="0" class="body" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; background-color: #f6f6f6;">
|
||||||
|
<tr>
|
||||||
|
<td style="font-family: sans-serif; font-size: 14px; vertical-align: top;"> </td>
|
||||||
|
<td class="container" style="font-family: sans-serif; font-size: 14px; vertical-align: top; display: block; Margin: 0 auto; max-width: 580px; padding: 10px; width: 580px;">
|
||||||
|
<div class="content" style="box-sizing: border-box; display: block; Margin: 0 auto; max-width: 580px; padding: 10px;">
|
||||||
|
|
||||||
|
<!-- START CENTERED WHITE CONTAINER -->
|
||||||
|
<table class="main" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; background: #ffffff; border-radius: 3px;">
|
||||||
|
|
||||||
|
<!-- START MAIN CONTENT AREA -->
|
||||||
|
<tr>
|
||||||
|
<td class="wrapper" style="font-family: sans-serif; font-size: 14px; vertical-align: top; box-sizing: border-box; padding: 20px;">
|
||||||
|
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;">
|
||||||
|
<tr>
|
||||||
|
<td style="font-family: sans-serif; font-size: 14px; vertical-align: top;">
|
||||||
|
<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; Margin-bottom: 15px;">Hi {{ $name }},</p>
|
||||||
|
<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; Margin-bottom: 15px;">Someone (hopefully you) has requested that your account at {{ config('app.name') }} be deleted.</p>
|
||||||
|
<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; Margin-bottom: 15px;">As a security measure, an email is always sent out to make sure you really want to delete your account.</p>
|
||||||
|
<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; Margin-bottom: 15px;">This is the IP address the request was made from: {{ $originalIP }}.</p>
|
||||||
|
<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; Margin-bottom: 15px;">If you don't do anything, your account will automatically be permanently deleted in 30 days, and will remain unaccessible for that time period.</p>
|
||||||
|
<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; Margin-bottom: 15px;">Click one of the buttons below to make a decision.</p>
|
||||||
|
|
||||||
|
|
||||||
|
<table border="0" cellpadding="0" cellspacing="0" class="btn btn-primary" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; box-sizing: border-box;">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td align="left" style="font-family: sans-serif; font-size: 14px; vertical-align: top; padding-bottom: 15px;">
|
||||||
|
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: auto;">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td style="font-family: sans-serif; font-size: 14px; vertical-align: top; background-color: #3498db; border-radius: 5px; text-align: center; margin-left: 5px;"> <a href="{{ route('processDeleteConfirmation', ['ID' => $userID, 'action' => 'confirm', 'token' => $deleteToken]) }}" target="_blank" style="display: inline-block; color: #ffffff; background-color: #3498db; border: solid 1px #3498db; border-radius: 5px; box-sizing: border-box; cursor: pointer; text-decoration: none; font-size: 14px; font-weight: bold; margin: 0; padding: 12px 25px; text-transform: capitalize; border-color: #3498db;">Delete Account</a> </td>
|
||||||
|
<td style="font-family: sans-serif; font-size: 14px; vertical-align: top; background-color: #3498db; border-radius: 5px; text-align: center; margin-left: 5px;"> <a href="{{ route('processDeleteConfirmation', ['ID' => $userID, 'action' => 'cancel', 'token' => $cancelToken]) }}" target="_blank" style="display: inline-block; color: #ffffff; background-color: #3498db; border: solid 1px #3498db; border-radius: 5px; box-sizing: border-box; cursor: pointer; text-decoration: none; font-size: 14px; font-weight: bold; margin: 0; padding: 12px 25px; text-transform: capitalize; border-color: #3498db;">Cancel Deletion</a> </td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; Margin-bottom: 15px;">Thank you!</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<!-- END MAIN CONTENT AREA -->
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<!-- START FOOTER -->
|
||||||
|
<div class="footer" style="clear: both; Margin-top: 10px; text-align: center; width: 100%;">
|
||||||
|
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;">
|
||||||
|
<tr>
|
||||||
|
<td class="content-block" style="font-family: sans-serif; vertical-align: top; padding-bottom: 10px; padding-top: 10px; font-size: 12px; color: #999999; text-align: center;">
|
||||||
|
<span class="apple-link" style="color: #999999; font-size: 12px; text-align: center;">Staff Manager</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<!-- END FOOTER -->
|
||||||
|
|
||||||
|
<!-- END CENTERED WHITE CONTAINER -->
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td style="font-family: sans-serif; font-size: 14px; vertical-align: top;"> </td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -30,6 +30,9 @@ Route::group(['prefix' => LaravelLocalization::setLocale(), 'middleware' => [ 'l
|
||||||
Route::post('/form/contact', 'ContactController@create')
|
Route::post('/form/contact', 'ContactController@create')
|
||||||
->name('sendSubmission');
|
->name('sendSubmission');
|
||||||
|
|
||||||
|
Route::get('/accounts/danger-zone/{ID}/{action}/{token}', 'UserController@processDeleteConfirmation')
|
||||||
|
->name('processDeleteConfirmation');
|
||||||
|
|
||||||
|
|
||||||
Route::group(['middleware' => ['auth', 'forcelogout', '2fa', 'verified']], function(){
|
Route::group(['middleware' => ['auth', 'forcelogout', '2fa', 'verified']], function(){
|
||||||
|
|
||||||
|
@ -41,8 +44,12 @@ Route::group(['prefix' => LaravelLocalization::setLocale(), 'middleware' => [ 'l
|
||||||
->name('directory');
|
->name('directory');
|
||||||
|
|
||||||
|
|
||||||
Route::post('teams/{team}/invites/send', 'TeamController@invite')
|
|
||||||
->name('sendInvite');
|
Route::post('teams/{team}/invites/send', 'TeamController@invite')
|
||||||
|
->name('sendInvite');
|
||||||
|
|
||||||
|
Route::get('teams/{team}/switch', 'TeamController@switchTeam')
|
||||||
|
->name('switchTeam');
|
||||||
|
|
||||||
Route::get('teams/invites/{action}/{token}', 'TeamController@processInviteAction')
|
Route::get('teams/invites/{action}/{token}', 'TeamController@processInviteAction')
|
||||||
->name('processInvite');
|
->name('processInvite');
|
||||||
|
@ -51,6 +58,8 @@ Route::group(['prefix' => LaravelLocalization::setLocale(), 'middleware' => [ 'l
|
||||||
Route::resource('teams', 'TeamController');
|
Route::resource('teams', 'TeamController');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Route::group(['prefix' => '/applications'], function (){
|
Route::group(['prefix' => '/applications'], function (){
|
||||||
|
|
||||||
Route::get('/my-applications', 'ApplicationController@showUserApps')
|
Route::get('/my-applications', 'ApplicationController@showUserApps')
|
||||||
|
@ -152,6 +161,9 @@ Route::group(['prefix' => LaravelLocalization::setLocale(), 'middleware' => [ 'l
|
||||||
Route::patch('/settings/account/twofa/disable', 'UserController@remove2FASecret')
|
Route::patch('/settings/account/twofa/disable', 'UserController@remove2FASecret')
|
||||||
->name('disable2FA');
|
->name('disable2FA');
|
||||||
|
|
||||||
|
Route::patch('/settings/account/dg/delete', 'UserController@userDelete')
|
||||||
|
->name('userDelete');
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue