From e926d78d6c9077812d60a3ca3691aa6a6020ba1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Zygad=C5=82o?= Date: Wed, 4 Oct 2023 07:16:16 +0200 Subject: [PATCH] #13 - contact management (#41) * #13 - wip * #13 - wip, refactor * #13 - wip, refactor * #13 - wip, working on icons * #13 - wip, refactored views to match new changes in forms * #15 - wip * #13 - finished working on basic version, added test * #13 - added spatie/laravel-options --- app/Enums/Icons.php | 16 ++ .../Dashboard/ContactInfoController.php | 71 ++++++++ app/Http/Requests/ContactInfoRequest.php | 21 +++ app/Models/ContactInfo.php | 26 +++ composer.json | 1 + composer.lock | 156 ++++++++++++++++-- database/factories/ContactInfoFactory.php | 24 +++ ...9_23_135710_create_contact_infos_table.php | 25 +++ resources/js/Layouts/DashboardLayout.vue | 2 +- .../js/Pages/Dashboard/ContactInfo/Create.vue | 84 ++++++++++ .../js/Pages/Dashboard/ContactInfo/Edit.vue | 85 ++++++++++ .../js/Pages/Dashboard/ContactInfo/Index.vue | 92 +++++++++++ resources/js/Pages/Public/Login.vue | 8 +- resources/js/Shared/Forms/Select.vue | 51 ++++++ resources/js/Shared/Icons/At-symbol.vue | 6 + resources/js/Shared/Icons/Envelope.vue | 5 + resources/js/Shared/Icons/Exclamation.vue | 5 + resources/js/Shared/Icons/Github.vue | 7 + resources/js/Shared/Icons/Information.vue | 5 + resources/js/Shared/Icons/Linkedin.vue | 8 + resources/js/Shared/Icons/Slack.vue | 5 + routes/web.php | 11 +- tests/Feature/ContactInfoTest.php | 98 +++++++++++ 23 files changed, 794 insertions(+), 18 deletions(-) create mode 100644 app/Enums/Icons.php create mode 100644 app/Http/Controllers/Dashboard/ContactInfoController.php create mode 100644 app/Http/Requests/ContactInfoRequest.php create mode 100644 app/Models/ContactInfo.php create mode 100644 database/factories/ContactInfoFactory.php create mode 100644 database/migrations/2023_09_23_135710_create_contact_infos_table.php create mode 100644 resources/js/Pages/Dashboard/ContactInfo/Create.vue create mode 100644 resources/js/Pages/Dashboard/ContactInfo/Edit.vue create mode 100644 resources/js/Pages/Dashboard/ContactInfo/Index.vue create mode 100644 resources/js/Shared/Forms/Select.vue create mode 100644 resources/js/Shared/Icons/At-symbol.vue create mode 100644 resources/js/Shared/Icons/Envelope.vue create mode 100644 resources/js/Shared/Icons/Exclamation.vue create mode 100644 resources/js/Shared/Icons/Github.vue create mode 100644 resources/js/Shared/Icons/Information.vue create mode 100644 resources/js/Shared/Icons/Linkedin.vue create mode 100644 resources/js/Shared/Icons/Slack.vue create mode 100644 tests/Feature/ContactInfoTest.php diff --git a/app/Enums/Icons.php b/app/Enums/Icons.php new file mode 100644 index 0000000..cc6d376 --- /dev/null +++ b/app/Enums/Icons.php @@ -0,0 +1,16 @@ +orderByDesc("created_at") + ->get(); + + return inertia("Dashboard/ContactInfo/Index", [ + "contactInfos" => $contactInfos, + "total" => ContactInfo::query()->count(), + "lastUpdate" => ContactInfo::query()->orderByDesc("updated_at")->first()?->updated_at->diffForHumans(), + ]); + } + + public function create(): Response + { + return inertia("Dashboard/ContactInfo/Create", [ + "icons" => Options::forEnum(Icons::class)->toArray(), + ]); + } + + public function store(ContactInfoRequest $request): RedirectResponse + { + ContactInfo::query()->create($request->validated()); + + return redirect() + ->route("contactInfo.index") + ->with("success", "Dodano formę kontaktu"); + } + + public function edit(ContactInfo $contactInfo): Response + { + return inertia("Dashboard/ContactInfo/Edit", [ + "contactInfo" => $contactInfo, + "icons" => Options::forEnum(Icons::class)->toArray(), + ]); + } + + public function update(ContactInfoRequest $request, ContactInfo $contactInfo): RedirectResponse + { + $contactInfo->update($request->validated()); + + return redirect() + ->route("contactInfo.index") + ->with("success", "Zaktualizowano formę kontaktu"); + } + + public function destroy(ContactInfo $contactInfo): RedirectResponse + { + $contactInfo->delete(); + + return redirect()->back() + ->with("success", "Usunięto formę kontaktu"); + } +} diff --git a/app/Http/Requests/ContactInfoRequest.php b/app/Http/Requests/ContactInfoRequest.php new file mode 100644 index 0000000..3313316 --- /dev/null +++ b/app/Http/Requests/ContactInfoRequest.php @@ -0,0 +1,21 @@ + ["required", "string", "max:255"], + "identifier" => ["required", "string", "max:255"], + "icon" => ["required", new Enum(Icons::class)], + ]; + } +} diff --git a/app/Models/ContactInfo.php b/app/Models/ContactInfo.php new file mode 100644 index 0000000..f1bb1f0 --- /dev/null +++ b/app/Models/ContactInfo.php @@ -0,0 +1,26 @@ + + */ +class ContactInfoFactory extends Factory +{ + public function definition(): array + { + return [ + "label" => fake()->domainWord(), + "identifier" => fake()->url(), + "icon" => fake()->randomElement(Icons::class), + ]; + } +} diff --git a/database/migrations/2023_09_23_135710_create_contact_infos_table.php b/database/migrations/2023_09_23_135710_create_contact_infos_table.php new file mode 100644 index 0000000..69e3d38 --- /dev/null +++ b/database/migrations/2023_09_23_135710_create_contact_infos_table.php @@ -0,0 +1,25 @@ +ulid("id")->primary(); + $table->string("label"); + $table->string("identifier"); + $table->string("icon")->nullable(); + $table->timestamps(); + }); + } + + public function down(): void + { + Schema::dropIfExists("contact_infos"); + } +}; diff --git a/resources/js/Layouts/DashboardLayout.vue b/resources/js/Layouts/DashboardLayout.vue index a73c745..5a1e8cc 100644 --- a/resources/js/Layouts/DashboardLayout.vue +++ b/resources/js/Layouts/DashboardLayout.vue @@ -33,7 +33,7 @@ const navigation = [ { name: 'Aktualizacja hasła', href: '/dashboard/password', icon: LockOpenIcon, current: false }, { name: 'Aktualności', href: '#', icon: NewspaperIcon, current: false }, { name: 'FAQ', href: '/dashboard/faqs', icon: QuestionMarkCircleIcon, current: false }, - { name: 'Formy kontaktu', href: '#', icon: AtSymbolIcon, current: false }, + { name: 'Formy kontaktu', href: '/dashboard/contact-infos', icon: AtSymbolIcon, current: false }, ], }, { diff --git a/resources/js/Pages/Dashboard/ContactInfo/Create.vue b/resources/js/Pages/Dashboard/ContactInfo/Create.vue new file mode 100644 index 0000000..4440422 --- /dev/null +++ b/resources/js/Pages/Dashboard/ContactInfo/Create.vue @@ -0,0 +1,84 @@ + + + + diff --git a/resources/js/Pages/Dashboard/ContactInfo/Edit.vue b/resources/js/Pages/Dashboard/ContactInfo/Edit.vue new file mode 100644 index 0000000..fb656fe --- /dev/null +++ b/resources/js/Pages/Dashboard/ContactInfo/Edit.vue @@ -0,0 +1,85 @@ + + + + diff --git a/resources/js/Pages/Dashboard/ContactInfo/Index.vue b/resources/js/Pages/Dashboard/ContactInfo/Index.vue new file mode 100644 index 0000000..f222f3f --- /dev/null +++ b/resources/js/Pages/Dashboard/ContactInfo/Index.vue @@ -0,0 +1,92 @@ + + + diff --git a/resources/js/Pages/Public/Login.vue b/resources/js/Pages/Public/Login.vue index 1365194..eb831e0 100644 --- a/resources/js/Pages/Public/Login.vue +++ b/resources/js/Pages/Public/Login.vue @@ -22,14 +22,14 @@ function attemptLogin() {
- +
Email
@@ -52,7 +52,7 @@ function attemptLogin() {
diff --git a/resources/js/Shared/Forms/Select.vue b/resources/js/Shared/Forms/Select.vue new file mode 100644 index 0000000..7ab0ac8 --- /dev/null +++ b/resources/js/Shared/Forms/Select.vue @@ -0,0 +1,51 @@ + + + diff --git a/resources/js/Shared/Icons/At-symbol.vue b/resources/js/Shared/Icons/At-symbol.vue new file mode 100644 index 0000000..818a60f --- /dev/null +++ b/resources/js/Shared/Icons/At-symbol.vue @@ -0,0 +1,6 @@ + + diff --git a/resources/js/Shared/Icons/Envelope.vue b/resources/js/Shared/Icons/Envelope.vue new file mode 100644 index 0000000..30f404d --- /dev/null +++ b/resources/js/Shared/Icons/Envelope.vue @@ -0,0 +1,5 @@ + diff --git a/resources/js/Shared/Icons/Exclamation.vue b/resources/js/Shared/Icons/Exclamation.vue new file mode 100644 index 0000000..558a4df --- /dev/null +++ b/resources/js/Shared/Icons/Exclamation.vue @@ -0,0 +1,5 @@ + diff --git a/resources/js/Shared/Icons/Github.vue b/resources/js/Shared/Icons/Github.vue new file mode 100644 index 0000000..7770f22 --- /dev/null +++ b/resources/js/Shared/Icons/Github.vue @@ -0,0 +1,7 @@ + diff --git a/resources/js/Shared/Icons/Information.vue b/resources/js/Shared/Icons/Information.vue new file mode 100644 index 0000000..45abc38 --- /dev/null +++ b/resources/js/Shared/Icons/Information.vue @@ -0,0 +1,5 @@ + diff --git a/resources/js/Shared/Icons/Linkedin.vue b/resources/js/Shared/Icons/Linkedin.vue new file mode 100644 index 0000000..0e261c5 --- /dev/null +++ b/resources/js/Shared/Icons/Linkedin.vue @@ -0,0 +1,8 @@ + diff --git a/resources/js/Shared/Icons/Slack.vue b/resources/js/Shared/Icons/Slack.vue new file mode 100644 index 0000000..69009df --- /dev/null +++ b/resources/js/Shared/Icons/Slack.vue @@ -0,0 +1,5 @@ + diff --git a/routes/web.php b/routes/web.php index c596dd5..5fe3020 100644 --- a/routes/web.php +++ b/routes/web.php @@ -2,6 +2,7 @@ declare(strict_types=1); +use App\Http\Controllers\Dashboard\ContactInfoController; use App\Http\Controllers\Dashboard\DashboardController; use App\Http\Controllers\Dashboard\FieldController; use App\Http\Controllers\Dashboard\LogoutController; @@ -24,9 +25,17 @@ Route::middleware("auth")->prefix("dashboard")->group(function (): void { Route::get("/", DashboardController::class)->name("dashboard"); + Route::post("/logout", LogoutController::class); Route::get("/password", [PasswordUpdateController::class, "edit"])->name("password.edit"); Route::patch("/password", [PasswordUpdateController::class, "update"])->name("password.update"); - Route::post("/logout", LogoutController::class); + Route::controller(ContactInfoController::class)->group(function (): void { + Route::get("/contact-infos", "index")->name("contactInfo.index"); + Route::get("/contact-infos/create", "create")->name("contactInfo.create"); + Route::post("/contact-infos", "store")->name("contactInfo.store"); + Route::get("/contact-infos/{contact_info}/edit", "edit")->name("contactInfo.edit"); + Route::patch("/contact-infos/{contact_info}", "update")->name("contactInfo.update"); + Route::delete("/contact-infos/{contact_info}", "destroy")->name("contactInfo.destroy"); + }); Route::controller(StudentController::class)->group(function (): void { Route::get("/students", "index")->name("students.index"); Route::get("/students/create", "create")->name("students.create"); diff --git a/tests/Feature/ContactInfoTest.php b/tests/Feature/ContactInfoTest.php new file mode 100644 index 0000000..96127d0 --- /dev/null +++ b/tests/Feature/ContactInfoTest.php @@ -0,0 +1,98 @@ +user = User::factory()->create(); + $this->actingAs($this->user); + } + + public function testContactInfoCanBeCreated(): void + { + $this->assertDatabaseCount("contact_infos", 0); + + $this->post("/dashboard/contact-infos", [ + "label" => "karol.zygadlo@collegiumwitelona.pl", + "identifier" => "mailto:karol.zygadlo@collegiumwitelona.pl", + "icon" => Icons::AtSymbol->value, + ])->assertSessionHasNoErrors(); + + $this->assertDatabaseCount("contact_infos", 1); + } + + public function testContactInfoCanBeUpdated(): void + { + $contactInfo = ContactInfo::factory()->create(); + + $this->assertDatabaseMissing("contact_infos", [ + "label" => "karol.zygadlo@collegiumwitelona.pl", + "identifier" => "mailto:karol.zygadlo@collegiumwitelona.pl", + "icon" => Icons::AtSymbol->value, + ]); + + $this->patch("/dashboard/contact-infos/{$contactInfo->id}", [ + "label" => "karol.zygadlo@collegiumwitelona.pl", + "identifier" => "mailto:karol.zygadlo@collegiumwitelona.pl", + "icon" => Icons::AtSymbol->value, + ])->assertSessionHasNoErrors(); + + $this->assertDatabaseHas("contact_infos", [ + "label" => "karol.zygadlo@collegiumwitelona.pl", + "identifier" => "mailto:karol.zygadlo@collegiumwitelona.pl", + "icon" => "at-symbol", + ]); + } + + public function testContactInfoCannotBeCreatedWithInvalidData(): void + { + $this->post("/dashboard/contact-infos", [ + "label" => Str::random(256), + "identifier" => Str::random(256), + "icon" => "test", + ])->assertSessionHasErrors([ + "label", + "identifier", + "icon", + ]); + + $this->assertDatabaseCount("contact_infos", 0); + } + + public function testContactInfoCannotBeCreatedWithoutData(): void + { + $this->post("/dashboard/contact-infos", [ + ])->assertSessionHasErrors([ + "label", + "identifier", + "icon", + ]); + + $this->assertDatabaseCount("contact_infos", 0); + } + + public function testContactInfoCanBeDeleted(): void + { + $contactInfo = ContactInfo::factory()->create(); + $this->assertDatabaseCount("contact_infos", 1); + + $this->delete("/dashboard/contact-infos/$contactInfo->id"); + + $this->assertDatabaseCount("contact_infos", 0); + } +}