feat(main): several improvements, home page updates

This commit is contained in:
Miguel Nogueira 2022-06-28 16:25:56 +01:00
parent 1236970bef
commit 92a3972371
16 changed files with 531 additions and 597 deletions

View File

@ -13,6 +13,8 @@ GUIDELINES_URL="#"
PRIVACY_URL="#"
TERMS_URL="#"
SOURCE_REPO="#"
SUPPORT_EMAIL=
SUPPORT_URL=
# The auth banner is a relative path

View File

@ -1,88 +0,0 @@
<?php
/*
* Copyright © 2020 Miguel Nogueira
*
* This file is part of Raspberry Staff Manager.
*
* Raspberry Staff Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Raspberry Staff Manager is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Raspberry Staff Manager. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Http\Controllers;
use App\Ban;
use App\Events\UserBannedEvent;
use App\Http\Requests\BanUserRequest;
use App\Services\AccountSuspensionService;
use App\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class BanController extends Controller
{
protected $suspensionService;
public function __construct(AccountSuspensionService $suspensionService)
{
// Inject the service via DI
$this->suspensionService = $suspensionService;
}
public function insert(BanUserRequest $request, User $user)
{
if (config('demo.is_enabled')) {
return redirect()
->back()
->with('error', __('This feature is disabled'));
}
$this->authorize('create', [Ban::class, $user]);
if (!$this->suspensionService->isSuspended($user)) {
$this->suspensionService->suspend($request->reason, $request->duration, $user, $request->suspensionType);
$request->session()->flash('success', __('Account suspended.'));
} else {
$request->session()->flash('error', __('Account already suspended!'));
}
return redirect()->back();
}
public function delete(Request $request, User $user)
{
if (config('demo.is_enabled')) {
return redirect()
->back()
->with('error', __('This feature is disabled'));
}
$this->authorize('delete', $user->bans);
if ($this->suspensionService->isSuspended($user)) {
$this->suspensionService->unsuspend($user);
$request->session()->flash('success', __('Account unsuspended successfully!'));
} else {
$request->session()->flash('error', __('This account isn\'t suspended!'));
}
return redirect()->back();
}
}

View File

@ -26,6 +26,7 @@ use App\Exceptions\ProfileCreationFailedException;
use App\Exceptions\ProfileNotFoundException;
use App\Facades\IP;
use App\Http\Requests\ProfileSave;
use App\Services\AccountSuspensionService;
use App\Services\ProfileService;
use App\User;
use Carbon\Carbon;
@ -35,7 +36,7 @@ use Spatie\Permission\Models\Role;
class ProfileController extends Controller
{
private $profileService;
private ProfileService $profileService;
public function __construct(ProfileService $profileService) {
$this->profileService = $profileService;
@ -63,7 +64,7 @@ class ProfileController extends Controller
]);
}
public function showSingleProfile(User $user)
public function showSingleProfile(AccountSuspensionService $accountSuspensionService, User $user)
{
if (is_null($user->profile)) {
@ -77,21 +78,10 @@ class ProfileController extends Controller
$socialMediaProfiles = json_decode($user->profile->socialLinks, true);
$createdDate = Carbon::parse($user->created_at);
$systemRoles = Role::all()->pluck('name')->all();
$userRoles = $user->roles->pluck('name')->all();
$roleList = [];
foreach ($systemRoles as $role) {
if (in_array($role, $userRoles)) {
$roleList[$role] = true;
} else {
$roleList[$role] = false;
}
}
$suspensionInfo = null;
if ($user->isBanned())
if ($accountSuspensionService->isSuspended($user))
{
$suspensionInfo = [

View File

@ -22,7 +22,9 @@
namespace App\Http\Controllers;
use App\Ban;
use App\Facades\IP;
use App\Http\Requests\Add2FASecretRequest;
use App\Http\Requests\BanUserRequest;
use App\Http\Requests\ChangeEmailRequest;
use App\Http\Requests\ChangePasswordRequest;
use App\Http\Requests\DeleteUserRequest;
@ -32,6 +34,7 @@ use App\Http\Requests\SearchPlayerRequest;
use App\Http\Requests\UpdateUserRequest;
use App\Notifications\ChangedPassword;
use App\Notifications\EmailChanged;
use App\Services\AccountSuspensionService;
use App\Traits\DisablesFeatures;
use App\Traits\HandlesAccountDeletion;
use App\Traits\ReceivesAccountTokens;
@ -45,8 +48,15 @@ use Spatie\Permission\Models\Role;
class UserController extends Controller
{
use HandlesAccountDeletion;
use HandlesAccountDeletion, DisablesFeatures;
/**
* Shows list of users
*
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function showUsers()
{
$this->authorize('viewPlayers', User::class);
@ -59,6 +69,15 @@ class UserController extends Controller
]);
}
/**
* Searches for a player with the given search query.
*
* @deprecated Until Algolia implementation
* @param SearchPlayerRequest $request
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function showPlayersLike(SearchPlayerRequest $request)
{
$this->authorize('viewPlayers', User::class);
@ -85,6 +104,16 @@ class UserController extends Controller
}
}
/**
* Shows the user account's settings page
*
* @param Request $request
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
* @throws \PragmaRX\Google2FA\Exceptions\IncompatibleWithGoogleAuthenticatorException
* @throws \PragmaRX\Google2FA\Exceptions\InvalidCharactersException
* @throws \PragmaRX\Google2FA\Exceptions\SecretKeyTooShortException
*/
public function showAccount(Request $request)
{
$QRCode = null;
@ -109,6 +138,49 @@ class UserController extends Controller
->with('twofaQRCode', $QRCode);
}
/**
* Show account management screen
*
* @param AccountSuspensionService $suspensionService
* @param Request $request
* @param User $user
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function showAcocuntManagement(AccountSuspensionService $suspensionService, Request $request, User $user)
{
$this->authorize('adminEdit', $user);
$systemRoles = Role::all()->pluck('name')->all();
$userRoles = $user->roles->pluck('name')->all();
$roleList = [];
foreach ($systemRoles as $role) {
if (in_array($role, $userRoles)) {
$roleList[$role] = true;
} else {
$roleList[$role] = false;
}
}
return view('dashboard.user.manage')
->with([
'user' => $user,
'roles' => $roleList,
'ipInfo' => IP::lookup($request->ip())
]);
}
/**
* Log out other sessions for the current user
*
* @param FlushSessionsRequest $request
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\AuthenticationException
*/
public function flushSessions(FlushSessionsRequest $request)
{
// TODO: Move all log calls to a listener, which binds to an event fired by each significant event, such as this one
@ -127,6 +199,14 @@ class UserController extends Controller
return redirect()->back();
}
/**
* Change the current user's password
*
* @param ChangePasswordRequest $request
* @return \Illuminate\Http\RedirectResponse|void
*/
public function changePassword(ChangePasswordRequest $request)
{
if (config('demo.is_enabled')) {
@ -155,13 +235,17 @@ class UserController extends Controller
}
}
/**
* Change the current user's email address
*
* @param ChangeEmailRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function changeEmail(ChangeEmailRequest $request)
{
if (config('demo.is_enabled')) {
return redirect()
->back()
->with('error', __('This feature is disabled'));
}
$this->disable();
$user = User::find(Auth::user()->id);
@ -184,13 +268,18 @@ class UserController extends Controller
return redirect()->back();
}
/**
* Delete the given user's account
*
* @param DeleteUserRequest $request
* @param User $user
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function delete(DeleteUserRequest $request, User $user)
{
if (config('demo.is_enabled')) {
return redirect()
->back()
->with('error', _('This feature is disabled'));
}
$this->disable();
$this->authorize('delete', $user);
@ -204,14 +293,19 @@ class UserController extends Controller
return redirect()->route('registeredPlayerList');
}
/**
* Update a given user's details
*
* @param UpdateUserRequest $request
* @param User $user
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function update(UpdateUserRequest $request, User $user)
{
if (config('demo.is_enabled')) {
return redirect()
->back()
->with('error', __('This feature is disabled'));
}
$this->authorize('adminEdit', $user);
$this->disable();
// Mass update would not be possible here without extra code, making route model binding useless
$user->email = $request->email;
@ -243,6 +337,16 @@ class UserController extends Controller
return redirect()->back();
}
/**
* Generate and add a 2FA secret for the current user
*
* @param Add2FASecretRequest $request
* @return \Illuminate\Http\RedirectResponse
* @throws \PragmaRX\Google2FA\Exceptions\IncompatibleWithGoogleAuthenticatorException
* @throws \PragmaRX\Google2FA\Exceptions\InvalidCharactersException
* @throws \PragmaRX\Google2FA\Exceptions\SecretKeyTooShortException
*/
public function add2FASecret(Add2FASecretRequest $request)
{
if (config('demo.is_enabled')) {
@ -285,6 +389,13 @@ class UserController extends Controller
return redirect()->back();
}
/**
* Remove the current user's two factor secret key
*
* @param Remove2FASecretRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function remove2FASecret(Remove2FASecretRequest $request)
{
Log::warning('SECURITY: Disabling two factor authentication (user initiated)', [
@ -300,6 +411,15 @@ class UserController extends Controller
return redirect()->back();
}
/**
* Demote the given user's privileges
*
* @param Request $request
* @param User $user
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function terminate(Request $request, User $user)
{
$this->authorize('terminate', User::class);
@ -330,4 +450,58 @@ class UserController extends Controller
//TODO: Dispatch event
return redirect()->back();
}
/**
* Suspend the given user
*
* @param AccountSuspensionService $suspensionService
* @param BanUserRequest $request
* @param User $user
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function suspend(AccountSuspensionService $suspensionService, BanUserRequest $request, User $user)
{
$this->authorize('create', [Ban::class, $user]);
$this->disable();
if (!$suspensionService->isSuspended($user)) {
$suspensionService->suspend($request->reason, $request->duration, $user, $request->suspensionType);
$request->session()->flash('success', __('Account suspended.'));
} else {
$request->session()->flash('error', __('Account already suspended!'));
}
return redirect()->back();
}
/**
* Unsuspend the given user
*
* @param AccountSuspensionService $suspensionService
* @param Request $request
* @param User $user
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function unsuspend(AccountSuspensionService $suspensionService, Request $request, User $user)
{
$this->authorize('delete', $user->bans);
$this->disable();
if ($suspensionService->isSuspended($user)) {
$suspensionService->unsuspend($user);
$request->session()->flash('success', __('Account unsuspended successfully!'));
} else {
$request->session()->flash('error', __('This account isn\'t suspended!'));
}
return redirect()->back();
}
}

30
app/Listeners/NewUser.php Executable file
View File

@ -0,0 +1,30 @@
<?php
namespace App\Listeners;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
class NewUser
{
/**
* Create the event listener.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Handle the event.
*
* @param object $event
* @return void
*/
public function handle($event)
{
//
}
}

