User side entry process complete

This commit is contained in:
Matt Young 2024-06-01 21:49:14 -05:00
parent 83e9c8fbd5
commit 695a0130c0
19 changed files with 197 additions and 82 deletions

View File

@ -4,12 +4,15 @@ namespace App\Actions\Fortify;
use A6digital\Image\DefaultProfileImage; use A6digital\Image\DefaultProfileImage;
use App\Models\User; use App\Models\User;
use App\Rules\ValidRegistrationCode;
use App\Settings;
use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator; use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule; use Illuminate\Validation\Rule;
use Laravel\Fortify\Contracts\CreatesNewUsers; use Laravel\Fortify\Contracts\CreatesNewUsers;
use function mb_substr; use function mb_substr;
use function sendMessage;
class CreateNewUser implements CreatesNewUsers class CreateNewUser implements CreatesNewUsers
{ {
@ -22,7 +25,9 @@ class CreateNewUser implements CreatesNewUsers
*/ */
public function create(array $input): User public function create(array $input): User
{ {
Validator::make($input, [ Validator::make($input, [
'registration_code' => [new ValidRegistrationCode],
'first_name' => ['required', 'string', 'max:255'], 'first_name' => ['required', 'string', 'max:255'],
'last_name' => ['required', 'string', 'max:255'], 'last_name' => ['required', 'string', 'max:255'],
'judging_preference' => ['required', 'string', 'max:255'], 'judging_preference' => ['required', 'string', 'max:255'],

View File

@ -4,6 +4,7 @@ namespace App\Http\Controllers;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use function dd;
use function redirect; use function redirect;
class DashboardController extends Controller class DashboardController extends Controller

View File

@ -17,7 +17,7 @@ class EntryController extends Controller
{ {
$entries = Auth::user()->entries()->with(['student','audition'])->get(); $entries = Auth::user()->entries()->with(['student','audition'])->get();
$auditions = Audition::all(); $auditions = Audition::deadlineNotPast();
$students = Auth::user()->students; $students = Auth::user()->students;
return view('entries.index',['entries' => $entries, 'students' => $students, 'auditions' => $auditions]); return view('entries.index',['entries' => $entries, 'students' => $students, 'auditions' => $auditions]);

View File

@ -0,0 +1,25 @@
<?php
namespace App\Http\Middleware;
use App\Settings;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class LoadAuditionSettings
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
Settings::loadSettings();
return $next($request);
}
}

View File

@ -15,4 +15,9 @@ class Audition extends Model
{ {
return $this->belongsTo(Event::class); return $this->belongsTo(Event::class);
} }
public static function deadlineNotPast()
{
return Audition::where('entry_deadline', '>=', now())->get();
}
} }

View File

@ -0,0 +1,23 @@
<?php
namespace App\Rules;
use App\Settings;
use Closure;
use Hamcrest\Core\Set;
use Illuminate\Contracts\Validation\ValidationRule;
class ValidRegistrationCode implements ValidationRule
{
/**
* Run the validation rule.
*
* @param \Closure(string): \Illuminate\Translation\PotentiallyTranslatedString $fail
*/
public function validate(string $attribute, mixed $value, Closure $fail): void
{
if ($value !== Settings::get('registrationCode')) {
$fail('Incorrect registration code provided');
}
}
}

View File

@ -1,5 +1,6 @@
<?php <?php
use App\Http\Middleware\LoadAuditionSettings;
use Illuminate\Foundation\Application; use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions; use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware; use Illuminate\Foundation\Configuration\Middleware;
@ -11,6 +12,7 @@ return Application::configure(basePath: dirname(__DIR__))
health: '/up', health: '/up',
) )
->withMiddleware(function (Middleware $middleware) { ->withMiddleware(function (Middleware $middleware) {
$middleware->append(LoadAuditionSettings::class);
$middleware->redirectUsersTo('/dashboard'); $middleware->redirectUsersTo('/dashboard');
$middleware->redirectGuestsTo('/'); $middleware->redirectGuestsTo('/');
}) })

View File

@ -11,7 +11,7 @@
</div> </div>
<form class="space-y-6" action="/forgot-password" method="POST"> <form class="space-y-6" action="/forgot-password" method="POST">
@csrf @csrf
<x-form.field name="email" label="Email address" type="email" autocomplete="email" required /> <x-form.field name="email" label_text="Email address" type="email" autocomplete="email" required />
<x-form.button>Send Password Reset</x-form.button> <x-form.button>Send Password Reset</x-form.button>
</form> </form>
</div> </div>

View File

@ -4,8 +4,8 @@
<div class="bg-white px-6 py-12 shadow sm:rounded-lg sm:px-12"> <div class="bg-white px-6 py-12 shadow sm:rounded-lg sm:px-12">
<form class="space-y-6" action="/login" method="POST"> <form class="space-y-6" action="/login" method="POST">
@csrf @csrf
<x-form.field name="email" label="Email address" type="email" autocomplete="email" required /> <x-form.field name="email" label_text="Email address" type="email" autocomplete="email" required />
<x-form.field name="password" label="Password" type="password" required /> <x-form.field name="password" label_text="Password" type="password" required />
<x-auth.rememberme-forgotpassword /> <x-auth.rememberme-forgotpassword />
<x-form.button>Log In</x-form.button> <x-form.button>Log In</x-form.button>
</form> </form>

View File

@ -1,20 +1,22 @@
@php use App\Settings; @endphp
<x-layout.guest> <x-layout.guest>
<x-slot:heading>Create an Account</x-slot:heading> <x-slot:heading>{{ Settings::get('auditionAbbreviation') }} - Create an Account</x-slot:heading>
<div class="mt-10 sm:mx-auto sm:w-full sm:max-w-[480px]"> <div class="mt-10 sm:mx-auto sm:w-full sm:max-w-[480px]">
<div class="bg-white px-6 py-12 shadow sm:rounded-lg sm:px-12"> <div class="bg-white px-6 py-12 shadow sm:rounded-lg sm:px-12">
<form class="space-y-6" action="/register" method="POST"> <form class="space-y-6" action="/register" method="POST">
@csrf @csrf
<x-form.field name="first_name" label="First Name" type="text" autocomplete="given-name" required /> <x-form.field name="registration_code" label_text="Registration Code" type="text" required />
<x-form.field name="last_name" label="Last Name" type="text" autocomplete="family-name" required /> <x-form.field name="first_name" label_text="First Name" type="text" autocomplete="given-name" required />
<x-form.field name="email" label="Email address" type="email" autocomplete="email" required /> <x-form.field name="last_name" label_text="Last Name" type="text" autocomplete="family-name" required />
<x-form.field name="cell_phone" label="Cell Phone Number" type="tel" autocomplete="tel-national" /> <x-form.field name="email" label_text="Email address" type="email" autocomplete="email" required />
<x-form.field name="judging_preference" label="Judging Preference" type="text" /> <x-form.field name="cell_phone" label_text="Cell Phone Number" type="tel" autocomplete="tel-national" />
<x-form.field name="password" label="Password" type="password" autocomplete="new-password" required /> <x-form.field name="judging_preference" label_text="Judging Preference" type="text" />
<x-form.field name="password_confirmation" label="Confirm Password" autocomplete="new-password" type="password" required /> <x-form.field name="password" label_text="Password" type="password" autocomplete="new-password" required />
<x-form.field name="password_confirmation" label_text="Confirm Password" autocomplete="new-password" type="password" required />
<x-form.button>Create Account</x-form.button> <x-form.button>Create Account</x-form.button>
</form> </form>
<div class="pt-4 border-t border-gray-900/10 mt-4"> <div class="pt-4 border-t border-gray-900/10 mt-4">
Already have an account? <a href="/login" class="font-semibold text-indigo-600 hover:text-indigo-500"> Click here to Log In. Already have an account? <a href="/login" class="font-semibold text-indigo-600 hover:text-indigo-500"> Click here to Log In.</a>
</div> </div>
</div> </div>
</div> </div>

View File

@ -4,9 +4,9 @@
<div class="bg-white px-6 py-12 shadow sm:rounded-lg sm:px-12"> <div class="bg-white px-6 py-12 shadow sm:rounded-lg sm:px-12">
<form class="space-y-6" action="/reset-password" method="POST"> <form class="space-y-6" action="/reset-password" method="POST">
@csrf @csrf
<x-form.field name="email" label="Email address" type="email" autocomplete="email" required /> <x-form.field name="email" label_text="Email address" type="email" autocomplete="email" required />
<x-form.field name="password" label="Password" type="password" required /> <x-form.field name="password" label_text="Password" type="password" required />
<x-form.field name="password_confirmation" label="Confirm Password" type="password" required /> <x-form.field name="password_confirmation" label_text="Confirm Password" type="password" required />
<input type="hidden" name="token" id="token" value="{{ request()->route('token') }}" > <input type="hidden" name="token" id="token" value="{{ request()->route('token') }}" >
<x-form.button>Reset Password</x-form.button> <x-form.button>Reset Password</x-form.button>
</form> </form>

View File

@ -38,4 +38,7 @@
<label for="{{ $name }}" class="{{ $label_classes }}">{{ $label_text }}</label> <label for="{{ $name }}" class="{{ $label_classes }}">{{ $label_text }}</label>
@endif @endif
<input {{ $attributes->merge($inputAttributes) }}> <input {{ $attributes->merge($inputAttributes) }}>
@error($name)
<p class="text-xs text-red-500 font-semibold mt-1 ml-3">{{ $message }}</p>
@enderror
</div> </div>

View File

@ -23,7 +23,7 @@
'for' => $name 'for' => $name
]; ];
$select_attribs = [ $select_attribs = [
'class' => 'mt-2 block w-full rounded-md border-0 py-1.5 pl-3 pr-10 text-gray-900 ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-indigo-600 sm:text-sm sm:leading-6', 'class' => 'block w-full rounded-md border-0 py-1.5 pl-3 pr-10 text-gray-900 ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-indigo-600 sm:text-sm sm:leading-6',
'id' => $name, 'id' => $name,
'name' => $name 'name' => $name
] ]

View File

@ -35,6 +35,13 @@
</x-card.list.row> </x-card.list.row>
</form> </form>
@endforeach @endforeach
<x-card.list.row>
<x-card.list.row-text-subtext>
Create new school
<x-slot:subtext>My school isn't listed</x-slot:subtext>
</x-card.list.row-text-subtext>
<x-slot:right_link_button href="/schools/create">Create new school</x-slot:right_link_button>
</x-card.list.row>
</x-card.list.body> </x-card.list.body>
</x-card.card> </x-card.card>
</div> </div>

View File

@ -56,6 +56,7 @@
<x-table.td first>{{ $entry->student->full_name(true) }}</x-table.td> <x-table.td first>{{ $entry->student->full_name(true) }}</x-table.td>
<x-table.td>{{ $entry->student->grade }}</x-table.td> <x-table.td>{{ $entry->student->grade }}</x-table.td>
<x-table.td>{{ $entry->audition->name }}</x-table.td> <x-table.td>{{ $entry->audition->name }}</x-table.td>
{{-- TODO block deletion of entries past the deadline--}}
<x-table.td for_button> <x-table.td for_button>
<form method="POST" action="/entries/{{ $entry->id }}" class="inline"> <form method="POST" action="/entries/{{ $entry->id }}" class="inline">
@csrf @csrf

View File

@ -1,24 +1,56 @@
<x-layout.app> <x-layout.app>
<x-slot:page_title>Create School</x-slot:page_title>
<div class="space-y-10 divide-y divide-gray-900/10">
<x-layout.page-section
section_name="School Information"
section_description=""
:first="true">
<x-form.card <x-card.card class="mx-auto max-w-lg">
submit-button-text="Create School" <x-card.heading>
method="POST" New School
action="/schools" </x-card.heading>
cols="9" <x-form.form method="POST" action="/schools" class="-mt-5">
> <x-form.body-grid columns="6" class="max-w-full">
<x-form.field name="name" label="School Name" div_classes="sm:col-span-6"/> <x-form.field name="name" label_text="School Name" colspan="6"/>
<x-form.field name="address" label="School Address" div_classes="sm:col-span-6"/> <x-form.field name="address" label_text="School Address" colspan="6"/>
<x-form.field name="city" label="City" div_classes="sm:col-span-3"/> <x-form.field name="city" label_text="City" colspan="3"/>
<x-form.field name="state" label="State" div_classes="sm:col-span-2"/> <x-form.field name="state" label_text="State" colspan="2"/>
<x-form.field name="zip" label="Zip" div_classes="sm:col-span-1"/> <x-form.field name="zip" label_text="Zip"/>
</x-form.card> </x-form.body-grid>
<x-form.footer submit-button-text="Create School"/>
</x-form.form>
</x-card.card>
{{-- <x-slot:page_title>Create School</x-slot:page_title>--}}
{{-- <div class="space-y-10 divide-y divide-gray-900/10">--}}
{{-- <x-layout.page-section :first="true">--}}
{{-- <x-slot:section_name>School Information</x-slot:section_name>--}}
{{-- <x-form.form method="POST" action="/schools">--}}
{{-- <x-form.body-grid columns="6" class="max-w-full">--}}
{{-- <x-form.field name="name" label_text="School Name" colspan="6"/>--}}
{{-- <x-form.field name="address" label_text="School Address" colspan="6"/>--}}
{{-- <x-form.field name="city" label_text="City" colspan="3"/>--}}
{{-- <x-form.field name="state" label_text="State" colspan="2"/>--}}
{{-- <x-form.field name="zip" label_text="Zip"/>--}}
{{-- </x-form.body-grid>--}}
{{-- </x-form.form>--}}
</x-layout.page-section> {{-- </x-layout.page-section>--}}
</div>
{{-- <x-layout.page-section--}}
{{-- :first="true">--}}
{{-- <x-slot:section_name>School Information</x-slot:section_name>--}}
{{-- <x-form.card--}}
{{-- submit-button-text="Create School"--}}
{{-- method="POST"--}}
{{-- action="/schools"--}}
{{-- cols="9"--}}
{{-- >--}}
{{-- <x-form.field name="name" label_text="School Name" div_classes="sm:col-span-6"/>--}}
{{-- <x-form.field name="address" label_text="School Address" div_classes="sm:col-span-6"/>--}}
{{-- <x-form.field name="city" label_text="City" div_classes="sm:col-span-3"/>--}}
{{-- <x-form.field name="state" label_text="State" div_classes="sm:col-span-2"/>--}}
{{-- <x-form.field name="zip" label_text="Zip" div_classes="sm:col-span-1"/>--}}
{{-- </x-form.card>--}}
{{-- </x-layout.page-section>--}}
{{-- </div>--}}
</x-layout.app> </x-layout.app>

View File

@ -1,6 +1,6 @@
@php use Illuminate\Support\Facades\Auth; @endphp @php use App\Models\Audition;use Illuminate\Support\Facades\Auth; @endphp
@push('scripts') @push('scripts')
{{-- Code from https://codepen.io/ryangjchandler/pen/WNQQKeR--}} {{-- Code from https://codepen.io/ryangjchandler/pen/WNQQKeR--}}
<script src="{{ asset('js/sort_table_by_column.js') }}"></script> <script src="{{ asset('js/sort_table_by_column.js') }}"></script>
@endpush @endpush
<x-layout.app> <x-layout.app>
@ -11,10 +11,19 @@
<x-slot:section_name>Add Student</x-slot:section_name> <x-slot:section_name>Add Student</x-slot:section_name>
<x-form.form method="POST" action="/students"> <x-form.form method="POST" action="/students">
<x-form.body-grid columns="8" class="max-w-full"> <x-form.body-grid columns="8" class="max-w-full">
<x-form.field name="first_name" label_text="First Name" colspan="3" /> <x-form.field name="first_name" label_text="First Name" colspan="3"/>
<x-form.field name="last_name" label_text="Last Name" colspan="3" /> <x-form.field name="last_name" label_text="Last Name" colspan="3"/>
<x-form.field name="grade" label_text="Grade" colspan="1" /> {{-- <x-form.field name="grade" label_text="Grade" colspan="1" />--}}
{{-- TODO make grade a dropdown --}}
<x-form.select name="grade">
<x-slot:label>Grade</x-slot:label>
@php($n = Audition::min('minimum_grade'))
@php($maxGrade = Audition::max('maximum_grade'))
@while($n <= $maxGrade)
<option value="{{ $n }}">{{ $n }}</option>
@php($n++);
@endwhile
</x-form.select>
<x-form.button class="mt-6">Save</x-form.button> <x-form.button class="mt-6">Save</x-form.button>
</x-form.body-grid> </x-form.body-grid>
</x-form.form> </x-form.form>
@ -24,40 +33,41 @@
<x-layout.page-section> <x-layout.page-section>
<x-slot:section_name>Student Listing</x-slot:section_name> <x-slot:section_name>Student Listing</x-slot:section_name>
<div class="px-4"> <div class="px-4">
<x-table.table> <x-table.table>
<thead> <thead>
<tr> <tr>
<x-table.th first>Name</x-table.th> <x-table.th first>Name</x-table.th>
<x-table.th>Grade</x-table.th> <x-table.th>Grade</x-table.th>
<x-table.th>Entries</x-table.th> <x-table.th>Entries</x-table.th>
<x-table.th spacer_only> <x-table.th spacer_only>
<span class="sr-only">Edit</span> <span class="sr-only">Edit</span>
</x-table.th> </x-table.th>
</tr> </tr>
</thead> </thead>
<x-table.body> <x-table.body>
@foreach($students as $student) @foreach($students as $student)
<tr> <tr>
<x-table.td first>{{ $student->full_name(true) }}</x-table.td> <x-table.td first>{{ $student->full_name(true) }}</x-table.td>
<x-table.td>{{ $student->grade }}</x-table.td> <x-table.td>{{ $student->grade }}</x-table.td>
<x-table.td>{{ $student->entries->count() }}</x-table.td> <x-table.td>{{ $student->entries->count() }}</x-table.td>
<x-table.td for_button> <x-table.td for_button>
@can('delete', $student) @if( $student->entries->count() > 0)
<form method="POST" action="/students/{{ $student->id }}" class="inline"> <form method="POST" action="/students/{{ $student->id }}" class="inline">
@csrf @csrf
@method('DELETE') @method('DELETE')
<x-table.button <x-table.button
onclick="return confirm('Please confirm you would like to delete the student {{ $student->full_name() }}');" onclick="return confirm('Please confirm you would like to delete the student {{ $student->full_name() }}');"
>Delete</x-table.button> >Delete
</form> </x-table.button>
| </form>
@endcan |
<x-table.button href="/students/{{ $student->id }}/edit">Edit</x-table.button> @endif
</x-table.td> <x-table.button href="/students/{{ $student->id }}/edit">Edit</x-table.button>
</tr> </x-table.td>
@endforeach </tr>
</x-table.body> @endforeach
</x-table.table> </x-table.body>
</x-table.table>
</div> </div>
</x-layout.page-section> </x-layout.page-section>
</x-layout.page-section-container> </x-layout.page-section-container>

View File

@ -2,10 +2,9 @@
<x-layout.app> <x-layout.app>
<x-slot:page_title>Test Page</x-slot:page_title> <x-slot:page_title>Test Page</x-slot:page_title>
@php( Settings::set('auditionName','Somewhere Band Directors Association')) {{ Audition::max('maximum_grade') }} is the oldest grade used. <br>
@php( Settings::set('auditionAbbreviation','SBDA')) {{ Audition::min('minimum_grade') }} is the youngest grade.
{{ Settings::get('auditionName') }}
</x-layout.app> </x-layout.app>
d d

View File

@ -21,7 +21,7 @@ Route::middleware(['auth','verified'])->group(function () {
}); });
// Entry Related Routes // Entry Related Routes
Route::middleware(['auth','verified'])->controller(EntryController::class)->group(function() { Route::middleware(['auth','verified','can:create,App\Models\Entry'])->controller(EntryController::class)->group(function() {
Route::get('/entries','index'); Route::get('/entries','index');
Route::get('/entries/create','create'); Route::get('/entries/create','create');
Route::post('/entries', 'store'); Route::post('/entries', 'store');
@ -35,7 +35,7 @@ Route::middleware(['auth','verified'])->controller(UserController::class)->group
}); });
// Student Related Routes // Student Related Routes
Route::middleware(['auth','verified'])->controller(StudentController::class)->group(function() { Route::middleware(['auth','verified','can:create,App\Models\Student'])->controller(StudentController::class)->group(function() {
Route::get('/students','index'); Route::get('/students','index');
Route::post('students', 'store'); Route::post('students', 'store');
Route::get('/students/{student}/edit','edit'); Route::get('/students/{student}/edit','edit');