12 Commits
0.3.0 ... 0.5.2

Author SHA1 Message Date
5f1f92a9ce Code review
This commit fixes some superficial instances of Broken Access Control 
(https://owasp.org/www-project-top-ten/OWASP_Top_Ten_2017/Top_10-2017_A5-Broken_Access_Control).
There may be some more instances of this, as authorization was only done 
after most of the controllers were done (big mistake).

Some refactoring was also performed, where Route Model Binding with DI 
(dependency injection) was used whenever possible, to increase 
testability of the codebase.
Some reused code was also moved to Helper classes as to enforce DRY; 
There may be some lines of code that are still copy-pasted from other 
parts of the codebase for reuse.

Non-breaking refactoring changes were made, but the app as a whole still 
needs full manual testing, and customised responses to HTTP 500 
responses. Some errors are also not handled gracefully and this wasn't 
checked in this commit.
2020-07-16 21:21:28 +01:00
9e2d571298 Fix homepage bg issue 2020-07-16 07:32:52 +01:00
e16be5dc46 Override styles for auth pages 2020-07-16 07:11:33 +01:00
1a04880489 Replace basic authentication pages 2020-07-16 06:50:59 +01:00
3693ce3431 Add footer to all dashboard pages 2020-07-16 06:46:20 +01:00
4a766620ff Fix appointment policy not being called correctly
This commit fixes the appointment policy being called at the wrong time, with the wrong arguments.
It also fixes wrong references on the auth service provider, also fixing other issues with poliy usage.

Fixes #3 and SPACEJEWEL-HOSTING-59.
2020-07-16 05:24:00 +01:00
bca6020ab0 Add ability to edit forms and add new fields
This commit adds the ability to edit and modify existing forms.
On the technical side, it also adds a new reusable validation Facade which helps reduce duplicated code.
2020-07-15 06:48:49 +01:00
1f50faaea7 Add ability to preview application 2020-07-12 19:36:12 +01:00
e978a5417b Added ability to delete single application
Also moved User observer code to Application observer
2020-07-12 17:01:33 +01:00
4dc412e53c Added check for constrained models when deleting 2020-07-12 06:39:39 +01:00
bd0664ce0d Add ability to edit Vacancies 2020-07-11 20:34:26 +01:00
4b390ea536 Added full Vacancy description
Also added support for Markdown
2020-07-11 05:34:12 +01:00
86 changed files with 1876 additions and 11098 deletions

1
.gitignore vendored
View File

@@ -4,6 +4,7 @@
/public/storage
/storage/*.key
/vendor
/tools
.env
.env.backup
.phpunit.result.cache

5
.phive/phars.xml Normal file
View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<phive xmlns="https://phar.io/phive">
<phar name="phpunit" version="^9.2.5" installed="9.2.5" location="./tools/phpunit" copy="false"/>
<phar name="php-cs-fixer" version="^2.16.4" installed="2.16.4" location="./tools/php-cs-fixer" copy="false"/>
</phive>

View File

@@ -16,11 +16,6 @@ class IP
public function lookup(string $IP): object
{
if (empty($IP))
{
throw new LogicException(__METHOD__ . 'is missing parameter IP!');
}
$params = [
'apiKey' => config('general.keys.ipapi.apikey'),
'ip' => $IP

View File

@@ -0,0 +1,14 @@
<?php
namespace App\Facades;
use Illuminate\Support\Facades\Facade;
class ContextAwareValidation extends Facade
{
protected static function getFacadeAccessor()
{
return 'contextAwareValidator';
}
}

View File

@@ -16,7 +16,7 @@ class Form extends Model
public function vacancies()
{
return $this->hasMany('vacancies', 'vacancyFormID', 'id');
return $this->hasMany('App\Vacancy', 'vacancyFormID', 'id');
}
public function responses()

View File

@@ -0,0 +1,138 @@
<?php
namespace App\Helpers;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Collection;
class ContextAwareValidator
{
/**
* The excludedNames array will make the validator ignore any of these names when including names into the rules.
* @var array
*/
private $excludedNames = [
'_token',
'_method',
'formName'
];
/**
* Utility wrapper for json_encode.
*
* @param array $value The array to be converted.
* @return string The JSON representation of $value
*/
private function encode(array $value) : string
{
return json_encode($value);
}
/**
* The getValidator() method will take an array of fields from the request body, iterates through them,
* and dynamically adds validation rules for them. Depending on parameters, it may or may not generate
* a form structure for rendering purposes.
*
* This method is mostly meant by internal use by means of static proxies (Facades), in order to reduce code repetition;
* Using it outside it's directed scope may cause unexpected results; For instance, the method expects inputs to be in array format, e.g. myFieldNameID1[],
* myFieldNameID2[], and so on and so forth.
*
* This isn't checked by the code yet, but if you're implementing it this way in the HTML markup, make sure it's consistent (e.g. use a loop).
*
* P.S This method automatically ignores the CSRF token for validation.
*
* @param array $fields The request form fields
* @param bool $generateStructure Whether to incldue a JSON-ready form structure for rendering
* @param bool $includeFormName Whether to include formName in the list of validation rules
* @return Validator|Collection A validator instance you can use to check for validity, or a Collection with a validator and structure (validator, structure)
*/
public function getValidator(array $fields, bool $generateStructure = false, bool $includeFormName = false)
{
$formStructure = [];
$validator = [];
if ($includeFormName)
{
$validator['formName'] = 'required|string|max:100';
}
foreach ($fields as $fieldName => $field)
{
if(!in_array($fieldName, $this->excludedNames))
{
$validator[$fieldName . ".0"] = 'required|string';
$validator[$fieldName . ".1"] = 'required|string';
if ($generateStructure)
{
$formStructure['fields'][$fieldName]['title'] = $field[0];
$formStructure['fields'][$fieldName]['type'] = $field[1];
}
}
}
$validatorInstance = Validator::make($fields, $validator);
return ($generateStructure) ?
collect([
'validator' => $validatorInstance,
'structure' => $this->encode($formStructure)
])
: $validatorInstance;
}
/**
* The getResponseValidator method is similar to the getValidator method; It basically takes
* an array of fields from a previous form (that probably went through the other method) and adds validation
* to the field names.
*
* Also generates the storable response structure if you tell it to.
*
* @param array $fields The received fields
* @param array $formStructure The form structure - You must supply this if you want the response structure
* @param bool $generateResponseStructure Whether to generate the response structure
* @return Validator|Collection A collection or a validator, depending on the args. Will return validatior if only fields are supplied.
*/
public function getResponseValidator(array $fields, array $formStructure = [], bool $generateResponseStructure = true)
{
$responseStructure = [];
$validator = [];
if (empty($formStructure) && $generateResponseStructure)
{
throw new \InvalidArgumentException('Illegal combination of arguments supplied! Please check the method\'s documentation.');
}
foreach($fields as $fieldName => $value)
{
if(!in_array($fieldName, $this->excludedNames))
{
$validator[$fieldName] = 'required|string';
if ($generateResponseStructure)
{
$responseStructure['responses'][$fieldName]['type'] = $formStructure['fields'][$fieldName]['type'] ?? 'Unavailable';
$responseStructure['responses'][$fieldName]['title'] = $formStructure['fields'][$fieldName]['title'];
$responseStructure['responses'][$fieldName]['response'] = $value;
}
}
}
$validatorInstance = Validator::make($fields, $validator);
return ($generateResponseStructure) ?
collect([
'validator' => $validatorInstance,
'responseStructure' => $this->encode($responseStructure)
])
: $validatorInstance;
}
}

View File