View File

@ -120,6 +120,17 @@ class AccountSuspensionService
}
/**
* Retrieves the reason for the user's suspension.
*
* @param User $user The user account to check
* @return string|bool Reason for the suspension, false if not suspended
*/
public function getSuspensionReason(User $user): string|bool {
return ($this->isSuspended($user)) ? $user->bans->reason : false;
}
/**
* Checks whether an account is locked
*

25
app/Traits/DisablesFeatures.php Executable file
View File

@ -0,0 +1,25 @@
<?php
namespace App\Traits;
use Illuminate\Http\RedirectResponse;
trait DisablesFeatures
{
/**
* Checks if demo mode is active. If so, it stops any more logic from running.
*
* @return RedirectResponse|null
*/
protected function disable(): RedirectResponse|null
{
if (config('demo.is_enabled')) {
return redirect()
->back()
->with('error', __('This feature is disabled'));
}
return null;
}
}

View File

@ -71,6 +71,7 @@ class User extends Authenticatable implements MustVerifyEmail
public function votes()
{
return $this->hasMany('App\Vote', 'userID', 'id');
}
public function profile()

View File

@ -110,6 +110,8 @@ return [
| RBRecruiter will display these URLs at appropriate locations and force users to accept them,
| if legally necessary, such as in the registration & application form pages.
|
| Additionally, you can also specify a support email and URL where your users/customers can send inquiries if necessary.
|
| You can leave these URLs empty if your website hasn't entered production yet, but we recommend
| you draft these documents as soon as possible.
|
@ -118,6 +120,8 @@ return [
'terms_url' => env('TERMS_URL', '#'),
'privacy_url' => env('PRIVACY_URL', '#'),
'guidelines_url' => env('GUIDELINES_URL', '#'),
'support_url' => env('SUPPORT_URL', 'https://support.example.com'),
'support_email' => env('SUPPORT_EMAIL', 'support@example.com'),
'source_repo' => env('SOURCE_REPO', 'https://code.webvokestudio.pt/miguel456/rbrecruiter'),
/*

View File

@ -0,0 +1,23 @@
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Profile>
*/
class ProfileFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition()
{
return [
//
];
}
}

View File

@ -1,47 +1,23 @@
<?php
/*
* Copyright © 2020 Miguel Nogueira
*
* This file is part of Raspberry Staff Manager.
*
* Raspberry Staff Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Raspberry Staff Manager is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Raspberry Staff Manager. If not, see <https://www.gnu.org/licenses/>.
*/
namespace Database\Factories;
use App\User;
use Faker\Generator as Faker;
use Illuminate\Support\Str;
use Illuminate\Database\Eloquent\Factories\Factory;
/*
|--------------------------------------------------------------------------
| Model Factories
|--------------------------------------------------------------------------
|
| This directory should contain each of the model factory definitions for
| your application. Factories provide a convenient way to generate new
| model instances for testing / seeding your application's database.
|
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\User>
*/
$factory->define(User::class, function (Faker $faker) {
class UserFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition()
{
return [
'name' => $faker->name,
'email' => $faker->unique()->safeEmail,
'email_verified_at' => now(),
'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
'remember_token' => Str::random(10),
//
];
});
}
}

