From fa6ed8521b9d0c6e6c868209b67c4fa72fd92f06 Mon Sep 17 00:00:00 2001 From: Dominik Prabucki <dominikprabucki.2002@gmail.com> Date: Thu, 8 Aug 2024 14:59:49 +0200 Subject: [PATCH 01/33] Add migration for schools_table and modify users_table --- app/Models/User.php | 3 ++ database/factories/UserFactory.php | 2 ++ .../0001_01_01_000000_create_users_table.php | 3 +- ...2024_08_08_113859_create_schools_table.php | 33 +++++++++++++++++++ ...08_123147_add_school_id_to_users_table.php | 29 ++++++++++++++++ database/seeders/DatabaseSeeder.php | 19 +++++++++++ 6 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 database/migrations/2024_08_08_113859_create_schools_table.php create mode 100644 database/migrations/2024_08_08_123147_add_school_id_to_users_table.php diff --git a/app/Models/User.php b/app/Models/User.php index f8b34523..52df1420 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -11,10 +11,13 @@ use Laravel\Sanctum\HasApiTokens; /** + * @property int $id * @property string $name + * @property string $surname * @property string $email * @property string $password * @property Carbon $email_verified_at + * @property int $school_id * @property Carbon $created_at * @property Carbon $updated_at */ diff --git a/database/factories/UserFactory.php b/database/factories/UserFactory.php index 61c04883..04e5af3f 100644 --- a/database/factories/UserFactory.php +++ b/database/factories/UserFactory.php @@ -17,10 +17,12 @@ public function definition(): array { return [ "name" => fake()->name(), + "surname" => fake()->name(), "email" => fake()->unique()->safeEmail(), "email_verified_at" => now(), "password" => "$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi", "remember_token" => Str::random(10), + "school_id" => 1, ]; } diff --git a/database/migrations/0001_01_01_000000_create_users_table.php b/database/migrations/0001_01_01_000000_create_users_table.php index 7c9f45db..38b52a4a 100644 --- a/database/migrations/0001_01_01_000000_create_users_table.php +++ b/database/migrations/0001_01_01_000000_create_users_table.php @@ -10,8 +10,9 @@ public function up(): void { Schema::create("users", function (Blueprint $table): void { - $table->id(); + $table->bigIncrements("id")->unique(); $table->string("name"); + $table->string("surname"); $table->string("email")->unique(); $table->timestamp("email_verified_at")->nullable(); $table->string("password"); diff --git a/database/migrations/2024_08_08_113859_create_schools_table.php b/database/migrations/2024_08_08_113859_create_schools_table.php new file mode 100644 index 00000000..f1ae6d65 --- /dev/null +++ b/database/migrations/2024_08_08_113859_create_schools_table.php @@ -0,0 +1,33 @@ +<?php + +use Illuminate\Database\Migrations\Migration; +use Illuminate\Database\Schema\Blueprint; +use Illuminate\Support\Facades\Schema; + +return new class extends Migration +{ + /** + * Run the migrations. + */ + public function up(): void + { + Schema::create('schools', function (Blueprint $table) { + $table->bigIncrements("id")->unique(); + $table->string("name"); + $table->string("city"); + $table->string("street"); + $table->string("building_number"); + $table->string("apartment_number")->nullable(); + $table->string("zipCode"); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('schools'); + } +}; diff --git a/database/migrations/2024_08_08_123147_add_school_id_to_users_table.php b/database/migrations/2024_08_08_123147_add_school_id_to_users_table.php new file mode 100644 index 00000000..92260e90 --- /dev/null +++ b/database/migrations/2024_08_08_123147_add_school_id_to_users_table.php @@ -0,0 +1,29 @@ +<?php + +use Illuminate\Database\Migrations\Migration; +use Illuminate\Database\Schema\Blueprint; +use Illuminate\Support\Facades\Schema; + +return new class extends Migration +{ + /** + * Run the migrations. + */ + public function up(): void + { + Schema::table('users', function (Blueprint $table) { + $table->bigInteger("school_id"); + $table->foreign("school_id")->references("id")->on("schools")->onDelete("cascade"); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn("users"); + }); + } +}; diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index de853239..190cb7d2 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -5,10 +5,29 @@ namespace Database\Seeders; use Illuminate\Database\Seeder; +use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Hash; +use Illuminate\Support\Str; class DatabaseSeeder extends Seeder { public function run(): void { + + DB::table('schools')->insert([ + 'name' => Str::random(10), + 'city' => Str::random(10), + 'street' => Str::random(10), + 'building_number' => Str::random(10), + 'zipCode' => Str::random(10), + ]); + + DB::table('users')->insert([ + 'name' => Str::random(10), + 'surname' => Str::random(10), + 'email' => Str::random(10).'@example.com', + 'password' => Hash::make('password'), + 'school_id' => 1, + ]); } } From 5fcd938470edd7732e43e8a3e76f9a9d9cde36f0 Mon Sep 17 00:00:00 2001 From: Dominik Prabucki <dominikprabucki.2002@gmail.com> Date: Thu, 8 Aug 2024 14:59:59 +0200 Subject: [PATCH 02/33] Add migration for schools_table and modify users_table --- app/Models/School.php | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 app/Models/School.php diff --git a/app/Models/School.php b/app/Models/School.php new file mode 100644 index 00000000..6e158e92 --- /dev/null +++ b/app/Models/School.php @@ -0,0 +1,33 @@ +<?php + +namespace App\Models; + +use Carbon\Carbon; +use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Model; + + +/** + * @property int $id + * @property string $name + * @property string $city + * @property string $street + * @property string $building_number + * @property string $apartment_number + * @property string $zipCode + * @property Carbon $created_at + * @property Carbon $updated_at + */ +class School extends Model +{ + use HasFactory; + + protected $fillable = [ + "name", + "city", + "street", + "building_number", + "apartment_number", + "zipCode", + ]; +} From cbb1f7c1f5dc0c515a9a5b38ada624d6a45cca76 Mon Sep 17 00:00:00 2001 From: Dominik Prabucki <dominikprabucki.2002@gmail.com> Date: Fri, 9 Aug 2024 13:39:40 +0200 Subject: [PATCH 03/33] Add: - register and login forms - RegisterUserController and RegisterUserRequest - ContestController - improve user model - add foreign key to user table --- .../AuthenticateSessionController.php | 36 +++ app/Http/Controllers/ContestController.php | 16 + .../Controllers/RegisterUserController.php | 33 ++ app/Http/Middleware/HandleInertiaRequests.php | 42 +++ .../Requests/Auth/RegisterUserRequest.php | 36 +++ app/Models/School.php | 5 +- app/Models/User.php | 11 +- bootstrap/app.php | 4 + ...2024_08_08_113859_create_schools_table.php | 15 +- ...08_123147_add_school_id_to_users_table.php | 15 +- database/seeders/DatabaseSeeder.php | 25 +- resources/js/Pages/Auth/Login.vue | 23 ++ resources/js/Pages/Auth/Register.vue | 32 ++ resources/js/Pages/Auth/Verify-Email.vue | 11 + resources/js/Pages/Welcome.vue | 297 +----------------- routes/web.php | 10 +- 16 files changed, 293 insertions(+), 318 deletions(-) create mode 100644 app/Http/Controllers/AuthenticateSessionController.php create mode 100644 app/Http/Controllers/ContestController.php create mode 100644 app/Http/Controllers/RegisterUserController.php create mode 100644 app/Http/Middleware/HandleInertiaRequests.php create mode 100644 app/Http/Requests/Auth/RegisterUserRequest.php create mode 100644 resources/js/Pages/Auth/Login.vue create mode 100644 resources/js/Pages/Auth/Register.vue create mode 100644 resources/js/Pages/Auth/Verify-Email.vue diff --git a/app/Http/Controllers/AuthenticateSessionController.php b/app/Http/Controllers/AuthenticateSessionController.php new file mode 100644 index 00000000..f931e0c5 --- /dev/null +++ b/app/Http/Controllers/AuthenticateSessionController.php @@ -0,0 +1,36 @@ +<?php + +namespace App\Http\Controllers; + +use Illuminate\Http\RedirectResponse; +use Illuminate\Http\Request; +use Illuminate\Support\Facades\Redirect; +use Inertia\Inertia; +use Inertia\Response; + +class AuthenticateSessionController extends Controller +{ + public function create(): Response + { + return Inertia::render("Auth/Login"); + } + + public function authenticate(Request $request): RedirectResponse + { + + $credentials = $request->validate([ + 'email' => 'required|email', + 'password' => 'required|string', + ]); + + + if (auth()->attempt($credentials)) { + $request->session()->regenerate(); + return Redirect::route("home")->with('succes'); + } + + return back()->withErrors([ + 'The provided mail or password is invalid, try again.', + ]); + } +} diff --git a/app/Http/Controllers/ContestController.php b/app/Http/Controllers/ContestController.php new file mode 100644 index 00000000..82b2c5e9 --- /dev/null +++ b/app/Http/Controllers/ContestController.php @@ -0,0 +1,16 @@ +<?php + +declare(strict_types=1); + +namespace App\Http\Controllers; + +use Inertia\Inertia; +use Inertia\Response; + +class ContestController extends Controller +{ + public function index(): Response + { + return Inertia::render("Welcome"); + } +} diff --git a/app/Http/Controllers/RegisterUserController.php b/app/Http/Controllers/RegisterUserController.php new file mode 100644 index 00000000..50ebd407 --- /dev/null +++ b/app/Http/Controllers/RegisterUserController.php @@ -0,0 +1,33 @@ +<?php + +declare(strict_types=1); + +namespace App\Http\Controllers; + +use App\Http\Requests\Auth\RegisterUserRequest; +use App\Models\User; +use Illuminate\Auth\Events\Registered; +use Illuminate\Http\RedirectResponse; +use Illuminate\Support\Facades\Hash; +use Illuminate\Support\Facades\Redirect; +use Inertia\Inertia; +use Inertia\Response; + +class RegisterUserController extends Controller +{ + public function create(): Response + { + return Inertia::render("Auth/Register"); + } + + public function store(RegisterUserRequest $request): RedirectResponse + { + $user = new User($request->validated()); + $user->password = Hash::make($request->getPassword()); + $user->save(); + + event(new Registered($user)); + + return Redirect::route("home"); + } +} diff --git a/app/Http/Middleware/HandleInertiaRequests.php b/app/Http/Middleware/HandleInertiaRequests.php new file mode 100644 index 00000000..1036bf69 --- /dev/null +++ b/app/Http/Middleware/HandleInertiaRequests.php @@ -0,0 +1,42 @@ +<?php + +declare(strict_types=1); + +namespace App\Http\Middleware; + +use Illuminate\Http\Request; +use Inertia\Middleware; + +class HandleInertiaRequests extends Middleware +{ + /** + * The root template that's loaded on the first page visit. + * + * @see https://inertiajs.com/server-side-setup#root-template + * + * @var string + */ + protected $rootView = "app"; + + /** + * Determines the current asset version. + * + * @see https://inertiajs.com/asset-versioning + */ + public function version(Request $request): ?string + { + return parent::version($request); + } + + /** + * Define the props that are shared by default. + * + * @see https://inertiajs.com/shared-data + * + * @return array<string, mixed> + */ + public function share(Request $request): array + { + return array_merge(parent::share($request), []); + } +} diff --git a/app/Http/Requests/Auth/RegisterUserRequest.php b/app/Http/Requests/Auth/RegisterUserRequest.php new file mode 100644 index 00000000..b1b570d1 --- /dev/null +++ b/app/Http/Requests/Auth/RegisterUserRequest.php @@ -0,0 +1,36 @@ +<?php + +declare(strict_types=1); + +namespace App\Http\Requests\Auth; + +use App\Models\User; +use Illuminate\Contracts\Validation\ValidationRule; +use Illuminate\Foundation\Http\FormRequest; + +class RegisterUserRequest extends FormRequest +{ + /** + * Determine if the user is authorized to make this request. + */ + public function authorize(): bool + { + return true; + } + + /** + * Get the validation rules that apply to the request. + * + * @return array<string, ValidationRule|array|string> + */ + public function rules(): array + { + return [ + "email" => "required|string|email|max:255|unique:" . User::class, + "name" => "required|string", + "surname" => "required|string", + "password" => "required|string|min:8", + "school_id" => "required|integer|exists:schools,id", + ]; + } +} diff --git a/app/Models/School.php b/app/Models/School.php index 6e158e92..391995c8 100644 --- a/app/Models/School.php +++ b/app/Models/School.php @@ -1,12 +1,13 @@ <?php +declare(strict_types=1); + namespace App\Models; use Carbon\Carbon; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; - /** * @property int $id * @property string $name @@ -29,5 +30,5 @@ class School extends Model "building_number", "apartment_number", "zipCode", - ]; + ]; } diff --git a/app/Models/User.php b/app/Models/User.php index 52df1420..c9570fb1 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -6,6 +6,7 @@ use Carbon\Carbon; use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; use Laravel\Sanctum\HasApiTokens; @@ -20,6 +21,8 @@ * @property int $school_id * @property Carbon $created_at * @property Carbon $updated_at + * + * @property School $school */ class User extends Authenticatable { @@ -29,14 +32,20 @@ class User extends Authenticatable protected $fillable = [ "name", + "surname", "email", "password", + "school_id", ]; protected $hidden = [ - "password", "remember_token", ]; + public function school(): BelongsTo + { + return $this->belongsTo(School::class); + } + protected function casts(): array { return [ diff --git a/bootstrap/app.php b/bootstrap/app.php index 91570f99..56dbeaeb 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -2,6 +2,7 @@ declare(strict_types=1); +use App\Http\Middleware\HandleInertiaRequests; use Illuminate\Foundation\Application; use Illuminate\Foundation\Configuration\Exceptions; use Illuminate\Foundation\Configuration\Middleware; @@ -13,6 +14,9 @@ health: "/up", ) ->withMiddleware(function (Middleware $middleware): void { + $middleware->web(append: [ + HandleInertiaRequests::class, + ]); }) ->withExceptions(function (Exceptions $exceptions): void { })->create(); diff --git a/database/migrations/2024_08_08_113859_create_schools_table.php b/database/migrations/2024_08_08_113859_create_schools_table.php index f1ae6d65..f78223b0 100644 --- a/database/migrations/2024_08_08_113859_create_schools_table.php +++ b/database/migrations/2024_08_08_113859_create_schools_table.php @@ -1,17 +1,15 @@ <?php +declare(strict_types=1); + use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -return new class extends Migration -{ - /** - * Run the migrations. - */ +return new class() extends Migration { public function up(): void { - Schema::create('schools', function (Blueprint $table) { + Schema::create("schools", function (Blueprint $table): void { $table->bigIncrements("id")->unique(); $table->string("name"); $table->string("city"); @@ -23,11 +21,8 @@ public function up(): void }); } - /** - * Reverse the migrations. - */ public function down(): void { - Schema::dropIfExists('schools'); + Schema::dropIfExists("schools"); } }; diff --git a/database/migrations/2024_08_08_123147_add_school_id_to_users_table.php b/database/migrations/2024_08_08_123147_add_school_id_to_users_table.php index 92260e90..f3210daa 100644 --- a/database/migrations/2024_08_08_123147_add_school_id_to_users_table.php +++ b/database/migrations/2024_08_08_123147_add_school_id_to_users_table.php @@ -1,28 +1,23 @@ <?php +declare(strict_types=1); + use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -return new class extends Migration -{ - /** - * Run the migrations. - */ +return new class() extends Migration { public function up(): void { - Schema::table('users', function (Blueprint $table) { + Schema::table("users", function (Blueprint $table): void { $table->bigInteger("school_id"); $table->foreign("school_id")->references("id")->on("schools")->onDelete("cascade"); }); } - /** - * Reverse the migrations. - */ public function down(): void { - Schema::table('users', function (Blueprint $table) { + Schema::table("users", function (Blueprint $table): void { $table->dropColumn("users"); }); } diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 190cb7d2..388241c7 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -13,21 +13,20 @@ class DatabaseSeeder extends Seeder { public function run(): void { - - DB::table('schools')->insert([ - 'name' => Str::random(10), - 'city' => Str::random(10), - 'street' => Str::random(10), - 'building_number' => Str::random(10), - 'zipCode' => Str::random(10), + DB::table("schools")->insert([ + "name" => Str::random(10), + "city" => Str::random(10), + "street" => Str::random(10), + "building_number" => Str::random(10), + "zipCode" => Str::random(10), ]); - DB::table('users')->insert([ - 'name' => Str::random(10), - 'surname' => Str::random(10), - 'email' => Str::random(10).'@example.com', - 'password' => Hash::make('password'), - 'school_id' => 1, + DB::table("users")->insert([ + "name" => Str::random(10), + "surname" => Str::random(10), + "email" => Str::random(10) . "@example.com", + "password" => Hash::make("password"), + "school_id" => 1, ]); } } diff --git a/resources/js/Pages/Auth/Login.vue b/resources/js/Pages/Auth/Login.vue new file mode 100644 index 00000000..4541407c --- /dev/null +++ b/resources/js/Pages/Auth/Login.vue @@ -0,0 +1,23 @@ +<script setup> +import { reactive } from 'vue' +import { router } from '@inertiajs/vue3' + +const form = reactive({ + email: null, + password: null, +}) + +function submit() { + router.post('/auth/login', form) +} +</script> + +<template> + <form @submit.prevent="submit"> + <label for="email">Email:</label> + <input v-model="form.email" name="email" type="email"> + <label for="password">password:</label> + <input v-model="form.password" name="password" type="password"> + <button type="submit">Login</button> + </form> +</template> diff --git a/resources/js/Pages/Auth/Register.vue b/resources/js/Pages/Auth/Register.vue new file mode 100644 index 00000000..cfaf89ab --- /dev/null +++ b/resources/js/Pages/Auth/Register.vue @@ -0,0 +1,32 @@ +<script setup> +import { reactive } from 'vue' +import { router } from '@inertiajs/vue3' + +const form = reactive({ + name: null, + surname: null, + email: null, + password: null, + school_id: null, +}) + +function submit() { + router.post('/auth/register', form) +} +</script> + +<template> + <form @submit.prevent="submit"> + <label for="name">Name:</label> + <input v-model="form.name" name="name" type="text"> + <label for="surname">Surname:</label> + <input v-model="form.surname" name="surname" type="text"> + <label for="email">Email:</label> + <input v-model="form.email" name="email" type="email"> + <label for="password">password:</label> + <input v-model="form.password" name="password" type="password"> + <label for="school_id">School ID:</label> + <input v-model="form.school_id" name="school_id" type="number"> + <button type="submit">Register</button> + </form> +</template> diff --git a/resources/js/Pages/Auth/Verify-Email.vue b/resources/js/Pages/Auth/Verify-Email.vue new file mode 100644 index 00000000..14a9dc2b --- /dev/null +++ b/resources/js/Pages/Auth/Verify-Email.vue @@ -0,0 +1,11 @@ +<script setup lang="ts"> + +</script> + +<template> + Weryfikacja Maila +</template> + +<style scoped> + +</style> diff --git a/resources/js/Pages/Welcome.vue b/resources/js/Pages/Welcome.vue index 80004bdb..b08ff677 100644 --- a/resources/js/Pages/Welcome.vue +++ b/resources/js/Pages/Welcome.vue @@ -1,286 +1,21 @@ <script setup> -import { Head } from '@inertiajs/vue3' +import { reactive } from 'vue' +import { router } from '@inertiajs/vue3' + +const form = reactive({ + name: null, + surname: null, + email: null, + password: null, + school_id: null, +}) + +function submit() { + router.post('/auth/register', form) +} </script> <template> - <Head title="Welcome" /> - - <div - class="bg-dots-darker dark:bg-dots-lighter relative min-h-screen bg-gray-100 bg-center selection:bg-red-500 selection:text-white dark:bg-gray-900 sm:flex sm:items-center sm:justify-center" - > - <div class="mx-auto max-w-7xl p-6 lg:p-8"> - <div class="flex justify-center"> - <svg - viewBox="0 0 62 65" - fill="none" - xmlns="http://www.w3.org/2000/svg" - class="h-16 w-auto bg-gray-100 dark:bg-gray-900" - > - <path - d="M61.8548 14.6253C61.8778 14.7102 61.8895 14.7978 61.8897 14.8858V28.5615C61.8898 28.737 61.8434 28.9095 61.7554 29.0614C61.6675 29.2132 61.5409 29.3392 61.3887 29.4265L49.9104 36.0351V49.1337C49.9104 49.4902 49.7209 49.8192 49.4118 49.9987L25.4519 63.7916C25.3971 63.8227 25.3372 63.8427 25.2774 63.8639C25.255 63.8714 25.2338 63.8851 25.2101 63.8913C25.0426 63.9354 24.8666 63.9354 24.6991 63.8913C24.6716 63.8838 24.6467 63.8689 24.6205 63.8589C24.5657 63.8389 24.5084 63.8215 24.456 63.7916L0.501061 49.9987C0.348882 49.9113 0.222437 49.7853 0.134469 49.6334C0.0465019 49.4816 0.000120578 49.3092 0 49.1337L0 8.10652C0 8.01678 0.0124642 7.92953 0.0348998 7.84477C0.0423783 7.8161 0.0598282 7.78993 0.0697995 7.76126C0.0884958 7.70891 0.105946 7.65531 0.133367 7.6067C0.152063 7.5743 0.179485 7.54812 0.20192 7.51821C0.230588 7.47832 0.256763 7.43719 0.290416 7.40229C0.319084 7.37362 0.356476 7.35243 0.388883 7.32751C0.425029 7.29759 0.457436 7.26518 0.498568 7.2415L12.4779 0.345059C12.6296 0.257786 12.8015 0.211853 12.9765 0.211853C13.1515 0.211853 13.3234 0.257786 13.475 0.345059L25.4531 7.2415H25.4556C25.4955 7.26643 25.5292 7.29759 25.5653 7.32626C25.5977 7.35119 25.6339 7.37362 25.6625 7.40104C25.6974 7.43719 25.7224 7.47832 25.7523 7.51821C25.7735 7.54812 25.8021 7.5743 25.8196 7.6067C25.8483 7.65656 25.8645 7.70891 25.8844 7.76126C25.8944 7.78993 25.9118 7.8161 25.9193 7.84602C25.9423 7.93096 25.954 8.01853 25.9542 8.10652V33.7317L35.9355 27.9844V14.8846C35.9355 14.7973 35.948 14.7088 35.9704 14.6253C35.9792 14.5954 35.9954 14.5692 36.0053 14.5405C36.0253 14.4882 36.0427 14.4346 36.0702 14.386C36.0888 14.3536 36.1163 14.3274 36.1375 14.2975C36.1674 14.2576 36.1923 14.2165 36.2272 14.1816C36.2559 14.1529 36.292 14.1317 36.3244 14.1068C36.3618 14.0769 36.3942 14.0445 36.4341 14.0208L48.4147 7.12434C48.5663 7.03694 48.7383 6.99094 48.9133 6.99094C49.0883 6.99094 49.2602 7.03694 49.4118 7.12434L61.3899 14.0208C61.4323 14.0457 61.4647 14.0769 61.5021 14.1055C61.5333 14.1305 61.5694 14.1529 61.5981 14.1803C61.633 14.2165 61.6579 14.2576 61.6878 14.2975C61.7103 14.3274 61.7377 14.3536 61.7551 14.386C61.7838 14.4346 61.8 14.4882 61.8199 14.5405C61.8312 14.5692 61.8474 14.5954 61.8548 14.6253ZM59.893 27.9844V16.6121L55.7013 19.0252L49.9104 22.3593V33.7317L59.8942 27.9844H59.893ZM47.9149 48.5566V37.1768L42.2187 40.4299L25.953 49.7133V61.2003L47.9149 48.5566ZM1.99677 9.83281V48.5566L23.9562 61.199V49.7145L12.4841 43.2219L12.4804 43.2194L12.4754 43.2169C12.4368 43.1945 12.4044 43.1621 12.3682 43.1347C12.3371 43.1097 12.3009 43.0898 12.2735 43.0624L12.271 43.0586C12.2386 43.0275 12.2162 42.9888 12.1887 42.9539C12.1638 42.9203 12.1339 42.8916 12.114 42.8567L12.1127 42.853C12.0903 42.8156 12.0766 42.7707 12.0604 42.7283C12.0442 42.6909 12.023 42.656 12.013 42.6161C12.0005 42.5688 11.998 42.5177 11.9931 42.4691C11.9881 42.4317 11.9781 42.3943 11.9781 42.3569V15.5801L6.18848 12.2446L1.99677 9.83281ZM12.9777 2.36177L2.99764 8.10652L12.9752 13.8513L22.9541 8.10527L12.9752 2.36177H12.9777ZM18.1678 38.2138L23.9574 34.8809V9.83281L19.7657 12.2459L13.9749 15.5801V40.6281L18.1678 38.2138ZM48.9133 9.14105L38.9344 14.8858L48.9133 20.6305L58.8909 14.8846L48.9133 9.14105ZM47.9149 22.3593L42.124 19.0252L37.9323 16.6121V27.9844L43.7219 31.3174L47.9149 33.7317V22.3593ZM24.9533 47.987L39.59 39.631L46.9065 35.4555L36.9352 29.7145L25.4544 36.3242L14.9907 42.3482L24.9533 47.987Z" - fill="#FF2D20" - /> - </svg> - </div> - - <div class="mt-16"> - <div class="grid grid-cols-1 gap-6 md:grid-cols-2 lg:gap-8"> - <a - href="https://laravel.com/docs" - class="duration-250 flex scale-100 rounded-lg bg-white from-gray-700/50 via-transparent p-6 shadow-2xl shadow-gray-500/20 transition-all focus:outline focus:outline-2 focus:outline-red-500 motion-safe:hover:scale-[1.01] dark:bg-gray-800/50 dark:bg-gradient-to-bl dark:shadow-none dark:ring-1 dark:ring-inset dark:ring-white/5" - > - <div> - <div - class="flex size-16 items-center justify-center rounded-full bg-red-50 dark:bg-red-800/20" - > - <svg - xmlns="http://www.w3.org/2000/svg" - fill="none" - viewBox="0 0 24 24" - stroke-width="1.5" - class="size-7 stroke-red-500" - > - <path - stroke-linecap="round" - stroke-linejoin="round" - d="M12 6.042A8.967 8.967 0 006 3.75c-1.052 0-2.062.18-3 .512v14.25A8.987 8.987 0 016 18c2.305 0 4.408.867 6 2.292m0-14.25a8.966 8.966 0 016-2.292c1.052 0 2.062.18 3 .512v14.25A8.987 8.987 0 0018 18a8.967 8.967 0 00-6 2.292m0-14.25v14.25" - /> - </svg> - </div> - - <h2 class="mt-6 text-xl font-semibold text-gray-900 dark:text-white">Documentation</h2> - - <p class="mt-4 text-sm leading-relaxed text-gray-500 dark:text-gray-400"> - Laravel has wonderful documentation covering every aspect of the framework. Whether you - are a newcomer or have prior experience with Laravel, we recommend reading our - documentation from beginning to end. - </p> - </div> - - <svg - xmlns="http://www.w3.org/2000/svg" - fill="none" - viewBox="0 0 24 24" - stroke-width="1.5" - class="mx-6 size-6 shrink-0 self-center stroke-red-500" - > - <path - stroke-linecap="round" - stroke-linejoin="round" - d="M4.5 12h15m0 0l-6.75-6.75M19.5 12l-6.75 6.75" - /> - </svg> - </a> - - <a - href="https://laracasts.com" - class="duration-250 flex scale-100 rounded-lg bg-white from-gray-700/50 via-transparent p-6 shadow-2xl shadow-gray-500/20 transition-all focus:outline focus:outline-2 focus:outline-red-500 motion-safe:hover:scale-[1.01] dark:bg-gray-800/50 dark:bg-gradient-to-bl dark:shadow-none dark:ring-1 dark:ring-inset dark:ring-white/5" - > - <div> - <div - class="flex size-16 items-center justify-center rounded-full bg-red-50 dark:bg-red-800/20" - > - <svg - xmlns="http://www.w3.org/2000/svg" - fill="none" - viewBox="0 0 24 24" - stroke-width="1.5" - class="size-7 stroke-red-500" - > - <path - stroke-linecap="round" - d="M15.75 10.5l4.72-4.72a.75.75 0 011.28.53v11.38a.75.75 0 01-1.28.53l-4.72-4.72M4.5 18.75h9a2.25 2.25 0 002.25-2.25v-9a2.25 2.25 0 00-2.25-2.25h-9A2.25 2.25 0 002.25 7.5v9a2.25 2.25 0 002.25 2.25z" - /> - </svg> - </div> - - <h2 class="mt-6 text-xl font-semibold text-gray-900 dark:text-white">Laracasts</h2> - - <p class="mt-4 text-sm leading-relaxed text-gray-500 dark:text-gray-400"> - Laracasts offers thousands of video tutorials on Laravel, PHP, and JavaScript - development. Check them out, see for yourself, and massively level up your development - skills in the process. - </p> - </div> - - <svg - xmlns="http://www.w3.org/2000/svg" - fill="none" - viewBox="0 0 24 24" - stroke-width="1.5" - class="mx-6 size-6 shrink-0 self-center stroke-red-500" - > - <path - stroke-linecap="round" - stroke-linejoin="round" - d="M4.5 12h15m0 0l-6.75-6.75M19.5 12l-6.75 6.75" - /> - </svg> - </a> - - <a - href="https://laravel-news.com" - class="duration-250 flex scale-100 rounded-lg bg-white from-gray-700/50 via-transparent p-6 shadow-2xl shadow-gray-500/20 transition-all focus:outline focus:outline-2 focus:outline-red-500 motion-safe:hover:scale-[1.01] dark:bg-gray-800/50 dark:bg-gradient-to-bl dark:shadow-none dark:ring-1 dark:ring-inset dark:ring-white/5" - > - <div> - <div - class="flex size-16 items-center justify-center rounded-full bg-red-50 dark:bg-red-800/20" - > - <svg - xmlns="http://www.w3.org/2000/svg" - fill="none" - viewBox="0 0 24 24" - stroke-width="1.5" - class="size-7 stroke-red-500" - > - <path - stroke-linecap="round" - stroke-linejoin="round" - d="M12 7.5h1.5m-1.5 3h1.5m-7.5 3h7.5m-7.5 3h7.5m3-9h3.375c.621 0 1.125.504 1.125 1.125V18a2.25 2.25 0 01-2.25 2.25M16.5 7.5V18a2.25 2.25 0 002.25 2.25M16.5 7.5V4.875c0-.621-.504-1.125-1.125-1.125H4.125C3.504 3.75 3 4.254 3 4.875V18a2.25 2.25 0 002.25 2.25h13.5M6 7.5h3v3H6v-3z" - /> - </svg> - </div> - - <h2 class="mt-6 text-xl font-semibold text-gray-900 dark:text-white">Laravel News</h2> - - <p class="mt-4 text-sm leading-relaxed text-gray-500 dark:text-gray-400"> - Laravel News is a community driven portal and newsletter aggregating all of the latest - and most important news in the Laravel ecosystem, including new package releases and - tutorials. - </p> - </div> - - <svg - xmlns="http://www.w3.org/2000/svg" - fill="none" - viewBox="0 0 24 24" - stroke-width="1.5" - class="mx-6 size-6 shrink-0 self-center stroke-red-500" - > - <path - stroke-linecap="round" - stroke-linejoin="round" - d="M4.5 12h15m0 0l-6.75-6.75M19.5 12l-6.75 6.75" - /> - </svg> - </a> - - <div - class="duration-250 flex scale-100 rounded-lg bg-white from-gray-700/50 via-transparent p-6 shadow-2xl shadow-gray-500/20 transition-all focus:outline focus:outline-2 focus:outline-red-500 motion-safe:hover:scale-[1.01] dark:bg-gray-800/50 dark:bg-gradient-to-bl dark:shadow-none dark:ring-1 dark:ring-inset dark:ring-white/5" - > - <div> - <div - class="flex size-16 items-center justify-center rounded-full bg-red-50 dark:bg-red-800/20" - > - <svg - xmlns="http://www.w3.org/2000/svg" - fill="none" - viewBox="0 0 24 24" - stroke-width="1.5" - class="size-7 stroke-red-500" - > - <path - stroke-linecap="round" - stroke-linejoin="round" - d="M6.115 5.19l.319 1.913A6 6 0 008.11 10.36L9.75 12l-.387.775c-.217.433-.132.956.21 1.298l1.348 1.348c.21.21.329.497.329.795v1.089c0 .426.24.815.622 1.006l.153.076c.433.217.956.132 1.298-.21l.723-.723a8.7 8.7 0 002.288-4.042 1.087 1.087 0 00-.358-1.099l-1.33-1.108c-.251-.21-.582-.299-.905-.245l-1.17.195a1.125 1.125 0 01-.98-.314l-.295-.295a1.125 1.125 0 010-1.591l.13-.132a1.125 1.125 0 011.3-.21l.603.302a.809.809 0 001.086-1.086L14.25 7.5l1.256-.837a4.5 4.5 0 001.528-1.732l.146-.292M6.115 5.19A9 9 0 1017.18 4.64M6.115 5.19A8.965 8.965 0 0112 3c1.929 0 3.716.607 5.18 1.64" - /> - </svg> - </div> - - <h2 class="mt-6 text-xl font-semibold text-gray-900 dark:text-white"> - Vibrant Ecosystem - </h2> - - <p class="mt-4 text-sm leading-relaxed text-gray-500 dark:text-gray-400"> - Laravel's robust library of first-party tools and libraries, such as - <a - href="https://forge.laravel.com" - class="underline hover:text-gray-700 focus:rounded-sm focus:outline focus:outline-2 focus:outline-red-500 dark:hover:text-white" - >Forge</a>, - <a - href="https://vapor.laravel.com" - class="underline hover:text-gray-700 focus:rounded-sm focus:outline focus:outline-2 focus:outline-red-500 dark:hover:text-white" - >Vapor</a>, - <a - href="https://nova.laravel.com" - class="underline hover:text-gray-700 focus:rounded-sm focus:outline focus:outline-2 focus:outline-red-500 dark:hover:text-white" - >Nova</a>, and - <a - href="https://envoyer.io" - class="underline hover:text-gray-700 focus:rounded-sm focus:outline focus:outline-2 focus:outline-red-500 dark:hover:text-white" - >Envoyer</a> - help you take your projects to the next level. Pair them with powerful open source - libraries like - <a - href="https://laravel.com/docs/billing" - class="underline hover:text-gray-700 focus:rounded-sm focus:outline focus:outline-2 focus:outline-red-500 dark:hover:text-white" - >Cashier</a>, - <a - href="https://laravel.com/docs/dusk" - class="underline hover:text-gray-700 focus:rounded-sm focus:outline focus:outline-2 focus:outline-red-500 dark:hover:text-white" - >Dusk</a>, - <a - href="https://laravel.com/docs/broadcasting" - class="underline hover:text-gray-700 focus:rounded-sm focus:outline focus:outline-2 focus:outline-red-500 dark:hover:text-white" - >Echo</a>, - <a - href="https://laravel.com/docs/horizon" - class="underline hover:text-gray-700 focus:rounded-sm focus:outline focus:outline-2 focus:outline-red-500 dark:hover:text-white" - >Horizon</a>, - <a - href="https://laravel.com/docs/sanctum" - class="underline hover:text-gray-700 focus:rounded-sm focus:outline focus:outline-2 focus:outline-red-500 dark:hover:text-white" - >Sanctum</a>, - <a - href="https://laravel.com/docs/telescope" - class="underline hover:text-gray-700 focus:rounded-sm focus:outline focus:outline-2 focus:outline-red-500 dark:hover:text-white" - >Telescope</a>, and more. - </p> - </div> - </div> - </div> - </div> - - <div class="mt-16 flex justify-center px-6 sm:items-center sm:justify-between"> - <div class="text-center text-sm text-gray-500 dark:text-gray-400 sm:text-left"> - <div class="flex items-center gap-4"> - <a - href="https://github.com/sponsors/taylorotwell" - class="group inline-flex items-center hover:text-gray-700 focus:rounded-sm focus:outline focus:outline-2 focus:outline-red-500 dark:hover:text-white" - > - <svg - xmlns="http://www.w3.org/2000/svg" - fill="none" - viewBox="0 0 24 24" - stroke-width="1.5" - class="-mt-px mr-1 size-5 stroke-gray-400 group-hover:stroke-gray-600 dark:stroke-gray-600 dark:group-hover:stroke-gray-400" - > - <path - stroke-linecap="round" - stroke-linejoin="round" - d="M21 8.25c0-2.485-2.099-4.5-4.688-4.5-1.935 0-3.597 1.126-4.312 2.733-.715-1.607-2.377-2.733-4.313-2.733C5.1 3.75 3 5.765 3 8.25c0 7.22 9 12 9 12s9-4.78 9-12z" - /> - </svg> - Sponsor - </a> - </div> - </div> - - <div class="ml-4 text-center text-sm text-gray-500 dark:text-gray-400 sm:ml-0 sm:text-right"> - Laravel - </div> - </div> - </div> - </div> + <a href="/auth/login">Login</a><br> + <a href="/auth/register">Register</a> </template> - -<style> -.bg-dots-darker { - background-image: url("data:image/svg+xml,%3Csvg width='30' height='30' viewBox='0 0 30 30' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1.22676 0C1.91374 0 2.45351 0.539773 2.45351 1.22676C2.45351 1.91374 1.91374 2.45351 1.22676 2.45351C0.539773 2.45351 0 1.91374 0 1.22676C0 0.539773 0.539773 0 1.22676 0Z' fill='rgba(0,0,0,0.07)'/%3E%3C/svg%3E"); -} -@media (prefers-color-scheme: dark) { - .dark\:bg-dots-lighter { - background-image: url("data:image/svg+xml,%3Csvg width='30' height='30' viewBox='0 0 30 30' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1.22676 0C1.91374 0 2.45351 0.539773 2.45351 1.22676C2.45351 1.91374 1.91374 2.45351 1.22676 2.45351C0.539773 2.45351 0 1.91374 0 1.22676C0 0.539773 0.539773 0 1.22676 0Z' fill='rgba(255,255,255,0.07)'/%3E%3C/svg%3E"); - } -} -</style> diff --git a/routes/web.php b/routes/web.php index 2301789d..6a7052ca 100644 --- a/routes/web.php +++ b/routes/web.php @@ -2,7 +2,15 @@ declare(strict_types=1); +use App\Http\Controllers\AuthenticateSessionController; +use App\Http\Controllers\ContestController; +use App\Http\Controllers\RegisterUserController; use Illuminate\Support\Facades\Route; use Inertia\Response; -Route::get("/", fn(): Response => inertia("Welcome")); +Route::get("/", [ContestController::class, "index"])->name("home"); +Route::get("/auth/register", [RegisterUserController::class, "create"])->name("register"); +Route::post("/auth/register", [RegisterUserController::class, "store"])->name("register"); +Route::get("/email/verify", fn(): Response => inertia("Auth/Verify-Email"))->middleware("auth")->name("verification.notice"); +Route::get("/auth/login", [AuthenticateSessionController::class, "create"])->name("login"); +Route::post("/auth/login", [AuthenticateSessionController::class, "authenticate"])->name("login"); From 687b8d3d1c199d864be06e1625b7a6a625b3a110 Mon Sep 17 00:00:00 2001 From: Dominik Prabucki <dominikprabucki.2002@gmail.com> Date: Fri, 9 Aug 2024 13:41:33 +0200 Subject: [PATCH 04/33] Fix code style --- .../AuthenticateSessionController.php | 13 ++++++------ resources/js/Pages/Welcome.vue | 14 ------------- tests/Feature/GuestCanRegister.php | 20 +++++++++++++++++++ 3 files changed, 27 insertions(+), 20 deletions(-) create mode 100644 tests/Feature/GuestCanRegister.php diff --git a/app/Http/Controllers/AuthenticateSessionController.php b/app/Http/Controllers/AuthenticateSessionController.php index f931e0c5..f1049bb9 100644 --- a/app/Http/Controllers/AuthenticateSessionController.php +++ b/app/Http/Controllers/AuthenticateSessionController.php @@ -1,5 +1,7 @@ <?php +declare(strict_types=1); + namespace App\Http\Controllers; use Illuminate\Http\RedirectResponse; @@ -17,20 +19,19 @@ public function create(): Response public function authenticate(Request $request): RedirectResponse { - $credentials = $request->validate([ - 'email' => 'required|email', - 'password' => 'required|string', + "email" => "required|email", + "password" => "required|string", ]); - if (auth()->attempt($credentials)) { $request->session()->regenerate(); - return Redirect::route("home")->with('succes'); + + return Redirect::route("home")->with("succes"); } return back()->withErrors([ - 'The provided mail or password is invalid, try again.', + "The provided mail or password is invalid, try again.", ]); } } diff --git a/resources/js/Pages/Welcome.vue b/resources/js/Pages/Welcome.vue index b08ff677..0a9c3838 100644 --- a/resources/js/Pages/Welcome.vue +++ b/resources/js/Pages/Welcome.vue @@ -1,18 +1,4 @@ <script setup> -import { reactive } from 'vue' -import { router } from '@inertiajs/vue3' - -const form = reactive({ - name: null, - surname: null, - email: null, - password: null, - school_id: null, -}) - -function submit() { - router.post('/auth/register', form) -} </script> <template> diff --git a/tests/Feature/GuestCanRegister.php b/tests/Feature/GuestCanRegister.php new file mode 100644 index 00000000..6f6c918a --- /dev/null +++ b/tests/Feature/GuestCanRegister.php @@ -0,0 +1,20 @@ +<?php + +declare(strict_types=1); + +namespace Tests\Feature; + +use Tests\TestCase; + +class GuestCanRegister extends TestCase +{ + /** + * A basic feature test example. + */ + public function testExample(): void + { + $response = $this->get("/"); + + $response->assertStatus(200); + } +} From 13ed6bf9eeb0dd2a296962f7df29456c007374b6 Mon Sep 17 00:00:00 2001 From: Dominik Prabucki <dominikprabucki.2002@gmail.com> Date: Fri, 9 Aug 2024 14:21:26 +0200 Subject: [PATCH 05/33] Add tests and SchoolFactory --- database/factories/SchoolFactory.php | 38 +++++++++++++++++++++ tests/Feature/AuthenticateSessionTest.php | 33 ++++++++++++++++++ tests/Feature/GuestCanRegister.php | 20 ----------- tests/Feature/RegisterUserTest.php | 41 +++++++++++++++++++++++ 4 files changed, 112 insertions(+), 20 deletions(-) create mode 100644 database/factories/SchoolFactory.php create mode 100644 tests/Feature/AuthenticateSessionTest.php delete mode 100644 tests/Feature/GuestCanRegister.php create mode 100644 tests/Feature/RegisterUserTest.php diff --git a/database/factories/SchoolFactory.php b/database/factories/SchoolFactory.php new file mode 100644 index 00000000..546831d1 --- /dev/null +++ b/database/factories/SchoolFactory.php @@ -0,0 +1,38 @@ +<?php + +declare(strict_types=1); + +namespace Database\Factories; + +use App\Models\School; +use Illuminate\Database\Eloquent\Factories\Factory; + +/** + * @extends Factory<School> + */ +class SchoolFactory extends Factory +{ + /** + * Define the model's default state. + * + * @return array<string, mixed> + */ + public function definition(): array + { + return [ + "name" => fake()->name(), + "city" => fake()->name(), + "street" => fake()->name(), + "building_number" => fake()->name(), + "apartment_number" => fake()->name(), + "zipCode" => fake()->name(), + ]; + } + + public function withoutApartment(): static + { + return $this->state(fn(array $attributes): array => [ + "apartment_number" => null, + ]); + } +} diff --git a/tests/Feature/AuthenticateSessionTest.php b/tests/Feature/AuthenticateSessionTest.php new file mode 100644 index 00000000..04e853a8 --- /dev/null +++ b/tests/Feature/AuthenticateSessionTest.php @@ -0,0 +1,33 @@ +<?php + +declare(strict_types=1); + +namespace Tests\Feature; + +use App\Models\School; +use App\Models\User; +use Illuminate\Foundation\Testing\RefreshDatabase; +use Tests\TestCase; + +class AuthenticateSessionTest extends TestCase +{ + use RefreshDatabase; + + /** + * A basic feature test example. + */ + public function testUserCanLogin(): void + { + $school = School::factory()->create(); + $user = User::factory()->create(["email" => "test@example.com", "password" => "1234567890"]); + + $this->post("/auth/login", [ + "email" => "test@example.com", + "password" => "123456890", + ])->assertRedirect("/"); + + $this->assertDatabaseHas("users", [ + "email" => "test@example.com", + ]); + } +} diff --git a/tests/Feature/GuestCanRegister.php b/tests/Feature/GuestCanRegister.php deleted file mode 100644 index 6f6c918a..00000000 --- a/tests/Feature/GuestCanRegister.php +++ /dev/null @@ -1,20 +0,0 @@ -<?php - -declare(strict_types=1); - -namespace Tests\Feature; - -use Tests\TestCase; - -class GuestCanRegister extends TestCase -{ - /** - * A basic feature test example. - */ - public function testExample(): void - { - $response = $this->get("/"); - - $response->assertStatus(200); - } -} diff --git a/tests/Feature/RegisterUserTest.php b/tests/Feature/RegisterUserTest.php new file mode 100644 index 00000000..977c3172 --- /dev/null +++ b/tests/Feature/RegisterUserTest.php @@ -0,0 +1,41 @@ +<?php + +declare(strict_types=1); + +namespace Tests\Feature; + +use App\Models\School; +use Illuminate\Foundation\Testing\RefreshDatabase; +use Tests\TestCase; + +class RegisterUserTest extends TestCase +{ + use RefreshDatabase; + + /** + * A basic feature test example. + */ + public function testUserCanRegister(): void + { + $school = School::factory()->create(); + + $this->post("/auth/register", [ + "name" => "Test", + "surname" => "Test", + "email" => "test@example.com", + "password" => "123456890", + "school_id" => $school->id, + ])->assertRedirect("/"); + + $this->assertDatabaseHas("users", [ + "name" => "Test", + "surname" => "Test", + "email" => "test@example.com", + "school_id" => $school->id, + ]); + + $this->assertDatabaseMissing("users", [ + "password" => "123456890", + ]); + } +} From a12b8334dc29741831cb83688c8449f287af2f99 Mon Sep 17 00:00:00 2001 From: Dominik Prabucki <dominikprabucki.2002@gmail.com> Date: Mon, 12 Aug 2024 10:37:08 +0200 Subject: [PATCH 06/33] Mail Verification --- .../Controllers/RegisterUserController.php | 4 +- app/Mail/RegistrationMail.php | 54 +++++++++++++++++++ app/Models/User.php | 3 +- resources/views/emails/verify-email.php | 1 + routes/web.php | 9 +++- 5 files changed, 68 insertions(+), 3 deletions(-) create mode 100644 app/Mail/RegistrationMail.php create mode 100644 resources/views/emails/verify-email.php diff --git a/app/Http/Controllers/RegisterUserController.php b/app/Http/Controllers/RegisterUserController.php index 50ebd407..25d72566 100644 --- a/app/Http/Controllers/RegisterUserController.php +++ b/app/Http/Controllers/RegisterUserController.php @@ -5,10 +5,12 @@ namespace App\Http\Controllers; use App\Http\Requests\Auth\RegisterUserRequest; +use App\Mail\RegistrationMail; use App\Models\User; use Illuminate\Auth\Events\Registered; use Illuminate\Http\RedirectResponse; use Illuminate\Support\Facades\Hash; +use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Redirect; use Inertia\Inertia; use Inertia\Response; @@ -27,7 +29,7 @@ public function store(RegisterUserRequest $request): RedirectResponse $user->save(); event(new Registered($user)); - + Mail::to($user->email)->send(new RegistrationMail()); return Redirect::route("home"); } } diff --git a/app/Mail/RegistrationMail.php b/app/Mail/RegistrationMail.php new file mode 100644 index 00000000..2a599df8 --- /dev/null +++ b/app/Mail/RegistrationMail.php @@ -0,0 +1,54 @@ +<?php + +namespace App\Mail; + +use Illuminate\Bus\Queueable; +use Illuminate\Mail\Mailable; +use Illuminate\Mail\Mailables\Attachment; +use Illuminate\Mail\Mailables\Content; +use Illuminate\Mail\Mailables\Envelope; +use Illuminate\Queue\SerializesModels; +use Illuminate\Support\Facades\Mail; + +class RegistrationMail extends Mailable +{ + use Queueable, SerializesModels; + + /** + * Create a new message instance. + */ + public function __construct() + { + // + } + + /** + * Get the message envelope. + */ + public function envelope(): Envelope + { + return new Envelope( + subject: 'Registration Mail', + ); + } + + /** + * Get the message content definition. + */ + public function content(): Content + { + return new Content( + view: 'emails.verify-email', + ); + } + + /** + * Get the attachments for the message. + * + * @return array<int, Attachment> + */ + public function attachments(): array + { + return []; + } +} diff --git a/app/Models/User.php b/app/Models/User.php index c9570fb1..89374b86 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -5,6 +5,7 @@ namespace App\Models; use Carbon\Carbon; +use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Foundation\Auth\User as Authenticatable; @@ -24,7 +25,7 @@ * * @property School $school */ -class User extends Authenticatable +class User extends Authenticatable implements MustVerifyEmail { use HasApiTokens; use HasFactory; diff --git a/resources/views/emails/verify-email.php b/resources/views/emails/verify-email.php new file mode 100644 index 00000000..fa3a9bb8 --- /dev/null +++ b/resources/views/emails/verify-email.php @@ -0,0 +1 @@ +Weryfikacja Maila diff --git a/routes/web.php b/routes/web.php index 6a7052ca..e0e43c39 100644 --- a/routes/web.php +++ b/routes/web.php @@ -5,12 +5,19 @@ use App\Http\Controllers\AuthenticateSessionController; use App\Http\Controllers\ContestController; use App\Http\Controllers\RegisterUserController; +use Illuminate\Foundation\Auth\EmailVerificationRequest; +use Illuminate\Support\Facades\Redirect; use Illuminate\Support\Facades\Route; use Inertia\Response; Route::get("/", [ContestController::class, "index"])->name("home"); Route::get("/auth/register", [RegisterUserController::class, "create"])->name("register"); Route::post("/auth/register", [RegisterUserController::class, "store"])->name("register"); -Route::get("/email/verify", fn(): Response => inertia("Auth/Verify-Email"))->middleware("auth")->name("verification.notice"); Route::get("/auth/login", [AuthenticateSessionController::class, "create"])->name("login"); Route::post("/auth/login", [AuthenticateSessionController::class, "authenticate"])->name("login"); +Route::get("/email/verify", fn(): Response => inertia("Auth/Verify-Email"))->middleware("auth")->name("verification.notice"); +Route::get("/email/verify/{id}/{hash}", function (EmailVerificationRequest $request) { + $request->fulfill(); + + return Redirect::route("home"); +})->middleware(["auth", "signed"])->name("verification.verify"); From 5370de153dffc7dace1e914717dbd6e9b87b5659 Mon Sep 17 00:00:00 2001 From: Dominik Prabucki <dominikprabucki.2002@gmail.com> Date: Mon, 12 Aug 2024 10:37:19 +0200 Subject: [PATCH 07/33] Test Mail --- app/Mail/TestMail.php | 53 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 app/Mail/TestMail.php diff --git a/app/Mail/TestMail.php b/app/Mail/TestMail.php new file mode 100644 index 00000000..7d062edb --- /dev/null +++ b/app/Mail/TestMail.php @@ -0,0 +1,53 @@ +<?php + +namespace App\Mail; + +use Illuminate\Bus\Queueable; +use Illuminate\Contracts\Queue\ShouldQueue; +use Illuminate\Mail\Mailable; +use Illuminate\Mail\Mailables\Content; +use Illuminate\Mail\Mailables\Envelope; +use Illuminate\Queue\SerializesModels; + +class TestMail extends Mailable +{ + use Queueable, SerializesModels; + + /** + * Create a new message instance. + */ + public function __construct() + { + // + } + + /** + * Get the message envelope. + */ + public function envelope(): Envelope + { + return new Envelope( + subject: 'Test Mail', + ); + } + + /** + * Get the message content definition. + */ + public function content(): Content + { + return new Content( + view: 'emails.verify-email', + ); + } + + /** + * Get the attachments for the message. + * + * @return array<int, \Illuminate\Mail\Mailables\Attachment> + */ + public function attachments(): array + { + return []; + } +} From 89ab83216ce3a27bbefbfbf381b9f5f3aa61b3a6 Mon Sep 17 00:00:00 2001 From: Dominik Prabucki <dominikprabucki.2002@gmail.com> Date: Tue, 13 Aug 2024 07:46:01 +0200 Subject: [PATCH 08/33] Add EmailVerifyController Add PasswordResetLinkController --- .../Controllers/EmailVerifyController.php | 23 ++++++++ .../PasswordResetLinkController.php | 34 ++++++++++++ app/Mail/RegistrationMail.php | 15 +++--- app/Mail/TestMail.php | 53 ------------------- routes/web.php | 16 +++--- 5 files changed, 71 insertions(+), 70 deletions(-) create mode 100644 app/Http/Controllers/EmailVerifyController.php create mode 100644 app/Http/Controllers/PasswordResetLinkController.php delete mode 100644 app/Mail/TestMail.php diff --git a/app/Http/Controllers/EmailVerifyController.php b/app/Http/Controllers/EmailVerifyController.php new file mode 100644 index 00000000..6cdfba0d --- /dev/null +++ b/app/Http/Controllers/EmailVerifyController.php @@ -0,0 +1,23 @@ +<?php + +namespace App\Http\Controllers; + +use Illuminate\Foundation\Auth\EmailVerificationRequest; +use Illuminate\Http\RedirectResponse; +use Illuminate\Support\Facades\Redirect; +use Inertia\Inertia; +use Inertia\Response; + +class EmailVerifyController extends Controller +{ + public function create(): Response + { + return Inertia::render('Auth/Verify-Email'); + } + + public function verify(EmailVerificationRequest $request): RedirectResponse + { + $request->fulfill(); + return Redirect::route('home'); + } +} diff --git a/app/Http/Controllers/PasswordResetLinkController.php b/app/Http/Controllers/PasswordResetLinkController.php new file mode 100644 index 00000000..78fad80a --- /dev/null +++ b/app/Http/Controllers/PasswordResetLinkController.php @@ -0,0 +1,34 @@ +<?php + +declare(strict_types=1); + +namespace App\Http\Controllers; + +use Illuminate\Http\RedirectResponse; +use Illuminate\Http\Request; +use Illuminate\Support\Facades\Password; +use Inertia\Inertia; +use Inertia\Response; + +class PasswordResetLinkController extends Controller +{ + public function create(): Response + { + return Inertia::render("Auth/Forgot-Password"); + } + + public function store(Request $request): RedirectResponse + { + $request->validate([ + "email" => "required|string|email", + ]); + + $status = Password::sendResetLink( + $request->only("email"), + ); + + return $status === Password::RESET_LINK_SENT + ? back()->with(["status" => __($status)]) + : back()->withErrors(["email" => __($status)]); + } +} diff --git a/app/Mail/RegistrationMail.php b/app/Mail/RegistrationMail.php index 2a599df8..071b44cc 100644 --- a/app/Mail/RegistrationMail.php +++ b/app/Mail/RegistrationMail.php @@ -1,5 +1,7 @@ <?php +declare(strict_types=1); + namespace App\Mail; use Illuminate\Bus\Queueable; @@ -8,19 +10,16 @@ use Illuminate\Mail\Mailables\Content; use Illuminate\Mail\Mailables\Envelope; use Illuminate\Queue\SerializesModels; -use Illuminate\Support\Facades\Mail; class RegistrationMail extends Mailable { - use Queueable, SerializesModels; + use Queueable; + use SerializesModels; /** * Create a new message instance. */ - public function __construct() - { - // - } + public function __construct() {} /** * Get the message envelope. @@ -28,7 +27,7 @@ public function __construct() public function envelope(): Envelope { return new Envelope( - subject: 'Registration Mail', + subject: "Registration Mail", ); } @@ -38,7 +37,7 @@ public function envelope(): Envelope public function content(): Content { return new Content( - view: 'emails.verify-email', + view: "emails.verify-email", ); } diff --git a/app/Mail/TestMail.php b/app/Mail/TestMail.php deleted file mode 100644 index 7d062edb..00000000 --- a/app/Mail/TestMail.php +++ /dev/null @@ -1,53 +0,0 @@ -<?php - -namespace App\Mail; - -use Illuminate\Bus\Queueable; -use Illuminate\Contracts\Queue\ShouldQueue; -use Illuminate\Mail\Mailable; -use Illuminate\Mail\Mailables\Content; -use Illuminate\Mail\Mailables\Envelope; -use Illuminate\Queue\SerializesModels; - -class TestMail extends Mailable -{ - use Queueable, SerializesModels; - - /** - * Create a new message instance. - */ - public function __construct() - { - // - } - - /** - * Get the message envelope. - */ - public function envelope(): Envelope - { - return new Envelope( - subject: 'Test Mail', - ); - } - - /** - * Get the message content definition. - */ - public function content(): Content - { - return new Content( - view: 'emails.verify-email', - ); - } - - /** - * Get the attachments for the message. - * - * @return array<int, \Illuminate\Mail\Mailables\Attachment> - */ - public function attachments(): array - { - return []; - } -} diff --git a/routes/web.php b/routes/web.php index e0e43c39..a53fbb11 100644 --- a/routes/web.php +++ b/routes/web.php @@ -4,20 +4,18 @@ use App\Http\Controllers\AuthenticateSessionController; use App\Http\Controllers\ContestController; +use App\Http\Controllers\EmailVerifyController; +use App\Http\Controllers\PasswordResetLinkController; use App\Http\Controllers\RegisterUserController; -use Illuminate\Foundation\Auth\EmailVerificationRequest; -use Illuminate\Support\Facades\Redirect; use Illuminate\Support\Facades\Route; -use Inertia\Response; Route::get("/", [ContestController::class, "index"])->name("home"); Route::get("/auth/register", [RegisterUserController::class, "create"])->name("register"); Route::post("/auth/register", [RegisterUserController::class, "store"])->name("register"); Route::get("/auth/login", [AuthenticateSessionController::class, "create"])->name("login"); Route::post("/auth/login", [AuthenticateSessionController::class, "authenticate"])->name("login"); -Route::get("/email/verify", fn(): Response => inertia("Auth/Verify-Email"))->middleware("auth")->name("verification.notice"); -Route::get("/email/verify/{id}/{hash}", function (EmailVerificationRequest $request) { - $request->fulfill(); - - return Redirect::route("home"); -})->middleware(["auth", "signed"])->name("verification.verify"); +Route::get("/auth/logout", [AuthenticateSessionController::class, "logout"])->name("logout"); +Route::get("/email/verify", [EmailVerifyController::class, "create"])->middleware("auth")->name("verification.notice"); +Route::get("/email/verify/{id}/{hash}", [EmailVerifyController::class, "verify"])->name("verification.verify"); +Route::get("/auth/forgot-password", [PasswordResetLinkController::class, "create"])->middleware("guest")->name("password.request"); +Route::post("/auth/forgot-password", [PasswordResetLinkController::class, "store"])->middleware("guest")->name("password.email"); From 39ef29ed99ceb261889d4a73f77c2900d09f79e2 Mon Sep 17 00:00:00 2001 From: Dominik Prabucki <dominikprabucki.2002@gmail.com> Date: Tue, 13 Aug 2024 07:46:34 +0200 Subject: [PATCH 09/33] Improve DatabaseSeeder and Factories --- app/Models/User.php | 6 ++---- database/factories/UserFactory.php | 9 ++++++--- database/seeders/DatabaseSeeder.php | 20 ++------------------ 3 files changed, 10 insertions(+), 25 deletions(-) diff --git a/app/Models/User.php b/app/Models/User.php index 89374b86..37b579a5 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -5,12 +5,12 @@ namespace App\Models; use Carbon\Carbon; +use Illuminate\Contracts\Auth\CanResetPassword; use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; -use Laravel\Sanctum\HasApiTokens; /** * @property int $id @@ -25,9 +25,8 @@ * * @property School $school */ -class User extends Authenticatable implements MustVerifyEmail +class User extends Authenticatable implements MustVerifyEmail, CanResetPassword { - use HasApiTokens; use HasFactory; use Notifiable; @@ -35,7 +34,6 @@ class User extends Authenticatable implements MustVerifyEmail "name", "surname", "email", - "password", "school_id", ]; protected $hidden = [ diff --git a/database/factories/UserFactory.php b/database/factories/UserFactory.php index 04e5af3f..8f05917c 100644 --- a/database/factories/UserFactory.php +++ b/database/factories/UserFactory.php @@ -4,8 +4,11 @@ namespace Database\Factories; +use App\Models\School; use App\Models\User; +use Carbon\Carbon; use Illuminate\Database\Eloquent\Factories\Factory; +use Illuminate\Support\Facades\Hash; use Illuminate\Support\Str; /** @@ -19,10 +22,10 @@ public function definition(): array "name" => fake()->name(), "surname" => fake()->name(), "email" => fake()->unique()->safeEmail(), - "email_verified_at" => now(), - "password" => "$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi", + "email_verified_at" => Carbon::now(), + "password" => Hash::make('password'), "remember_token" => Str::random(10), - "school_id" => 1, + "school_id" => School::factory(), ]; } diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 388241c7..cb574698 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -4,29 +4,13 @@ namespace Database\Seeders; +use App\Models\User; use Illuminate\Database\Seeder; -use Illuminate\Support\Facades\DB; -use Illuminate\Support\Facades\Hash; -use Illuminate\Support\Str; class DatabaseSeeder extends Seeder { public function run(): void { - DB::table("schools")->insert([ - "name" => Str::random(10), - "city" => Str::random(10), - "street" => Str::random(10), - "building_number" => Str::random(10), - "zipCode" => Str::random(10), - ]); - - DB::table("users")->insert([ - "name" => Str::random(10), - "surname" => Str::random(10), - "email" => Str::random(10) . "@example.com", - "password" => Hash::make("password"), - "school_id" => 1, - ]); + User::factory()->create(); } } From f5c1e6be8d53ff9c92b17fb416668683e2fb3a9c Mon Sep 17 00:00:00 2001 From: Dominik Prabucki <dominikprabucki.2002@gmail.com> Date: Tue, 13 Aug 2024 07:47:25 +0200 Subject: [PATCH 10/33] Fix RegisterUserController Add and improve tests --- .../AuthenticateSessionController.php | 20 +++++++++-- .../Controllers/RegisterUserController.php | 6 ++-- tests/Feature/AuthenticateSessionTest.php | 35 ++++++++++++++++--- tests/Feature/RegisterUserTest.php | 30 ++++++++++++++++ 4 files changed, 79 insertions(+), 12 deletions(-) diff --git a/app/Http/Controllers/AuthenticateSessionController.php b/app/Http/Controllers/AuthenticateSessionController.php index f1049bb9..d46782f9 100644 --- a/app/Http/Controllers/AuthenticateSessionController.php +++ b/app/Http/Controllers/AuthenticateSessionController.php @@ -7,6 +7,7 @@ use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Support\Facades\Redirect; +use Illuminate\Validation\ValidationException; use Inertia\Inertia; use Inertia\Response; @@ -17,6 +18,9 @@ public function create(): Response return Inertia::render("Auth/Login"); } + /** + * @throws ValidationException + */ public function authenticate(Request $request): RedirectResponse { $credentials = $request->validate([ @@ -27,11 +31,21 @@ public function authenticate(Request $request): RedirectResponse if (auth()->attempt($credentials)) { $request->session()->regenerate(); - return Redirect::route("home")->with("succes"); + return Redirect::route("home")->with("success"); } - return back()->withErrors([ - "The provided mail or password is invalid, try again.", + throw ValidationException::withMessages([ + "email" => "Wrong email or password", ]); } + + public function logout(): RedirectResponse + { + auth()->logout(); + + request()->session()->invalidate(); + request()->session()->regenerateToken(); + + return Redirect::route("home")->with("succes"); + } } diff --git a/app/Http/Controllers/RegisterUserController.php b/app/Http/Controllers/RegisterUserController.php index 25d72566..7162b140 100644 --- a/app/Http/Controllers/RegisterUserController.php +++ b/app/Http/Controllers/RegisterUserController.php @@ -5,12 +5,10 @@ namespace App\Http\Controllers; use App\Http\Requests\Auth\RegisterUserRequest; -use App\Mail\RegistrationMail; use App\Models\User; use Illuminate\Auth\Events\Registered; use Illuminate\Http\RedirectResponse; use Illuminate\Support\Facades\Hash; -use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Redirect; use Inertia\Inertia; use Inertia\Response; @@ -25,11 +23,11 @@ public function create(): Response public function store(RegisterUserRequest $request): RedirectResponse { $user = new User($request->validated()); - $user->password = Hash::make($request->getPassword()); + $user->password = Hash::make($request->password); $user->save(); event(new Registered($user)); - Mail::to($user->email)->send(new RegistrationMail()); + return Redirect::route("home"); } } diff --git a/tests/Feature/AuthenticateSessionTest.php b/tests/Feature/AuthenticateSessionTest.php index 04e853a8..ffb78de0 100644 --- a/tests/Feature/AuthenticateSessionTest.php +++ b/tests/Feature/AuthenticateSessionTest.php @@ -18,16 +18,41 @@ class AuthenticateSessionTest extends TestCase */ public function testUserCanLogin(): void { - $school = School::factory()->create(); - $user = User::factory()->create(["email" => "test@example.com", "password" => "1234567890"]); + $user = User::factory()->create(["email" => "test@example.com", "password" => "goodPassword"]); $this->post("/auth/login", [ "email" => "test@example.com", - "password" => "123456890", + "password" => "goodPassword", ])->assertRedirect("/"); + } + + public function testUserCanNotLoginWithWrongPassword(): void + { + $user = User::factory()->create(["email" => "test@example.com", "password" => "goodPassword"]); - $this->assertDatabaseHas("users", [ + $this->from('/test')->post("/auth/login", [ "email" => "test@example.com", - ]); + "password" => "wrongPasswordExample", + ])->assertRedirect("/test")->assertSessionHasErrors(["email" => "Wrong email or password"]); + } + + public function testUserCanNotLoginWithWrongEmail(): void + { + $user = User::factory()->create(["email" => "test@example.com", "password" => "goodPassword"]); + + $this->from('/test')->post("/auth/login", [ + "email" => "test", + "password" => "wrongPasswordExample", + ])->assertRedirect("/test")->assertSessionHasErrors(["email" => "The email field must be a valid email address."]); + } + + public function testUserCanNotLoginWithEmptyEmailAndPassword(): void + { + $user = User::factory()->create(["email" => "test@example.com", "password" => "goodPassword"]); + + $this->from('/test')->post("/auth/login", [ + "email" => null, + "password" => null, + ])->assertRedirect("/test")->assertSessionHasErrors(["email" => "The email field is required.", "password" => "The password field is required."]); } } diff --git a/tests/Feature/RegisterUserTest.php b/tests/Feature/RegisterUserTest.php index 977c3172..169ca6a8 100644 --- a/tests/Feature/RegisterUserTest.php +++ b/tests/Feature/RegisterUserTest.php @@ -5,6 +5,7 @@ namespace Tests\Feature; use App\Models\School; +use App\Models\User; use Illuminate\Foundation\Testing\RefreshDatabase; use Tests\TestCase; @@ -38,4 +39,33 @@ public function testUserCanRegister(): void "password" => "123456890", ]); } + + public function testUserCanNotRegisterWithAlreadyTakenEmail(): void + { + $school = School::factory()->create(); + $user = User::factory()->create(); + + $this->post("/auth/register", [ + "name" => "Test", + "surname" => "Test", + "email" => $user->email, + "password" => "123456890", + "school_id" => $school->id, + ])->assertRedirect("/")->assertSessionHasErrors(["email" => "The email has already been taken."]); + + } + + public function testUserCanNotRegisterWithWrongSchool(): void + { + $school = School::factory()->create(); + + $this->post("/auth/register", [ + "name" => "Test", + "surname" => "Test", + "email" => "test@example.com", + "password" => "123456890", + "school_id" => $school->id+99999, + ])->assertRedirect("/")->assertSessionHasErrors(["school_id" => "The selected school id is invalid."]); + + } } From 47dddf266a6c58250b54d3e26a933b2aa91f22ed Mon Sep 17 00:00:00 2001 From: Dominik Prabucki <dominikprabucki.2002@gmail.com> Date: Tue, 13 Aug 2024 07:47:38 +0200 Subject: [PATCH 11/33] Increase bcrypt round to 12 --- phpunit.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpunit.xml b/phpunit.xml index fe6dd827..b5ee5401 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -20,7 +20,7 @@ <php> <env name="APP_ENV" value="testing"/> <env name="APP_KEY" value="base64:SKEJSy9oF9chQBCMbxqgj5zhtAvug9kwZ+cDiP1Y8A8="/> - <env name="BCRYPT_ROUNDS" value="4"/> + <env name="BCRYPT_ROUNDS" value="12"/> <env name="CACHE_DRIVER" value="array"/> <env name="DB_HOST" value="interns2024b-db-dev"/> <env name="DB_DATABASE" value="interns2024b-test"/> From ac652575058735f6724b7862fda9f97ef24287fb Mon Sep 17 00:00:00 2001 From: Dominik Prabucki <dominikprabucki.2002@gmail.com> Date: Tue, 13 Aug 2024 07:47:57 +0200 Subject: [PATCH 12/33] Add .vue for Forgot-Password --- resources/js/Pages/Auth/Forgot-Password.vue | 21 +++++++++++++++++++++ resources/js/Pages/Auth/Login.vue | 2 ++ 2 files changed, 23 insertions(+) create mode 100644 resources/js/Pages/Auth/Forgot-Password.vue diff --git a/resources/js/Pages/Auth/Forgot-Password.vue b/resources/js/Pages/Auth/Forgot-Password.vue new file mode 100644 index 00000000..92751ccb --- /dev/null +++ b/resources/js/Pages/Auth/Forgot-Password.vue @@ -0,0 +1,21 @@ +<script setup> +import { reactive } from 'vue' +import { router } from '@inertiajs/vue3' + +const form = reactive({ + email: null, + +}) + +function submit() { + router.post('/auth/forget-password', form) +} +</script> + +<template> + <form @submit.prevent="submit"> + <label for="email">Email:</label> + <input v-model="form.email" name="email" type="email"> + <button type="submit">Reset?</button> + </form> +</template> diff --git a/resources/js/Pages/Auth/Login.vue b/resources/js/Pages/Auth/Login.vue index 4541407c..9493fd69 100644 --- a/resources/js/Pages/Auth/Login.vue +++ b/resources/js/Pages/Auth/Login.vue @@ -20,4 +20,6 @@ function submit() { <input v-model="form.password" name="password" type="password"> <button type="submit">Login</button> </form> + + <a href="/auth/forgot-password">I forgot the password :(</a> </template> From 3fdd083acb58c7a0d385d21d6fa82318c2c8cfc0 Mon Sep 17 00:00:00 2001 From: Dominik Prabucki <dominikprabucki.2002@gmail.com> Date: Tue, 13 Aug 2024 07:48:51 +0200 Subject: [PATCH 13/33] Code style fix --- app/Http/Controllers/EmailVerifyController.php | 7 +++++-- database/factories/UserFactory.php | 2 +- tests/Feature/AuthenticateSessionTest.php | 7 +++---- tests/Feature/RegisterUserTest.php | 4 +--- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app/Http/Controllers/EmailVerifyController.php b/app/Http/Controllers/EmailVerifyController.php index 6cdfba0d..1fa77230 100644 --- a/app/Http/Controllers/EmailVerifyController.php +++ b/app/Http/Controllers/EmailVerifyController.php @@ -1,5 +1,7 @@ <?php +declare(strict_types=1); + namespace App\Http\Controllers; use Illuminate\Foundation\Auth\EmailVerificationRequest; @@ -12,12 +14,13 @@ class EmailVerifyController extends Controller { public function create(): Response { - return Inertia::render('Auth/Verify-Email'); + return Inertia::render("Auth/Verify-Email"); } public function verify(EmailVerificationRequest $request): RedirectResponse { $request->fulfill(); - return Redirect::route('home'); + + return Redirect::route("home"); } } diff --git a/database/factories/UserFactory.php b/database/factories/UserFactory.php index 8f05917c..c805c16a 100644 --- a/database/factories/UserFactory.php +++ b/database/factories/UserFactory.php @@ -23,7 +23,7 @@ public function definition(): array "surname" => fake()->name(), "email" => fake()->unique()->safeEmail(), "email_verified_at" => Carbon::now(), - "password" => Hash::make('password'), + "password" => Hash::make("password"), "remember_token" => Str::random(10), "school_id" => School::factory(), ]; diff --git a/tests/Feature/AuthenticateSessionTest.php b/tests/Feature/AuthenticateSessionTest.php index ffb78de0..f2efe6ea 100644 --- a/tests/Feature/AuthenticateSessionTest.php +++ b/tests/Feature/AuthenticateSessionTest.php @@ -4,7 +4,6 @@ namespace Tests\Feature; -use App\Models\School; use App\Models\User; use Illuminate\Foundation\Testing\RefreshDatabase; use Tests\TestCase; @@ -30,7 +29,7 @@ public function testUserCanNotLoginWithWrongPassword(): void { $user = User::factory()->create(["email" => "test@example.com", "password" => "goodPassword"]); - $this->from('/test')->post("/auth/login", [ + $this->from("/test")->post("/auth/login", [ "email" => "test@example.com", "password" => "wrongPasswordExample", ])->assertRedirect("/test")->assertSessionHasErrors(["email" => "Wrong email or password"]); @@ -40,7 +39,7 @@ public function testUserCanNotLoginWithWrongEmail(): void { $user = User::factory()->create(["email" => "test@example.com", "password" => "goodPassword"]); - $this->from('/test')->post("/auth/login", [ + $this->from("/test")->post("/auth/login", [ "email" => "test", "password" => "wrongPasswordExample", ])->assertRedirect("/test")->assertSessionHasErrors(["email" => "The email field must be a valid email address."]); @@ -50,7 +49,7 @@ public function testUserCanNotLoginWithEmptyEmailAndPassword(): void { $user = User::factory()->create(["email" => "test@example.com", "password" => "goodPassword"]); - $this->from('/test')->post("/auth/login", [ + $this->from("/test")->post("/auth/login", [ "email" => null, "password" => null, ])->assertRedirect("/test")->assertSessionHasErrors(["email" => "The email field is required.", "password" => "The password field is required."]); diff --git a/tests/Feature/RegisterUserTest.php b/tests/Feature/RegisterUserTest.php index 169ca6a8..ef18735c 100644 --- a/tests/Feature/RegisterUserTest.php +++ b/tests/Feature/RegisterUserTest.php @@ -52,7 +52,6 @@ public function testUserCanNotRegisterWithAlreadyTakenEmail(): void "password" => "123456890", "school_id" => $school->id, ])->assertRedirect("/")->assertSessionHasErrors(["email" => "The email has already been taken."]); - } public function testUserCanNotRegisterWithWrongSchool(): void @@ -64,8 +63,7 @@ public function testUserCanNotRegisterWithWrongSchool(): void "surname" => "Test", "email" => "test@example.com", "password" => "123456890", - "school_id" => $school->id+99999, + "school_id" => $school->id + 99999, ])->assertRedirect("/")->assertSessionHasErrors(["school_id" => "The selected school id is invalid."]); - } } From 9e87e2c727a666a62bbb7b8218b6a236cd41e2bc Mon Sep 17 00:00:00 2001 From: Dominik Prabucki <dominikprabucki.2002@gmail.com> Date: Tue, 13 Aug 2024 10:19:58 +0200 Subject: [PATCH 14/33] Add reset password --- .../Controllers/EmailVerifyController.php | 14 ++++++-- .../PasswordResetLinkController.php | 35 +++++++++++++++++++ .../Controllers/RegisterUserController.php | 2 ++ resources/js/Pages/Auth/Forgot-Password.vue | 2 +- resources/js/Pages/Auth/Reset-Password.vue | 31 ++++++++++++++++ routes/web.php | 6 +++- 6 files changed, 85 insertions(+), 5 deletions(-) create mode 100644 resources/js/Pages/Auth/Reset-Password.vue diff --git a/app/Http/Controllers/EmailVerifyController.php b/app/Http/Controllers/EmailVerifyController.php index 1fa77230..f954d147 100644 --- a/app/Http/Controllers/EmailVerifyController.php +++ b/app/Http/Controllers/EmailVerifyController.php @@ -6,21 +6,29 @@ use Illuminate\Foundation\Auth\EmailVerificationRequest; use Illuminate\Http\RedirectResponse; +use Illuminate\Http\Request; use Illuminate\Support\Facades\Redirect; use Inertia\Inertia; use Inertia\Response; class EmailVerifyController extends Controller { + public function __invoke(EmailVerificationRequest $request): RedirectResponse + { + $request->fulfill(); + + return Redirect::route("home"); + } + public function create(): Response { return Inertia::render("Auth/Verify-Email"); } - public function verify(EmailVerificationRequest $request): RedirectResponse + public function send(Request $request): RedirectResponse { - $request->fulfill(); + $request->user()->sendEmailVerificationNotification(); - return Redirect::route("home"); + return back()->with("message", "Verification link sent!"); } } diff --git a/app/Http/Controllers/PasswordResetLinkController.php b/app/Http/Controllers/PasswordResetLinkController.php index 78fad80a..d2a1cd76 100644 --- a/app/Http/Controllers/PasswordResetLinkController.php +++ b/app/Http/Controllers/PasswordResetLinkController.php @@ -4,9 +4,13 @@ namespace App\Http\Controllers; +use App\Models\User; +use Illuminate\Auth\Events\PasswordReset; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Password; +use Illuminate\Support\Str; use Inertia\Inertia; use Inertia\Response; @@ -31,4 +35,35 @@ public function store(Request $request): RedirectResponse ? back()->with(["status" => __($status)]) : back()->withErrors(["email" => __($status)]); } + + public function resetCreate(string $token): Response + { + return Inertia::render("Auth/Reset-Password", ["token" => $token]); + } + + public function resetStore(Request $request): RedirectResponse + { + $request->validate([ + "token" => "required", + "email" => "required|email", + "password" => "required|min:8|confirmed", + ]); + + $status = Password::reset( + $request->only("email", "password", "password_confirmation", "token"), + function (User $user, string $password): void { + $user->forceFill([ + "password" => Hash::make($password), + ])->setRememberToken(Str::random(60)); + + $user->save(); + + event(new PasswordReset($user)); + }, + ); + + return $status === Password::PASSWORD_RESET + ? redirect()->route("login")->with("status", __($status)) + : back()->withErrors(["email" => [__($status)]]); + } } diff --git a/app/Http/Controllers/RegisterUserController.php b/app/Http/Controllers/RegisterUserController.php index 7162b140..e4112562 100644 --- a/app/Http/Controllers/RegisterUserController.php +++ b/app/Http/Controllers/RegisterUserController.php @@ -8,6 +8,7 @@ use App\Models\User; use Illuminate\Auth\Events\Registered; use Illuminate\Http\RedirectResponse; +use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Redirect; use Inertia\Inertia; @@ -27,6 +28,7 @@ public function store(RegisterUserRequest $request): RedirectResponse $user->save(); event(new Registered($user)); + Auth::login($user); return Redirect::route("home"); } diff --git a/resources/js/Pages/Auth/Forgot-Password.vue b/resources/js/Pages/Auth/Forgot-Password.vue index 92751ccb..60c87c59 100644 --- a/resources/js/Pages/Auth/Forgot-Password.vue +++ b/resources/js/Pages/Auth/Forgot-Password.vue @@ -8,7 +8,7 @@ const form = reactive({ }) function submit() { - router.post('/auth/forget-password', form) + router.post('/auth/forgot-password', form) } </script> diff --git a/resources/js/Pages/Auth/Reset-Password.vue b/resources/js/Pages/Auth/Reset-Password.vue new file mode 100644 index 00000000..1e065ef2 --- /dev/null +++ b/resources/js/Pages/Auth/Reset-Password.vue @@ -0,0 +1,31 @@ +<script setup lang="ts"> +import { reactive } from 'vue' +import { router } from '@inertiajs/vue3' + +const props = defineProps<{ token: string }>() + +const form = reactive({ + email: null, + password: null, + password_confirmation: null, + token: props.token, + +}) + +function submit() { + router.post('/auth/password/reset', form) +} +</script> + +<template> + <form @submit.prevent="submit"> + <label for="email">Email:</label> + <input v-model="form.email" name="email" type="email"><br> + <label for="email">Password:</label> + <input v-model="form.password" name="password" type="password" required><br> + <label for="email">Password Confirmation:</label> + <input v-model="form.password_confirmation" name="password_confirmation" type="password"><br> + <input v-model="form.token" name="token" type="hidden"> + <button type="submit">Change Password?</button> + </form> +</template> diff --git a/routes/web.php b/routes/web.php index a53fbb11..e04c3007 100644 --- a/routes/web.php +++ b/routes/web.php @@ -16,6 +16,10 @@ Route::post("/auth/login", [AuthenticateSessionController::class, "authenticate"])->name("login"); Route::get("/auth/logout", [AuthenticateSessionController::class, "logout"])->name("logout"); Route::get("/email/verify", [EmailVerifyController::class, "create"])->middleware("auth")->name("verification.notice"); -Route::get("/email/verify/{id}/{hash}", [EmailVerifyController::class, "verify"])->name("verification.verify"); +Route::get("/email/{id}/{hash}", EmailVerifyController::class)->middleware(["signed", "throttle:6,1"])->name("verification.verify"); +Route::post("email/verification-notification", [EmailVerifyController::class, "send"])->middleware("auth", "throttle:6,1")->name("verification.send"); + Route::get("/auth/forgot-password", [PasswordResetLinkController::class, "create"])->middleware("guest")->name("password.request"); Route::post("/auth/forgot-password", [PasswordResetLinkController::class, "store"])->middleware("guest")->name("password.email"); +Route::get("/auth/password/reset/{token}", [PasswordResetLinkController::class, "resetCreate"])->middleware("guest")->name("password.reset"); +Route::post("/auth/password/reset", [PasswordResetLinkController::class, "resetStore"])->name("password.update"); From 276e7f36a7c8f2bb1f998549d49246e7b640a4f5 Mon Sep 17 00:00:00 2001 From: Dominik Prabucki <dominikprabucki.2002@gmail.com> Date: Tue, 13 Aug 2024 11:09:21 +0200 Subject: [PATCH 15/33] Add tests for reset password --- tests/Feature/ForgotPasswordTest.php | 57 +++++++++++++ tests/Feature/ResetPasswordTest.php | 118 +++++++++++++++++++++++++++ 2 files changed, 175 insertions(+) create mode 100644 tests/Feature/ForgotPasswordTest.php create mode 100644 tests/Feature/ResetPasswordTest.php diff --git a/tests/Feature/ForgotPasswordTest.php b/tests/Feature/ForgotPasswordTest.php new file mode 100644 index 00000000..a9aa3f86 --- /dev/null +++ b/tests/Feature/ForgotPasswordTest.php @@ -0,0 +1,57 @@ +<?php + +declare(strict_types=1); + +namespace Feature; + +use App\Models\User; +use Illuminate\Auth\Notifications\ResetPassword; +use Illuminate\Foundation\Testing\RefreshDatabase; +use Illuminate\Support\Facades\Notification; +use JsonException; +use Tests\TestCase; + +class ForgotPasswordTest extends TestCase +{ + use RefreshDatabase; + + /** + * A basic feature test example. + * @throws JsonException + */ + public function testUserCanSendForgotPasswordRequest(): void + { + Notification::fake(); + $user = User::factory()->create(["email" => "test@example.com"]); + + $this->post("/auth/forgot-password", [ + "email" => "test@example.com", + ])->assertSessionHasNoErrors(); + + Notification::assertSentTo( + [$user], + ResetPassword::class, + ); + } + + public function testUserCanNotSendForgotPasswordRequestWithWrongEmail(): void + { + Notification::fake(); + $user = User::factory()->create(["email" => "test@example.com"]); + + $this->post("/auth/forgot-password", [ + "email" => "wrongTest@example.com", + ])->assertSessionHasErrors(["email" => "We can't find a user with that email address."]); + Notification::assertNothingSent(); + + $this->post("/auth/forgot-password", [ + "email" => "wrongTest", + ])->assertSessionHasErrors(["email" => "The email field must be a valid email address."]); + Notification::assertNothingSent(); + + $this->post("/auth/forgot-password", [ + "email" => null, + ])->assertSessionHasErrors(["email" => "The email field is required."]); + Notification::assertNothingSent(); + } +} diff --git a/tests/Feature/ResetPasswordTest.php b/tests/Feature/ResetPasswordTest.php new file mode 100644 index 00000000..d048bb90 --- /dev/null +++ b/tests/Feature/ResetPasswordTest.php @@ -0,0 +1,118 @@ +<?php + +declare(strict_types=1); + +namespace Feature; + +use App\Models\User; +use Illuminate\Auth\Events\PasswordReset; +use Illuminate\Foundation\Testing\RefreshDatabase; +use Illuminate\Support\Facades\Event; +use Illuminate\Support\Facades\Hash; +use Illuminate\Support\Facades\Password; +use Illuminate\Support\Str; +use Tests\TestCase; + +class ResetPasswordTest extends TestCase +{ + use RefreshDatabase; + + public function testUserCanResetPasswordWithValidToken(): void + { + Event::fake(); + + $user = User::factory()->create([ + "email" => "test@example.com", + "password" => Hash::make("oldPassword"), + ]); + + $token = Password::createToken($user); + + $response = $this->post("/auth/password/reset", [ + "token" => $token, + "email" => "test@example.com", + "password" => "newPassword", + "password_confirmation" => "newPassword", + ]); + + $response->assertRedirect("/auth/login"); + $response->assertSessionHas("status", trans(Password::PASSWORD_RESET)); + + $this->assertTrue(Hash::check("newPassword", $user->fresh()->password)); + + $loginResponse = $this->post("/auth/login", [ + "email" => "test@example.com", + "password" => "newPassword", + ]); + + $loginResponse->assertRedirect("/"); + $this->assertAuthenticated(); + Event::assertDispatched(PasswordReset::class); + } + + public function testUserCannotResetPasswordWithInvalidToken(): void + { + $user = User::factory()->create([ + "email" => "test@example.com", + "password" => Hash::make("oldPassword"), + ]); + + $invalidToken = Str::random(60); + + $response = $this->post("/auth/password/reset", [ + "token" => $invalidToken, + "email" => "test@example.com", + "password" => "newPassword", + "password_confirmation" => "newPassword", + ]); + + $response->assertRedirect("/"); + $response->assertSessionHasErrors(["email" => trans(Password::INVALID_TOKEN)]); + + $this->assertTrue(Hash::check("oldPassword", $user->fresh()->password)); + } + + public function testUserCannotResetPasswordWithInvalidEmail(): void + { + $user = User::factory()->create([ + "email" => "test@example.com", + "password" => Hash::make("oldPassword"), + ]); + + $token = Password::createToken($user); + + $response = $this->post("/auth/password/reset", [ + "token" => $token, + "email" => "wrong@example.com", + "password" => "newPassword", + "password_confirmation" => "newPassword", + ]); + + $response->assertRedirect(); + $response->assertSessionHasErrors(["email" => trans(Password::INVALID_USER)]); + + $this->assertTrue(Hash::check("oldPassword", $user->fresh()->password)); + } + + public function testUserCannotResetPasswordWithMismatchedPasswords(): void + { + $user = User::factory()->create([ + "email" => "test@example.com", + "password" => Hash::make("oldPassword"), + ]); + + $token = Password::createToken($user); + + $response = $this->post("/auth/password/reset", [ + "token" => $token, + "email" => "test@example.com", + "password" => "newPassword", + "password_confirmation" => "differentPassword", + ]); + + $response->assertRedirect(); + $response->assertSessionHasErrors(["password" => trans("validation.confirmed", ["attribute" => "password"])]); + + $this->assertTrue(Hash::check("oldPassword", $user->fresh()->password)); + } +} From f742250698ec76499732a7655f433defed5afa31 Mon Sep 17 00:00:00 2001 From: Dominik Prabucki <dominikprabucki.2002@gmail.com> Date: Tue, 13 Aug 2024 11:32:08 +0200 Subject: [PATCH 16/33] fix code style --- database/seeders/DatabaseSeeder.php | 2 +- routes/web.php | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 29baefc6..5d75acc6 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -4,9 +4,9 @@ namespace Database\Seeders; -use App\Models\User; use App\Models\Answer; use App\Models\Quiz; +use App\Models\User; use Illuminate\Database\Seeder; class DatabaseSeeder extends Seeder diff --git a/routes/web.php b/routes/web.php index ac4df511..afbf5b15 100644 --- a/routes/web.php +++ b/routes/web.php @@ -2,16 +2,16 @@ declare(strict_types=1); -use App\Http\Controllers\QuestionAnswerController; -use App\Http\Controllers\QuizController; -use App\Http\Controllers\QuizQuestionController; -use App\Models\Answer; -use App\Models\Question; use App\Http\Controllers\AuthenticateSessionController; use App\Http\Controllers\ContestController; use App\Http\Controllers\EmailVerifyController; use App\Http\Controllers\PasswordResetLinkController; +use App\Http\Controllers\QuestionAnswerController; +use App\Http\Controllers\QuizController; +use App\Http\Controllers\QuizQuestionController; use App\Http\Controllers\RegisterUserController; +use App\Models\Answer; +use App\Models\Question; use Illuminate\Support\Facades\Route; Route::get("/", [ContestController::class, "index"])->name("home"); From d8815a6f7408bb4cacfc4bc75b2c1edd2295a543 Mon Sep 17 00:00:00 2001 From: Dominik Prabucki <dominikprabucki.2002@gmail.com> Date: Wed, 14 Aug 2024 08:14:11 +0200 Subject: [PATCH 17/33] Add validation errors and messages Add: validation errors and messages Improve: Controllers with requests Tests --- .../AuthenticateSessionController.php | 11 +-- .../PasswordResetLinkController.php | 17 +--- .../Auth/AuthenticateSessionRequest.php | 43 +++++++++ .../Requests/Auth/ForgotPasswordRequest.php | 37 ++++++++ .../Requests/Auth/RegisterUserRequest.php | 39 ++++++-- .../Requests/Auth/ResetPasswordRequest.php | 47 ++++++++++ resources/js/Pages/Auth/Forgot-Password.vue | 13 ++- resources/js/Pages/Auth/Login.vue | 20 +++- resources/js/Pages/Auth/Register.vue | 41 ++++++--- resources/js/Pages/Auth/Reset-Password.vue | 34 +++++-- tests/Feature/AuthenticateSessionTest.php | 5 +- tests/Feature/ForgotPasswordTest.php | 11 +-- tests/Feature/RegisterUserTest.php | 92 ++++++++++++++++--- 13 files changed, 335 insertions(+), 75 deletions(-) create mode 100644 app/Http/Requests/Auth/AuthenticateSessionRequest.php create mode 100644 app/Http/Requests/Auth/ForgotPasswordRequest.php create mode 100644 app/Http/Requests/Auth/ResetPasswordRequest.php diff --git a/app/Http/Controllers/AuthenticateSessionController.php b/app/Http/Controllers/AuthenticateSessionController.php index d46782f9..831ba41e 100644 --- a/app/Http/Controllers/AuthenticateSessionController.php +++ b/app/Http/Controllers/AuthenticateSessionController.php @@ -4,8 +4,8 @@ namespace App\Http\Controllers; +use App\Http\Requests\Auth\AuthenticateSessionRequest; use Illuminate\Http\RedirectResponse; -use Illuminate\Http\Request; use Illuminate\Support\Facades\Redirect; use Illuminate\Validation\ValidationException; use Inertia\Inertia; @@ -21,12 +21,9 @@ public function create(): Response /** * @throws ValidationException */ - public function authenticate(Request $request): RedirectResponse + public function authenticate(AuthenticateSessionRequest $request): RedirectResponse { - $credentials = $request->validate([ - "email" => "required|email", - "password" => "required|string", - ]); + $credentials = $request->only("email", "password"); if (auth()->attempt($credentials)) { $request->session()->regenerate(); @@ -46,6 +43,6 @@ public function logout(): RedirectResponse request()->session()->invalidate(); request()->session()->regenerateToken(); - return Redirect::route("home")->with("succes"); + return Redirect::route("home")->with("success"); } } diff --git a/app/Http/Controllers/PasswordResetLinkController.php b/app/Http/Controllers/PasswordResetLinkController.php index d2a1cd76..31b46384 100644 --- a/app/Http/Controllers/PasswordResetLinkController.php +++ b/app/Http/Controllers/PasswordResetLinkController.php @@ -4,10 +4,11 @@ namespace App\Http\Controllers; +use App\Http\Requests\Auth\ForgotPasswordRequest; +use App\Http\Requests\Auth\ResetPasswordRequest; use App\Models\User; use Illuminate\Auth\Events\PasswordReset; use Illuminate\Http\RedirectResponse; -use Illuminate\Http\Request; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Password; use Illuminate\Support\Str; @@ -21,11 +22,9 @@ public function create(): Response return Inertia::render("Auth/Forgot-Password"); } - public function store(Request $request): RedirectResponse + public function store(ForgotPasswordRequest $request): RedirectResponse { - $request->validate([ - "email" => "required|string|email", - ]); + $request->only("email"); $status = Password::sendResetLink( $request->only("email"), @@ -41,14 +40,8 @@ public function resetCreate(string $token): Response return Inertia::render("Auth/Reset-Password", ["token" => $token]); } - public function resetStore(Request $request): RedirectResponse + public function resetStore(ResetPasswordRequest $request): RedirectResponse { - $request->validate([ - "token" => "required", - "email" => "required|email", - "password" => "required|min:8|confirmed", - ]); - $status = Password::reset( $request->only("email", "password", "password_confirmation", "token"), function (User $user, string $password): void { diff --git a/app/Http/Requests/Auth/AuthenticateSessionRequest.php b/app/Http/Requests/Auth/AuthenticateSessionRequest.php new file mode 100644 index 00000000..360bbd10 --- /dev/null +++ b/app/Http/Requests/Auth/AuthenticateSessionRequest.php @@ -0,0 +1,43 @@ +<?php + +declare(strict_types=1); + +namespace App\Http\Requests\Auth; + +use Illuminate\Contracts\Validation\ValidationRule; +use Illuminate\Foundation\Http\FormRequest; + +class AuthenticateSessionRequest extends FormRequest +{ + public function authorize(): bool + { + return true; + } + + /** + * @return array<string, ValidationRule|array|string> + */ + public function rules(): array + { + return [ + "email" => "required|email|max:255", + "password" => "required|string", + ]; + } + + public function messages(): array + { + return [ + "email" => [ + "required" => "The email field is required.", + "max" => "Your email is too long. It must not be greater than 255 characters.", + "unique" => "The email has already been taken.", + "email" => "Your email is invalid.", + ], + + "password" => [ + "required" => "The password field is required.", + ], + ]; + } +} diff --git a/app/Http/Requests/Auth/ForgotPasswordRequest.php b/app/Http/Requests/Auth/ForgotPasswordRequest.php new file mode 100644 index 00000000..5d21cfdb --- /dev/null +++ b/app/Http/Requests/Auth/ForgotPasswordRequest.php @@ -0,0 +1,37 @@ +<?php + +declare(strict_types=1); + +namespace App\Http\Requests\Auth; + +use Illuminate\Contracts\Validation\ValidationRule; +use Illuminate\Foundation\Http\FormRequest; + +class ForgotPasswordRequest extends FormRequest +{ + public function authorize(): bool + { + return true; + } + + /** + * @return array<string, ValidationRule|array|string> + */ + public function rules(): array + { + return [ + "email" => "required|string|email:dns|max:255", + ]; + } + + public function messages(): array + { + return [ + "email" => [ + "required" => "The email field is required.", + "max" => "Your email is too long. It must not be greater than 255 characters.", + "email" => "Your email is invalid.", + ], + ]; + } +} diff --git a/app/Http/Requests/Auth/RegisterUserRequest.php b/app/Http/Requests/Auth/RegisterUserRequest.php index b1b570d1..5bc1bc45 100644 --- a/app/Http/Requests/Auth/RegisterUserRequest.php +++ b/app/Http/Requests/Auth/RegisterUserRequest.php @@ -10,27 +10,50 @@ class RegisterUserRequest extends FormRequest { - /** - * Determine if the user is authorized to make this request. - */ public function authorize(): bool { return true; } /** - * Get the validation rules that apply to the request. - * * @return array<string, ValidationRule|array|string> */ public function rules(): array { return [ - "email" => "required|string|email|max:255|unique:" . User::class, - "name" => "required|string", - "surname" => "required|string", + "email" => "required|string|email:rfc,dns|max:255|unique:" . User::class, + "name" => "required|string|max:255", + "surname" => "required|string|max:255", "password" => "required|string|min:8", "school_id" => "required|integer|exists:schools,id", ]; } + + public function messages(): array + { + return [ + "email" => [ + "required" => "The email field is required.", + "max" => "Your email is too long. It must not be greater than 255 characters.", + "unique" => "The email has already been taken.", + "email" => "Your email is invalid.", + ], + "name" => [ + "required" => "The name field is required", + "max" => "Your name is too long. It must not be greater than 255 characters.", + ], + "surname" => [ + "required" => "The surname field is required", + "max" => "Your surname is too long. It must not be greater than 255 characters.", + ], + "password" => [ + "required" => "The password field is required.", + "min" => "Your password is too short. It must be at least 8 characters.", + ], + "school_id" => [ + "required" => "The school field is required.", + "exists" => "Your school is invalid. Check it again.", + ], + ]; + } } diff --git a/app/Http/Requests/Auth/ResetPasswordRequest.php b/app/Http/Requests/Auth/ResetPasswordRequest.php new file mode 100644 index 00000000..496b0f23 --- /dev/null +++ b/app/Http/Requests/Auth/ResetPasswordRequest.php @@ -0,0 +1,47 @@ +<?php + +declare(strict_types=1); + +namespace App\Http\Requests\Auth; + +use Illuminate\Contracts\Validation\ValidationRule; +use Illuminate\Foundation\Http\FormRequest; + +class ResetPasswordRequest extends FormRequest +{ + public function authorize(): bool + { + return true; + } + + /** + * @return array<string, ValidationRule|array|string> + */ + public function rules(): array + { + return [ + "token" => "required", + "email" => "required|email|max:255", + "password" => "required|min:8|confirmed:password_confirmation", + ]; + } + + public function messages(): array + { + return [ + "token" => [ + "required" => "The token field is required.", + ], + "email" => [ + "required" => "The email field is required.", + "max" => "Your email is too long. It must not be greater than 255 characters.", + "email" => "Your email is invalid.", + ], + "password" => [ + "required" => "The password field is required.", + "min" => "Your password is too short. It must be at least 8 characters.", + "confirmed" => "The password field confirmation does not match.", + ], + ]; + } +} diff --git a/resources/js/Pages/Auth/Forgot-Password.vue b/resources/js/Pages/Auth/Forgot-Password.vue index 60c87c59..19a26513 100644 --- a/resources/js/Pages/Auth/Forgot-Password.vue +++ b/resources/js/Pages/Auth/Forgot-Password.vue @@ -1,7 +1,11 @@ -<script setup> +<script setup lang="ts"> import { reactive } from 'vue' import { router } from '@inertiajs/vue3' +const { errors } = defineProps<{ + errors: Record<string, string[]> +}>() + const form = reactive({ email: null, @@ -14,8 +18,11 @@ function submit() { <template> <form @submit.prevent="submit"> - <label for="email">Email:</label> - <input v-model="form.email" name="email" type="email"> + <div> + <label for="email">Email:</label> + <input v-model="form.email" name="email" type="email"> + <div v-if="errors.email">{{ errors.email }}</div> + </div> <button type="submit">Reset?</button> </form> </template> diff --git a/resources/js/Pages/Auth/Login.vue b/resources/js/Pages/Auth/Login.vue index 9493fd69..35580000 100644 --- a/resources/js/Pages/Auth/Login.vue +++ b/resources/js/Pages/Auth/Login.vue @@ -1,7 +1,11 @@ -<script setup> +<script setup lang="ts"> import { reactive } from 'vue' import { router } from '@inertiajs/vue3' +const { errors } = defineProps<{ + errors: Record<string, string[]> +}>() + const form = reactive({ email: null, password: null, @@ -14,10 +18,16 @@ function submit() { <template> <form @submit.prevent="submit"> - <label for="email">Email:</label> - <input v-model="form.email" name="email" type="email"> - <label for="password">password:</label> - <input v-model="form.password" name="password" type="password"> + <div> + <label for="email">Email:</label> + <input v-model="form.email" name="email" type="email"> + <div v-if="errors.email">{{ errors.email }}</div> + </div> + <div> + <label for="password">password:</label> + <input v-model="form.password" name="password" type="password"> + <div v-if="errors.password">{{ errors.password }}</div> + </div> <button type="submit">Login</button> </form> diff --git a/resources/js/Pages/Auth/Register.vue b/resources/js/Pages/Auth/Register.vue index cfaf89ab..d2a566e6 100644 --- a/resources/js/Pages/Auth/Register.vue +++ b/resources/js/Pages/Auth/Register.vue @@ -1,7 +1,11 @@ -<script setup> +<script setup lang="ts"> import { reactive } from 'vue' import { router } from '@inertiajs/vue3' +const { errors } = defineProps<{ + errors: Record<string, string[]> +}>() + const form = reactive({ name: null, surname: null, @@ -17,16 +21,31 @@ function submit() { <template> <form @submit.prevent="submit"> - <label for="name">Name:</label> - <input v-model="form.name" name="name" type="text"> - <label for="surname">Surname:</label> - <input v-model="form.surname" name="surname" type="text"> - <label for="email">Email:</label> - <input v-model="form.email" name="email" type="email"> - <label for="password">password:</label> - <input v-model="form.password" name="password" type="password"> - <label for="school_id">School ID:</label> - <input v-model="form.school_id" name="school_id" type="number"> + <div> + <label for="name">Name:</label> + <input v-model="form.name" name="name" type="text"> + <div v-if="errors.name">{{ errors.name }}</div> + </div> + <div> + <label for="surname">Surname:</label> + <input v-model="form.surname" name="surname" type="text"> + <div v-if="errors.surname">{{ errors.surname }}</div> + </div> + <div> + <label for="email">Email:</label> + <input v-model="form.email" name="email" type="email"> + <div v-if="errors.email">{{ errors.email }}</div> + </div> + <div> + <label for="password">password:</label> + <input v-model="form.password" name="password" type="password"> + <div v-if="errors.password">{{ errors.password }}</div> + </div> + <div> + <label for="school_id">School ID:</label> + <input v-model="form.school_id" name="school_id" type="number"> + <div v-if="errors.school_id">{{ errors.school_id }}</div> + </div> <button type="submit">Register</button> </form> </template> diff --git a/resources/js/Pages/Auth/Reset-Password.vue b/resources/js/Pages/Auth/Reset-Password.vue index 1e065ef2..4183fcac 100644 --- a/resources/js/Pages/Auth/Reset-Password.vue +++ b/resources/js/Pages/Auth/Reset-Password.vue @@ -2,7 +2,14 @@ import { reactive } from 'vue' import { router } from '@inertiajs/vue3' -const props = defineProps<{ token: string }>() + +const props = defineProps< +{ + token: string + errors: Record<string, string[]> +}>() + + const form = reactive({ email: null, @@ -19,13 +26,24 @@ function submit() { <template> <form @submit.prevent="submit"> - <label for="email">Email:</label> - <input v-model="form.email" name="email" type="email"><br> - <label for="email">Password:</label> - <input v-model="form.password" name="password" type="password" required><br> - <label for="email">Password Confirmation:</label> - <input v-model="form.password_confirmation" name="password_confirmation" type="password"><br> - <input v-model="form.token" name="token" type="hidden"> + <div> + <label for="email">Email:</label> + <input v-model="form.email" name="email" type="email"><br> + <div v-if="errors.email">{{ errors.email }}</div> + </div> + <div> + <label for="email">Password:</label> + <input v-model="form.password" name="password" type="password" required><br> + <label for="email">Password Confirmation:</label> + <input v-model="form.password_confirmation" name="password_confirmation" type="password"><br> + <div v-if="errors.password">{{ errors.password }}</div> + </div> + + + <div> + <input v-model="form.token" name="token" type="hidden"> + <div v-if="errors.token">{{ errors.token }}</div> + </div> <button type="submit">Change Password?</button> </form> </template> diff --git a/tests/Feature/AuthenticateSessionTest.php b/tests/Feature/AuthenticateSessionTest.php index f2efe6ea..2fabcd28 100644 --- a/tests/Feature/AuthenticateSessionTest.php +++ b/tests/Feature/AuthenticateSessionTest.php @@ -12,9 +12,6 @@ class AuthenticateSessionTest extends TestCase { use RefreshDatabase; - /** - * A basic feature test example. - */ public function testUserCanLogin(): void { $user = User::factory()->create(["email" => "test@example.com", "password" => "goodPassword"]); @@ -42,7 +39,7 @@ public function testUserCanNotLoginWithWrongEmail(): void $this->from("/test")->post("/auth/login", [ "email" => "test", "password" => "wrongPasswordExample", - ])->assertRedirect("/test")->assertSessionHasErrors(["email" => "The email field must be a valid email address."]); + ])->assertRedirect("/test")->assertSessionHasErrors(["email" => "Your email is invalid."]); } public function testUserCanNotLoginWithEmptyEmailAndPassword(): void diff --git a/tests/Feature/ForgotPasswordTest.php b/tests/Feature/ForgotPasswordTest.php index a9aa3f86..5a54c377 100644 --- a/tests/Feature/ForgotPasswordTest.php +++ b/tests/Feature/ForgotPasswordTest.php @@ -16,16 +16,15 @@ class ForgotPasswordTest extends TestCase use RefreshDatabase; /** - * A basic feature test example. * @throws JsonException */ public function testUserCanSendForgotPasswordRequest(): void { Notification::fake(); - $user = User::factory()->create(["email" => "test@example.com"]); + $user = User::factory()->create(["email" => "test@gmail.com"]); $this->post("/auth/forgot-password", [ - "email" => "test@example.com", + "email" => "test@gmail.com", ])->assertSessionHasNoErrors(); Notification::assertSentTo( @@ -37,16 +36,16 @@ public function testUserCanSendForgotPasswordRequest(): void public function testUserCanNotSendForgotPasswordRequestWithWrongEmail(): void { Notification::fake(); - $user = User::factory()->create(["email" => "test@example.com"]); + $user = User::factory()->create(["email" => "test@gmail.com"]); $this->post("/auth/forgot-password", [ - "email" => "wrongTest@example.com", + "email" => "wrongTest@gmail.com", ])->assertSessionHasErrors(["email" => "We can't find a user with that email address."]); Notification::assertNothingSent(); $this->post("/auth/forgot-password", [ "email" => "wrongTest", - ])->assertSessionHasErrors(["email" => "The email field must be a valid email address."]); + ])->assertSessionHasErrors(["email" => "Your email is invalid."]); Notification::assertNothingSent(); $this->post("/auth/forgot-password", [ diff --git a/tests/Feature/RegisterUserTest.php b/tests/Feature/RegisterUserTest.php index ef18735c..24b7f498 100644 --- a/tests/Feature/RegisterUserTest.php +++ b/tests/Feature/RegisterUserTest.php @@ -7,15 +7,13 @@ use App\Models\School; use App\Models\User; use Illuminate\Foundation\Testing\RefreshDatabase; +use Illuminate\Support\Str; use Tests\TestCase; class RegisterUserTest extends TestCase { use RefreshDatabase; - /** - * A basic feature test example. - */ public function testUserCanRegister(): void { $school = School::factory()->create(); @@ -23,7 +21,7 @@ public function testUserCanRegister(): void $this->post("/auth/register", [ "name" => "Test", "surname" => "Test", - "email" => "test@example.com", + "email" => "test@gmail.com", "password" => "123456890", "school_id" => $school->id, ])->assertRedirect("/"); @@ -31,7 +29,7 @@ public function testUserCanRegister(): void $this->assertDatabaseHas("users", [ "name" => "Test", "surname" => "Test", - "email" => "test@example.com", + "email" => "test@gmail.com", "school_id" => $school->id, ]); @@ -43,27 +41,99 @@ public function testUserCanRegister(): void public function testUserCanNotRegisterWithAlreadyTakenEmail(): void { $school = School::factory()->create(); - $user = User::factory()->create(); + $user = User::factory()->create([ + "email" => "test@gmail.com", + ]); $this->post("/auth/register", [ "name" => "Test", "surname" => "Test", - "email" => $user->email, + "email" => "test@gmail.com", "password" => "123456890", "school_id" => $school->id, - ])->assertRedirect("/")->assertSessionHasErrors(["email" => "The email has already been taken."]); + ])->assertRedirect("/") + ->assertSessionHasErrors(["email" => "The email has already been taken."]); } - public function testUserCanNotRegisterWithWrongSchool(): void + public function testUserCanNotRegisterWithWrongSchoolIndex(): void { $school = School::factory()->create(); $this->post("/auth/register", [ "name" => "Test", "surname" => "Test", - "email" => "test@example.com", + "email" => "test@gmail.com", "password" => "123456890", "school_id" => $school->id + 99999, - ])->assertRedirect("/")->assertSessionHasErrors(["school_id" => "The selected school id is invalid."]); + ])->assertRedirect("/") + ->assertSessionHasErrors(["school_id" => "Your school is invalid. Check it again."]); + } + + public function testUserCanNotRegisterWithTooLongEmail(): void + { + $longMail = Str::random(250); + $school = School::factory()->create(); + + $this->post("/auth/register", [ + "name" => "Test", + "surname" => "Test", + "email" => $longMail . "@gmail.com", + "password" => "123456890", + "school_id" => $school->id, + ])->assertRedirect("/")->assertSessionHasErrors(["email" => "Your email is too long. It must not be greater than 255 characters."]); + } + + public function testUserCanNotRegisterWithWrongEmailDomain(): void + { + $school = School::factory()->create(); + + $this->post("/auth/register", [ + "name" => "Test", + "surname" => "Test", + "email" => "test@gmail.pl", + "password" => "123456890", + "school_id" => $school->id, + ])->assertRedirect("/")->assertSessionHasErrors(["email" => "Your email is invalid."]); + } + + public function testUserCanNotRegisterWithTooLongName(): void + { + $longName = Str::random(256); + $school = School::factory()->create(); + + $this->post("/auth/register", [ + "name" => $longName, + "surname" => "Test", + "email" => "test@gmail.com", + "password" => "123456890", + "school_id" => $school->id, + ])->assertRedirect("/")->assertSessionHasErrors(["name" => "Your name is too long. It must not be greater than 255 characters."]); + } + + public function testUserCanNotRegisterWithTooLongSurname(): void + { + $longSurname = Str::random(256); + $school = School::factory()->create(); + + $this->post("/auth/register", [ + "name" => "Test", + "surname" => $longSurname, + "email" => "test@gmail.com", + "password" => "123456890", + "school_id" => $school->id, + ])->assertRedirect("/")->assertSessionHasErrors(["surname" => "Your surname is too long. It must not be greater than 255 characters."]); + } + + public function testUserCanNotRegisterWithTooShortPassword(): void + { + $school = School::factory()->create(); + + $this->post("/auth/register", [ + "name" => "Test", + "surname" => "Test", + "email" => "test@gmail.com", + "password" => "123", + "school_id" => $school->id, + ])->assertRedirect("/")->assertSessionHasErrors(["password" => "Your password is too short. It must be at least 8 characters."]); } } From ee5cab679b8fa6836a26e7e5cc353877641ec9c7 Mon Sep 17 00:00:00 2001 From: Dominik Prabucki <dominikprabucki.2002@gmail.com> Date: Mon, 19 Aug 2024 09:37:12 +0200 Subject: [PATCH 18/33] Add validation translation files Change to pl language in .env.example. Change some files name into PascalCase. Remove unused verify-email. Remove messages from requests. --- .env.example | 2 +- .../PasswordResetLinkController.php | 4 +- .../Auth/AuthenticateSessionRequest.php | 20 --- .../Requests/Auth/ForgotPasswordRequest.php | 15 -- .../Requests/Auth/RegisterUserRequest.php | 32 ---- .../Requests/Auth/ResetPasswordRequest.php | 23 --- app/Mail/RegistrationMail.php | 11 -- config/app.php | 4 +- lang/en/auth.php | 9 + lang/en/pagination.php | 8 + lang/en/passwords.php | 11 ++ lang/en/validation.php | 161 ++++++++++++++++++ lang/pl/auth.php | 9 + lang/pl/pagination.php | 8 + lang/pl/passwords.php | 11 ++ lang/pl/validation.php | 141 +++++++++++++++ ...Forgot-Password.vue => ForgotPassword.vue} | 0 .../{Reset-Password.vue => ResetPassword.vue} | 0 resources/js/Pages/Auth/Verify-Email.vue | 11 -- tests/Feature/AuthenticateSessionTest.php | 4 +- tests/Feature/ForgotPasswordTest.php | 6 +- tests/Feature/RegisterUserTest.php | 14 +- tests/Feature/ResetPasswordTest.php | 2 +- 23 files changed, 376 insertions(+), 130 deletions(-) create mode 100644 lang/en/auth.php create mode 100644 lang/en/pagination.php create mode 100644 lang/en/passwords.php create mode 100644 lang/en/validation.php create mode 100644 lang/pl/auth.php create mode 100644 lang/pl/pagination.php create mode 100644 lang/pl/passwords.php create mode 100644 lang/pl/validation.php rename resources/js/Pages/Auth/{Forgot-Password.vue => ForgotPassword.vue} (100%) rename resources/js/Pages/Auth/{Reset-Password.vue => ResetPassword.vue} (100%) delete mode 100644 resources/js/Pages/Auth/Verify-Email.vue diff --git a/.env.example b/.env.example index d7235cd8..5797ae1c 100644 --- a/.env.example +++ b/.env.example @@ -5,7 +5,7 @@ APP_KEY=base64:sCsJw8z+d/4ymp0OvzSip2h4Vp2hZZhpV2uOxgTqP94= APP_DEBUG=true APP_URL=http://interns2024b.blumilk.localhost -APP_LOCALE=en +APP_LOCALE=pl APP_FALLBACK_LOCALE=en APP_FAKER_LOCALE=en_US diff --git a/app/Http/Controllers/PasswordResetLinkController.php b/app/Http/Controllers/PasswordResetLinkController.php index 31b46384..ff77b96f 100644 --- a/app/Http/Controllers/PasswordResetLinkController.php +++ b/app/Http/Controllers/PasswordResetLinkController.php @@ -19,7 +19,7 @@ class PasswordResetLinkController extends Controller { public function create(): Response { - return Inertia::render("Auth/Forgot-Password"); + return Inertia::render("Auth/ForgotPassword"); } public function store(ForgotPasswordRequest $request): RedirectResponse @@ -37,7 +37,7 @@ public function store(ForgotPasswordRequest $request): RedirectResponse public function resetCreate(string $token): Response { - return Inertia::render("Auth/Reset-Password", ["token" => $token]); + return Inertia::render("Auth/ResetPassword", ["token" => $token]); } public function resetStore(ResetPasswordRequest $request): RedirectResponse diff --git a/app/Http/Requests/Auth/AuthenticateSessionRequest.php b/app/Http/Requests/Auth/AuthenticateSessionRequest.php index 360bbd10..a92bb5dc 100644 --- a/app/Http/Requests/Auth/AuthenticateSessionRequest.php +++ b/app/Http/Requests/Auth/AuthenticateSessionRequest.php @@ -4,7 +4,6 @@ namespace App\Http\Requests\Auth; -use Illuminate\Contracts\Validation\ValidationRule; use Illuminate\Foundation\Http\FormRequest; class AuthenticateSessionRequest extends FormRequest @@ -14,9 +13,6 @@ public function authorize(): bool return true; } - /** - * @return array<string, ValidationRule|array|string> - */ public function rules(): array { return [ @@ -24,20 +20,4 @@ public function rules(): array "password" => "required|string", ]; } - - public function messages(): array - { - return [ - "email" => [ - "required" => "The email field is required.", - "max" => "Your email is too long. It must not be greater than 255 characters.", - "unique" => "The email has already been taken.", - "email" => "Your email is invalid.", - ], - - "password" => [ - "required" => "The password field is required.", - ], - ]; - } } diff --git a/app/Http/Requests/Auth/ForgotPasswordRequest.php b/app/Http/Requests/Auth/ForgotPasswordRequest.php index 5d21cfdb..c0f61083 100644 --- a/app/Http/Requests/Auth/ForgotPasswordRequest.php +++ b/app/Http/Requests/Auth/ForgotPasswordRequest.php @@ -4,7 +4,6 @@ namespace App\Http\Requests\Auth; -use Illuminate\Contracts\Validation\ValidationRule; use Illuminate\Foundation\Http\FormRequest; class ForgotPasswordRequest extends FormRequest @@ -14,24 +13,10 @@ public function authorize(): bool return true; } - /** - * @return array<string, ValidationRule|array|string> - */ public function rules(): array { return [ "email" => "required|string|email:dns|max:255", ]; } - - public function messages(): array - { - return [ - "email" => [ - "required" => "The email field is required.", - "max" => "Your email is too long. It must not be greater than 255 characters.", - "email" => "Your email is invalid.", - ], - ]; - } } diff --git a/app/Http/Requests/Auth/RegisterUserRequest.php b/app/Http/Requests/Auth/RegisterUserRequest.php index 5bc1bc45..91792fef 100644 --- a/app/Http/Requests/Auth/RegisterUserRequest.php +++ b/app/Http/Requests/Auth/RegisterUserRequest.php @@ -5,7 +5,6 @@ namespace App\Http\Requests\Auth; use App\Models\User; -use Illuminate\Contracts\Validation\ValidationRule; use Illuminate\Foundation\Http\FormRequest; class RegisterUserRequest extends FormRequest @@ -15,9 +14,6 @@ public function authorize(): bool return true; } - /** - * @return array<string, ValidationRule|array|string> - */ public function rules(): array { return [ @@ -28,32 +24,4 @@ public function rules(): array "school_id" => "required|integer|exists:schools,id", ]; } - - public function messages(): array - { - return [ - "email" => [ - "required" => "The email field is required.", - "max" => "Your email is too long. It must not be greater than 255 characters.", - "unique" => "The email has already been taken.", - "email" => "Your email is invalid.", - ], - "name" => [ - "required" => "The name field is required", - "max" => "Your name is too long. It must not be greater than 255 characters.", - ], - "surname" => [ - "required" => "The surname field is required", - "max" => "Your surname is too long. It must not be greater than 255 characters.", - ], - "password" => [ - "required" => "The password field is required.", - "min" => "Your password is too short. It must be at least 8 characters.", - ], - "school_id" => [ - "required" => "The school field is required.", - "exists" => "Your school is invalid. Check it again.", - ], - ]; - } } diff --git a/app/Http/Requests/Auth/ResetPasswordRequest.php b/app/Http/Requests/Auth/ResetPasswordRequest.php index 496b0f23..80e155f9 100644 --- a/app/Http/Requests/Auth/ResetPasswordRequest.php +++ b/app/Http/Requests/Auth/ResetPasswordRequest.php @@ -4,7 +4,6 @@ namespace App\Http\Requests\Auth; -use Illuminate\Contracts\Validation\ValidationRule; use Illuminate\Foundation\Http\FormRequest; class ResetPasswordRequest extends FormRequest @@ -14,9 +13,6 @@ public function authorize(): bool return true; } - /** - * @return array<string, ValidationRule|array|string> - */ public function rules(): array { return [ @@ -25,23 +21,4 @@ public function rules(): array "password" => "required|min:8|confirmed:password_confirmation", ]; } - - public function messages(): array - { - return [ - "token" => [ - "required" => "The token field is required.", - ], - "email" => [ - "required" => "The email field is required.", - "max" => "Your email is too long. It must not be greater than 255 characters.", - "email" => "Your email is invalid.", - ], - "password" => [ - "required" => "The password field is required.", - "min" => "Your password is too short. It must be at least 8 characters.", - "confirmed" => "The password field confirmation does not match.", - ], - ]; - } } diff --git a/app/Mail/RegistrationMail.php b/app/Mail/RegistrationMail.php index 071b44cc..78854ed8 100644 --- a/app/Mail/RegistrationMail.php +++ b/app/Mail/RegistrationMail.php @@ -16,14 +16,8 @@ class RegistrationMail extends Mailable use Queueable; use SerializesModels; - /** - * Create a new message instance. - */ public function __construct() {} - /** - * Get the message envelope. - */ public function envelope(): Envelope { return new Envelope( @@ -31,9 +25,6 @@ public function envelope(): Envelope ); } - /** - * Get the message content definition. - */ public function content(): Content { return new Content( @@ -42,8 +33,6 @@ public function content(): Content } /** - * Get the attachments for the message. - * * @return array<int, Attachment> */ public function attachments(): array diff --git a/config/app.php b/config/app.php index 1c531b92..03e49b77 100644 --- a/config/app.php +++ b/config/app.php @@ -8,8 +8,8 @@ "debug" => (bool)env("APP_DEBUG", false), "url" => env("APP_URL", "http://localhost"), "timezone" => env("APP_TIMEZONE", "UTC"), - "locale" => env("APP_LOCALE", "en"), - "fallback_locale" => env("APP_FALLBACK_LOCALE", "en"), + "locale" => env("APP_LOCALE", "pl"), + "fallback_locale" => env("APP_FALLBACK_LOCALE", "pl"), "faker_locale" => env("APP_FAKER_LOCALE", "en_US"), "cipher" => "AES-256-CBC", "key" => env("APP_KEY"), diff --git a/lang/en/auth.php b/lang/en/auth.php new file mode 100644 index 00000000..48da02fc --- /dev/null +++ b/lang/en/auth.php @@ -0,0 +1,9 @@ +<?php + +declare(strict_types=1); + +return [ + "failed" => "These credentials do not match our records.", + "password" => "The provided password is incorrect.", + "throttle" => "Too many login attempts. Please try again in :seconds seconds.", +]; diff --git a/lang/en/pagination.php b/lang/en/pagination.php new file mode 100644 index 00000000..d654f2e3 --- /dev/null +++ b/lang/en/pagination.php @@ -0,0 +1,8 @@ +<?php + +declare(strict_types=1); + +return [ + "previous" => "« Previous", + "next" => "Next »", +]; diff --git a/lang/en/passwords.php b/lang/en/passwords.php new file mode 100644 index 00000000..7d62b40f --- /dev/null +++ b/lang/en/passwords.php @@ -0,0 +1,11 @@ +<?php + +declare(strict_types=1); + +return [ + "reset" => "Your password has been reset.", + "sent" => "We have emailed your password reset link.", + "throttled" => "Please wait before retrying.", + "token" => "This password reset token is invalid.", + "user" => "We can't find a user with that email address.", +]; diff --git a/lang/en/validation.php b/lang/en/validation.php new file mode 100644 index 00000000..d1a0e8e2 --- /dev/null +++ b/lang/en/validation.php @@ -0,0 +1,161 @@ +<?php + +declare(strict_types=1); + +return [ + "accepted" => "The :attribute field must be accepted.", + "accepted_if" => "The :attribute field must be accepted when :other is :value.", + "active_url" => "The :attribute field must be a valid URL.", + "after" => "The :attribute field must be a date after :date.", + "after_or_equal" => "The :attribute field must be a date after or equal to :date.", + "alpha" => "The :attribute field must only contain letters.", + "alpha_dash" => "The :attribute field must only contain letters, numbers, dashes, and underscores.", + "alpha_num" => "The :attribute field must only contain letters and numbers.", + "array" => "The :attribute field must be an array.", + "ascii" => "The :attribute field must only contain single-byte alphanumeric characters and symbols.", + "before" => "The :attribute field must be a date before :date.", + "before_or_equal" => "The :attribute field must be a date before or equal to :date.", + "between" => [ + "array" => "The :attribute field must have between :min and :max items.", + "file" => "The :attribute field must be between :min and :max kilobytes.", + "numeric" => "The :attribute field must be between :min and :max.", + "string" => "The :attribute field must be between :min and :max characters.", + ], + "boolean" => "The :attribute field must be true or false.", + "can" => "The :attribute field contains an unauthorized value.", + "confirmed" => "The :attribute field confirmation does not match.", + "contains" => "The :attribute field is missing a required value.", + "current_password" => "The password is incorrect.", + "date" => "The :attribute field must be a valid date.", + "date_equals" => "The :attribute field must be a date equal to :date.", + "date_format" => "The :attribute field must match the format :format.", + "decimal" => "The :attribute field must have :decimal decimal places.", + "declined" => "The :attribute field must be declined.", + "declined_if" => "The :attribute field must be declined when :other is :value.", + "different" => "The :attribute field and :other must be different.", + "digits" => "The :attribute field must be :digits digits.", + "digits_between" => "The :attribute field must be between :min and :max digits.", + "dimensions" => "The :attribute field has invalid image dimensions.", + "distinct" => "The :attribute field has a duplicate value.", + "doesnt_end_with" => "The :attribute field must not end with one of the following: :values.", + "doesnt_start_with" => "The :attribute field must not start with one of the following: :values.", + "email" => "The :attribute field must be a valid email address.", + "ends_with" => "The :attribute field must end with one of the following: :values.", + "enum" => "The selected :attribute is invalid.", + "exists" => "The selected :attribute is invalid.", + "extensions" => "The :attribute field must have one of the following extensions: :values.", + "file" => "The :attribute field must be a file.", + "filled" => "The :attribute field must have a value.", + "gt" => [ + "array" => "The :attribute field must have more than :value items.", + "file" => "The :attribute field must be greater than :value kilobytes.", + "numeric" => "The :attribute field must be greater than :value.", + "string" => "The :attribute field must be greater than :value characters.", + ], + "gte" => [ + "array" => "The :attribute field must have :value items or more.", + "file" => "The :attribute field must be greater than or equal to :value kilobytes.", + "numeric" => "The :attribute field must be greater than or equal to :value.", + "string" => "The :attribute field must be greater than or equal to :value characters.", + ], + "hex_color" => "The :attribute field must be a valid hexadecimal color.", + "image" => "The :attribute field must be an image.", + "in" => "The selected :attribute is invalid.", + "in_array" => "The :attribute field must exist in :other.", + "integer" => "The :attribute field must be an integer.", + "ip" => "The :attribute field must be a valid IP address.", + "ipv4" => "The :attribute field must be a valid IPv4 address.", + "ipv6" => "The :attribute field must be a valid IPv6 address.", + "json" => "The :attribute field must be a valid JSON string.", + "list" => "The :attribute field must be a list.", + "lowercase" => "The :attribute field must be lowercase.", + "lt" => [ + "array" => "The :attribute field must have less than :value items.", + "file" => "The :attribute field must be less than :value kilobytes.", + "numeric" => "The :attribute field must be less than :value.", + "string" => "The :attribute field must be less than :value characters.", + ], + "lte" => [ + "array" => "The :attribute field must not have more than :value items.", + "file" => "The :attribute field must be less than or equal to :value kilobytes.", + "numeric" => "The :attribute field must be less than or equal to :value.", + "string" => "The :attribute field must be less than or equal to :value characters.", + ], + "mac_address" => "The :attribute field must be a valid MAC address.", + "max" => [ + "array" => "The :attribute field must not have more than :max items.", + "file" => "The :attribute field must not be greater than :max kilobytes.", + "numeric" => "The :attribute field must not be greater than :max.", + "string" => "The :attribute field must not be greater than :max characters.", + ], + "max_digits" => "The :attribute field must not have more than :max digits.", + "mimes" => "The :attribute field must be a file of type: :values.", + "mimetypes" => "The :attribute field must be a file of type: :values.", + "min" => [ + "array" => "The :attribute field must have at least :min items.", + "file" => "The :attribute field must be at least :min kilobytes.", + "numeric" => "The :attribute field must be at least :min.", + "string" => "The :attribute field must be at least :min characters.", + ], + "min_digits" => "The :attribute field must have at least :min digits.", + "missing" => "The :attribute field must be missing.", + "missing_if" => "The :attribute field must be missing when :other is :value.", + "missing_unless" => "The :attribute field must be missing unless :other is :value.", + "missing_with" => "The :attribute field must be missing when :values is present.", + "missing_with_all" => "The :attribute field must be missing when :values are present.", + "multiple_of" => "The :attribute field must be a multiple of :value.", + "not_in" => "The selected :attribute is invalid.", + "not_regex" => "The :attribute field format is invalid.", + "numeric" => "The :attribute field must be a number.", + "password" => [ + "letters" => "The :attribute field must contain at least one letter.", + "mixed" => "The :attribute field must contain at least one uppercase and one lowercase letter.", + "numbers" => "The :attribute field must contain at least one number.", + "symbols" => "The :attribute field must contain at least one symbol.", + "uncompromised" => "The given :attribute has appeared in a data leak. Please choose a different :attribute.", + ], + "present" => "The :attribute field must be present.", + "present_if" => "The :attribute field must be present when :other is :value.", + "present_unless" => "The :attribute field must be present unless :other is :value.", + "present_with" => "The :attribute field must be present when :values is present.", + "present_with_all" => "The :attribute field must be present when :values are present.", + "prohibited" => "The :attribute field is prohibited.", + "prohibited_if" => "The :attribute field is prohibited when :other is :value.", + "prohibited_unless" => "The :attribute field is prohibited unless :other is in :values.", + "prohibits" => "The :attribute field prohibits :other from being present.", + "regex" => "The :attribute field format is invalid.", + "required" => "The :attribute field is required.", + "required_array_keys" => "The :attribute field must contain entries for: :values.", + "required_if" => "The :attribute field is required when :other is :value.", + "required_if_accepted" => "The :attribute field is required when :other is accepted.", + "required_if_declined" => "The :attribute field is required when :other is declined.", + "required_unless" => "The :attribute field is required unless :other is in :values.", + "required_with" => "The :attribute field is required when :values is present.", + "required_with_all" => "The :attribute field is required when :values are present.", + "required_without" => "The :attribute field is required when :values is not present.", + "required_without_all" => "The :attribute field is required when none of :values are present.", + "same" => "The :attribute field must match :other.", + "size" => [ + "array" => "The :attribute field must contain :size items.", + "file" => "The :attribute field must be :size kilobytes.", + "numeric" => "The :attribute field must be :size.", + "string" => "The :attribute field must be :size characters.", + ], + "starts_with" => "The :attribute field must start with one of the following: :values.", + "string" => "The :attribute field must be a string.", + "timezone" => "The :attribute field must be a valid timezone.", + "unique" => "The :attribute has already been taken.", + "uploaded" => "The :attribute failed to upload.", + "uppercase" => "The :attribute field must be uppercase.", + "url" => "The :attribute field must be a valid URL.", + "ulid" => "The :attribute field must be a valid ULID.", + "uuid" => "The :attribute field must be a valid UUID.", + + "custom" => [ + "attribute-name" => [ + "rule-name" => "custom-message", + ], + ], + + "attributes" => [], +]; diff --git a/lang/pl/auth.php b/lang/pl/auth.php new file mode 100644 index 00000000..b8255a78 --- /dev/null +++ b/lang/pl/auth.php @@ -0,0 +1,9 @@ +<?php + +declare(strict_types=1); + +return [ + "failed" => "Błędny login lub hasło.", + "password" => "Podane hasło jest nieprawidłowe.", + "throttle" => "Za dużo nieudanych prób logowania. Proszę spróbować za :seconds sekund.", +]; diff --git a/lang/pl/pagination.php b/lang/pl/pagination.php new file mode 100644 index 00000000..49e8df0e --- /dev/null +++ b/lang/pl/pagination.php @@ -0,0 +1,8 @@ +<?php + +declare(strict_types=1); + +return [ + "next" => "Następna", + "previous" => "Poprzednia", +]; diff --git a/lang/pl/passwords.php b/lang/pl/passwords.php new file mode 100644 index 00000000..2a4dd05e --- /dev/null +++ b/lang/pl/passwords.php @@ -0,0 +1,11 @@ +<?php + +declare(strict_types=1); + +return [ + "reset" => "Hasło zostało zresetowane!", + "sent" => "Przypomnienie hasła zostało wysłane!", + "throttled" => "Proszę zaczekać zanim spróbujesz ponownie.", + "token" => "Token resetowania hasła jest nieprawidłowy.", + "user" => "Nie znaleziono użytkownika z takim adresem e-mail.", +]; diff --git a/lang/pl/validation.php b/lang/pl/validation.php new file mode 100644 index 00000000..6c6ef4cd --- /dev/null +++ b/lang/pl/validation.php @@ -0,0 +1,141 @@ +<?php + +declare(strict_types=1); + +return [ + "accepted" => "Pole :attribute musi zostać zaakceptowane.", + "active_url" => "Pole :attribute jest nieprawidłowym adresem URL.", + "after" => "Pole :attribute musi być datą późniejszą od :date.", + "after_or_equal" => "Pole :attribute musi być datą nie wcześniejszą niż :date.", + "alpha" => "Pole :attribute może zawierać jedynie litery.", + "alpha_dash" => "Pole :attribute może zawierać jedynie litery, cyfry i myślniki.", + "alpha_num" => "Pole :attribute może zawierać jedynie litery i cyfry.", + "array" => "Pole :attribute musi być tablicą.", + "attached" => "Ten :attribute jest już dołączony.", + "before" => "Pole :attribute musi być datą wcześniejszą od :date.", + "before_or_equal" => "Pole :attribute musi być datą nie późniejszą niż :date.", + "between" => [ + "array" => "Pole :attribute musi składać się z :min - :max elementów.", + "file" => "Pole :attribute musi zawierać się w granicach :min - :max kilobajtów.", + "numeric" => "Pole :attribute musi zawierać się w granicach :min - :max.", + "string" => "Pole :attribute musi zawierać się w granicach :min - :max znaków.", + ], + "boolean" => "Pole :attribute musi mieć wartość logiczną prawda albo fałsz.", + "confirmed" => "Potwierdzenie pola :attribute nie zgadza się.", + "current_password" => "Hasło jest nieprawidłowe.", + "date" => "Pole :attribute nie jest prawidłową datą.", + "date_equals" => "Pole :attribute musi być datą równą :date.", + "date_format" => "Pole :attribute nie jest w formacie :format.", + "different" => "Pole :attribute oraz :other muszą się różnić.", + "digits" => "Pole :attribute musi składać się z :digits cyfr.", + "digits_between" => "Pole :attribute musi mieć od :min do :max cyfr.", + "dimensions" => "Pole :attribute ma niepoprawne wymiary.", + "distinct" => "Pole :attribute ma zduplikowane wartości.", + "email" => "Pole :attribute nie jest poprawnym adresem e-mail.", + "ends_with" => "Pole :attribute musi kończyć się jedną z następujących wartości: :values.", + "exists" => "Zaznaczone pole :attribute jest nieprawidłowe.", + "file" => "Pole :attribute musi być plikiem.", + "filled" => "Pole :attribute nie może być puste.", + "gt" => [ + "array" => "Pole :attribute musi mieć więcej niż :value elementów.", + "file" => "Pole :attribute musi być większe niż :value kilobajtów.", + "numeric" => "Pole :attribute musi być większe niż :value.", + "string" => "Pole :attribute musi być dłuższe niż :value znaków.", + ], + "gte" => [ + "array" => "Pole :attribute musi mieć :value lub więcej elementów.", + "file" => "Pole :attribute musi być większe lub równe :value kilobajtów.", + "numeric" => "Pole :attribute musi być większe lub równe :value.", + "string" => "Pole :attribute musi być dłuższe lub równe :value znaków.", + ], + "image" => "Pole :attribute musi być obrazkiem.", + "in" => "Zaznaczony element :attribute jest nieprawidłowy.", + "in_array" => "Pole :attribute nie znajduje się w :other.", + "integer" => "Pole :attribute musi być liczbą całkowitą.", + "ip" => "Pole :attribute musi być prawidłowym adresem IP.", + "ipv4" => "Pole :attribute musi być prawidłowym adresem IPv4.", + "ipv6" => "Pole :attribute musi być prawidłowym adresem IPv6.", + "json" => "Pole :attribute musi być poprawnym ciągiem znaków JSON.", + "lt" => [ + "array" => "Pole :attribute musi mieć mniej niż :value elementów.", + "file" => "Pole :attribute musi być mniejsze niż :value kilobajtów.", + "numeric" => "Pole :attribute musi być mniejsze niż :value.", + "string" => "Pole :attribute musi być krótsze niż :value znaków.", + ], + "lte" => [ + "array" => "Pole :attribute musi mieć :value lub mniej elementów.", + "file" => "Pole :attribute musi być mniejsze lub równe :value kilobajtów.", + "numeric" => "Pole :attribute musi być mniejsze lub równe :value.", + "string" => "Pole :attribute musi być krótsze lub równe :value znaków.", + ], + "max" => [ + "array" => "Pole :attribute nie może mieć więcej niż :max elementów.", + "file" => "Pole :attribute nie może być większe niż :max kilobajtów.", + "numeric" => "Pole :attribute nie może być większe niż :max.", + "string" => "Pole :attribute nie może być dłuższe niż :max znaków.", + ], + "mimes" => "Pole :attribute musi być plikiem typu :values.", + "mimetypes" => "Pole :attribute musi być plikiem typu :values.", + "min" => [ + "array" => "Pole :attribute musi mieć przynajmniej :min elementów.", + "file" => "Pole :attribute musi mieć przynajmniej :min kilobajtów.", + "numeric" => "Pole :attribute musi być nie mniejsze od :min.", + "string" => "Pole :attribute musi mieć przynajmniej :min znaków.", + ], + "multiple_of" => "Pole :attribute musi być wielokrotnością wartości :value", + "not_in" => "Zaznaczony :attribute jest nieprawidłowy.", + "not_regex" => "Format pola :attribute jest nieprawidłowy.", + "numeric" => "Pole :attribute musi być liczbą.", + "password" => "Hasło jest nieprawidłowe.", + "present" => "Pole :attribute musi być obecne.", + "prohibited" => "Pole :attribute jest zabronione.", + "prohibited_if" => "Pole :attribute jest zabronione, gdy :other to :value.", + "prohibited_unless" => "Pole :attribute jest zabronione, chyba że :other jest w :values.", + "regex" => "Format pola :attribute jest nieprawidłowy.", + "relatable" => "Ten :attribute może nie być powiązany z tym zasobem.", + "required" => "Pole :attribute jest wymagane.", + "required_if" => "Pole :attribute jest wymagane gdy :other ma wartość :value.", + "required_unless" => "Pole :attribute jest wymagane jeżeli :other nie znajduje się w :values.", + "required_with" => "Pole :attribute jest wymagane gdy :values jest obecny.", + "required_with_all" => "Pole :attribute jest wymagane gdy wszystkie :values są obecne.", + "required_without" => "Pole :attribute jest wymagane gdy :values nie jest obecny.", + "required_without_all" => "Pole :attribute jest wymagane gdy żadne z :values nie są obecne.", + "same" => "Pole :attribute i :other muszą być takie same.", + "size" => [ + "array" => "Pole :attribute musi zawierać :size elementów.", + "file" => "Pole :attribute musi mieć :size kilobajtów.", + "numeric" => "Pole :attribute musi mieć :size.", + "string" => "Pole :attribute musi mieć :size znaków.", + ], + "starts_with" => "Pole :attribute musi zaczynać się jedną z następujących wartości: :values.", + "string" => "Pole :attribute musi być ciągiem znaków.", + "timezone" => "Pole :attribute musi być prawidłową strefą czasową.", + "unique" => "Taki :attribute już występuje.", + "uploaded" => "Nie udało się wgrać pliku :attribute.", + "url" => "Format pola :attribute jest nieprawidłowy.", + "uuid" => "Pole :attribute musi być poprawnym identyfikatorem UUID.", + + "custom" => [ + "school_id" => [ + "required" => "Pole :attribute jest wymagane.", + "exists" => "Szkoła nie istnieje. Sprawdź ponownie.", + ], + "surname" => [ + "required" => "Pole :attribute jest wymagane.", + "max" => "Pole :attribute nie może być dłuższe niż :max znaków.", + ], + "name" => [ + "required" => "Pole :attribute jest wymagane.", + "max" => "Pole :attribute nie może być dłuższe niż :max znaków.", + ], + ], + + "attributes" => [ + "name" => "imię", + "surname" => "nazwisko", + "email" => "e-mail", + "date" => "data", + "password" => "hasło", + "school_id" => "szkoła", + ], +]; diff --git a/resources/js/Pages/Auth/Forgot-Password.vue b/resources/js/Pages/Auth/ForgotPassword.vue similarity index 100% rename from resources/js/Pages/Auth/Forgot-Password.vue rename to resources/js/Pages/Auth/ForgotPassword.vue diff --git a/resources/js/Pages/Auth/Reset-Password.vue b/resources/js/Pages/Auth/ResetPassword.vue similarity index 100% rename from resources/js/Pages/Auth/Reset-Password.vue rename to resources/js/Pages/Auth/ResetPassword.vue diff --git a/resources/js/Pages/Auth/Verify-Email.vue b/resources/js/Pages/Auth/Verify-Email.vue deleted file mode 100644 index 14a9dc2b..00000000 --- a/resources/js/Pages/Auth/Verify-Email.vue +++ /dev/null @@ -1,11 +0,0 @@ -<script setup lang="ts"> - -</script> - -<template> - Weryfikacja Maila -</template> - -<style scoped> - -</style> diff --git a/tests/Feature/AuthenticateSessionTest.php b/tests/Feature/AuthenticateSessionTest.php index 2fabcd28..5b22054a 100644 --- a/tests/Feature/AuthenticateSessionTest.php +++ b/tests/Feature/AuthenticateSessionTest.php @@ -39,7 +39,7 @@ public function testUserCanNotLoginWithWrongEmail(): void $this->from("/test")->post("/auth/login", [ "email" => "test", "password" => "wrongPasswordExample", - ])->assertRedirect("/test")->assertSessionHasErrors(["email" => "Your email is invalid."]); + ])->assertRedirect("/test")->assertSessionHasErrors(["email" => "Pole e-mail nie jest poprawnym adresem e-mail."]); } public function testUserCanNotLoginWithEmptyEmailAndPassword(): void @@ -49,6 +49,6 @@ public function testUserCanNotLoginWithEmptyEmailAndPassword(): void $this->from("/test")->post("/auth/login", [ "email" => null, "password" => null, - ])->assertRedirect("/test")->assertSessionHasErrors(["email" => "The email field is required.", "password" => "The password field is required."]); + ])->assertRedirect("/test")->assertSessionHasErrors(["email" => "Pole e-mail jest wymagane.", "password" => "Pole hasło jest wymagane."]); } } diff --git a/tests/Feature/ForgotPasswordTest.php b/tests/Feature/ForgotPasswordTest.php index 5a54c377..7dfafd49 100644 --- a/tests/Feature/ForgotPasswordTest.php +++ b/tests/Feature/ForgotPasswordTest.php @@ -40,17 +40,17 @@ public function testUserCanNotSendForgotPasswordRequestWithWrongEmail(): void $this->post("/auth/forgot-password", [ "email" => "wrongTest@gmail.com", - ])->assertSessionHasErrors(["email" => "We can't find a user with that email address."]); + ])->assertSessionHasErrors(["email" => "Nie znaleziono użytkownika z takim adresem e-mail."]); Notification::assertNothingSent(); $this->post("/auth/forgot-password", [ "email" => "wrongTest", - ])->assertSessionHasErrors(["email" => "Your email is invalid."]); + ])->assertSessionHasErrors(["email" => "Pole e-mail nie jest poprawnym adresem e-mail."]); Notification::assertNothingSent(); $this->post("/auth/forgot-password", [ "email" => null, - ])->assertSessionHasErrors(["email" => "The email field is required."]); + ])->assertSessionHasErrors(["email" => "Pole e-mail jest wymagane."]); Notification::assertNothingSent(); } } diff --git a/tests/Feature/RegisterUserTest.php b/tests/Feature/RegisterUserTest.php index 24b7f498..6d085c2e 100644 --- a/tests/Feature/RegisterUserTest.php +++ b/tests/Feature/RegisterUserTest.php @@ -52,7 +52,7 @@ public function testUserCanNotRegisterWithAlreadyTakenEmail(): void "password" => "123456890", "school_id" => $school->id, ])->assertRedirect("/") - ->assertSessionHasErrors(["email" => "The email has already been taken."]); + ->assertSessionHasErrors(["email" => "Taki e-mail już występuje."]); } public function testUserCanNotRegisterWithWrongSchoolIndex(): void @@ -66,7 +66,7 @@ public function testUserCanNotRegisterWithWrongSchoolIndex(): void "password" => "123456890", "school_id" => $school->id + 99999, ])->assertRedirect("/") - ->assertSessionHasErrors(["school_id" => "Your school is invalid. Check it again."]); + ->assertSessionHasErrors(["school_id" => "Szkoła nie istnieje. Sprawdź ponownie."]); } public function testUserCanNotRegisterWithTooLongEmail(): void @@ -80,7 +80,7 @@ public function testUserCanNotRegisterWithTooLongEmail(): void "email" => $longMail . "@gmail.com", "password" => "123456890", "school_id" => $school->id, - ])->assertRedirect("/")->assertSessionHasErrors(["email" => "Your email is too long. It must not be greater than 255 characters."]); + ])->assertRedirect("/")->assertSessionHasErrors(["email" => "Pole e-mail nie może być dłuższe niż 255 znaków."]); } public function testUserCanNotRegisterWithWrongEmailDomain(): void @@ -93,7 +93,7 @@ public function testUserCanNotRegisterWithWrongEmailDomain(): void "email" => "test@gmail.pl", "password" => "123456890", "school_id" => $school->id, - ])->assertRedirect("/")->assertSessionHasErrors(["email" => "Your email is invalid."]); + ])->assertRedirect("/")->assertSessionHasErrors(["email" => "Pole e-mail nie jest poprawnym adresem e-mail."]); } public function testUserCanNotRegisterWithTooLongName(): void @@ -107,7 +107,7 @@ public function testUserCanNotRegisterWithTooLongName(): void "email" => "test@gmail.com", "password" => "123456890", "school_id" => $school->id, - ])->assertRedirect("/")->assertSessionHasErrors(["name" => "Your name is too long. It must not be greater than 255 characters."]); + ])->assertRedirect("/")->assertSessionHasErrors(["name" => "Pole imię nie może być dłuższe niż 255 znaków."]); } public function testUserCanNotRegisterWithTooLongSurname(): void @@ -121,7 +121,7 @@ public function testUserCanNotRegisterWithTooLongSurname(): void "email" => "test@gmail.com", "password" => "123456890", "school_id" => $school->id, - ])->assertRedirect("/")->assertSessionHasErrors(["surname" => "Your surname is too long. It must not be greater than 255 characters."]); + ])->assertRedirect("/")->assertSessionHasErrors(["surname" => "Pole nazwisko nie może być dłuższe niż 255 znaków."]); } public function testUserCanNotRegisterWithTooShortPassword(): void @@ -134,6 +134,6 @@ public function testUserCanNotRegisterWithTooShortPassword(): void "email" => "test@gmail.com", "password" => "123", "school_id" => $school->id, - ])->assertRedirect("/")->assertSessionHasErrors(["password" => "Your password is too short. It must be at least 8 characters."]); + ])->assertRedirect("/")->assertSessionHasErrors(["password" => "Pole hasło musi mieć przynajmniej 8 znaków."]); } } diff --git a/tests/Feature/ResetPasswordTest.php b/tests/Feature/ResetPasswordTest.php index d048bb90..b51bdc22 100644 --- a/tests/Feature/ResetPasswordTest.php +++ b/tests/Feature/ResetPasswordTest.php @@ -111,7 +111,7 @@ public function testUserCannotResetPasswordWithMismatchedPasswords(): void ]); $response->assertRedirect(); - $response->assertSessionHasErrors(["password" => trans("validation.confirmed", ["attribute" => "password"])]); + $response->assertSessionHasErrors(["password" => trans("validation.confirmed", ["attribute" => "hasło"])]); $this->assertTrue(Hash::check("oldPassword", $user->fresh()->password)); } From f52cabfb1da53760c8742ef19008bf8f4d95eeb9 Mon Sep 17 00:00:00 2001 From: Dominik Prabucki <dominikprabucki.2002@gmail.com> Date: Mon, 19 Aug 2024 11:22:59 +0200 Subject: [PATCH 19/33] Fix email error message for existing email addresses --- app/Http/Controllers/PasswordResetLinkController.php | 4 ++-- app/Http/Controllers/RegisterUserController.php | 9 ++++++--- app/Http/Requests/Auth/RegisterUserRequest.php | 3 +-- resources/js/Pages/Auth/ForgotPassword.vue | 8 ++++++-- tests/Feature/AuthenticateSessionTest.php | 8 ++++---- tests/Feature/ForgotPasswordTest.php | 4 ++-- tests/Feature/RegisterUserTest.php | 7 +++---- 7 files changed, 24 insertions(+), 19 deletions(-) diff --git a/app/Http/Controllers/PasswordResetLinkController.php b/app/Http/Controllers/PasswordResetLinkController.php index ff77b96f..f9df0564 100644 --- a/app/Http/Controllers/PasswordResetLinkController.php +++ b/app/Http/Controllers/PasswordResetLinkController.php @@ -19,7 +19,7 @@ class PasswordResetLinkController extends Controller { public function create(): Response { - return Inertia::render("Auth/ForgotPassword"); + return Inertia::render("Auth/ForgotPassword", ["status" => session()->get("status")]); } public function store(ForgotPasswordRequest $request): RedirectResponse @@ -32,7 +32,7 @@ public function store(ForgotPasswordRequest $request): RedirectResponse return $status === Password::RESET_LINK_SENT ? back()->with(["status" => __($status)]) - : back()->withErrors(["email" => __($status)]); + : back()->withErrors(["email" => __("passwords.sent")]); } public function resetCreate(string $token): Response diff --git a/app/Http/Controllers/RegisterUserController.php b/app/Http/Controllers/RegisterUserController.php index e4112562..10c448a2 100644 --- a/app/Http/Controllers/RegisterUserController.php +++ b/app/Http/Controllers/RegisterUserController.php @@ -23,12 +23,15 @@ public function create(): Response public function store(RegisterUserRequest $request): RedirectResponse { + $userExists = User::query()->where("email", $request->email)->exists(); $user = new User($request->validated()); $user->password = Hash::make($request->password); - $user->save(); - event(new Registered($user)); - Auth::login($user); + if (!$userExists) { + $user->save(); + event(new Registered($user)); + Auth::login($user); + } return Redirect::route("home"); } diff --git a/app/Http/Requests/Auth/RegisterUserRequest.php b/app/Http/Requests/Auth/RegisterUserRequest.php index 91792fef..a86fa65c 100644 --- a/app/Http/Requests/Auth/RegisterUserRequest.php +++ b/app/Http/Requests/Auth/RegisterUserRequest.php @@ -4,7 +4,6 @@ namespace App\Http\Requests\Auth; -use App\Models\User; use Illuminate\Foundation\Http\FormRequest; class RegisterUserRequest extends FormRequest @@ -17,7 +16,7 @@ public function authorize(): bool public function rules(): array { return [ - "email" => "required|string|email:rfc,dns|max:255|unique:" . User::class, + "email" => "required|string|email:rfc,dns|max:255", "name" => "required|string|max:255", "surname" => "required|string|max:255", "password" => "required|string|min:8", diff --git a/resources/js/Pages/Auth/ForgotPassword.vue b/resources/js/Pages/Auth/ForgotPassword.vue index 19a26513..9762a0b0 100644 --- a/resources/js/Pages/Auth/ForgotPassword.vue +++ b/resources/js/Pages/Auth/ForgotPassword.vue @@ -2,10 +2,13 @@ import { reactive } from 'vue' import { router } from '@inertiajs/vue3' -const { errors } = defineProps<{ +const { errors, status } = defineProps<{ errors: Record<string, string[]> + status?: string }>() +console.log(status) + const form = reactive({ email: null, @@ -21,8 +24,9 @@ function submit() { <div> <label for="email">Email:</label> <input v-model="form.email" name="email" type="email"> + <button type="submit">Reset?</button> <div v-if="errors.email">{{ errors.email }}</div> </div> - <button type="submit">Reset?</button> + <div v-if="status" class="alert alert-success">{{ status }}</div> </form> </template> diff --git a/tests/Feature/AuthenticateSessionTest.php b/tests/Feature/AuthenticateSessionTest.php index 5b22054a..86a62bef 100644 --- a/tests/Feature/AuthenticateSessionTest.php +++ b/tests/Feature/AuthenticateSessionTest.php @@ -14,7 +14,7 @@ class AuthenticateSessionTest extends TestCase public function testUserCanLogin(): void { - $user = User::factory()->create(["email" => "test@example.com", "password" => "goodPassword"]); + User::factory()->create(["email" => "test@example.com", "password" => "goodPassword"]); $this->post("/auth/login", [ "email" => "test@example.com", @@ -24,7 +24,7 @@ public function testUserCanLogin(): void public function testUserCanNotLoginWithWrongPassword(): void { - $user = User::factory()->create(["email" => "test@example.com", "password" => "goodPassword"]); + User::factory()->create(["email" => "test@example.com", "password" => "goodPassword"]); $this->from("/test")->post("/auth/login", [ "email" => "test@example.com", @@ -34,7 +34,7 @@ public function testUserCanNotLoginWithWrongPassword(): void public function testUserCanNotLoginWithWrongEmail(): void { - $user = User::factory()->create(["email" => "test@example.com", "password" => "goodPassword"]); + User::factory()->create(["email" => "test@example.com", "password" => "goodPassword"]); $this->from("/test")->post("/auth/login", [ "email" => "test", @@ -44,7 +44,7 @@ public function testUserCanNotLoginWithWrongEmail(): void public function testUserCanNotLoginWithEmptyEmailAndPassword(): void { - $user = User::factory()->create(["email" => "test@example.com", "password" => "goodPassword"]); + User::factory()->create(["email" => "test@example.com", "password" => "goodPassword"]); $this->from("/test")->post("/auth/login", [ "email" => null, diff --git a/tests/Feature/ForgotPasswordTest.php b/tests/Feature/ForgotPasswordTest.php index 7dfafd49..32f887ba 100644 --- a/tests/Feature/ForgotPasswordTest.php +++ b/tests/Feature/ForgotPasswordTest.php @@ -36,11 +36,11 @@ public function testUserCanSendForgotPasswordRequest(): void public function testUserCanNotSendForgotPasswordRequestWithWrongEmail(): void { Notification::fake(); - $user = User::factory()->create(["email" => "test@gmail.com"]); + User::factory()->create(["email" => "test@gmail.com"]); $this->post("/auth/forgot-password", [ "email" => "wrongTest@gmail.com", - ])->assertSessionHasErrors(["email" => "Nie znaleziono użytkownika z takim adresem e-mail."]); + ])->assertSessionHasErrors(["email" => "Przypomnienie hasła zostało wysłane!"]); Notification::assertNothingSent(); $this->post("/auth/forgot-password", [ diff --git a/tests/Feature/RegisterUserTest.php b/tests/Feature/RegisterUserTest.php index 6d085c2e..184d93c2 100644 --- a/tests/Feature/RegisterUserTest.php +++ b/tests/Feature/RegisterUserTest.php @@ -38,10 +38,10 @@ public function testUserCanRegister(): void ]); } - public function testUserCanNotRegisterWithAlreadyTakenEmail(): void + public function testUserCanNotCheckIfEmailIsAlreadyTakenViaRegisterForm(): void { $school = School::factory()->create(); - $user = User::factory()->create([ + User::factory()->create([ "email" => "test@gmail.com", ]); @@ -51,8 +51,7 @@ public function testUserCanNotRegisterWithAlreadyTakenEmail(): void "email" => "test@gmail.com", "password" => "123456890", "school_id" => $school->id, - ])->assertRedirect("/") - ->assertSessionHasErrors(["email" => "Taki e-mail już występuje."]); + ])->assertRedirect("/"); } public function testUserCanNotRegisterWithWrongSchoolIndex(): void From 231188e02f60e486820749d4138ff199f1412666 Mon Sep 17 00:00:00 2001 From: Dominik Prabucki <dominikprabucki.2002@gmail.com> Date: Mon, 19 Aug 2024 15:08:35 +0200 Subject: [PATCH 20/33] Link frontend with backend Add SchoolResource Remove unused route and pages --- .../AuthenticateSessionController.php | 7 --- app/Http/Controllers/ContestController.php | 6 +- .../Controllers/RegisterUserController.php | 7 --- .../Requests/Auth/ForgotPasswordRequest.php | 2 +- app/Http/Resources/SchoolResource.php | 22 +++++++ resources/js/Pages/Auth/Login.vue | 35 ----------- resources/js/Pages/Auth/Register.vue | 51 --------------- resources/js/Pages/Home.vue | 10 ++- resources/js/Pages/Welcome.vue | 7 --- resources/js/Types/School.d.ts | 4 ++ resources/js/components/Common/Searchbar.vue | 34 +++++----- resources/js/components/Home/AuthSection.vue | 11 +++- resources/js/components/Home/LoginForm.vue | 32 ++++++++-- resources/js/components/Home/RegisterForm.vue | 62 +++++++++++-------- routes/web.php | 2 - 15 files changed, 132 insertions(+), 160 deletions(-) create mode 100644 app/Http/Resources/SchoolResource.php delete mode 100644 resources/js/Pages/Auth/Login.vue delete mode 100644 resources/js/Pages/Auth/Register.vue delete mode 100644 resources/js/Pages/Welcome.vue create mode 100644 resources/js/Types/School.d.ts diff --git a/app/Http/Controllers/AuthenticateSessionController.php b/app/Http/Controllers/AuthenticateSessionController.php index 831ba41e..3f9795ea 100644 --- a/app/Http/Controllers/AuthenticateSessionController.php +++ b/app/Http/Controllers/AuthenticateSessionController.php @@ -8,16 +8,9 @@ use Illuminate\Http\RedirectResponse; use Illuminate\Support\Facades\Redirect; use Illuminate\Validation\ValidationException; -use Inertia\Inertia; -use Inertia\Response; class AuthenticateSessionController extends Controller { - public function create(): Response - { - return Inertia::render("Auth/Login"); - } - /** * @throws ValidationException */ diff --git a/app/Http/Controllers/ContestController.php b/app/Http/Controllers/ContestController.php index 82b2c5e9..cb69b6aa 100644 --- a/app/Http/Controllers/ContestController.php +++ b/app/Http/Controllers/ContestController.php @@ -4,6 +4,8 @@ namespace App\Http\Controllers; +use App\Http\Resources\SchoolResource; +use App\Models\School; use Inertia\Inertia; use Inertia\Response; @@ -11,6 +13,8 @@ class ContestController extends Controller { public function index(): Response { - return Inertia::render("Welcome"); + $schools = School::all(); + + return Inertia::render("Home", ["schools" => SchoolResource::collection($schools)]); } } diff --git a/app/Http/Controllers/RegisterUserController.php b/app/Http/Controllers/RegisterUserController.php index 10c448a2..f4ec5d20 100644 --- a/app/Http/Controllers/RegisterUserController.php +++ b/app/Http/Controllers/RegisterUserController.php @@ -11,16 +11,9 @@ use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Redirect; -use Inertia\Inertia; -use Inertia\Response; class RegisterUserController extends Controller { - public function create(): Response - { - return Inertia::render("Auth/Register"); - } - public function store(RegisterUserRequest $request): RedirectResponse { $userExists = User::query()->where("email", $request->email)->exists(); diff --git a/app/Http/Requests/Auth/ForgotPasswordRequest.php b/app/Http/Requests/Auth/ForgotPasswordRequest.php index c0f61083..67451768 100644 --- a/app/Http/Requests/Auth/ForgotPasswordRequest.php +++ b/app/Http/Requests/Auth/ForgotPasswordRequest.php @@ -16,7 +16,7 @@ public function authorize(): bool public function rules(): array { return [ - "email" => "required|string|email:dns|max:255", + "email" => "required|string|email|max:255", ]; } } diff --git a/app/Http/Resources/SchoolResource.php b/app/Http/Resources/SchoolResource.php new file mode 100644 index 00000000..d2b86956 --- /dev/null +++ b/app/Http/Resources/SchoolResource.php @@ -0,0 +1,22 @@ +<?php + +declare(strict_types=1); + +namespace App\Http\Resources; + +use Illuminate\Http\Request; +use Illuminate\Http\Resources\Json\JsonResource; + +class SchoolResource extends JsonResource +{ + /** + * @return array<string, mixed> + */ + public function toArray(Request $request): array + { + return [ + "id" => $this->id, + "name" => $this->name, + ]; + } +} diff --git a/resources/js/Pages/Auth/Login.vue b/resources/js/Pages/Auth/Login.vue deleted file mode 100644 index 35580000..00000000 --- a/resources/js/Pages/Auth/Login.vue +++ /dev/null @@ -1,35 +0,0 @@ -<script setup lang="ts"> -import { reactive } from 'vue' -import { router } from '@inertiajs/vue3' - -const { errors } = defineProps<{ - errors: Record<string, string[]> -}>() - -const form = reactive({ - email: null, - password: null, -}) - -function submit() { - router.post('/auth/login', form) -} -</script> - -<template> - <form @submit.prevent="submit"> - <div> - <label for="email">Email:</label> - <input v-model="form.email" name="email" type="email"> - <div v-if="errors.email">{{ errors.email }}</div> - </div> - <div> - <label for="password">password:</label> - <input v-model="form.password" name="password" type="password"> - <div v-if="errors.password">{{ errors.password }}</div> - </div> - <button type="submit">Login</button> - </form> - - <a href="/auth/forgot-password">I forgot the password :(</a> -</template> diff --git a/resources/js/Pages/Auth/Register.vue b/resources/js/Pages/Auth/Register.vue deleted file mode 100644 index d2a566e6..00000000 --- a/resources/js/Pages/Auth/Register.vue +++ /dev/null @@ -1,51 +0,0 @@ -<script setup lang="ts"> -import { reactive } from 'vue' -import { router } from '@inertiajs/vue3' - -const { errors } = defineProps<{ - errors: Record<string, string[]> -}>() - -const form = reactive({ - name: null, - surname: null, - email: null, - password: null, - school_id: null, -}) - -function submit() { - router.post('/auth/register', form) -} -</script> - -<template> - <form @submit.prevent="submit"> - <div> - <label for="name">Name:</label> - <input v-model="form.name" name="name" type="text"> - <div v-if="errors.name">{{ errors.name }}</div> - </div> - <div> - <label for="surname">Surname:</label> - <input v-model="form.surname" name="surname" type="text"> - <div v-if="errors.surname">{{ errors.surname }}</div> - </div> - <div> - <label for="email">Email:</label> - <input v-model="form.email" name="email" type="email"> - <div v-if="errors.email">{{ errors.email }}</div> - </div> - <div> - <label for="password">password:</label> - <input v-model="form.password" name="password" type="password"> - <div v-if="errors.password">{{ errors.password }}</div> - </div> - <div> - <label for="school_id">School ID:</label> - <input v-model="form.school_id" name="school_id" type="number"> - <div v-if="errors.school_id">{{ errors.school_id }}</div> - </div> - <button type="submit">Register</button> - </form> -</template> diff --git a/resources/js/Pages/Home.vue b/resources/js/Pages/Home.vue index 329ce6ae..49242d42 100644 --- a/resources/js/Pages/Home.vue +++ b/resources/js/Pages/Home.vue @@ -5,8 +5,16 @@ import AuthBanner from '@/components/Home/AuthBanner.vue' import GeneralSection from '@/components/Home/GeneralSection.vue' import AuthSection from '@/components/Home/AuthSection.vue' import BackgroundEffect from '@/components/Home/BackgroundEffect.vue' +import type {School} from '@/Types/School' const isLogin = ref(null) + +const { errors, schools } = defineProps<{ + errors: Record<string, string[]> + schools: School[] +}>() + + provide('isLoginRef', isLogin) </script> @@ -16,7 +24,7 @@ provide('isLoginRef', isLogin) <AuthBanner :is-login="isLogin" /> <div class="flex flex-col lg:flex-row lg:justify-evenly lg:gap-x-[5vw] lg:px-[5vw]"> <GeneralSection /> - <AuthSection ref="isLogin" /> + <AuthSection ref="isLogin" :errors="errors" :schools="schools" /> </div> <Footer /> </div> diff --git a/resources/js/Pages/Welcome.vue b/resources/js/Pages/Welcome.vue deleted file mode 100644 index 0a9c3838..00000000 --- a/resources/js/Pages/Welcome.vue +++ /dev/null @@ -1,7 +0,0 @@ -<script setup> -</script> - -<template> - <a href="/auth/login">Login</a><br> - <a href="/auth/register">Register</a> -</template> diff --git a/resources/js/Types/School.d.ts b/resources/js/Types/School.d.ts new file mode 100644 index 00000000..a73fce39 --- /dev/null +++ b/resources/js/Types/School.d.ts @@ -0,0 +1,4 @@ +export interface School { + id: number + name: string +} diff --git a/resources/js/components/Common/Searchbar.vue b/resources/js/components/Common/Searchbar.vue index ff6cbb25..16fd09a6 100644 --- a/resources/js/components/Common/Searchbar.vue +++ b/resources/js/components/Common/Searchbar.vue @@ -1,20 +1,22 @@ <script lang="ts" setup> import {computed, ref, defineProps} from 'vue' +import {type School} from '@/Types/School' const isSearchFocused = ref(false) const searchQuery = ref('') -const props = defineProps({options: {type: Object, default:()=>{}}}) -const filteredSchools: any = computed( - () => { - return props.options.filter( - (obj:any) => obj.option.toString().includes(searchQuery.value), - ) - }, -) -const selectedOption = ref('') +const props = defineProps<{ + schools: School[] +}>() -const onOptionClick = (obj:any)=>{ - selectedOption.value = obj.option +const filteredSchools = computed(() => props.schools.filter(({ name }) => name.includes(searchQuery.value))) +const selected = ref<School>() + +const emit = defineEmits(['change']) + +const onOptionClick = (school:School)=>{ + selected.value = school isSearchFocused.value = false + + emit('change', school.id) } </script> @@ -28,12 +30,12 @@ const onOptionClick = (obj:any)=>{ </div> <input - :value="isSearchFocused ? searchQuery : selectedOption" + :value="isSearchFocused ? searchQuery : selected?.name" class="outline-none py-3 bg-transparent w-full text-gray-900" - required name="search-schools" :class="{'cursor-pointer' : !isSearchFocused}" + required name="school_id" :class="{'cursor-pointer' : !isSearchFocused}" type="text" - :placeholder="selectedOption" - @input="(e:any)=>searchQuery=e.target.value" + :placeholder="selected?.name" + @input="(e:any)=>searchQuery=e.currentTarget.value" @focus="isSearchFocused=true" @blur="isSearchFocused=false" > @@ -46,7 +48,7 @@ const onOptionClick = (obj:any)=>{ :key="obj.id" class="cursor-pointer block px-4 py-2 hover:bg-primary/10 text-[0.9rem]" @mousedown="onOptionClick(obj)" - >{{ obj.option }}</span> + >{{ obj.name }}</span> </div> <span v-else class="block px-4 py-2 text-sm"> Nie znaleziono szkoły diff --git a/resources/js/components/Home/AuthSection.vue b/resources/js/components/Home/AuthSection.vue index 8bb75bf8..fca474bc 100644 --- a/resources/js/components/Home/AuthSection.vue +++ b/resources/js/components/Home/AuthSection.vue @@ -3,8 +3,15 @@ import { ref, defineExpose } from 'vue' import AuthButton from '@/components/Common/AuthButton.vue' import RegisterForm from '@/components/Home/RegisterForm.vue' import LoginForm from '@/components/Home/LoginForm.vue' +import type {School} from '@/Types/School' const isLogin = ref(false) defineExpose({ isLogin }) + +const { errors, schools } = defineProps<{ + errors: Record<string, string[]> + schools: School[] +}>() + </script> <template> @@ -13,8 +20,8 @@ defineExpose({ isLogin }) <AuthButton v-model:is-login="isLogin" /> <div class="grid"> <Transition :name="isLogin ? 'slide-right' : 'slide-left'"> - <LoginForm v-if="isLogin" key="on" /> - <RegisterForm v-else key="off" /> + <LoginForm v-if="isLogin" key="on" :errors /> + <RegisterForm v-else key="off" :errors="errors" :schools="schools" /> </Transition> </div> </div> diff --git a/resources/js/components/Home/LoginForm.vue b/resources/js/components/Home/LoginForm.vue index 0b2e3973..615d1122 100644 --- a/resources/js/components/Home/LoginForm.vue +++ b/resources/js/components/Home/LoginForm.vue @@ -1,11 +1,32 @@ +<script setup lang="ts"> +import { reactive } from 'vue' +import { router } from '@inertiajs/vue3' + +const { errors } = defineProps<{ + errors: Record<string, string[]> +}>() + +const form = reactive({ + email: null, + password: null, +}) + +function submit() { + router.post('/auth/login', form) +} + + +</script> + <template> - <form class="row-start-1 col-start-1 space-y-6" method="POST" @submit.prevent=""> + <form class="row-start-1 col-start-1 space-y-6" method="POST" @submit.prevent="submit"> <div> <label for="email" class="block text-sm font-medium leading-6 text-gray-900">E-mail</label> <div class="mt-2"> - <input id="email" required name="email" type="email" autocomplete="email" + <input id="email" v-model="form.email" required name="email" type="email" autocomplete="email" class="duration-200 ring-inset outline-none focus:ring focus:ring-primary bg-white/30 rounded-lg w-full p-3 text-gray-900 ring-2 ring-primary/30 placeholder:text-gray-400" > + <div v-if="errors.email" class="text-red">{{ errors.email }}</div> </div> </div> @@ -14,15 +35,16 @@ <label for="password" class="block text-sm font-medium leading-6 text-gray-900">Hasło</label> </div> <div class="mt-2"> - <input id="password" required name="password" type="password" autocomplete="current-password" + <input id="password" v-model="form.password" required name="password" type="password" autocomplete="current-password" class="duration-200 ring-inset outline-none focus:ring focus:ring-primary bg-white/30 rounded-lg w-full p-3 text-gray-900 ring-2 ring-primary/30 placeholder:text-gray-400" > + <div v-if="errors.password" class="text-red">{{ errors.password }}</div> </div> <div class="mt-2"> - <a href="#" class="duration-200 font-semibold leading-6 text-primary hover:text-primary-950 text-sm">Nie pamiętam hasła</a> + <a href="/auth/forgot-password" class="duration-200 font-semibold leading-6 text-primary hover:text-primary-950 text-sm">Nie pamiętam hasła</a> </div> </div> - + <div> <button type="submit" class=" rounded-lg text-md flex w-full justify-center bg-primary p-3 font-bold text-white transition hover:bg-primary-950 diff --git a/resources/js/components/Home/RegisterForm.vue b/resources/js/components/Home/RegisterForm.vue index a0731826..049e3443 100644 --- a/resources/js/components/Home/RegisterForm.vue +++ b/resources/js/components/Home/RegisterForm.vue @@ -1,56 +1,67 @@ <script lang="ts" setup> -import {ref} from 'vue' +import { reactive } from 'vue' import Checkbox from '@/components/Common/Checkbox.vue' import Searchbar from '@/components/Common/Searchbar.vue' +import { router } from '@inertiajs/vue3' +import { type School } from '@/Types/School' -// example options for schools school -const searchbarOptions = ref([ - {id: 0, option: 'Szkoła #1'}, - {id: 1, option: 'Szkoła #2'}, - {id: 2, option: 'Szkoła #3'}, - {id: 3, option: 'Szkoła #4'}, - {id: 4, option: 'Szkoła #5'}, - {id: 5, option: 'Szkoła #6'}, - {id: 6, option: 'Szkoła #7'}, - {id: 7, option: 'Szkoła #8'}, -]) +const { errors, schools } = defineProps<{ + errors: Record<string, string[]> + schools: School[] +}>() + +const form = reactive({ + name: null, + surname: null, + email: null, + password: null, + school_id: null, +}) + +function submit() { + router.post('/auth/register', form) +} </script> <template> - <form class="row-start-1 col-start-1 space-y-6" method="POST" @submit.prevent=""> + <form class="row-start-1 col-start-1 space-y-6" @submit.prevent="submit"> <div class="flex flex-col gap-6 sm:flex-row"> <div class="w-full"> <label for="name" class="text-sm font-medium leading-6 text-gray-900">Imię</label> <div class="mt-2 w-full"> - <input id="name" required name="name" type="text" - class="duration-200 ring-inset outline-none focus:ring focus:ring-primary bg-white/30 rounded-lg w-full p-3 text-gray-900 ring-2 ring-primary/30 placeholder:text-gray-400" + <input id="name" v-model="form.name" required name="name" type="text" + :class="{'ring-red focus:ring-red':errors.name}" class="duration-200 ring-inset outline-none focus:ring focus:ring-primary bg-white/30 rounded-lg w-full p-3 text-gray-900 ring-2 ring-primary/30 placeholder:text-gray-400" > + <div v-if="errors.name" class="text-red">{{ errors.name }}</div> </div> </div> <div class="w-full"> <label for="surname" class="text-sm font-medium leading-6 text-gray-900">Nazwisko</label> <div class="mt-2 w-full"> - <input id="surname" required name="surname" type="text" - class="duration-200 ring-inset outline-none focus:ring focus:ring-primary bg-white/30 rounded-lg w-full p-3 text-gray-900 ring-2 ring-primary/30 placeholder:text-gray-400" + <input id="surname" v-model="form.surname" required name="surname" type="text" + :class="{'ring-red focus:ring-red':errors.surname}" class="duration-200 ring-inset outline-none focus:ring focus:ring-primary bg-white/30 rounded-lg w-full p-3 text-gray-900 ring-2 ring-primary/30 placeholder:text-gray-400" > + <div v-if="errors.surname" class="text-red">{{ errors.surname }}</div> </div> </div> </div> <div> <div class="flex items-center justify-between"> - <label for="search-schools" class="block text-sm font-medium leading-6 text-gray-900">Szkoła</label> + <label for="school_id" class="block text-sm font-medium leading-6 text-gray-900">Szkoła</label> </div> - <Searchbar :options="searchbarOptions" /> + <Searchbar :schools="schools" @change="school => form.school_id = school" /> + <div v-if="errors.school_id" class="text-red">{{ errors.school_id }}</div> </div> <div> <label for="email" class="block text-sm font-medium leading-6 text-gray-900">E-mail</label> <div class="mt-2"> - <input id="email" required name="email" type="email" autocomplete="email" - class="duration-200 ring-inset outline-none focus:ring focus:ring-primary bg-white/30 rounded-lg w-full p-3 text-gray-900 ring-2 ring-primary/30 placeholder:text-gray-400" + <input id="email" v-model="form.email" required name="email" type="email" autocomplete="email" + :class="{'ring-red focus:ring-red':errors.email}" class="duration-200 ring-inset outline-none focus:ring focus:ring-primary bg-white/30 rounded-lg w-full p-3 text-gray-900 ring-2 ring-primary/30 placeholder:text-gray-400" > + <div v-if="errors.email" class="text-red">{{ errors.email }}</div> </div> </div> @@ -59,9 +70,10 @@ const searchbarOptions = ref([ <label for="password" class="block text-sm font-medium leading-6 text-gray-900">Hasło</label> </div> <div class="mt-2"> - <input id="password" required name="password" type="password" autocomplete="current-password" - class="duration-200 ring-inset outline-none focus:ring focus:ring-primary bg-white/30 rounded-lg w-full p-3 text-gray-900 ring-2 ring-primary/30 placeholder:text-gray-400" + <input id="password" v-model="form.password" required name="password" type="password" autocomplete="current-password" + :class="{'ring-red focus:ring-red':errors.password}" class="duration-200 ring-inset outline-none focus:ring focus:ring-primary bg-white/30 rounded-lg w-full p-3 text-gray-900 ring-2 ring-primary/30 placeholder:text-gray-400" > + <div v-if="errors.password" class="text-red">{{ errors.password }}</div> </div> </div> @@ -92,9 +104,9 @@ const searchbarOptions = ref([ } ::-webkit-scrollbar-track { - background: #ffffff4c; + background: #ffffff4c; } - + /* Handle */ ::-webkit-scrollbar-thumb { background: #262c8926; diff --git a/routes/web.php b/routes/web.php index afbf5b15..4546b844 100644 --- a/routes/web.php +++ b/routes/web.php @@ -15,9 +15,7 @@ use Illuminate\Support\Facades\Route; Route::get("/", [ContestController::class, "index"])->name("home"); -Route::get("/auth/register", [RegisterUserController::class, "create"])->name("register"); Route::post("/auth/register", [RegisterUserController::class, "store"])->name("register"); -Route::get("/auth/login", [AuthenticateSessionController::class, "create"])->name("login"); Route::post("/auth/login", [AuthenticateSessionController::class, "authenticate"])->name("login"); Route::get("/auth/logout", [AuthenticateSessionController::class, "logout"])->name("logout"); Route::get("/email/verify", [EmailVerifyController::class, "create"])->middleware("auth")->name("verification.notice"); From cb569baef8a70d30b49006a61f176618725c38f9 Mon Sep 17 00:00:00 2001 From: Dominik Prabucki <dominikprabucki.2002@gmail.com> Date: Tue, 20 Aug 2024 08:12:30 +0200 Subject: [PATCH 21/33] Pack routes into group middleware --- routes/web.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/routes/web.php b/routes/web.php index 4546b844..69d9a411 100644 --- a/routes/web.php +++ b/routes/web.php @@ -22,9 +22,12 @@ Route::get("/email/{id}/{hash}", EmailVerifyController::class)->middleware(["signed", "throttle:6,1"])->name("verification.verify"); Route::post("email/verification-notification", [EmailVerifyController::class, "send"])->middleware("auth", "throttle:6,1")->name("verification.send"); -Route::get("/auth/forgot-password", [PasswordResetLinkController::class, "create"])->middleware("guest")->name("password.request"); -Route::post("/auth/forgot-password", [PasswordResetLinkController::class, "store"])->middleware("guest")->name("password.email"); -Route::get("/auth/password/reset/{token}", [PasswordResetLinkController::class, "resetCreate"])->middleware("guest")->name("password.reset"); +Route::middleware(["guest"])->group(function (): void { + Route::get("/auth/forgot-password", [PasswordResetLinkController::class, "create"])->name("password.request"); + Route::post("/auth/forgot-password", [PasswordResetLinkController::class, "store"])->name("password.email"); + Route::get("/auth/password/reset/{token}", [PasswordResetLinkController::class, "resetCreate"])->name("password.reset"); +}); + Route::post("/auth/password/reset", [PasswordResetLinkController::class, "resetStore"])->name("password.update"); Route::get("/quizzes", [QuizController::class, "index"]); From b45bd2db003a4a5a492dc163b4532562929ea95f Mon Sep 17 00:00:00 2001 From: Dominik Prabucki <dominikprabucki.2002@gmail.com> Date: Tue, 20 Aug 2024 08:19:42 +0200 Subject: [PATCH 22/33] Remove empty spaces --- lang/en/validation.php | 2 -- lang/pl/validation.php | 1 - resources/js/Pages/Auth/ForgotPassword.vue | 2 -- resources/js/Pages/Auth/ResetPassword.vue | 5 ----- resources/js/Pages/Home.vue | 1 - resources/js/components/Home/LoginForm.vue | 1 - 6 files changed, 12 deletions(-) diff --git a/lang/en/validation.php b/lang/en/validation.php index d1a0e8e2..a9fb9422 100644 --- a/lang/en/validation.php +++ b/lang/en/validation.php @@ -150,12 +150,10 @@ "url" => "The :attribute field must be a valid URL.", "ulid" => "The :attribute field must be a valid ULID.", "uuid" => "The :attribute field must be a valid UUID.", - "custom" => [ "attribute-name" => [ "rule-name" => "custom-message", ], ], - "attributes" => [], ]; diff --git a/lang/pl/validation.php b/lang/pl/validation.php index 6c6ef4cd..5fa78ed0 100644 --- a/lang/pl/validation.php +++ b/lang/pl/validation.php @@ -129,7 +129,6 @@ "max" => "Pole :attribute nie może być dłuższe niż :max znaków.", ], ], - "attributes" => [ "name" => "imię", "surname" => "nazwisko", diff --git a/resources/js/Pages/Auth/ForgotPassword.vue b/resources/js/Pages/Auth/ForgotPassword.vue index 9762a0b0..6114b281 100644 --- a/resources/js/Pages/Auth/ForgotPassword.vue +++ b/resources/js/Pages/Auth/ForgotPassword.vue @@ -7,8 +7,6 @@ const { errors, status } = defineProps<{ status?: string }>() -console.log(status) - const form = reactive({ email: null, diff --git a/resources/js/Pages/Auth/ResetPassword.vue b/resources/js/Pages/Auth/ResetPassword.vue index 4183fcac..2eb4f67f 100644 --- a/resources/js/Pages/Auth/ResetPassword.vue +++ b/resources/js/Pages/Auth/ResetPassword.vue @@ -2,21 +2,17 @@ import { reactive } from 'vue' import { router } from '@inertiajs/vue3' - const props = defineProps< { token: string errors: Record<string, string[]> }>() - - const form = reactive({ email: null, password: null, password_confirmation: null, token: props.token, - }) function submit() { @@ -39,7 +35,6 @@ function submit() { <div v-if="errors.password">{{ errors.password }}</div> </div> - <div> <input v-model="form.token" name="token" type="hidden"> <div v-if="errors.token">{{ errors.token }}</div> diff --git a/resources/js/Pages/Home.vue b/resources/js/Pages/Home.vue index 49242d42..0d502cc4 100644 --- a/resources/js/Pages/Home.vue +++ b/resources/js/Pages/Home.vue @@ -14,7 +14,6 @@ const { errors, schools } = defineProps<{ schools: School[] }>() - provide('isLoginRef', isLogin) </script> diff --git a/resources/js/components/Home/LoginForm.vue b/resources/js/components/Home/LoginForm.vue index 615d1122..5e33f7f0 100644 --- a/resources/js/components/Home/LoginForm.vue +++ b/resources/js/components/Home/LoginForm.vue @@ -15,7 +15,6 @@ function submit() { router.post('/auth/login', form) } - </script> <template> From ff649cfa9064b7c70c7acbe9facec55dc53b05b4 Mon Sep 17 00:00:00 2001 From: Dominik Prabucki <dominikprabucki.2002@gmail.com> Date: Tue, 20 Aug 2024 08:19:58 +0200 Subject: [PATCH 23/33] Rename zipCode to zip_code --- app/Models/School.php | 4 ++-- database/factories/SchoolFactory.php | 2 +- .../migrations/2024_08_08_113859_create_schools_table.php | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/Models/School.php b/app/Models/School.php index 391995c8..3524f74e 100644 --- a/app/Models/School.php +++ b/app/Models/School.php @@ -15,7 +15,7 @@ * @property string $street * @property string $building_number * @property string $apartment_number - * @property string $zipCode + * @property string $zip_code * @property Carbon $created_at * @property Carbon $updated_at */ @@ -29,6 +29,6 @@ class School extends Model "street", "building_number", "apartment_number", - "zipCode", + "zip_code", ]; } diff --git a/database/factories/SchoolFactory.php b/database/factories/SchoolFactory.php index 546831d1..a4cf9f2e 100644 --- a/database/factories/SchoolFactory.php +++ b/database/factories/SchoolFactory.php @@ -25,7 +25,7 @@ public function definition(): array "street" => fake()->name(), "building_number" => fake()->name(), "apartment_number" => fake()->name(), - "zipCode" => fake()->name(), + "zip_code" => fake()->name(), ]; } diff --git a/database/migrations/2024_08_08_113859_create_schools_table.php b/database/migrations/2024_08_08_113859_create_schools_table.php index f78223b0..1178a14b 100644 --- a/database/migrations/2024_08_08_113859_create_schools_table.php +++ b/database/migrations/2024_08_08_113859_create_schools_table.php @@ -16,7 +16,7 @@ public function up(): void $table->string("street"); $table->string("building_number"); $table->string("apartment_number")->nullable(); - $table->string("zipCode"); + $table->string("zip_code"); $table->timestamps(); }); } From cdf719ad38ec1fd290ad7a07f3dec54b64354377 Mon Sep 17 00:00:00 2001 From: Dominik Prabucki <dominikprabucki.2002@gmail.com> Date: Tue, 20 Aug 2024 08:32:02 +0200 Subject: [PATCH 24/33] Remove app_locale from .env.example --- .env.example | 1 - 1 file changed, 1 deletion(-) diff --git a/.env.example b/.env.example index 5797ae1c..21ddafe5 100644 --- a/.env.example +++ b/.env.example @@ -5,7 +5,6 @@ APP_KEY=base64:sCsJw8z+d/4ymp0OvzSip2h4Vp2hZZhpV2uOxgTqP94= APP_DEBUG=true APP_URL=http://interns2024b.blumilk.localhost -APP_LOCALE=pl APP_FALLBACK_LOCALE=en APP_FAKER_LOCALE=en_US From 804f0dbaa0ee49a7dcfc885f46513f4d58cf112e Mon Sep 17 00:00:00 2001 From: Dominik Prabucki <dominikprabucki.2002@gmail.com> Date: Tue, 20 Aug 2024 08:32:34 +0200 Subject: [PATCH 25/33] Move some logic into if in RegisterUserController --- app/Http/Controllers/RegisterUserController.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/RegisterUserController.php b/app/Http/Controllers/RegisterUserController.php index f4ec5d20..7c3dd6be 100644 --- a/app/Http/Controllers/RegisterUserController.php +++ b/app/Http/Controllers/RegisterUserController.php @@ -17,10 +17,10 @@ class RegisterUserController extends Controller public function store(RegisterUserRequest $request): RedirectResponse { $userExists = User::query()->where("email", $request->email)->exists(); - $user = new User($request->validated()); - $user->password = Hash::make($request->password); if (!$userExists) { + $user = new User($request->validated()); + $user->password = Hash::make($request->password); $user->save(); event(new Registered($user)); Auth::login($user); From a48af86eb33cc019000b11a5743d8841c6c9c5fa Mon Sep 17 00:00:00 2001 From: Dominik Prabucki <dominikprabucki.2002@gmail.com> Date: Tue, 20 Aug 2024 08:32:49 +0200 Subject: [PATCH 26/33] Remove dead code --- app/Mail/RegistrationMail.php | 9 --------- 1 file changed, 9 deletions(-) diff --git a/app/Mail/RegistrationMail.php b/app/Mail/RegistrationMail.php index 78854ed8..9bde402f 100644 --- a/app/Mail/RegistrationMail.php +++ b/app/Mail/RegistrationMail.php @@ -6,7 +6,6 @@ use Illuminate\Bus\Queueable; use Illuminate\Mail\Mailable; -use Illuminate\Mail\Mailables\Attachment; use Illuminate\Mail\Mailables\Content; use Illuminate\Mail\Mailables\Envelope; use Illuminate\Queue\SerializesModels; @@ -31,12 +30,4 @@ public function content(): Content view: "emails.verify-email", ); } - - /** - * @return array<int, Attachment> - */ - public function attachments(): array - { - return []; - } } From 8d5243fa74a6a5d2b372241844981390ae6bcfe5 Mon Sep 17 00:00:00 2001 From: Dominik Prabucki <dominikprabucki.2002@gmail.com> Date: Tue, 20 Aug 2024 08:33:13 +0200 Subject: [PATCH 27/33] Fix code style --- app/Http/Requests/Auth/AuthenticateSessionRequest.php | 4 ++-- app/Http/Requests/Auth/ForgotPasswordRequest.php | 2 +- app/Http/Requests/Auth/RegisterUserRequest.php | 10 +++++----- app/Http/Requests/Auth/ResetPasswordRequest.php | 6 +++--- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/app/Http/Requests/Auth/AuthenticateSessionRequest.php b/app/Http/Requests/Auth/AuthenticateSessionRequest.php index a92bb5dc..caadae17 100644 --- a/app/Http/Requests/Auth/AuthenticateSessionRequest.php +++ b/app/Http/Requests/Auth/AuthenticateSessionRequest.php @@ -16,8 +16,8 @@ public function authorize(): bool public function rules(): array { return [ - "email" => "required|email|max:255", - "password" => "required|string", + "email" => ["required", "email", "max:255"], + "password" => ["required", "string"], ]; } } diff --git a/app/Http/Requests/Auth/ForgotPasswordRequest.php b/app/Http/Requests/Auth/ForgotPasswordRequest.php index 67451768..b86fa0bc 100644 --- a/app/Http/Requests/Auth/ForgotPasswordRequest.php +++ b/app/Http/Requests/Auth/ForgotPasswordRequest.php @@ -16,7 +16,7 @@ public function authorize(): bool public function rules(): array { return [ - "email" => "required|string|email|max:255", + "email" => ["required", "string", "email", "max:255"], ]; } } diff --git a/app/Http/Requests/Auth/RegisterUserRequest.php b/app/Http/Requests/Auth/RegisterUserRequest.php index a86fa65c..4495fc06 100644 --- a/app/Http/Requests/Auth/RegisterUserRequest.php +++ b/app/Http/Requests/Auth/RegisterUserRequest.php @@ -16,11 +16,11 @@ public function authorize(): bool public function rules(): array { return [ - "email" => "required|string|email:rfc,dns|max:255", - "name" => "required|string|max:255", - "surname" => "required|string|max:255", - "password" => "required|string|min:8", - "school_id" => "required|integer|exists:schools,id", + "email" => ["required", "string", "email:rfc,dns", "max:255"], + "name" => ["required", "string", "max:255"], + "surname" => ["required", "string", "max:255"], + "password" => ["required", "string", "min:8"], + "school_id" => ["required", "integer", "exists:schools,id"], ]; } } diff --git a/app/Http/Requests/Auth/ResetPasswordRequest.php b/app/Http/Requests/Auth/ResetPasswordRequest.php index 80e155f9..603d409a 100644 --- a/app/Http/Requests/Auth/ResetPasswordRequest.php +++ b/app/Http/Requests/Auth/ResetPasswordRequest.php @@ -16,9 +16,9 @@ public function authorize(): bool public function rules(): array { return [ - "token" => "required", - "email" => "required|email|max:255", - "password" => "required|min:8|confirmed:password_confirmation", + "token" => ["required"], + "email" => ["required", "email", "max:255"], + "password" => ["required", "min:8", "confirmed:password_confirmation"], ]; } } From a2c597d7e1f1f103a10f078ee9da25d3dc752a8a Mon Sep 17 00:00:00 2001 From: Dominik Prabucki <dominikprabucki.2002@gmail.com> Date: Tue, 20 Aug 2024 08:55:53 +0200 Subject: [PATCH 28/33] Change redirect to home after password reset --- app/Http/Controllers/PasswordResetLinkController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Controllers/PasswordResetLinkController.php b/app/Http/Controllers/PasswordResetLinkController.php index f9df0564..0d4ba169 100644 --- a/app/Http/Controllers/PasswordResetLinkController.php +++ b/app/Http/Controllers/PasswordResetLinkController.php @@ -56,7 +56,7 @@ function (User $user, string $password): void { ); return $status === Password::PASSWORD_RESET - ? redirect()->route("login")->with("status", __($status)) + ? redirect()->route("home")->with("status", __($status)) : back()->withErrors(["email" => [__($status)]]); } } From e4e4aea816547b9add76c5ceacbece2c2ac1a040 Mon Sep 17 00:00:00 2001 From: Dominik Prabucki <dominikprabucki.2002@gmail.com> Date: Tue, 20 Aug 2024 09:00:44 +0200 Subject: [PATCH 29/33] Update redirect path in ResetPasswordTest --- tests/Feature/ResetPasswordTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Feature/ResetPasswordTest.php b/tests/Feature/ResetPasswordTest.php index b51bdc22..3b0af68b 100644 --- a/tests/Feature/ResetPasswordTest.php +++ b/tests/Feature/ResetPasswordTest.php @@ -35,7 +35,7 @@ public function testUserCanResetPasswordWithValidToken(): void "password_confirmation" => "newPassword", ]); - $response->assertRedirect("/auth/login"); + $response->assertRedirect("/"); $response->assertSessionHas("status", trans(Password::PASSWORD_RESET)); $this->assertTrue(Hash::check("newPassword", $user->fresh()->password)); From 6cdcd7e91483d94bfb6f9c2e2b60432cb2daf022 Mon Sep 17 00:00:00 2001 From: Dominik Prabucki <dominikprabucki.2002@gmail.com> Date: Tue, 20 Aug 2024 10:05:46 +0200 Subject: [PATCH 30/33] Fix return message in forgot password --- app/Http/Controllers/PasswordResetLinkController.php | 2 +- tests/Feature/ForgotPasswordTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/PasswordResetLinkController.php b/app/Http/Controllers/PasswordResetLinkController.php index 0d4ba169..0bdffc80 100644 --- a/app/Http/Controllers/PasswordResetLinkController.php +++ b/app/Http/Controllers/PasswordResetLinkController.php @@ -32,7 +32,7 @@ public function store(ForgotPasswordRequest $request): RedirectResponse return $status === Password::RESET_LINK_SENT ? back()->with(["status" => __($status)]) - : back()->withErrors(["email" => __("passwords.sent")]); + : back()->with(["status" => __("passwords.sent")]); } public function resetCreate(string $token): Response diff --git a/tests/Feature/ForgotPasswordTest.php b/tests/Feature/ForgotPasswordTest.php index 32f887ba..73b6c8e2 100644 --- a/tests/Feature/ForgotPasswordTest.php +++ b/tests/Feature/ForgotPasswordTest.php @@ -40,7 +40,7 @@ public function testUserCanNotSendForgotPasswordRequestWithWrongEmail(): void $this->post("/auth/forgot-password", [ "email" => "wrongTest@gmail.com", - ])->assertSessionHasErrors(["email" => "Przypomnienie hasła zostało wysłane!"]); + ])->assertSessionHas(["status" => "Przypomnienie hasła zostało wysłane!"]); Notification::assertNothingSent(); $this->post("/auth/forgot-password", [ From 658c3a0e271071ac6c15384fbbeba5c69801d574 Mon Sep 17 00:00:00 2001 From: Dominik Prabucki <dominikprabucki.2002@gmail.com> Date: Tue, 20 Aug 2024 10:15:06 +0200 Subject: [PATCH 31/33] Add: - Schools are sortable by name in searchbar in register form --- app/Http/Controllers/ContestController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Controllers/ContestController.php b/app/Http/Controllers/ContestController.php index cb69b6aa..50793353 100644 --- a/app/Http/Controllers/ContestController.php +++ b/app/Http/Controllers/ContestController.php @@ -13,7 +13,7 @@ class ContestController extends Controller { public function index(): Response { - $schools = School::all(); + $schools = School::all()->sortBy("name"); return Inertia::render("Home", ["schools" => SchoolResource::collection($schools)]); } From 0a07083683e011943bcc4206d90a74af33016ab7 Mon Sep 17 00:00:00 2001 From: Dominik Prabucki <dominikprabucki.2002@gmail.com> Date: Tue, 20 Aug 2024 10:23:48 +0200 Subject: [PATCH 32/33] Remove : APP_FALLBACK_LOCALE and APP_FAKER_LOCALE from .env.example --- .env.example | 3 --- 1 file changed, 3 deletions(-) diff --git a/.env.example b/.env.example index 21ddafe5..b8d27577 100644 --- a/.env.example +++ b/.env.example @@ -5,9 +5,6 @@ APP_KEY=base64:sCsJw8z+d/4ymp0OvzSip2h4Vp2hZZhpV2uOxgTqP94= APP_DEBUG=true APP_URL=http://interns2024b.blumilk.localhost -APP_FALLBACK_LOCALE=en -APP_FAKER_LOCALE=en_US - APP_MAINTENANCE_DRIVER=file APP_MAINTENANCE_STORE=database From 004e4a918eccf0f55484abd895bc39cd2ad426ca Mon Sep 17 00:00:00 2001 From: Dominik Prabucki <dominikprabucki.2002@gmail.com> Date: Tue, 20 Aug 2024 11:43:34 +0200 Subject: [PATCH 33/33] Add attribute required to checkbox --- resources/js/components/Common/Checkbox.vue | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/resources/js/components/Common/Checkbox.vue b/resources/js/components/Common/Checkbox.vue index 63cf6c7a..22a9ddc6 100644 --- a/resources/js/components/Common/Checkbox.vue +++ b/resources/js/components/Common/Checkbox.vue @@ -1,10 +1,11 @@ <template> <div class="inline-flex items-center"> - <label class="relative flex items-center rounded-full cursor-pointer" htmlFor="checkbox"> - <input + <label class="relative flex items-center rounded-full cursor-pointer"> + <input id="checkbox" + required type="checkbox" - class="before:content[''] peer relative size-5 cursor-pointer appearance-none rounded-md border border-blue-gray-200 transition-all before:absolute before:top-2/4 before:left-2/4 before:block before:size-12 before:-translate-y-2/4 before:-translate-x-2/4 before:rounded-full before:bg-blue-gray-500 before:opacity-0 before:transition-opacity checked:border-primary checked:bg-primary checked:before:bg-primary hover:before:opacity-10" checked + class="before:content[''] peer relative size-5 cursor-pointer appearance-none rounded-md border border-blue-gray-200 transition-all before:absolute before:top-2/4 before:left-2/4 before:block before:size-12 before:-translate-y-2/4 before:-translate-x-2/4 before:rounded-full before:bg-blue-gray-500 before:opacity-0 before:transition-opacity checked:border-primary checked:bg-primary checked:before:bg-primary hover:before:opacity-10" > <span class="absolute text-white transition-opacity opacity-0 pointer-events-none top-2/4 left-2/4 -translate-y-2/4 -translate-x-2/4 peer-checked:opacity-100"