@@ -18,8 +18,11 @@ use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Log;
use ContextAwareValidator;
class ApplicationController extends Controller
{
private function canVote($votes)
{
$allvotes = collect([]);
@@ -45,11 +48,8 @@ class ApplicationController extends Controller
}
public function showUserApp(Request $request, $applicationID)
public function showUserApp(Request $request, Application $application)
{
// TODO: Inject it instead (do this where there is no injection, not just here)
$application = Application::find($applicationID);
$this->authorize('view', $application);
if (!is_null($application))
@@ -78,6 +78,8 @@ class ApplicationController extends Controller
public function showAllApps()
{
$this->authorize('viewAny', Application::class);
return view('dashboard.appmanagement.all')
->with('applications', Application::paginate(6));
}
@@ -186,36 +188,16 @@ class ApplicationController extends Controller
Log::info('Processing new application!');
$formStructure = json_decode($vacancy->first()->forms->formStructure, true);
$responseStructure = [];
$excludedNames = [
'_token',
];
$validator = [];
foreach($request->all() as $fieldName => $value)
{
if(!in_array($fieldName, $excludedNames))
{
$validator[$fieldName] = 'required|string';
$responseStructure['responses'][$fieldName]['type'] = $formStructure['fields'][$fieldName]['type'] ?? 'Unavailable';
$responseStructure['responses'][$fieldName]['title'] = $formStructure['fields'][$fieldName]['title'];
$responseStructure['responses'][$fieldName]['response'] = $value;
}
}
$responseValidation = ContextAwareValidator::getResponseValidator($request->all(), $formStructure);
Log::info('Built response & validator structure!');
$validation = Validator::make($request->all(), $validator);
if (!$validation->fails())
if (!$responseValidation->get('validator')->fails())
{
$response = Response::create([
'responseFormID' => $vacancy->first()->forms->id,
'associatedVacancyID' => $vacancy->first()->id, // Since a form can be used by multiple vacancies, we can only know which specific vacancy this response ties to by using a vacancy ID
'responseData' => json_encode($responseStructure)
'responseData' => $responseValidation->get('responseStructure')
]);
Log::info('Registered form response for user ' . Auth::user()->name . ' for vacancy ' . $vacancy->first()->vacancyName);
@@ -249,37 +231,41 @@ class ApplicationController extends Controller
return redirect()->back();
}
public function updateApplicationStatus(Request $request, $applicationID, $newStatus)
public function updateApplicationStatus(Request $request, $application, $newStatus)
{
$application = Application::find($applicationID);
$this->authorize('update', Application::class);
if (!is_null($application))
switch ($newStatus)
{
switch ($newStatus)
{
case 'deny':
case 'deny':
event(new ApplicationDeniedEvent($application));
break;
event(new ApplicationDeniedEvent($application));
break;
case 'interview':
Log::info('User ' . Auth::user()->name . ' has moved application ID ' . $application->id . 'to interview stage');
$request->session()->flash('success', 'Application moved to interview stage! (:');
$application->setStatus('STAGE_INTERVIEW');
case 'interview':
Log::info('User ' . Auth::user()->name . ' has moved application ID ' . $application->id . 'to interview stage');
$request->session()->flash('success', 'Application moved to interview stage! (:');
$application->setStatus('STAGE_INTERVIEW');
$application->user->notify(new ApplicationMoved());
break;
$application->user->notify(new ApplicationMoved());
break;
default:
$request->session()->flash('error', 'There are no suitable statuses to update to. Do not mess with the URL.');
}
}
else
{
$request->session()->flash('The application you\'re trying to update does not exist.');
default:
$request->session()->flash('error', 'There are no suitable statuses to update to. Do not mess with the URL.');
}
return redirect()->back();
}
public function delete(Request $request, Application $application)
{
$this->authorize('delete', $application);
$application->delete(); // observers will run, cleaning it up
$request->session()->flash('success', 'Application deleted. Comments, appointments and responses have also been deleted.');
return redirect()->back();
}
}

View File

@@ -24,82 +24,56 @@ class AppointmentController extends Controller
];
public function saveAppointment(Request $request, $applicationID)
public function saveAppointment(Request $request, Application $application)
{
// Unrelated TODO: change if's in application page to a switch statement, & have the row encompass it
$this->authorize('create', Appointment::class);
$appointmentDate = Carbon::parse($request->appointmentDateTime);
$app = Application::find($applicationID);
if (!is_null($app))
{
// make sure this is a valid date by parsing it first
$appointmentDate = Carbon::parse($request->appointmentDateTime);
$appointment = Appointment::create([
'appointmentDescription' => $request->appointmentDescription,
'appointmentDate' => $appointmentDate->toDateTimeString(),
'applicationID' => $application->id,
'appointmentLocation' => (in_array($request->appointmentLocation, $this->allowedPlatforms)) ? $request->appointmentLocation : 'DISCORD',
]);
$application->setStatus('STAGE_INTERVIEW_SCHEDULED');
$appointment = Appointment::create([
'appointmentDescription' => $request->appointmentDescription,
'appointmentDate' => $appointmentDate->toDateTimeString(),
'applicationID' => $applicationID,
'appointmentLocation' => (in_array($request->appointmentLocation, $this->allowedPlatforms)) ? $request->appointmentLocation : 'DISCORD',
]);
$app->setStatus('STAGE_INTERVIEW_SCHEDULED');
Log::info('User ' . Auth::user()->name . ' has scheduled an appointment with ' . $application->user->name . ' for application ID' . $application->id, [
'datetime' => $appointmentDate->toDateTimeString(),
'scheduled' => now()
]);
$application->user->notify(new AppointmentScheduled($appointment));
$request->session()->flash('success', 'Appointment successfully scheduled @ ' . $appointmentDate->toDateTimeString());
Log::info('User ' . Auth::user()->name . ' has scheduled an appointment with ' . $app->user->name . ' for application ID' . $app->id, [
'datetime' => $appointmentDate->toDateTimeString(),
'scheduled' => now()
]);
$app->user->notify(new AppointmentScheduled($appointment));
$request->session()->flash('success', 'Appointment successfully scheduled @ ' . $appointmentDate->toDateTimeString());
}
else
{
$request->session()->flash('error', 'Cant\'t schedule an appointment for an application that doesn\'t exist.');
}
return redirect()->back();
}
public function updateAppointment(Request $request, $applicationID, $status)
public function updateAppointment(Request $request, Application $application, $status)
{
$this->authorize('update', $application->appointment);
$this->authorize('update', Appointment::class);
$application = Application::find($applicationID);
$validStatuses = [
'SCHEDULED',
'CONCLUDED'
];
// NOTE: This is a little confusing, refactor
$application->appointment->appointmentStatus = (in_array($status, $validStatuses)) ? strtoupper($status) : 'SCHEDULED';
$application->appointment->save();
if (!is_null($application))
{
// NOTE: This is a little confusing, refactor
$application->appointment->appointmentStatus = (in_array($status, $validStatuses)) ? strtoupper($status) : 'SCHEDULED';
$application->appointment->save();
$application->setStatus('STAGE_PEERAPPROVAL');
$application->user->notify(new ApplicationMoved());
$application->setStatus('STAGE_PEERAPPROVAL');
$application->user->notify(new ApplicationMoved());
$request->session()->flash('success', 'Interview finished! Staff members can now vote on it.');
}
else
{
$request->session()->flash('error', 'The application you\'re trying to update doesn\'t exist or have an appointment.');
}
$request->session()->flash('success', 'Interview finished! Staff members can now vote on it.');
return redirect()->back();
}
// also updates
public function saveNotes(SaveNotesRequest $request, $applicationID)
public function saveNotes(SaveNotesRequest $request, $application)
{
$application = Application::find($applicationID);
if (!is_null($application))
{
$application->appointment->meetingNotes = $request->noteText;
@@ -109,7 +83,7 @@ class AppointmentController extends Controller
}
else
{
$request->session()->flash('error', 'Sanity check failed: There\'s no appointment to save notes to!');
$request->session()->flash('error', 'There\'s no appointment to save notes to!');
}
return redirect()->back();

View File

@@ -15,11 +15,7 @@ class BanController extends Controller
public function insert(BanUserRequest $request, User $user)
{
if ($user->is(Auth::user()))
{
$request->session()->flash('error', 'You can\'t ban yourself!');
return redirect()->back();
}
$this->authorize('create', Ban::class);
if (is_null($user->bans))
{

View File

@@ -22,7 +22,7 @@ class CommentController extends Controller
public function insert(NewCommentRequest $request, Application $application)
{
$this->authorize('create', Comment::class);
$comment = Comment::create([
'authorID' => Auth::user()->id,
'applicationID' => $application->id,
@@ -32,14 +32,6 @@ class CommentController extends Controller
if ($comment)
{
foreach (User::all() as $user)
{
if ($user->isStaffMember())
{
$user->notify(new NewComment($comment, $application));
}
}
$request->session()->flash('success', 'Comment posted! (:');
}
else

View File

@@ -4,11 +4,23 @@ namespace App\Http\Controllers;
use Illuminate\Http\Request;
use GuzzleHttp;
use App\Notifications\NewContact;
use Illuminate\Support\Facades\Http;
use App\User;
class ContactController extends Controller
{
protected $users;
public function __construct(User $users)
{
$this->users = $users;
}
public function create(Request $request)
{
$name = $request->name;
@@ -18,12 +30,14 @@ class ContactController extends Controller
$challenge = $request->input('captcha');
// TODO: now: add middleware for this verification, move to invisible captcha
$verifyrequest = Http::asForm()->post(config('recaptcha.verify.apiurl'), [
'secret' => config('recaptcha.keys.secret'),
'response' => $challenge,
'remoteip' => $_SERVER['REMOTE_ADDR']
'remoteip' => $request->ip()
]);
$response = json_decode($verifyrequest->getBody(), true);
if (!$response['success'])
@@ -32,7 +46,18 @@ class ContactController extends Controller
return redirect()->back();
}
// TODO: Send mail
foreach(User::all() as $user)
{
if ($user->hasRole('admin'))
{
$user->notify(new NewContact(collect([
'message' => $msg,
'ip' => $request->ip(),
'email' => $email
])));
}
}
$request->session()->flash('success', 'Message sent successfully! We usually respond within 48 hours.');
return redirect()->back();

View File

@@ -6,16 +6,30 @@ use App\Application;
use App\Events\ApplicationApprovedEvent;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class DevToolsController extends Controller
{
// The use case for Laravel's gate and/or validation Requests is so tiny here that a full-blown policy would be overkill.
protected function isolatedAuthorise()
{
if (!Auth::user()->can('admin.developertools.use'))
{
abort(403, 'You\'re not authorized to access this page.');
}
}
public function index()
{
$this->isolatedAuthorise();
return view('dashboard.administration.devtools')
->with('applications', Application::where('applicationStatus', 'STAGE_PEERAPPROVAL')->get());
}
public function forceVoteCount(Request $request)
{
$this->isolatedAuthorise();
$application = Application::find($request->application);
if (!is_null($application))

View File

@@ -7,6 +7,8 @@ use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Facades\Auth;
use ContextAwareValidator;
class FormController extends Controller
{
@@ -29,39 +31,17 @@ class FormController extends Controller
{
$this->authorize('create', Form::class);
$fields = $request->all();
$formFields = $request->all();
$contextValidation = ContextAwareValidator::getValidator($fields, true, true);
$formStructure = [];
$excludedNames = [
'_token',
'formName' // It's added outside the loop. Not excluding causes unwanted duplication.
];
$validator = [
'formName' => 'required|string|max:100'
];
foreach ($formFields as $fieldName => $field)
if (!$contextValidation->get('validator')->fails())
{
if(!in_array($fieldName, $excludedNames))
{
$validator[$fieldName . ".0"] = 'required|string';
$validator[$fieldName . ".1"] = 'required|string';
$formStructure['fields'][$fieldName]['title'] = $field[0];
$formStructure['fields'][$fieldName]['type'] = $field[1];
}
}
$validation = Validator::make($formFields, $validator);
if (!$validation->fails())
{
$storableFormStructure = json_encode($formStructure);
$storableFormStructure = $contextValidation->get('structure');
Form::create(
[
'formName' => $formFields['formName'],
'formName' => $fields['formName'],
'formStructure' => $storableFormStructure,
'formStatus' => 'ACTIVE'
]
@@ -71,28 +51,81 @@ class FormController extends Controller
return redirect()->to(route('showForms'));
}
$request->session()->flash('errors', $validation->errors()->getMessages());
$request->session()->flash('errors', $contextValidation->get('validator')->errors()->getMessages());
return redirect()->back();
}
public function destroy(Request $request, $id)
public function destroy(Request $request, Form $form)
{
$form = Form::find($id);
$this->authorize('delete', $form);
$deletable = true;
// TODO: Check if form is linked to vacancies before allowing deletion
if (!is_null($form))
if (!is_null($form) && !is_null($form->vacancies) && $form->vacancies->count() !== 0 || !is_null($form->responses))
{
$form->delete();
$request->session()->flash('success', 'Form deleted successfully.');
return redirect()->back();
$deletable = false;
}
if ($deletable)
{
$form->delete();
$request->session()->flash('success', 'Form deleted successfully.');
}
else
{
$request->session()->flash('error', 'You cannot delete this form because it\'s tied to one or more applications and ranks, or because it doesn\'t exist.');
}
$request->session()->flash('error', 'The form you\'re trying to delete does not exist.');
return redirect()->back();
}
public function preview(Request $request, Form $form)
{
$this->authorize('viewAny', Form::class);
return view('dashboard.administration.formpreview')
->with('form', json_decode($form->formStructure, true))
->with('title', $form->formName)
->with('formID', $form->id);
}
public function edit(Request $request, Form $form)
{
$this->authorize('update', $form);
return view('dashboard.administration.editform')
->with('formStructure', json_decode($form->formStructure, true))
->with('title', $form->formName)
->with('formID', $form->id);
}
public function update(Request $request, Form $form)
{
$this->authorize('update', $form);
$contextValidation = ContextAwareValidator::getValidator($request->all(), true);
$this->authorize('update', $form);
if (!$contextValidation->get('validator')->fails())
{
// Add the new structure into the form. New, subsquent fields will be identified by the "new" prefix
// This prefix doesn't actually change the app's behavior when it receives applications.
// Additionally, old applications won't of course display new and updated fields, because we can't travel into the past and get data for them
$form->formStructure = $contextValidation->get('structure');
$form->save();
$request->session()->flash('success', 'Hooray! Your form was updated. New applications for it\'s vacancy will use it.');
}
else
{
$request->session()->flash('errors', $contextValidation->get('validator')->errors()->getMessages());
}
return redirect()->to(route('previewForm', ['form' => $form->id]));
}
}

View File

@@ -15,15 +15,12 @@ class HomeController extends Controller
*/
public function index()
{
// TODO: Relationships for Applications, Users and Responses
// Also prevent apps if user already has one in the space of 30d
// Display apps in the relevant menus
$positions = DB::table('vacancies')
->where('vacancyStatus', 'OPEN')
->where('vacancyCount', '!=', 0)
->get();
$positions = Vacancy::where('vacancyStatus', 'OPEN')
->where('vacancyCount', '<>', 0)
->get();
return view('home')
->with('positions', $positions);
}

View File

@@ -87,7 +87,6 @@ class ProfileController extends Controller
public function saveProfile(ProfileSave $request)
{
// TODO: Switch to route model binding
$profile = User::find(Auth::user()->id)->profile;
$social = [];
@@ -120,19 +119,6 @@ class ProfileController extends Controller
$request->session()->flash('success', 'Profile settings saved successfully.');
}
else
{
$gm = 'Guru Meditation #' . rand(0, 1000);
Log::alert('[GURU MEDITATION]: Could not find profile for authenticated user ' . Auth::user()->name . 'whilst trying to update it! Please verify that profiles are being created automatically during signup.',
[
'uuid' => Auth::user()->uuid,
'timestamp' => now(),
'route' => $request->route()->getName(),
'gmcode' => $gm // If this error is reported, the GM code, denoting a severe error, will help us find this entry in the logs
]);
$request->session()->flash('error', 'A technical error has occurred whilst trying to save your profile. Incident details have been recorded. Please report this incident to administrators with the following case number: ' . $gm);
}
return redirect()->back();

View File

@@ -189,6 +189,9 @@ class UserController extends Controller
public function delete(DeleteUserRequest $request, User $user)
{
$this->authorize('delete', $user);
if ($request->confirmPrompt == 'DELETE ACCOUNT')
{
$user->delete();
@@ -206,6 +209,8 @@ class UserController extends Controller
public function update(UpdateUserRequest $request, User $user)
{
$this->authorize('adminEdit', $user);
// Mass update would not be possible here without extra code, making route model binding useless
$user->email = $request->email;
$user->name = $request->name;

View File

@@ -3,15 +3,19 @@
namespace App\Http\Controllers;
use App\Http\Requests\VacancyRequest;
use App\Http\Requests\VacancyEditRequest;
use App\Vacancy;
use App\User;
use App\Form;
use App\Notifications\VacancyClosed;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Auth;
class VacancyController extends Controller
{
public function index()
@@ -31,10 +35,16 @@ class VacancyController extends Controller
if (!is_null($form))
{
/* note: since we can't convert HTML back to Markdown, we'll have to do the converting when the user requests a page,
* and leave the database with Markdown only so it can be used and edited everywhere.
* for several vacancies, this would require looping through all of them and replacing MD with HTML, which is obviously not the most clean solution;
* however, the Model can be configured to return MD instead of HTML on that specific field saving us from looping.
*/
Vacancy::create([
'vacancyName' => $request->vacancyName,
'vacancyDescription' => $request->vacancyDescription,
'vacancyFullDescription' => $request->vacancyFullDescription,
'vacancySlug' => Str::slug($request->vacancyName),
'permissionGroupName' => $request->permissionGroup,
'discordRoleID' => $request->discordRole,
@@ -54,10 +64,9 @@ class VacancyController extends Controller
}
public function updatePositionAvailability(Request $request, $status, $id)
public function updatePositionAvailability(Request $request, $status, Vacancy $vacancy)
{
$vacancy = Vacancy::find($id);
$this->authorize('update', $vacancy);
if (!is_null($vacancy))
@@ -101,4 +110,29 @@ class VacancyController extends Controller
return redirect()->back();
}
public function edit(Request $request, Vacancy $position)
{
$this->authorize('update', $vacancy);
return view('dashboard.administration.editposition')
->with('vacancy', $position);
}
public function update(VacancyEditRequest $request, Vacancy $position)
{
$this->authorize('update', $vacancy);
$position->vacancyFullDescription = $request->vacancyFullDescription;
$position->vacancyDescription = $request->vacancyDescription;
$position->vacancyCount = $request->vacancyCount;
$position->save();
$request->session()->flash('success', 'Vacancy successfully updated.');
return redirect()->back();
}
}

View File

@@ -13,33 +13,23 @@ use Illuminate\Support\Facades\Log;
class VoteController extends Controller
{
public function vote(VoteRequest $voteRequest, $applicationID)
public function vote(VoteRequest $voteRequest, Application $application)
{
$application = Application::find($applicationID);
$this->authorize('create', Vote::class);
if (!is_null($application))
{
$vote = Vote::create([
'userID' => Auth::user()->id,
'allowedVoteType' => $voteRequest->voteType,
]);
$vote = Vote::create([
'userID' => Auth::user()->id,
'allowedVoteType' => $voteRequest->voteType,
]);
$vote->application()->attach($applicationID);
$vote->application()->attach($applicationID);
Log::info('User ' . Auth::user()->name . ' has voted in applicant ' . $application->user->name . '\'s application', [
'voteType' => $voteRequest->voteType
]);
$voteRequest->session()->flash('success', 'Your vote has been registered! You will now be notified about the outcome of this application.');
}
else
{
$voteRequest->session()->flash('error', 'Can\t vote a non existant application!');
}
Log::info('User ' . Auth::user()->name . ' has voted in applicant ' . $application->user->name . '\'s application', [
'voteType' => $voteRequest->voteType
]);
$voteRequest->session()->flash('success', 'Your vote has been registered!');
// Cron job will run command that processes votes
return redirect()->back();
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth;
class VacancyEditRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return Auth::user()->can('admin.hiring.vacancy.edit');
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'vacancyDescription' => 'required|string',
'vacancyFullDescription' => 'nullable|string',
'vacancyCount' => 'required|integer|min:1'
];
}
}

View File

@@ -26,6 +26,7 @@ class VacancyRequest extends FormRequest
return [
'vacancyName' => 'required|string',
'vacancyDescription' => 'required|string',
'vacancyFullDescription' => 'nullable|string',
'permissionGroup' => 'required|string',
'discordRole' => 'required|string',
'vacancyCount' => 'required|integer',

View File

@@ -52,7 +52,7 @@ class ApplicationDenied extends Notification implements ShouldQueue
->line('Your most recent application has been denied.')
->line('Our review team denies applications for several reasons, including poor answers.')
->line('Please review your application and try again in 30 days.')
->action('Review application', url(route('showUserApp', ['id' => $this->application->id])))
->action('Review application', url(route('showUserApp', ['application' => $this->application->id])))
->line('Better luck next time!');
}

View File

@@ -55,7 +55,7 @@ class NewApplicant extends Notification implements ShouldQueue
->subject(config('app.name') . ' - New application')
->line('Someone has just applied for a position. Check it out!')
->line('You are receiving this because you\'re a staff member at ' . config('app.name') . '.')
->action('View Application', url(route('showUserApp', ['id' => $this->application->id])))
->action('View Application', url(route('showUserApp', ['application' => $this->application->id])))
->line('Thank you!');
}
@@ -67,7 +67,7 @@ class NewApplicant extends Notification implements ShouldQueue
$vacancyDetails['name'] = $this->vacancy->vacancyName;
$vacancyDetails['slots'] = $this->vacancy->vacancyCount;
$url = route('showUserApp', ['id' => $this->application->id]);
$url = route('showUserApp', ['application' => $this->application->id]);
$applicant = $this->application->user->name;
return (new SlackMessage)

View File

@@ -50,7 +50,7 @@ class NewComment extends Notification implements ShouldQueue
->subject(config('app.name') . ' - New comment')
->line('Someone has just posted a new comment on an application you follow.')
->line('You\'re receiving this email because you\'ve voted/commented on this application.')
->action('Check it out', url(route('showUserApp', ['id' => $this->application->id])))
->action('Check it out', url(route('showUserApp', ['application' => $this->application->id])))
->line('Thank you!');
}

View File

@@ -0,0 +1,78 @@
<?php
namespace App\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
use Illuminate\Support\Collection;
class NewContact extends Notification
{
use Queueable;
public $message;
/**
* Create a new notification instance.
*
* @return void
*/
public function __construct(Collection $message)
{
$this->message = $message;
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @return array
*/
public function via($notifiable)
{
return ['mail'];
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
* @return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
if ($this->message->has([
'message',
'ip',
'email'
]))
{
return (new MailMessage)
->line('We\'ve received a new contact form submission in the StaffManagement app center.')
->line('This is what they sent: ')
->line('')
->line($this->message->get('message'))
->line('')
->line('This message was received from ' . $this->message->get('ip') . ' and submitted by ' . $this->message->get('email') . '.')
->action('Sign in', url(route('login')))
->line('Thank you!');
}
throw new \InvalidArgumentException("Invalid arguments supplied to NewContact!");
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
* @return array
*/
public function toArray($notifiable)
{
return [
//
];
}
}

View File

@@ -6,12 +6,13 @@ use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
use Illuminate\Queue\SerializesModels;
use App\Vacancy;
class VacancyClosed extends Notification implements ShouldQueue
{
use Queueable;
use Queueable, SerializesModels;
protected $vacancy;

View File

@@ -0,0 +1,93 @@
<?php
namespace App\Observers;
use App\Application;
class ApplicationObserver
{
/**
* Handle the application "created" event.
*
* @param \App\Application $application
* @return void
*/
public function created(Application $application)
{
//
}
/**
* Handle the application "updated" event.
*
* @param \App\Application $application
* @return void
*/
public function updated(Application $application)
{
//
}
public function deleting(Application $application)
{
$application->response()->delete();
$votes = $application->votes;
foreach ($votes as $vote)
{
Log::debug('Referential integrity cleanup: Deleting and detaching vote ' . $vote->id);
$vote->application()->detach($application->id);
$vote->delete();
}
if (!is_null($application->appointment))
{
Log::debug('RIC: Deleting appointment!');
$application->appointment()->delete();
}
if (!$application->comments->isEmpty())
{
Log::debug('RIC: Deleting comments!');
foreach($application->comments as $comment)
{
$comment->delete();
}
}
// application can now be deleted
}
/**
* Handle the application "deleted" event.
*
* @param \App\Application $application
* @return void
*/
public function deleted(Application $application)
{
//
}
/**
* Handle the application "restored" event.
*
* @param \App\Application $application
* @return void
*/
public function restored(Application $application)
{
//
}
/**
* Handle the application "force deleted" event.
*
* @param \App\Application $application
* @return void
*/
public function forceDeleted(Application $application)
{
//
}
}

View File

@@ -48,30 +48,7 @@ class UserObserver
Log::debug('RIC: Now trying to delete applications and responses...');
foreach($applications as $application)
{
$application->response()->delete();
$votes = $application->votes;
foreach ($votes as $vote)
{
Log::debug('RIC: Deleting and detaching vote ' . $vote->id);
$vote->application()->detach($application->id);
$vote->delete();
}
if (!is_null($application->appointment))
{
Log::debug('RIC: Deleting appointment!');
$application->appointment()->delete();
}
if (!$application->comments->isEmpty())
{
Log::debug('RIC: Deleting comments!');
foreach($application->comments as $comment)
{
$comment->delete();
}
}
// code moved to Application observer, where it gets rid of attached elements individually
Log::debug('RIC: Deleting application ' . $application->id);
$application->delete();

View File

@@ -0,0 +1,64 @@
<?php
namespace App\Observers;
use App\Vacancy;
class VacancyObserver
{
/**
* Handle the vacancy "created" event.
*
* @param \App\Vacancy $vacancy
* @return void
*/
public function created(Vacancy $vacancy)
{
//
}
/**
* Handle the vacancy "updated" event.
*
* @param \App\Vacancy $vacancy
* @return void
*/
public function updated(Vacancy $vacancy)
{
//
}
/**
* Handle the vacancy "deleted" event.
*
* @param \App\Vacancy $vacancy
* @return void
*/
public function deleted(Vacancy $vacancy)
{
// TODO: Handle deletion of children's data
}
/**
* Handle the vacancy "restored" event.
*
* @param \App\Vacancy $vacancy
* @return void
*/
public function restored(Vacancy $vacancy)
{
//
}
/**
* Handle the vacancy "force deleted" event.
*
* @param \App\Vacancy $vacancy
* @return void
*/
public function forceDeleted(Vacancy $vacancy)
{
//
}
}

View File

@@ -45,4 +45,11 @@ class ApplicationPolicy
{
return $user->hasAnyRole('admin', 'hiringManager');
}
public function delete(User $user, Application $application)
{
return $user->hasRole('admin');
}
}

View File

@@ -41,7 +41,7 @@ class BanPolicy
*/
public function create(User $user)
{
//
return $user->hasRole('admin') && $user->isNot(Auth::user());
}
/**
@@ -53,7 +53,7 @@ class BanPolicy
*/
public function update(User $user, Ban $ban)
{
//
return $user->hasRole('admin');
}
/**

View File

@@ -57,7 +57,7 @@ class FormPolicy
*/
public function update(User $user, Form $form)
{
// unused
return $user->can('admin.hiring.forms');
}
/**

View File

@@ -24,6 +24,12 @@ class UserPolicy
return $authUser->is($user) || $authUser->hasRole('admin');
}
// This refers to the admin tools that let staff update more information than users themselves can
public function adminEdit(User $authUser, User $user)
{
return $authUser->hasRole('admin') && $authUser->isNot($user);
}
public function viewStaff(User $user)
{
return $user->can('admin.stafflist');
@@ -38,4 +44,9 @@ class UserPolicy
{
return $authUser->hasRole('admin');
}
public function delete(User $authUser, User $subject)
{
return $authUser->hasRole('admin') && $authUser->isNot($subject);
}
}

View File

@@ -53,7 +53,7 @@ class VacancyPolicy
*/
public function update(User $user, Vacancy $vacancy)
{
return $user->hasRole('admin', 'hiringManager');
return $user->hasAnyRole('admin', 'hiringManager');
}
/**

View File

@@ -2,15 +2,15 @@
namespace App\Providers;
use App\Http\Controllers\BanController;
use App\Http\Controllers\VoteController;
use App\Http\Controllers\ProfileController;
use App\Http\Controllers\AppointmentController;
use App\Policies\ProfilePolicy;
use App\Policies\VacancyPolicy;
use App\Policies\UserPolicy;
use App\Policies\BanPolicy;
use App\Policies\FormPolicy;
use App\Policies\VotePolicy;
use App\Policies\ApplicationPolicy;
use App\Policies\AppointmentPolicy;
use App\User;
use App\Form;
use App\Vote;
@@ -18,6 +18,8 @@ use App\Vacancy;
use App\Application;
use App\Appointment;
use App\Ban;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;
@@ -36,9 +38,9 @@ class AuthServiceProvider extends ServiceProvider
Vacancy::class => VacancyPolicy::class,
//Form::class => FormPolicy::class
'App\Form' => 'App\Policies\FormPolicy',
Vote::class => VoteController::class,
Ban::class => BanController::class,
Appointment::class => AppointmentController::class
Vote::class => VotePolicy::class,
Ban::class => BanPolicy::class,
Appointment::class => AppointmentPolicy::class
];
/**

View File

@@ -0,0 +1,34 @@
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use App;
class ContextAwareValidatorProvider extends ServiceProvider
{
/**
* Register services.
*
* @return void
*/
public function register()
{
//
}
/**
* Bootstrap services.
*
* @return void
*/
public function boot()
{
App::bind('contextAwareValidator', function(){
return new App\Helpers\ContextAwareValidator();
});
}
}

View File

@@ -6,6 +6,9 @@ use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
use GrahamCampbell\Markdown\Facades\Markdown;
class Vacancy extends Model
{
public $fillable = [
@@ -13,6 +16,7 @@ class Vacancy extends Model
'permissionGroupName',
'vacancyName',
'vacancyDescription',
'vacancyFullDescription',
'discordRoleID',
'vacancyFormID',
'vacancyCount',
@@ -21,6 +25,26 @@ class Vacancy extends Model
];
/**
* Get the HTML variant of the vacancyFullDescription attribute.
*
* @param string $value The original value
* @return string
*/
public function getVacancyFullDescriptionAttribute($value)
{
if (!is_null($value))
{
return Markdown::convertToHTML($value);
}
else
{
return null;
}
}
public function forms()
{
return $this->belongsTo('App\Form', 'vacancyFormID', 'id');

View File

@@ -0,0 +1,33 @@
<?php
namespace App\View\Components;
use Illuminate\View\Component;
class Form extends Component
{
public $formFields;
public $disableFields = false;
/**
* Create a new component instance.
*
* @return void
*/
public function __construct($disableFields = false)
{
$this->disableFields = $disableFields;
}
/**
* Get the view / contents that represent the component.
*
* @return \Illuminate\View\View|string
*/
public function render()
{
return view('components.form');
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace App\View\Components;
use Illuminate\View\Component;
class NoPermission extends Component
{
public $type;
public $inDashboard;
/**
* Create a new component instance.
*
* @return void
*/
public function __construct($type, $inDashboard = true)
{
$this->type = $type;
$this->inDashboard = $inDashboard;
}
/**
* Get the view / contents that represent the component.
*
* @return \Illuminate\View\View|string
*/
public function render()
{
return view('components.no-permission');
}
}

View File

@@ -15,6 +15,7 @@
"fideloper/proxy": "^4.2",
"fruitcake/laravel-cors": "^1.0",
"geo-sot/laravel-env-editor": "^0.9.9",
"graham-campbell/markdown": "^12.0",
"guzzlehttp/guzzle": "^6.5",
"jeroennoten/laravel-adminlte": "^3.2",
"laravel/framework": "^7.0",

69
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "16e8efbacd91ef3417b21bd2648e1629",
"content-hash": "51429857899e8134bbe6b4fa61145cc3",
"packages": [
{
"name": "almasaeed2010/adminlte",
@@ -1091,6 +1091,73 @@
],
"time": "2020-04-17T23:33:36+00:00"
},
{
"name": "graham-campbell/markdown",
"version": "v12.0.2",
"source": {
"type": "git",
"url": "https://github.com/GrahamCampbell/Laravel-Markdown.git",
"reference": "584eb9f24004238b80ee98b6e7be82f0933554dd"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/GrahamCampbell/Laravel-Markdown/zipball/584eb9f24004238b80ee98b6e7be82f0933554dd",
"reference": "584eb9f24004238b80ee98b6e7be82f0933554dd",
"shasum": ""
},
"require": {
"illuminate/contracts": "^6.0|^7.0",
"illuminate/support": "^6.0|^7.0",
"illuminate/view": "^6.0|^7.0",
"league/commonmark": "^1.3",
"php": "^7.2.5"
},
"require-dev": {
"graham-campbell/analyzer": "^3.0",
"graham-campbell/testbench": "^5.4",
"mockery/mockery": "^1.3.1",
"phpunit/phpunit": "^8.5|^9.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "12.0-dev"
},
"laravel": {
"providers": [
"GrahamCampbell\\Markdown\\MarkdownServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"GrahamCampbell\\Markdown\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Graham Campbell",
"email": "graham@alt-three.com"
}
],
"description": "Markdown Is A CommonMark Wrapper For Laravel",
"keywords": [
"Graham Campbell",
"GrahamCampbell",
"Laravel Markdown",
"Laravel-Markdown",
"common mark",
"commonmark",
"framework",
"laravel",
"markdown"
],
"time": "2020-04-14T16:14:52+00:00"
},
{
"name": "guzzlehttp/guzzle",
"version": "6.5.4",

View File

@@ -528,6 +528,16 @@ return [
]
]
],
[
'name' => 'AuthCustomisations',
'active' => true,
'files' => [
[
'type' => 'css',
'asset' => false,
'location' => '/css/authpages.css'
]
]
]
],
];

View File

@@ -164,7 +164,9 @@ return [
/*
* Package Service Providers...
*/
GrahamCampbell\Markdown\MarkdownServiceProvider::class,
/*
* Application Service Providers...
@@ -173,6 +175,7 @@ return [
App\Providers\AuthServiceProvider::class,
App\Providers\UUIDConversionProvider::class,
App\Providers\IPInfoProvider::class,
App\Providers\ContextAwareValidatorProvider::class,
// App\Providers\BroadcastServiceProvider::class,
App\Providers\EventServiceProvider::class,
App\Providers\RouteServiceProvider::class,
@@ -230,7 +233,9 @@ return [
'Validator' => Illuminate\Support\Facades\Validator::class,
'View' => Illuminate\Support\Facades\View::class,
'UUID' => App\Facades\UUID::class,
'IP' => App\Facades\IP::class
'IP' => App\Facades\IP::class,
'Markdown' => GrahamCampbell\Markdown\Facades\Markdown::class,
'ContextAwareValidator' => App\Facades\ContextAwareValidation::class
],

158
config/markdown.php Normal file
View File

@@ -0,0 +1,158 @@
<?php
declare(strict_types=1);
/*
* This file is part of Laravel Markdown.
*
* (c) Graham Campbell <graham@alt-three.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
return [
/*
|--------------------------------------------------------------------------
| Enable View Integration
|--------------------------------------------------------------------------
|
| This option specifies if the view integration is enabled so you can write
| markdown views and have them rendered as html. The following extensions
| are currently supported: ".md", ".md.php", and ".md.blade.php". You may
| disable this integration if it is conflicting with another package.
|
| Default: true
|
*/
'views' => true,
/*
|--------------------------------------------------------------------------
| CommonMark Extensions
|--------------------------------------------------------------------------
|
| This option specifies what extensions will be automatically enabled.
| Simply provide your extension class names here.
|
| Default: []
|
*/
'extensions' => [],
/*
|--------------------------------------------------------------------------
| Renderer Configuration
|--------------------------------------------------------------------------
|
| This option specifies an array of options for rendering HTML.
|
| Default: [
| 'block_separator' => "\n",
| 'inner_separator' => "\n",
| 'soft_break' => "\n",
| ]
|
*/
'renderer' => [
'block_separator' => "\n",
'inner_separator' => "\n",
'soft_break' => "\n",
],
/*
|--------------------------------------------------------------------------
| Enable Em Tag Parsing
|--------------------------------------------------------------------------
|
| This option specifies if `<em>` parsing is enabled.
|
| Default: true
|
*/
'enable_em' => true,
/*
|--------------------------------------------------------------------------
| Enable Strong Tag Parsing
|--------------------------------------------------------------------------
|
| This option specifies if `<strong>` parsing is enabled.
|
| Default: true
|
*/
'enable_strong' => true,
/*
|--------------------------------------------------------------------------
| Enable Asterisk Parsing
|--------------------------------------------------------------------------
|
| This option specifies if `*` should be parsed for emphasis.
|
| Default: true
|
*/
'use_asterisk' => true,
/*
|--------------------------------------------------------------------------
| Enable Underscore Parsing
|--------------------------------------------------------------------------
|
| This option specifies if `_` should be parsed for emphasis.
|
| Default: true
|
*/
'use_underscore' => true,
/*
|--------------------------------------------------------------------------
| HTML Input
|--------------------------------------------------------------------------
|
| This option specifies how to handle untrusted HTML input.
|
| Default: 'strip'
|
*/
'html_input' => 'strip',
/*
|--------------------------------------------------------------------------
| Allow Unsafe Links
|--------------------------------------------------------------------------
|
| This option specifies whether to allow risky image URLs and links.
|
| Default: true
|
*/
'allow_unsafe_links' => false,
/*
|--------------------------------------------------------------------------
| Maximum Nesting Level
|--------------------------------------------------------------------------
|
| This option specifies the maximum permitted block nesting level.
|
| Default: INF
|
*/
'max_nesting_level' => INF,
];

View File

@@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddDetailedDescriptionToVacancy extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('vacancies', function (Blueprint $table) {
$table->longText('vacancyFullDescription')->nullable()->after('vacancyDescription');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('vacancies', function (Blueprint $table) {
$table->dropColumn('vacancyFullDescription');
});
}
}

10395
package-lock.json generated

File diff suppressed because it is too large Load Diff

2
public/app.css vendored
View File

@@ -1,4 +1,4 @@
.page-bg {
.view {
background: url("/slides/06.png")no-repeat center center;
background-size: cover;
}

6
public/css/authpages.css vendored Normal file
View File

@@ -0,0 +1,6 @@
/* overrides customizations for the AdminLTE auth pages */
.login-page, .register-page {
background-image: url('/img/authbg.jpg') !important;
background-size: cover !important;
}

1
public/img/403.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 17 KiB

BIN
public/img/authbg.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 623 KiB

1
public/img/editor.svg Normal file
View File

@@ -0,0 +1 @@
<svg id="a5860ba0-ef67-4914-ac96-07e2ebcccb4f" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" width="1095.74023" height="664.03433" viewBox="0 0 1095.74023 664.03433"><title>abstract</title><rect x="701" y="661" width="394.74023" height="2" fill="#3f3d56"/><polygon points="474 429 474 216 718 216 718 0 0 0 0 643 718 643 718 429 474 429" fill="#3f3d56"/><polygon points="718 216 718 0 469 0 469 213 249 213 249 0 0 0 0 216 225 216 225 429 0 429 0 643 476 643 476 429 474 429 474 216 718 216" opacity="0.1"/><path d="M224.12988,117.98284a172.00382,172.00382,0,0,1-172,172v-172Z" transform="translate(-52.12988 -117.98284)" fill="#6c63ff"/><path d="M132.12988,117.98284a80.00357,80.00357,0,0,1-80,80v44a124.00426,124.00426,0,0,0,124-124Z" transform="translate(-52.12988 -117.98284)" opacity="0.1"/><polygon points="718 60 718 29 553 29 553 0 517 0 517 29 517 60 517 84 517 115 553 115 718 115 718 84 553 84 553 60 718 60" fill="#d0cde1"/><path d="M481.12988,448.98284a81,81,0,1,0-92,80.23639V642.98284H256.36627a81,81,0,1,0,0,22H389.12988v48h22v-48h45v-22h-45V529.21923A81.00034,81.00034,0,0,0,481.12988,448.98284Z" transform="translate(-52.12988 -117.98284)" fill="#d0cde1"/><polygon points="626.5 445.263 662.508 507.631 698.517 570 626.5 570 554.483 570 590.492 507.631 626.5 445.263" fill="#d0cde1"/><polygon points="610.5 578 538.483 578 574.492 515.631 574.742 515.198 561.5 492.263 525.492 554.631 489.483 617 561.5 617 633.517 617 611 578 610.5 578" fill="#6c63ff"/><polygon points="159 345 159 291 186 291 186 259 52 259 52 393 186 393 186 345 159 345" fill="#d0cde1"/><rect x="89" y="234" width="50" height="50" fill="#6c63ff"/><path d="M989.162,357.83165,972.618,401.5551l-59.664,20.935s-36-4-36,16c0,14.18058,43,2,43,2l78.66169-23.57271,24.816-61.44917Z" transform="translate(-52.12988 -117.98284)" fill="#ffb8b8"/><rect x="635" y="228" width="234" height="202" fill="#6c63ff"/><polygon points="747.5 289.263 783.508 351.631 819.517 414 747.5 414 675.483 414 711.492 351.631 747.5 289.263" fill="#3f3d56"/><polygon points="747.331 334.415 763.665 362.708 780 391 747.331 391 714.661 391 730.996 362.708 747.331 334.415" fill="#d0cde1"/><polygon points="747.331 242.415 763.665 270.708 780 299 747.331 299 714.661 299 730.996 270.708 747.331 242.415" fill="#d0cde1"/><path d="M1024.15387,329.42035s-16.544-2.36343-24.816,28.36116-10.63544,33.088-10.63544,33.088,48.45031,7.09029,49.632,4.72686S1048.96988,336.51064,1024.15387,329.42035Z" transform="translate(-52.12988 -117.98284)" fill="#2f2e41"/><circle cx="962.57027" cy="167.71406" r="31.9063" fill="#ffb8b8"/><path d="M1000.51957,311.69463s10.63544,27.17944,7.09029,30.72459,43.72345,8.272,43.72345,8.272,3.54515-17.72572-10.63543-28.36116c0,0-5.90858-17.72572-4.72686-21.27087S1000.51957,311.69463,1000.51957,311.69463Z" transform="translate(-52.12988 -117.98284)" fill="#ffb8b8"/><path d="M1028.88073,323.51178a85.216,85.216,0,0,0-31.9063,21.27087c-14.18058,15.36229-21.27087,20.08915-21.27087,20.08915s-16.544,5.90857-10.63544,34.26973c2.95429,14.18058.88629,30.13373-1.92028,42.54174a265.8223,265.8223,0,0,0-6.35172,58.65559v12.2473s-108.71777,228.071-72.08461,243.43328,69.72118,20.08915,72.08461,16.544,44.90517-187.89267,44.90517-187.89267,11.81715,197.34639,41.36,197.34639,77.99318-8.272,77.99318-8.272l-60.26746-236.343s14.18058-40.17831-21.27087-68.53947l-4.72686-12.99886c19.60447.01,28.56614-124.06968,8.272-134.7155C1043.06131,321.14835,1038.33445,317.6032,1028.88073,323.51178Z" transform="translate(-52.12988 -117.98284)" fill="#2f2e41"/><path d="M999.33786,387.32438l-16.544,43.72345-59.664,20.935s-36-4-36,16c0,14.18058,43,2,43,2l78.6617-23.57271,24.816-61.44918Z" transform="translate(-52.12988 -117.98284)" fill="#ffb8b8"/><ellipse cx="868.62393" cy="652.21718" rx="20.68001" ry="11.81715" fill="#2f2e41"/><ellipse cx="1026.97373" cy="652.21718" rx="20.68001" ry="11.81715" fill="#2f2e41"/><path d="M1027.01734,240.95422h-.00012a25.34145,25.34145,0,0,0-4.89258.48651,21.44172,21.44172,0,0,0-9.34021-2.15271h-.84777c-16.34473,0-29.59461,14.7901-29.59461,33.03461v.00006h5.47669l.88452-6.73224L990,272.32269h4.56226a64.57589,64.57589,0,0,0-2.119,16.55036v33.62384h8.28027l4.79395-12.45795-1.19849,12.45795h53.22314l4.35816-11.32538-1.0896,11.32538h5.99243V296.09613C1066.80311,265.64215,1048.99037,240.95422,1027.01734,240.95422Z" transform="translate(-52.12988 -117.98284)" fill="#2f2e41"/></svg>

After

Width:  |  Height:  |  Size: 4.3 KiB

1
public/img/preview.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.3 KiB

View File

@@ -1,3 +1,4 @@
// TODO: Add cleaner and less verbose solution found in formeditor.js
$(document).ready(function() {
$("#add").click(function() {
var lastField = $("#buildyourform div:last");

19
public/js/formeditor.js vendored Normal file
View File

@@ -0,0 +1,19 @@
// reminder: use vuejs instead, this is still an ugly and cheap solution
$(document).ready(function(){
var fieldID = 0;
var wrapper = $('.field-container');
var newBtn = $('#add');
$(newBtn).click(function(e){
e.preventDefault()
fieldID++;
$(wrapper).append('<div id=group' + fieldID + '><input type="text" name="newFieldID' + fieldID + '[]" class="form-control" />');
$(wrapper).append('<select name="newFieldID' + fieldID + '[]" class="custom-select"> <option value="nil" disabled>Choose a type</option> <option value="textbox">Textbox</option> <option value="textarea">Multi line answer</option> <option value="checkbox">Checkbox</option> </select>');
//$(wrapper).append('<button type="button" class="btn btn-danger btn-sm float-right delete"><i class="fas fa-minus"></i></button></div>');
});
});

View File

@@ -1,73 +1 @@
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">{{ __('Login') }}</div>
<div class="card-body">
<form method="POST" action="{{ route('login') }}">
@csrf
<div class="form-group row">
<label for="email" class="col-md-4 col-form-label text-md-right">{{ __('E-Mail Address') }}</label>
<div class="col-md-6">
<input id="email" type="email" class="form-control @error('email') is-invalid @enderror" name="email" value="{{ old('email') }}" required autocomplete="email" autofocus>
@error('email')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="form-group row">
<label for="password" class="col-md-4 col-form-label text-md-right">{{ __('Password') }}</label>
<div class="col-md-6">
<input id="password" type="password" class="form-control @error('password') is-invalid @enderror" name="password" required autocomplete="current-password">
@error('password')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="form-group row">
<div class="col-md-6 offset-md-4">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="remember" id="remember" {{ old('remember') ? 'checked' : '' }}>
<label class="form-check-label" for="remember">
{{ __('Remember Me') }}
</label>
</div>
</div>
</div>
<div class="form-group row mb-0">
<div class="col-md-8 offset-md-4">
<button type="submit" class="btn btn-primary">
{{ __('Login') }}
</button>
@if (Route::has('password.request'))
<a class="btn btn-link" href="{{ route('password.request') }}">
{{ __('Forgot Your Password?') }}
</a>
@endif
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection
@extends('adminlte::auth.login')

View File

@@ -1,49 +1 @@
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">{{ __('Confirm Password') }}</div>
<div class="card-body">
{{ __('Please confirm your password before continuing.') }}
<form method="POST" action="{{ route('password.confirm') }}">
@csrf
<div class="form-group row">
<label for="password" class="col-md-4 col-form-label text-md-right">{{ __('Password') }}</label>
<div class="col-md-6">
<input id="password" type="password" class="form-control @error('password') is-invalid @enderror" name="password" required autocomplete="current-password">
@error('password')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="form-group row mb-0">
<div class="col-md-8 offset-md-4">
<button type="submit" class="btn btn-primary">
{{ __('Confirm Password') }}
</button>
@if (Route::has('password.request'))
<a class="btn btn-link" href="{{ route('password.request') }}">
{{ __('Forgot Your Password?') }}
</a>
@endif
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection
@extends('adminlte::auth.passwords.confirm')

View File

@@ -1,47 +1 @@
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">{{ __('Reset Password') }}</div>
<div class="card-body">
@if (session('status'))
<div class="alert alert-success" role="alert">
{{ session('status') }}
</div>
@endif
<form method="POST" action="{{ route('password.email') }}">
@csrf
<div class="form-group row">
<label for="email" class="col-md-4 col-form-label text-md-right">{{ __('E-Mail Address') }}</label>
<div class="col-md-6">
<input id="email" type="email" class="form-control @error('email') is-invalid @enderror" name="email" value="{{ old('email') }}" required autocomplete="email" autofocus>
@error('email')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="form-group row mb-0">
<div class="col-md-6 offset-md-4">
<button type="submit" class="btn btn-primary">
{{ __('Send Password Reset Link') }}
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection
@extends('adminlte::auth.passwords.email')

View File

@@ -1,65 +1 @@
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">{{ __('Reset Password') }}</div>
<div class="card-body">
<form method="POST" action="{{ route('password.update') }}">
@csrf
<input type="hidden" name="token" value="{{ $token }}">
<div class="form-group row">
<label for="email" class="col-md-4 col-form-label text-md-right">{{ __('E-Mail Address') }}</label>
<div class="col-md-6">
<input id="email" type="email" class="form-control @error('email') is-invalid @enderror" name="email" value="{{ $email ?? old('email') }}" required autocomplete="email" autofocus>
@error('email')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="form-group row">
<label for="password" class="col-md-4 col-form-label text-md-right">{{ __('Password') }}</label>
<div class="col-md-6">
<input id="password" type="password" class="form-control @error('password') is-invalid @enderror" name="password" required autocomplete="new-password">
@error('password')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="form-group row">
<label for="password-confirm" class="col-md-4 col-form-label text-md-right">{{ __('Confirm Password') }}</label>
<div class="col-md-6">
<input id="password-confirm" type="password" class="form-control" name="password_confirmation" required autocomplete="new-password">
</div>
</div>
<div class="form-group row mb-0">
<div class="col-md-6 offset-md-4">
<button type="submit" class="btn btn-primary">
{{ __('Reset Password') }}
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection
@extends('adminlte::auth.passwords.reset')

View File

@@ -1,104 +1 @@
@extends('layouts.app')
@section('content')
<x-global-errors></x-global-errors>
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
@if (session()->has('error'))
<div class="alert alert-danger">
<i class="fas fa-exclamation"></i><b> Please verify your submission</b>
<p>
{{ session('error') }}
</p>
</div>
@endif
<div class="card">
<div class="card-header">{{ __('Register') }}</div>
<div class="card-body">
<form method="POST" action="{{ route('register') }}">
@csrf
<div class="form-group row">
<label for="name" class="col-md-4 col-form-label text-md-right">{{ __('Name') }}</label>
<div class="col-md-6">
<input id="name" type="text" class="form-control @error('name') is-invalid @enderror" name="name" value="{{ old('name') }}" required autocomplete="name" autofocus>
@error('name')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="form-group row">
<label for="email" class="col-md-4 col-form-label text-md-right">{{ __('E-Mail Address') }}</label>
<div class="col-md-6">
<input id="email" type="email" class="form-control @error('email') is-invalid @enderror" name="email" value="{{ old('email') }}" required autocomplete="email">
@error('email')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="form-group row">
<label for="password" class="col-md-4 col-form-label text-md-right">{{ __('Password') }}</label>
<div class="col-md-6">
<input id="password" type="password" class="form-control @error('password') is-invalid @enderror" name="password" required autocomplete="new-password">
@error('password')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="form-group row">
<label for="password-confirm" class="col-md-4 col-form-label text-md-right">{{ __('Confirm Password') }}</label>
<div class="col-md-6">
<input id="password-confirm" type="password" class="form-control" name="password_confirmation" required autocomplete="new-password">
</div>
</div>
<div class="form-group row mt-5">
<label for="minecraftUsername" class="col-md-4 col-form-label text-md-right">Minecraft Username</label>
<div class="col-md-6">
<input type="text" id="minecraftUsername" name="uuid" class="form-control @error('uuid') is-invalid @enderror" required>
@error('uuid')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="form-group row mb-0">
<div class="col-md-6 offset-md-4">
<button type="submit" class="btn btn-primary">
{{ __('Register') }}
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection
@extends('adminlte::auth.register')

View File

@@ -1,28 +1 @@
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">{{ __('Verify Your Email Address') }}</div>
<div class="card-body">
@if (session('resent'))
<div class="alert alert-success" role="alert">
{{ __('A fresh verification link has been sent to your email address.') }}
</div>
@endif
{{ __('Before proceeding, please check your email for a verification link.') }}
{{ __('If you did not receive the email') }},
<form class="d-inline" method="POST" action="{{ route('verification.resend') }}">
@csrf
<button type="submit" class="btn btn-link p-0 m-0 align-baseline">{{ __('click here to request another') }}</button>.
</form>
</div>
</div>
</div>
</div>
</div>
@endsection
@extends('adminlte::auth.verify')

View File

@@ -0,0 +1,25 @@
<div class="dashboard-footer">
<div class="row">
<div class="col">
<li class="d-inline-block">
<a class="mr-3" href="https://github.com/spacejewel-hosting/staffmanagement"><i class="fab fa-github"></i> Github</a>
<a class="mr-3" href="https://github.com/spacejewel-hosting/staffmanagement/issues"><i class="fas fa-bug"></i> Issue Tracker</a>
</li>
</div>
<div class="col-4 d-inline-block">
<p>&copy; Miguel N. 2020 &mdash; <a href="https://www.gnu.org/licenses/gpl-3.0.en.html">GNU General Public License</a></p>
</div>
</div>
</div>

View File

@@ -96,7 +96,7 @@
<h5>Welcome to our team management center!</h5>
<br>
<p>Here, you can apply for open staff member positions, view your application status, and manage your profile. </p>
<p>Sign up with Twitch or Email to continue.</p>
<p>Sign up with Email to continue.</p>
</div>
</div>
</div>

View File

@@ -0,0 +1,32 @@
@foreach($form['fields'] as $fieldName => $field)
@switch ($field['type'])
@case('textarea')
<div class="form-group mt-2 mb-2">
<label for="{{$fieldName}}">{{$field['title']}}</label>
<textarea class="form-control" rows="7" name="{{$fieldName}}" id="{{$fieldName}}" {{ ($disableFields) ? 'disabled' : '' }}>
</textarea>
</div>
@break
@case('textbox')
<div class="form-group mt-2 mb-2">
<label for="{{$fieldName}}">{{$field['title']}}</label>
<input type="text" name="{{$fieldName}}" id="{{$fieldName}}" {{ ($disableFields) ? 'disabled' : '' }} class="form-control">
</div>
@break
@endswitch
@endforeach

View File

@@ -0,0 +1,74 @@
@if ($inDashboard)
<div class="row mb-4">
<div class="col-6 offset-5">
<img src="/img/403.svg" width="150px" alt="Access denied" />
</div>
</div>
<div class="row">
<!-- People find pleasure in different ways. I find it in keeping my mind clear. - Marcus Aurelius -->
<div class="col">
<div class="alert alert-{{$type}}">
<h4><i class="fas fa-user-lock"></i> Access Denied</h2>
<p>
We're sorry, but you do not have permission to access this web page.
</p>
<p>
Please contact your administrator if you believe this was in error.
</p>
</div>
</div>
</div>
@else
@extends('adminlte::page')
@section('title', 'Raspberry Network | Access Denied')
@section('content_header')
<h4>Access Denied - HTTP 403</h4>
@stop
@section('content')
<div class="row mb-4">
<div class="col-6 offset-5">
<img src="/img/403.svg" width="150px" alt="Access denied" />
</div>
</div>
<div class="row">
<div class="col">
<div class="alert alert-{{$type}}">
<h4><i class="fas fa-user-lock"></i> Access Denied</h2>
<p class="text-muted">
@if (isset($slot))
{{ $slot }}
@endif
</p>
<p>
We're sorry, but you do not have permission to access this web page.
</p>
<p>
Please contact your administrator if you believe this was in error.
</p>
</div>
</div>
</div>
@stop
@endif

View File

@@ -73,3 +73,7 @@
</div>
@stop
@section('footer')
@include('breadcrumbs.dashboard.footer')
@stop

View File

@@ -0,0 +1,107 @@
@extends('adminlte::page')
@section('title', 'Raspberry Network | Edit From')
@section('content_header')
<h4>Administration / Forms / Editor</h4>
@stop
@section('js')
<script src="/js/formeditor.js"></script>
<x-global-errors></x-global-errors>
@stop
@section('content')
<div class="row mb-5">
<div class="col">
</div>
<div class="col text-center">
<img src="/img/editor.svg" width="250px" alt="Editor illustration" class="img-responsive" />
</div>
<div class="col">
</div>
</div>
<div class="row">
<div class="col">
</div>
<div class="col">
<form id="editForm" method="POST" action="{{ route('updateForm', ['form' => $formID]) }}">
@csrf
@method('PATCH')
<div class="card">
<div class="card-header">
<h4>Editing {{ $title }}...</h4>
</div>
<div class="card-body">
@foreach($formStructure['fields'] as $fieldName => $field)
<div class="form-group mt-4 mb-4">
<input autocomplete="false" type="text" id="{{ $fieldName }}" class="form-control" name="{{ $fieldName }}[]" value="{{ $field['title'] }}" />
<select class="custom-select" id="{{ $fieldName }}-type" name="{{ $fieldName }}[]">
<option value="nil" disabled>Choose a type</option>
<option value="textbox" {{ ($field['type'] == 'textbox' ? 'selected' : '') }}>Textbox</option>
<option value="textarea" {{ ($field['type'] == 'textarea' ? 'selected' : '') }}>Multi line answer</option>
<option value="checkbox" {{ ($field['type'] == 'checkbox' ? 'selected' : '') }}>Checkbox</option>
</select>
</div>
@endforeach
<div class="field-container mt-4 mb-4">
</div>
</div>
<div class="card-footer text-center">
<button type="button" class="btn btn-warning ml-2" onclick="$('#editForm').submit()"><i class="fas fa-save"></i> Save & Quit</button>
<button type="button" class="btn btn-primary ml-2" id="add"><i class="fas fa-plus"></i> New field</button>
</div>
</div>
</form>
</div>
<div class="col">
</div>
</div>
@stop
@section('footer')
@include('breadcrumbs.dashboard.footer')
@stop

View File

@@ -0,0 +1,159 @@
@extends('adminlte::page')
@section('title', 'Raspberry Network | Edit Positions')
@section('content_header')
<h4>Administration / Positions / Edit</h4>
@stop
@section('js')
<x-global-errors>
</x-global-errors>
@stop
@section('content')
<div class="row">
<div class="col center">
<h3>Vacancy Editor</h3>
</div>
</div>
<div class="row">
<div class="col">
<div class="card">
<div class="card-header">
<h3 class="card-title"><i class="fas fa-clipboard"></i> {{ $vacancy->vacancyName }}</h3>
</div>
<div class="card-body">
<p class="text-muted"><i class="fas fa-question-circle"></i> For consistency purposes, grayed out fields can't be edited.</p>
<form method="POST" id="editPositionForm" action="{{ route('updatePosition', ['position' => $vacancy->id]) }}">
@csrf
@method('PATCH')
<div class="row">
<div class="col">
<label for="vacancyName">Vacancy Name</label>
<input type="text" value="{{ $vacancy->vacancyName }}" class="form-control" disabled />
</div>
<div class="col">
<label for="vacancyDescription">Vacancy description</label>
<input type="vacancyDescription" class="form-control" name="vacancyDescription" value="{{ $vacancy->vacancyDescription }}" />
</div>
</div>
<div class="row">
<div class="col">
<!-- skipping the accessor for obvious reasons -->
<label for="vacanyDetails">Vacancy details</label>
<textarea name="vacancyFullDescription" class="form-control" placeholder="{{ (is_null($vacancy->vacancyFullDescription)) ? 'No details yet. Add some!' : '' }}" rows="20">{{ $vacancy->getAttributes()['vacancyFullDescription'] }}</textarea>
<span class="text-muted"><i class="fab fa-markdown"></i> Markdown supported</span>
</div>
</div>
<div class="row">
<div class="col">
<label for "permissionGroupName">Permission Group</label>
<input type="text" class="form-control" value="{{ $vacancy->permissionGroupName }}" id="permissionGroupName" disabled />
</div>
<div class="col">
<label for "discordRoleID">Discord Role ID</label>
<input type="text" class="form-control" value="{{ $vacancy->discordRoleID }}" id="discordRoleID" disabled />
</div>
</div>
<div class="row">
<div class="col">
<label for "currentForm">Current Form (uneditable)</label>
<input type="text" class="form-control" value="{{ $vacancy->forms->formName }}" id="currentForm" disabled />
<label for "remainingSlots">Remaining slots</label>
<input type="text" class="form-control" value="{{ $vacancy->vacancyCount }}" id="remainingSlots" name="vacancyCount" />
</div>
</div>
</form>
</div>
<div class="card-footer">
<button type="button" class="btn btn-warning" onclick="$('#editPositionForm').submit()"><i class="fas fa-edit"></i> Save Changes</button>
<button type="button" class="btn btn-danger" onclick="window.location.href='{{ route('showPositions') }}'"><i class="fas fa-times"></i> Cancel</button>
@if($vacancy->vacancyStatus == 'OPEN')
<form method="POST" action="{{ route('updatePositionAvailability', ['vacancy' => $vacancy->id, 'status' => 'close']) }}" style="display: inline">
@method('PATCH')
@csrf
<button type="submit" class="ml-4 btn btn-danger"><i class="fas fa-ban"></i> Close Position</button>
</form>
@endif
</div>
</div>
</div>
</div>
@stop
@section('footer')
@include('breadcrumbs.dashboard.footer')
@stop

View File

@@ -70,3 +70,7 @@
</div>
@stop
@section('footer')
@include('breadcrumbs.dashboard.footer')
@stop

View File

@@ -0,0 +1,83 @@
@extends('adminlte::page')
@section('title', 'Raspberry Network | Application Form Preview')
@section('content_header')
<h4>Administration / Form Builder / Preview</h4>
@stop
@section('js')
<x-global-errors></x-global-errors>
@stop
@section('content')
<div class="row">
<div class="col-6 offset-4">
<img src="/img/preview.svg" width="250px" alt="Form preview illustration" />
</div>
</div>
<div class="row mt-4">
<div class="col">
<div class="alert alert-success">
<h5><i class="fas fa-eye"></i> This is how your form looks like to applicants</h3>
<p>
You may edit it and add more fields later.
</p>
</div>
</div>
</div>
<div class="row">
<div class="col">
<div class="card">
<div class="card-header">
<h3>{{ $title }}'s form</h2>
</div>
<div class="card-body">
@component('components.form', ['form' => $form, 'disableFields' => true])
@endcomponent
</div>
<div class="card-footer text-center">
<button type="button" class="btn btn-success ml-2" onclick="window.location.href='{{ route('showForms') }}'"><i class="fas fa-chevron-left"></i> Go back</button>
<button type="button" class="btn btn-warning ml-2" onclick="window.location.href='{{ route('editForm', ['form' => $formID]) }}'"><i class="far fa-edit"></i> Edit</button>
</div>
</div>
</div>
</div>
@stop
@section('footer')
@include('breadcrumbs.dashboard.footer')
@stop

View File

@@ -38,6 +38,7 @@
<th>#</th>
<th>Form Title</th>
<th>Created On</th>
<th>Updated On</th>
<th>Actions</th>
</tr>
@@ -51,15 +52,16 @@
<td>{{$form->id}}</td>
<td>{{$form->formName}}</td>
<td>{{$form->created_at}}</td>
<td>{{ $form->updated_at }}</td>
<td>
<form style="display: inline-block; white-space: nowrap" action="{{route('destroyForm', ['id' => $form->id])}}" method="POST">
<form style="display: inline-block; white-space: nowrap" action="{{route('destroyForm', ['form' => $form->id])}}" method="POST">
@method('DELETE')
@csrf
<button type="submit" class="btn btn-sm btn-danger mr-2"><i class="fa fa-trash"></i> Delete</button>
</form>
<button type="button" class="btn btn-sm btn-success"><i class="fa fa-eye"></i> Preview</button>
<button type="button" class="btn btn-sm btn-success" onclick="window.location.href='{{ route('previewForm', ['form' => $form->id]) }}'"><i class="fa fa-eye"></i> Preview</button>
</td>
</tr>
@@ -94,3 +96,7 @@
</div>
@stop
@section('footer')
@include('breadcrumbs.dashboard.footer')
@stop

View File

@@ -176,3 +176,7 @@ I
</div>
@stop
@section('footer')
@include('breadcrumbs.dashboard.footer')
@stop

View File

@@ -4,7 +4,11 @@
@section('content_header')
<h4>Administration / Open Positions</h4>
@if (Auth::user()->hasAnyRole('admin', 'hiringManager'))
<h4>Administration / Open Positions</h4>
@else
<h4>Application Access Denied</h4>
@endif
@stop
@@ -33,7 +37,8 @@
@stop
@section('content')
@if (Auth::user()->hasAnyRole('admin', 'hiringManager'))
<!-- todo: switch to modal component -->
<div class="modal fade" tabindex="-1" id="newVacancyForm" role="dialog" aria-labelledby="modalFormLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
@@ -55,6 +60,9 @@
<label for="vacancyDescription">Vacancy Description</label>
<input type="text" id="vacancyDescription" name="vacancyDescription" class="form-control">
<label for="vacancyFullDescription">Vacancy Details</label>
<textarea name="vacancyFullDescription" class="form-control" rel="txtTooltip" title="Add things like admission requirements, rank resposibilities and roles, and anything else you feel is necessary" data-toggle="tooltip" data-placement="bottom"></textarea>
<span class="right text-muted"><i class="fab fa-markdown"></i> Markdown supported</span>
<div class="row mt-3">
<div class="col">
@@ -153,9 +161,8 @@
<thead>
<tr>
<th>#</th>
<th>Vacancy Name</th>
<th>Vacancy Description</th>
<th>Name</th>
<th>Description</th>
<th>Discord Role ID</th>
<th>Perm. Group Name</th>
<th>Open Slots</th>
@@ -171,8 +178,6 @@
@foreach($vacancies as $vacancy)
<tr>
<td>{{$vacancy->id}}</td>
<td>{{$vacancy->vacancyName}}</td>
<td>{{substr($vacancy->vacancyDescription, 0, 20)}}...</td>
<td><span class="badge badge-success">{{$vacancy->discordRoleID}}</span></td>
@@ -185,20 +190,23 @@
@endif
<td>{{$vacancy->created_at}}</td>
<td>
<button type="button" class="btn btn-sm btn-warning" onclick="window.location.href='{{ route('editPosition', ['position' => $vacancy->id]) }}'"><i class="fas fa-edit"></i></button>
@if ($vacancy->vacancyStatus == 'OPEN')
<form action="{{route('updatePositionAvailability', ['status' => 'close', 'id' => $vacancy->id])}}" method="POST" id="closePosition">
<form action="{{route('updatePositionAvailability', ['status' => 'close', 'vacancy' => $vacancy->id])}}" method="POST" id="closePosition" style="display: inline">
@csrf
@method('PATCH')
<button type="submit" class="btn btn-sm btn-danger"><i class="fa fa-ban"></i> Close</button>
<button type="submit" class="btn btn-sm btn-danger"><i class="fa fa-ban"></i></button>
</form>
@else
<form action="{{route('updatePositionAvailability', ['status' => 'open', 'id' => $vacancy->id])}}" method="POST" id="openPosition">
<form action="{{route('updatePositionAvailability', ['status' => 'open', 'vacancy' => $vacancy->id])}}" method="POST" id="openPosition" style="display: inline">
@csrf
@method('PATCH')
<button type="submit" class="btn btn-sm btn-success"><i class="fa fa-check"></i> Open</button>
<button type="submit" class="btn btn-sm btn-success"><i class="fa fa-check"></i></button>
</form>
@endif
@@ -232,5 +240,11 @@
</div>
</div>
@else
<x-no-permission type="danger"></x-no-permission>
@endif
@stop
@section('footer')
@include('breadcrumbs.dashboard.footer')
@stop

View File

@@ -98,3 +98,7 @@
</div>
@stop
@section('footer')
@include('breadcrumbs.dashboard.footer')
@stop

View File

@@ -93,38 +93,10 @@
<form action="{{route('saveApplicationForm', ['vacancySlug' => $vacancy->vacancySlug])}}" method="POST" id="submitApplicationForm">
@csrf
@foreach($preprocessedForm['fields'] as $fieldName => $field)
@switch ($field['type'])
@component('components.form', ['form' => $preprocessedForm, 'disableFields' => false])
@case('textarea')
<div class="form-group mt-2 mb-2">
<label for="{{$fieldName}}">{{$field['title']}}</label>
<textarea class="form-control" rows="7" name="{{$fieldName}}" id="{{$fieldName}}">
</textarea>
</div>
@break
@case('textbox')
<div class="form-group mt-2 mb-2">
<label for="{{$fieldName}}">{{$field['title']}}</label>
<input type="text" name="{{$fieldName}}" id="{{$fieldName}}" class="form-control">
</div>
@break
@endswitch
@endforeach
@endcomponent
</form>
@@ -154,3 +126,7 @@
@endif
@stop
@section('footer')
@include('breadcrumbs.dashboard.footer')
@stop

View File

@@ -11,12 +11,37 @@
@section('js')
<script type="text/javascript" src="/js/app.js"></script>
<x-global-errors></x-global-errors>
@stop
@section('content')
@foreach($applications as $application)
<x-modal id="deletionConfirmationModal-{{ $application->id }}" modal-label="deletion-{{ $application->id }}" modal-title="Are you sure?" include-close-button="true">
<h4><i class="fas fa-exclamation-triangle"></i> Really delete this?</h3>
<p>
This action is <b>IRREVERSBILE.</b>
</p>
<p>Comments, appointments and any votes attached to this application WILL be deleted too. Please make sure this application really needs to be deleted.</p>
<x-slot name="modalFooter">
<form method="POST" action="{{ route('deleteApplication', ['application' => $application->id]) }}">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-danger"><i class="fas fa-check-double"></i> Confirm</button>
</form>
</x-slot>
</x-modal>
@endforeach
<div class="row">
@@ -166,7 +191,8 @@
</td>
<td>{{ $application->created_at }}</td>
<td>
<button type="button" class="btn btn-success btn-sm" onclick="window.location.href='{{ route('showUserApp', ['id' => $application->id]) }}'"><i class="fas fa-eye"></i> View</button>
<button type="button" class="btn btn-success btn-sm" onclick="window.location.href='{{ route('showUserApp', ['application' => $application->id]) }}'"><i class="fas fa-eye"></i> View</button>
<button type="button" class="btn btn-danger btn-sm ml-2" onclick="$('#deletionConfirmationModal-{{ $application->id }}').modal('show')"><i class="fa fa-trash"></i> Delete</button>
</td>
</tr>
@@ -215,3 +241,7 @@
</div>
@stop
@section('footer')
@include('breadcrumbs.dashboard.footer')
@stop

View File

@@ -77,7 +77,7 @@
<td>{{$application->user->name}}</td>
<td><span class="badge-warning badge">{{($application->applicationStatus == 'STAGE_INTERVIEW') ? 'Pending Interview' : 'Unknown Status'}}</span></td>
<td>
<button type="button" class="btn btn-sm btn-success" onclick="window.location.href='{{route('showUserApp', ['id' => $application->id])}}'"><i class="fa fa-eye"></i> View</button>
<button type="button" class="btn btn-sm btn-success" onclick="window.location.href='{{route('showUserApp', ['application' => $application->id])}}'"><i class="fa fa-eye"></i> View</button>
<button type="button" class="btn btn-sm btn-warning"><i class="fa fa-clock"></i> Schedule</button>
</td>
</tr>
@@ -151,7 +151,7 @@
<td><span class="badge badge-success"><i class="fa fa-check"></i> {{ucfirst(strtolower($upcomingApp->appointment->appointmentLocation))}}</span></td>
@endif
<td>
<button type="button" class="btn btn-sm btn-success" onclick="window.location.href='{{route('showUserApp', ['id' => $upcomingApp->id])}}'"><i class="fa fa-eye"></i> View Details</button>
<button type="button" class="btn btn-sm btn-success" onclick="window.location.href='{{route('showUserApp', ['application' => $upcomingApp->id])}}'"><i class="fa fa-eye"></i> View Details</button>
</td>
</tr>
@@ -191,3 +191,7 @@
</div>
@stop
@section('footer')
@include('breadcrumbs.dashboard.footer')
@stop

View File

@@ -70,7 +70,7 @@
<td>{{$application->created_at}}</td>
<td>{{$application->updated_at}}</td>
<td>
<button type="button" class="btn btn-sm btn-warning" onclick="window.location.href='{{route('showUserApp', ['id' => $application->id])}}'"><i class="fas fa-clipboard-check"></i> Review</button>
<button type="button" class="btn btn-sm btn-warning" onclick="window.location.href='{{route('showUserApp', ['application' => $application->id])}}'"><i class="fas fa-clipboard-check"></i> Review</button>
</td>
</tr>
@@ -104,3 +104,7 @@
</div>
@stop
@section('footer')
@include('breadcrumbs.dashboard.footer')
@stop

View File

@@ -66,7 +66,7 @@
<td>{{$application->created_at}}</td>
<td><span class="badge badge-warning">{{($application->applicationStatus == 'STAGE_PEERAPPROVAL') ? 'Peer Review' : 'Unknown'}}</span></td>
<td>
<button type="button" class="btn btn-info btn-sm" onclick="window.location.href='{{route('showUserApp', ['id' => $application->id])}}'"><i class="far fa-clipboard"></i> Review</button>
<button type="button" class="btn btn-info btn-sm" onclick="window.location.href='{{route('showUserApp', ['application' => $application->id])}}'"><i class="far fa-clipboard"></i> Review</button>
</td>
@endforeach
@@ -92,3 +92,7 @@
</div>
@stop
@section('footer')
@include('breadcrumbs.dashboard.footer')
@stop

View File

@@ -14,6 +14,38 @@
@section('content')
@if (!$vacancies->isEmpty())
@foreach($vacancies as $vacancy)
<x-modal id="{{ $vacancy->vacancySlug . '-details' }}" modal-label="{{ $vacancy->vacancySlug . '-details-label' }}" modal-title="Vacancy details" include-close-button="true">
@if (is_null($vacancy->vacancyFullDescription))
<div class="alert alert-warning">
<h3><i class="fas fa-question-circle"></i> There don't seem to be any details</h3>
<p>
This vacancy does not have any details yet.
</p>
</div>
@else
{!! $vacancy->vacancyFullDescription !!}
<p class="text-sm text-muted">
Last updated @ {{ $vacancy->updated_at }}
</p>
@endif
<x-slot name="modalFooter"></x-slot>
</x-modal>
@endforeach
@endif
<div class="row mt-5">
<div class="col">
@@ -190,7 +222,7 @@
<div class="card-footer text-center">
<button type="button" class="btn btn-primary btn-sm" onclick="window.location.href='{{ route('renderApplicationForm', ['vacancySlug' => $vacancy->vacancySlug]) }}'">Apply</button>
<button type="button" class="btn btn-warning btn-sm">Learn More</button>
<button type="button" class="btn btn-warning btn-sm" onclick="$('#{{ $vacancy->vacancySlug }}-details').modal('show')">Learn More</button>
</div>
</div>
@@ -230,3 +262,6 @@
</div>
@stop
@section('footer')
@include('breadcrumbs.dashboard.footer')
@stop

View File

@@ -109,7 +109,7 @@
</td>
<td>
<button type="button" class="btn btn-success" onclick="window.location.href='{{route('showUserApp', ['id' => $application->id])}}'"><i class="fa fa-eye"></i> View</button>
<button type="button" class="btn btn-success" onclick="window.location.href='{{route('showUserApp', ['application' => $application->id])}}'"><i class="fa fa-eye"></i> View</button>
</td>
</tr>
@@ -141,3 +141,6 @@
</div>
@stop
@section('footer')
@include('breadcrumbs.dashboard.footer')
@stop

View File

@@ -105,3 +105,6 @@
@endif
@stop
@section('footer')
@include('breadcrumbs.dashboard.footer')
@stop

View File

@@ -405,7 +405,8 @@
</div>
@stop
@section('footer')
@include('breadcrumbs.dashboard.footer')
@stop

View File

@@ -164,3 +164,6 @@
</div>
@stop
@section('footer')
@include('breadcrumbs.dashboard.footer')
@stop

View File

@@ -215,3 +215,6 @@
</form>
@stop
@section('footer')
@include('breadcrumbs.dashboard.footer')
@stop

View File

@@ -38,7 +38,7 @@
<x-modal id="notes" modal-label="notes" modal-title="Shared Notepad" include-close-button="true">
<form id="meetingNotes" method="POST" action="{{route('saveNotes', ['applicationID' => $application->id])}}">
<form id="meetingNotes" method="POST" action="{{route('saveNotes', ['application' => $application->id])}}">
@csrf
@method('PATCH')
<textarea name="noteText" rows="5" class="form-control">{{$application->appointment->meetingNotes ?? 'There are no notes yet. Add some!'}}</textarea>
@@ -62,7 +62,7 @@
<x-slot name="modalFooter">
<form id="updateApplication" action="{{route('updateApplicationStatus', ['id' => $application->id, 'newStatus' => 'deny'])}}" method="POST">
<form id="updateApplication" action="{{route('updateApplicationStatus', ['application' => $application->id, 'newStatus' => 'deny'])}}" method="POST">
@csrf
@method('PATCH')
<button type="submit" class="btn btn-danger">Confirm: Deny Applicant</button>
@@ -75,6 +75,18 @@
@endhasrole
<div class="row">
<div class="col">
<div class="alert alert-warning alert-dismissible">
<a href="#" class="close" data-dismiss="alert" aria-label="close">&times;</a>
<strong>Reminder:</strong> If this form has been updated, new fields and updated questions will not show up here!
</div>
</div>
</div>
<div class="row">
@@ -188,7 +200,7 @@
</div>
<div class="col">
<form method="POST" action="{{route('updateApplicationStatus', ['id' => $application->id, 'newStatus' => 'interview'])}}">
<form method="POST" action="{{route('updateApplicationStatus', ['application' => $application->id, 'newStatus' => 'interview'])}}">
@csrf
@method('PATCH')
<button type="submit" class="btn btn-success" {{($application->applicationStatus == 'DENIED') ? 'disabled' : ''}}><i class="fas fa-arrow-right" ></i> Move to next stage</button>
@@ -218,7 +230,7 @@
</x-slot>
<form id="scheduleAppointment" action="{{route('scheduleAppointment', ['applicationID' => $application->id])}}" method="POST">
<form id="scheduleAppointment" action="{{route('scheduleAppointment', ['application' => $application->id])}}" method="POST">
@csrf
@@ -274,7 +286,7 @@
<x-slot name="cardFooter">
@can('appointments.schedule.edit')
<form style="white-space: nowrap;display:inline-block" class="footer-button" action="{{route('updateAppointment', ['applicationID' => $application->id, 'status' => 'concluded'])}}" method="POST">
<form style="white-space: nowrap;display:inline-block" class="footer-button" action="{{route('updateAppointment', ['application' => $application->id, 'status' => 'concluded'])}}" method="POST">
@csrf
@method('PATCH')
<button type="submit" class="btn btn-success">Finish Meeting</button>
@@ -310,12 +322,12 @@
@if($canVote)
<form class="d-inline-block" method="POST" action="{{route('voteApplication', ['id' => $application->id])}}">
<form class="d-inline-block" method="POST" action="{{route('voteApplication', ['application' => $application->id])}}">
@csrf
<input type="hidden" name="voteType" value="VOTE_APPROVE">
<button type="submit" class="btn btn-sm btn-warning">Vote: Approve Applicant</button>
</form>
<form class="d-inline-block" method="POST" action="{{route('voteApplication', ['id' => $application->id])}}">
<form class="d-inline-block" method="POST" action="{{route('voteApplication', ['application' => $application->id])}}">
@csrf
<input type="hidden" name="voteType" value="VOTE_DENY">
<button type="submit" class="btn btn-sm btn-warning">Vote: Deny Applicant</button>
@@ -357,9 +369,9 @@
</div>
<div class="row">
<div class="col">
@if ($comments->isEmpty())
<div class="alert alert-warning">
@@ -378,11 +390,11 @@
@if (!$comments->isEmpty())
@foreach($comments as $comment)
<div class="row mt-3 mb-3">
<div class="row mt-3 mb-3">
<div class="col-md-2">
<div class="text-center">
@if($application->user->avatarPreference == 'gravatar')
<img class="profile-user-img img-fluid img-circle" src="https://gravatar.com/avatar/{{md5($comment->user->email)}}" alt="User profile picture">
@@ -394,24 +406,24 @@
</div>
<div class="card comment">
<div class="card-header comment-header">
<h1 class="commenter">{{$comment->user->name}} &#9679; {{Carbon\Carbon::parse($comment->created_at)->diffForHumans()}}</h3>
</div>
<div class="card-body">
{{$comment->text}}
</div>
@if(Auth::user()->is($comment->user) || Auth::user()->hasRole('admin'))
<div class="card-footer comment-footer">
<form method="POST" id="deleteComment" action="{{route('deleteApplicationComment', ['comment' => $comment->id])}}">
@csrf
@method('DELETE')
@@ -434,7 +446,7 @@
<div class="row mt-5">
<div class="col-md-2">
<div class="text-center">
@if($application->user->avatarPreference == 'gravatar')
<img class="profile-user-img img-fluid img-circle" src="https://gravatar.com/avatar/{{md5(Auth::user()->email)}}" alt="User profile picture">
@@ -443,15 +455,15 @@
@endif
</div>
</div>
</div>
<div class="col">
<div class="card border-top border-bottom">
<div class="card-body">
<form id="newComment" method="POST" action="{{route('addApplicationComment', ['application' => $application->id])}}">
@csrf
<textarea id="comment" name="comment" class="form-control" id="commentText"></textarea>
@@ -459,14 +471,14 @@
</form>
<div class="row">
<div class="col text-left">
<p class="text-sm text-muted">Commenting as {{Auth::user()->name}}</p>
</div>
<div class="col text-right">
<p class="text-sm text-muted"><span id="charcount">0</span>/600 max characters</p>
</div>
@@ -476,7 +488,7 @@
</div>
<div class="card-footer text-right">
<button type="button" id="submitComment" class="btn btn-sm btn-secondary">Post</button>
</div>
@@ -488,4 +500,8 @@
@endif
@endhasanyrole
@endsection
@stop
@section('footer')
@include('breadcrumbs.dashboard.footer')
@stop

View File

@@ -2,6 +2,40 @@
@section('content')
@if(!$positions->isEmpty())
<!-- todo: details component -->
@foreach($positions as $position)
<x-modal id="{{ $position->vacancySlug . '-details' }}" modal-label="{{ $position->vacancySlug . '-details-label' }}" modal-title="Opening details" include-close-button="true">
@if (is_null($position->vacancyFullDescription))
<div class="alert alert-warning">
<h3><i class="fas fa-question-circle"></i> There don't seem to be any details</h3>
<p>
This opening does not have any details yet.
</p>
</div>
@else
{!! $position->vacancyFullDescription !!}
<p class="text-sm text-muted">
Last updated @ {{ $position->updated_at }}
</p>
@endif
<x-slot name="modalFooter"></x-slot>
</x-modal>
@endforeach
@endif
<!--Main Layout-->
<main class="py-5">
@@ -25,7 +59,7 @@
<div class="col-md-4">
<div class="card">
<div class="card mt-3">
<div class="card-header text-center">
@@ -57,6 +91,7 @@
@guest
<button type="button" class="btn btn-success" onclick="window.location.href='{{route('renderApplicationForm', ['vacancySlug' => $position->vacancySlug])}}'">Apply</button>
<button type="button" class="btn btn-info" onclick="$('#{{ $position->vacancySlug }}-details').modal('show')">Learn more</button>
@endguest
</div>
@@ -235,7 +270,7 @@
<div class="md-form">
<textarea rows="3" name="message" id="message" class="md-textarea form-control"></textarea>
<textarea rows="3" name="msg" id="message" class="md-textarea form-control"></textarea>
</div>

View File

@@ -40,7 +40,7 @@ Route::group(['middleware' => ['auth', 'forcelogout']], function(){
->name('showUserApps')
->middleware('eligibility');
Route::get('/view/{id}', 'ApplicationController@showUserApp')
Route::get('/view/{application}', 'ApplicationController@showUserApp')
->name('showUserApp');
Route::post('/{application}/comments', 'CommentController@insert')
@@ -54,9 +54,12 @@ Route::group(['middleware' => ['auth', 'forcelogout']], function(){
->name('saveNotes');
Route::patch('/update/{id}/{newStatus}', 'ApplicationController@updateApplicationStatus')
Route::patch('/update/{application}/{newStatus}', 'ApplicationController@updateApplicationStatus')
->name('updateApplicationStatus');
Route::delete('{application}/delete', 'ApplicationController@delete')
->name('deleteApplication');
Route::get('/staff/all', 'ApplicationController@showAllApps')
->name('allApplications');
@@ -75,7 +78,7 @@ Route::group(['middleware' => ['auth', 'forcelogout']], function(){
Route::post('{id}/staff/vote', 'VoteController@vote')
Route::post('{application}/staff/vote', 'VoteController@vote')
->name('voteApplication');
@@ -83,10 +86,10 @@ Route::group(['middleware' => ['auth', 'forcelogout']], function(){
Route::group(['prefix' => 'appointments'], function (){
Route::post('schedule/appointments/{applicationID}', 'AppointmentController@saveAppointment')
Route::post('schedule/appointments/{application}', 'AppointmentController@saveAppointment')
->name('scheduleAppointment');
Route::patch('update/appointments/{applicationID}/{status}', 'AppointmentController@updateAppointment')
Route::patch('update/appointments/{application}/{status}', 'AppointmentController@updateAppointment')
->name('updateAppointment');
});
@@ -153,6 +156,8 @@ Route::group(['middleware' => ['auth', 'forcelogout']], function(){
Route::delete('players/unban/{user}', 'BanController@delete')
->name('unbanUser');
Route::delete('players/delete/{user}', 'UserController@delete')
->name('deleteUser');
@@ -168,7 +173,14 @@ Route::group(['middleware' => ['auth', 'forcelogout']], function(){
->name('savePosition');
Route::patch('positions/availability/{status}/{id}', 'VacancyController@updatePositionAvailability')
Route::get('positions/edit/{position}', 'VacancyController@edit')
->name('editPosition');
Route::patch('positions/update/{position}', 'VacancyController@update')
->name('updatePosition');
Route::patch('positions/availability/{status}/{vacancy}', 'VacancyController@updatePositionAvailability')
->name('updatePositionAvailability');
@@ -184,6 +196,15 @@ Route::group(['middleware' => ['auth', 'forcelogout']], function(){
Route::get('forms', 'FormController@index')
->name('showForms');
Route::get('forms/preview/{form}', 'FormController@preview')
->name('previewForm');
Route::get('forms/edit/{form}', 'FormController@edit')
->name('editForm');
Route::patch('forms/update/{form}', 'FormController@update')
->name('updateForm');
Route::get('devtools', 'DevToolsController@index')
->name('devTools');
@@ -195,5 +216,3 @@ Route::group(['middleware' => ['auth', 'forcelogout']], function(){
});
});
//Route::get('/dashboard/login', '');