View File

@ -103,7 +103,8 @@
</td>
<td>{{$user->created_at}}</td>
<td>
<button type="button" class="btn btn-sm btn-success" onclick="window.location.href='{{route('showSingleProfile', ['user' => $user->id])}}'"><i class="fa fa-eye"></i></button>
<button type="button" class="btn btn-sm btn-success" onclick="window.location.href='{{route('showSingleProfile', ['user' => $user->id])}}'"><i class="fa fa-eye"></i> {{ __('View') }}</button>
<a class="ml-2 btn btn-sm btn-warning" href="{{ route('manageUser', ['user' => $user->id]) }}"><i class="fas fa-wrench"></i> {{ __('Manage') }}</a>
</td>
</tr>

View File

@ -0,0 +1,211 @@
@extends('adminlte::page')
@section('title', config('app.name') . ' | ' . __('Account Management'))
@section('content_header')
<h4>{{ __('Users / Accounts / :username / Manage', ['username' => $user->name]) }}</h4>
@stop
@section('js')
<script src="/js/app.js"></script>
<x-global-errors></x-global-errors>
@stop
@section('content')
<x-modal id="banAccountModal" modal-label="banAccount" modal-title="{{__('Please confirm')}}" include-close-button="true">
<p>{{__("Please confirm that you want to suspend this account. You'll need to add a reason and expiration date to confirm this.")}}</p>
<form id="banAccountForm" name="banAccount" method="POST" action="{{route('banUser', ['user' => $user->id])}}">
@csrf
@if($demoActive)
<div class="alert alert-danger">
<p class="font-weight-bold"><i class="fas fa-exclamation-triangle"></i> {{ __('This feature is disabled') }}</p>
</div>
@endif
<div class="row">
<div class="col">
<label for="reason">{{__('Public note')}}</label>
<input type="text" name="reason" id="reason" class="form-control" placeholder="{{__('e.g. Spamming')}}">
</div>
<div class="col">
<label for="duration">{{ __('Duration') }}</label>
<input type="text" name="duration" id="duration" class="form-control" placeholder="{{ __('in days') }}">
</div>
</div>
<div class="mt-2">
<input type="hidden" name="suspensionType" value="off">
<label for="suspensionType">{{ __('Suspension type') }}</label><br>
<input type="checkbox" id="suspensionType" name="suspensionType" checked data-toggle="toggle" data-on="Temporary" data-off="Permanent" data-onstyle="success" data-offstyle="danger" data-width="130" data-height="40">
<p class="text-muted text-sm"><i class="fas fa-info-circle"></i> {{ __('Temporary suspensions will be automatically lifted. The suspension note is visible to all users. Suspended users will not be able to login or register.') }}</p>
</div>
</form>
<x-slot name="modalFooter">
<button id="banAccountButton" type="button" class="btn btn-danger" {{ ($demoActive) ? 'disabled' : '' }} ><i class="fa fa-gavel"></i> {{__('Confirm')}}</button>
</x-slot>
</x-modal>
@if (!Auth::user()->is($user) && $user->isStaffMember())
<x-modal id="terminateUser" modal-label="terminateUser" modal-title="{{__('Please Confirm')}}" include-close-button="true">
@if($demoActive)
<div class="alert alert-danger">
<p class="font-weight-bold"><i class="fas fa-exclamation-triangle"></i> {{ __('This feature is disabled') }}</p>
</div>
@endif
<p><i class="fa fa-exclamation-triangle"></i> <b>{{__('You are about to terminate a recruited staff member')}}</b></p>
<p>
{{__('Terminating a staff member will remove their privileges on the application management site and connected integrations configured for the vacancy they applied for.')}}
</p>
<p>
<b>{{__('THIS PROCESS IS IRREVERSIBLE AND IMMEDIATE')}}</b>
</p>
<x-slot name="modalFooter">
<form method="POST" action="{{route('terminateStaffMember', ['user' => $user->id])}}" id="terminateUserForm">
@csrf
@method('PATCH')
<button type="submit" class="btn btn-warning" {{ ($demoActive) ? 'disabled' : '' }}><i class="fas fa-exclamation-circle"></i> {{__('Confirm')}}</button>
</form>
</x-slot>
</x-modal>
@endif
<x-modal id="deleteAccount" modal-label="deleteAccount" modal-title="{{__('Confirm')}}" include-close-button="true">
@if($demoActive)
<div class="alert alert-danger">
<p class="font-weight-bold"><i class="fas fa-exclamation-triangle"></i> {{ __('This feature is disabled') }}</p>
</div>
@endif
<p><i class="fa fa-exclamation-triangle"></i><b> {{__('WARNING: This is a potentially destructive action!')}}</b></p>
<p>{{__("Deleting a user's account is an irreversible process. Historic and current applications, votes, and profile content, as well as any personally identifiable information will be immediately erased.")}}</p>
<form id="deleteAccountForm" method="POST" action={{route('deleteUser', ['user' => $user->id])}}>
@csrf
@method('DELETE')
<label for="promptConfirm">{{__('Type to confirm: ')}} "DELETE ACCOUNT"</label>
<input type="text" name="confirmPrompt" class="form-control" placeholder="{{__('Please type the above text')}}">
</form>
<x-slot name="modalFooter">
<button type="button" class="btn btn-danger" {{ ($demoActive) ? 'disabled' : '' }} onclick="document.getElementById('deleteAccountForm').submit()"><i class="fa fa-trash"></i> {{strtoupper(__('Confirm'))}}</button>
</x-slot>
</x-modal>
<x-modal id="editUser" modal-label="editUser" modal-title="{{__('Edit account')}}" include-close-button="true">
@if($demoActive)
<div class="alert alert-danger">
<p class="font-weight-bold"><i class="fas fa-exclamation-triangle"></i> {{ __('This feature is disabled') }}</p>
</div>
@endif
<form id="updateUserForm" method="post" action="{{ route('updateUser', ['user' => $user->id]) }}">
@csrf
@method('PATCH')
<label for="email">{{__('Email')}}</label>
<input {{ ($demoActive) ? 'disabled' : '' }} id="email" type="text" name="email" class="form-control" required value="{{ $user->email }}" />
<label for="name">{{__('Name')}}</label>
<input {{ ($demoActive) ? 'disabled' : '' }} id="name" type="text" name="name" class="form-control" required value="{{ $user->name }}" />
<label for="uuid">{{ __('Mojang UUID (deprecated)') }}</label>
<input {{ ($demoActive) ? 'disabled' : '' }} id="uuid" type="text" name="uuid" class="form-control" required value="{{ $user->uuid ?? "disabled" }}" />
<p class="text-muted text-sm">
<i class="fas fa-exclamation-triangle"></i> {{__('If the setting "Require Valid Game License" is activated, editing this field may have unintended consequences. Proceed with caution.')}}
</p>
<div class="form-group mt-3">
<label>{{__('Roles')}}</label>
<table class="table table-borderless">
<tbody>
@foreach($roles as $roleName => $status)
<tr>
<th><input {{ ($demoActive) ? 'disabled' : '' }} type="checkbox" name="roles[]" value="{{ $roleName }}" {{ ($status) ? 'checked' : '' }}></th>
<td class="col-md-2">{{ ucfirst($roleName) }}</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</form>
<x-slot name="modalFooter">
<button type="button" {{ ($demoActive) ? 'disabled' : '' }} class="btn btn-warning" onclick="$('#updateUserForm').submit()"><i class="fa fa-exclamation-cicle"></i> {{__('Save changes')}}</button>
</x-slot>
</x-modal>
<div class="row">
<div class="col">
<div class="card">
<div class="card-header">
<h3>{{ __('Account data') }}</h3>
</div>
<div class="row">
<div class="col">
</div>
</div>
</div>
</div>
</div>
@stop
@section('footer')
@include('breadcrumbs.dashboard.footer')
@stop

View File

@ -34,256 +34,6 @@
@endif
@if (Auth::user()->hasRole('admin'))
<x-modal id="banAccountModal" modal-label="banAccount" modal-title="{{__('Please confirm')}}" include-close-button="true">
<p>{{__("Please confirm that you want to suspend this account. You'll need to add a reason and expiration date to confirm this.")}}</p>
<form id="banAccountForm" name="banAccount" method="POST" action="{{route('banUser', ['user' => $profile->user->id])}}">
@csrf
@if($demoActive)
<div class="alert alert-danger">
<p class="font-weight-bold"><i class="fas fa-exclamation-triangle"></i> {{ __('This feature is disabled') }}</p>
</div>
@endif
<div class="row">
<div class="col">
<label for="reason">{{__('Public note')}}</label>
<input type="text" name="reason" id="reason" class="form-control" placeholder="{{__('e.g. Spamming')}}">
</div>
<div class="col">
<label for="duration">{{ __('Duration') }}</label>
<input type="text" name="duration" id="duration" class="form-control" placeholder="{{ __('in days') }}">
</div>
</div>
<div class="mt-2">
<input type="hidden" name="suspensionType" value="off">
<label for="suspensionType">{{ __('Suspension type') }}</label><br>
<input type="checkbox" id="suspensionType" name="suspensionType" checked data-toggle="toggle" data-on="Temporary" data-off="Permanent" data-onstyle="success" data-offstyle="danger" data-width="130" data-height="40">
<p class="text-muted text-sm"><i class="fas fa-info-circle"></i> {{ __('Temporary suspensions will be automatically lifted. The suspension note is visible to all users. Suspended users will not be able to login or register.') }}</p>
</div>
</form>
<x-slot name="modalFooter">
<button id="banAccountButton" type="button" class="btn btn-danger" {{ ($demoActive) ? 'disabled' : '' }} ><i class="fa fa-gavel"></i> {{__('Confirm')}}</button>
</x-slot>
</x-modal>
@if (!Auth::user()->is($profile->user) && $profile->user->isStaffMember())
<x-modal id="terminateUser" modal-label="terminateUser" modal-title="{{__('Please Confirm')}}" include-close-button="true">
@if($demoActive)
<div class="alert alert-danger">
<p class="font-weight-bold"><i class="fas fa-exclamation-triangle"></i> {{ __('This feature is disabled') }}</p>
</div>
@endif
<p><i class="fa fa-exclamation-triangle"></i> <b>{{__('You are about to terminate a recruited staff member')}}</b></p>
<p>
{{__('Terminating a staff member will remove their privileges on the application management site and connected integrations configured for the vacancy they applied for.')}}
</p>
<p>
<b>{{__('THIS PROCESS IS IRREVERSIBLE AND IMMEDIATE')}}</b>
</p>
<x-slot name="modalFooter">
<form method="POST" action="{{route('terminateStaffMember', ['user' => $profile->user->id])}}" id="terminateUserForm">
@csrf
@method('PATCH')
<button type="submit" class="btn btn-warning" {{ ($demoActive) ? 'disabled' : '' }}><i class="fas fa-exclamation-circle"></i> {{__('Confirm')}}</button>
</form>
</x-slot>
</x-modal>
@endif
<x-modal id="deleteAccount" modal-label="deleteAccount" modal-title="{{__('Confirm')}}" include-close-button="true">
@if($demoActive)
<div class="alert alert-danger">
<p class="font-weight-bold"><i class="fas fa-exclamation-triangle"></i> {{ __('This feature is disabled') }}</p>
</div>
@endif
<p><i class="fa fa-exclamation-triangle"></i><b> {{__('WARNING: This is a potentially destructive action!')}}</b></p>
<p>{{__("Deleting a user's account is an irreversible process. Historic and current applications, votes, and profile content, as well as any personally identifiable information will be immediately erased.")}}</p>
<form id="deleteAccountForm" method="POST" action={{route('deleteUser', ['user' => $profile->user->id])}}>
@csrf
@method('DELETE')
<label for="promptConfirm">{{__('Type to confirm: ')}} "DELETE ACCOUNT"</label>
<input type="text" name="confirmPrompt" class="form-control" placeholder="{{__('Please type the above text')}}">
</form>
<x-slot name="modalFooter">
<button type="button" class="btn btn-danger" {{ ($demoActive) ? 'disabled' : '' }} onclick="document.getElementById('deleteAccountForm').submit()"><i class="fa fa-trash"></i> {{strtoupper(__('Confirm'))}}</button>
</x-slot>
</x-modal>
<x-modal id="ipInfo" modal-label="ipInfo" modal-title="{{__('IP Address Information')}}" include-close-button="true">
<h4 class="text-center">{{__('Search results')}}</h4>
@if (!isset($ipInfo->message))
<table class="table table-borderless">
<tbody>
<tr>
<th>{{__('Origin country')}}</th>
<td>{{$ipInfo->country_name ?? 'N/A'}}</td>
</tr>
<tr>
<th>{{__('State/Province')}}</th>
<td>{{$ipInfo->state_prov ?? 'None'}}</td>
</tr>
<tr>
<th>{{__('District (if any)')}}</th>
<td>{{$ipInfo->district ?? 'N/A'}}</td>
</tr>
<tr>
<th>{{__('City')}}</th>
<td>{{$ipInfo->city ?? 'N/A'}}</td>
</tr>
<tr>
<th>{{__('Postal code')}}</th>
<td>{{$ipInfo->zipcode ?? 'N/A'}}</td>
</tr>
<tr>
<th>{{__('Geographical coordinates')}}</th>
<td>{{$ipInfo->latitude ?? 0}}, {{$ipInfo->longitude ?? 0}}</td>
</tr>
<tr>
<th>{{__('European?')}}</th>
<td>{{($ipInfo->is_eu) ? __('Yes') : __('No')}}</td>
</tr>
<tr>
<th>{{__('ISP')}}</th>
<td>{{$ipInfo->isp ?? 'N/A'}}</td>
</tr>
<tr>
<th>{{__('Organization')}}</th>
<td>{{$ipInfo->organization ?? 'N/A'}}</td>
</tr>
<tr>
<th>{{__('Connection type (e.g. datacenter, home)')}}</th>
<td>{{$ipInfo->connection_type ?? 'N/A'}}</td>
</tr>
<tr>
<th>{{__('Timezone')}}</th>
<td>{{$ipInfo->time_zone->name ?? __('N/A')}}</td>
</tr>
</tbody>
</table>
@else
<div class="alert alert-danger">
<i class="fas fa-exclamation-circle"></i> <b>{{__("This query didn't return any results.")}}</b>
<p>
{{$ipInfo->message}}
</p>
</div>
@endif
<x-slot name="modalFooter"></x-slot>
</x-modal>
<x-modal id="editUser" modal-label="editUser" modal-title="{{__('Edit account')}}" include-close-button="true">
@if($demoActive)
<div class="alert alert-danger">
<p class="font-weight-bold"><i class="fas fa-exclamation-triangle"></i> {{ __('This feature is disabled') }}</p>
</div>
@endif
<form id="updateUserForm" method="post" action="{{ route('updateUser', ['user' => $profile->user->id]) }}">
@csrf
@method('PATCH')
<label for="email">{{__('Email')}}</label>
<input {{ ($demoActive) ? 'disabled' : '' }} id="email" type="text" name="email" class="form-control" required value="{{ $profile->user->email }}" />
<label for="name">{{__('Name')}}</label>
<input {{ ($demoActive) ? 'disabled' : '' }} id="name" type="text" name="name" class="form-control" required value="{{ $profile->user->name }}" />
<label for="uuid">{{ __('Mojang UUID (deprecated)') }}</label>
<input {{ ($demoActive) ? 'disabled' : '' }} id="uuid" type="text" name="uuid" class="form-control" required value="{{ $profile->user->uuid ?? "disabled" }}" />
<p class="text-muted text-sm">
<i class="fas fa-exclamation-triangle"></i> {{__('If the setting "Require Valid Game License" is activated, editing this field may have unintended consequences. Proceed with caution.')}}
</p>
<div class="form-group mt-3">
<label>{{__('Roles')}}</label>
<table class="table table-borderless">
<tbody>
@foreach($roles as $roleName => $status)
<tr>
<th><input {{ ($demoActive) ? 'disabled' : '' }} type="checkbox" name="roles[]" value="{{ $roleName }}" {{ ($status) ? 'checked' : '' }}></th>
<td class="col-md-2">{{ ucfirst($roleName) }}</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</form>
<x-slot name="modalFooter">
<button type="button" {{ ($demoActive) ? 'disabled' : '' }} class="btn btn-warning" onclick="$('#updateUserForm').submit()"><i class="fa fa-exclamation-cicle"></i> {{__('Save changes')}}</button>
</x-slot>
</x-modal>
@endif
<div class="row mb-3">
<div class="col">
@ -316,14 +66,9 @@
<p class="text-muted">{{$profile->profileShortBio}}</p>
<p class="text-muted">{{__('Member since :date', ['date' => $since])}}</p>
@if (Auth::user()->hasRole('admin'))
<button type="button" class="btn btn-sm btn-info" onclick="$('#ipInfo').modal('show')">{{__('Lookup :ipAddress', ['ipAddress' => ($shouldCollect) ? $profile->user->originalIP : '0.0.0.0'])}}</button>
@endif
@if ($profile->user->is(Auth::user()))
<button type="button" class="btn btn-sm btn-warning" onclick="window.location.href='{{route('showProfileSettings')}}'"><i class="fas fa-pencil-alt"></i></button>
@elseif (Auth::user()->hasRole('admin') && $profile->user->isStaffMember())
<button type="button" class="btn btn-sm btn-danger" onclick="$('#terminateUser').modal('show')">{{__('Terminate Staff Member')}}</button>
@endif
</div>
@ -342,46 +87,6 @@
</div>
@if (Auth::user()->hasRole('admin'))
<div class="col">
<div class="card">
<h5 class="card-header">
<a class="collapsed d-block" data-toggle="collapse" href="#collapse-collapsed" aria-expanded="true" aria-controls="collapse-collapsed" id="heading-collapsed">
<i class="fa fa-chevron-down pull-right"></i>
{{__('Account management (admin)')}}
</a>
</h5>
<div id="collapse-collapsed" class="collapse" aria-labelledby="heading-collapsed">
<div class="card-body">
<div class="management-btn text-center">
@if (!$profile->user->isBanned())
<button class="btn btn-danger mb-2" id="banAccountTrigger"><i class="fa fa-ban"></i> {{__('Suspend')}}</button><br>
@else
<form method="post" action="{{route('unbanUser', ['user' => $profile->user->id])}}">
@method('DELETE')
@csrf
<button type="submit" class="btn btn-warning mb-2"><i class="fa fa-check"></i> {{__('Lift Suspension')}}</button>
</form>
@endif
<button class="btn btn-danger mb-2" onclick="$('#deleteAccount').modal('show')"><i class="fas fa-trash-alt"></i> {{__('Delete Account')}}</button><br>
<button class="btn btn-warning mb-2" onclick="$('#editUser').modal('show')"><i class="fas fa-pencil-alt"></i> {{__('Edit Account')}}</button><br>
</div>
</div>
</div>
</div>
</div><!-- .col -->
@endif
</div>
<div class="row buttonBar">

View File

@ -56,36 +56,6 @@
</div>
@endif
<div class="row">
<div class="col-8">
<div class="jumbotron">
<h1 class="display-4">Junte-se ao nosso Discord!</h1>
<p class="lead">O Discord é o coração da nossa comunidade onde toda a diversão acontece.</p>
<hr class="my-4">
<p>Venha conhecer a nossa comunidade de perto aderindo ao nosso servidor Discord. Conheça novos amigos, jogos, vários minigames divertidos e fale com a nossa equipe amigável! Todo mundo é bem-vindo.</p>
<p>Registre-se também no nosso portal online, que lhe permite gerir suas candidaturas à nossa equipe, ligar a sua conta Discord (e receber vantagens no servidor!), ver o diretório de usuários, fazer doação e muito mais!</p>
<p class="lead">
<a class="btn btn-primary btn-lg" href="https://discord.gg/tbxKEEeBpf" role="button">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-discord" viewBox="0 0 16 16">
<path d="M6.552 6.712c-.456 0-.816.4-.816.888s.368.888.816.888c.456 0 .816-.4.816-.888.008-.488-.36-.888-.816-.888zm2.92 0c-.456 0-.816.4-.816.888s.368.888.816.888c.456 0 .816-.4.816-.888s-.36-.888-.816-.888z"/>
<path d="M13.36 0H2.64C1.736 0 1 .736 1 1.648v10.816c0 .912.736 1.648 1.64 1.648h9.072l-.424-1.48 1.024.952.968.896L15 16V1.648C15 .736 14.264 0 13.36 0zm-3.088 10.448s-.288-.344-.528-.648c1.048-.296 1.448-.952 1.448-.952-.328.216-.64.368-.92.472-.4.168-.784.28-1.16.344a5.604 5.604 0 0 1-2.072-.008 6.716 6.716 0 0 1-1.176-.344 4.688 4.688 0 0 1-.584-.272c-.024-.016-.048-.024-.072-.04-.016-.008-.024-.016-.032-.024-.144-.08-.224-.136-.224-.136s.384.64 1.4.944c-.24.304-.536.664-.536.664-1.768-.056-2.44-1.216-2.44-1.216 0-2.576 1.152-4.664 1.152-4.664 1.152-.864 2.248-.84 2.248-.84l.08.096c-1.44.416-2.104 1.048-2.104 1.048s.176-.096.472-.232c.856-.376 1.536-.48 1.816-.504.048-.008.088-.016.136-.016a6.521 6.521 0 0 1 4.024.752s-.632-.6-1.992-1.016l.112-.128s1.096-.024 2.248.84c0 0 1.152 2.088 1.152 4.664 0 0-.68 1.16-2.448 1.216z"/>
</svg> Aderir ao Discord</a>
<a class="btn btn-primary btn-lg" href="{{route('register')}}" role="button"><i class="fas fa-plus"></i> Aderir ao portal</a>
</p>
</div>
</div>
<div class="col text-center">
<iframe src="https://discord.com/widget?id=866521211550433301&theme=dark" width="350px" height="95%" allowtransparency="true" frameborder="0" sandbox="allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts"></iframe>
</div>
</div>
<div class="row mt-5">
@ -183,118 +153,13 @@
<div class="col text-center">
<h3>Convencido?</h3>
<h3>{{ __('A gestão da :appName responde a todas candidaturas dentro de 48 horas.', ['appName' => config('app.name')]) }}</h3>
<p>{!! __('Se você tiver algum dúvida sobre a sua conta de recrutamento, candidatura, ou qualquer outra questão, visite o nosso <a href=":supportURL" target="_blank">site de atendimento</a>, ou <a href="mailto::supportEmail">envie-nos um email</a>.', ['supportURL' => config('app.support_url'), 'supportEmail' => config('app.support_email')]) !!}</p>
</div>
</div>
<div class="row">
<div class="col text-center">
<p>
Esperamos você! Junte-se hoje e desfrute de uma nova experiência.
</p>
</div>
</div>
<div class="row text-center mt-5 mb-4">
<div class="col">
<h3>{{__('Any questions? Leave a message!')}}</h3>
<p class="text-muted">{{__('*This is not an application form. Any applications sent here will be ignored. Additionally, please keep messages on topic (about this site only). For anything else, please use other contact options available.')}}</p>
</div>
</div>
<div class="row text-center">
<div class="col">
<form method="POST" action="{{route('sendSubmission')}}" id="contactForm">
@csrf
<!-- Tamper warning: Your captcha will fail if you modify this value programmatically/manually. -->
<input type="hidden" name="captcha" id="captcha">
<div class="row">
<div class="col-md-6">
<div class="md-form">
<input type="text" name="name" class="form-control" id="firstName">
<label for="firstName">{{__('Name')}}</label>
</div>
</div>
<div class="col-md-6">
<div class="md-form">
<input type="email" name="email" class="form-control" id="email">
<label for="email">{{__('Email')}}</label>
</div>
</div>
</div>
<div class="col-md-12">
<div class="md-form">
<input type="text" name="subject" id="subject" class="form-control">
<label for="subject">{{__('Subject')}}</label>
</div>
</div>
<div class="col-md-12">
<div class="md-form">
<textarea rows="3" name="msg" id="message" class="md-textarea form-control" placeholder="{{ __('Your message') }}"></textarea>
</div>
</div>
</form>
</div>
</div>
<div class="row text-center">
<div class="col">
<script>
function gcallback(response)
{
document.getElementById('captcha').value = response
}
</script>
<!-- align: deprecated cheap hack, but quick -->
<div align="center" class="g-recaptcha pb-3" data-callback="gcallback" data-sitekey="{{config('recaptcha.keys.sitekey')}}"></div>
<button type="button" class="btn btn-info" onclick="document.getElementById('contactForm').submit()">{{__('Send')}}</button>
</div>
</div>
</div>

View File

@ -291,18 +291,22 @@ Route::group(['prefix' => LaravelLocalization::setLocale(), 'middleware' => ['lo
Route::patch('settings/game/update', [OptionsController::class, 'saveGameIntegration'])
->name('saveGameIntegration');
Route::post('players/ban/{user}', [BanController::class, 'insert'])
Route::post('accounts/suspend/{user}', [UserController::class, 'suspend'])
->name('banUser');
Route::delete('players/unban/{user}', [BanController::class, 'delete'])
Route::delete('accounts/unsuspend/{user}', [UserController::class, 'unsuspend'])
->name('unbanUser');
Route::delete('players/delete/{user}', [UserController::class, 'delete'])
Route::delete('accounts/delete/{user}', [UserController::class, 'delete'])
->name('deleteUser');
Route::patch('players/update/{user}', [UserController::class, 'update'])
Route::patch('accounts/update/{user}', [UserController::class, 'update'])
->name('updateUser');
Route::get('accounts/manage/{user}', [UserController::class, 'showAcocuntManagement'])
->name('manageUser');
Route::get('positions', [VacancyController::class, 'index'])
->name('showPositions');