From 2cdd712136ff0017c52bccb73ef0afba2a673a6f Mon Sep 17 00:00:00 2001 From: Kamil Piech Date: Thu, 9 Nov 2023 13:43:46 +0100 Subject: [PATCH] #35 - main page management (#68) * #34 - general settings added * #34 - js lint fix * #34 - cr fix * #34 - added one more test * #34 - validation fixes * - main page sections update feature * #35 - added some updates * #35 - section ordering fixes * #35 - added typography for tailwind * #35 - lintf --- app/Enums/SectionType.php | 19 ++ .../Dashboard/SectionController.php | 54 +++ .../Dashboard/SectionSettingsController.php | 28 ++ .../Controllers/Public/HomeController.php | 36 +- app/Http/Requests/SectionRequest.php | 21 ++ app/Models/Section.php | 54 +++ app/Models/SectionSettings.php | 38 +++ database/factories/SectionFactory.php | 38 +++ database/factories/SectionSettingsFactory.php | 20 ++ ...8_193906_create_section_settings_table.php | 26 ++ ...023_10_18_193906_create_sections_table.php | 25 ++ database/seeders/DatabaseSeeder.php | 10 + lang/pl.json | 4 +- lang/pl/validation.php | 4 +- package-lock.json | 6 + package.json | 1 + resources/js/Layouts/DashboardLayout.vue | 2 + resources/js/Pages/Dashboard/Section/Show.vue | 311 ++++++++++++++++++ resources/js/Pages/Public/Home.vue | 31 +- routes/web.php | 9 + tailwind.config.js | 1 + tests/Feature/ExampleTest.php | 2 + tests/Feature/SectionsTest.php | 152 +++++++++ tests/Feature/SettingsTest.php | 2 +- 24 files changed, 845 insertions(+), 49 deletions(-) create mode 100644 app/Enums/SectionType.php create mode 100644 app/Http/Controllers/Dashboard/SectionController.php create mode 100644 app/Http/Controllers/Dashboard/SectionSettingsController.php create mode 100644 app/Http/Requests/SectionRequest.php create mode 100644 app/Models/Section.php create mode 100644 app/Models/SectionSettings.php create mode 100644 database/factories/SectionFactory.php create mode 100644 database/factories/SectionSettingsFactory.php create mode 100644 database/migrations/2023_10_18_193906_create_section_settings_table.php create mode 100644 database/migrations/2023_10_18_193906_create_sections_table.php create mode 100644 resources/js/Pages/Dashboard/Section/Show.vue create mode 100644 tests/Feature/SectionsTest.php diff --git a/app/Enums/SectionType.php b/app/Enums/SectionType.php new file mode 100644 index 0000000..2071647 --- /dev/null +++ b/app/Enums/SectionType.php @@ -0,0 +1,19 @@ + __("About"), + "counter" => __("Counters"), + ]; + } +} diff --git a/app/Http/Controllers/Dashboard/SectionController.php b/app/Http/Controllers/Dashboard/SectionController.php new file mode 100644 index 0000000..0ce7e82 --- /dev/null +++ b/app/Http/Controllers/Dashboard/SectionController.php @@ -0,0 +1,54 @@ + Options::forEnum(SectionType::class)->toArray(), + "sectionSettings" => SectionSettings::query()->first(), + "about" => Section::query()->about()->orderBy("created_at")->get(), + "counters" => Section::query()->counter()->orderBy("created_at")->get(), + ]); + } + + public function store(SectionRequest $request): RedirectResponse + { + Section::query()->create($request->validated()); + + return redirect() + ->back() + ->with("success", "Dodano sekcję"); + } + + public function update(SectionRequest $request, Section $section): RedirectResponse + { + $section->update($request->validated()); + + return redirect() + ->back() + ->with("success", "Zaktualizowano sekcję"); + } + + public function destroy(Section $section): RedirectResponse + { + $section->delete(); + + return redirect() + ->back() + ->with("success", "Usunięto sekcję"); + } +} diff --git a/app/Http/Controllers/Dashboard/SectionSettingsController.php b/app/Http/Controllers/Dashboard/SectionSettingsController.php new file mode 100644 index 0000000..3adbedf --- /dev/null +++ b/app/Http/Controllers/Dashboard/SectionSettingsController.php @@ -0,0 +1,28 @@ +first() + ->update([ + "banner_enabled" => $request->boolean("banner_enabled"), + "about_enabled" => $request->boolean("about_enabled"), + "counters_enabled" => $request->boolean("counters_enabled"), + "contact_enabled" => $request->boolean("contact_enabled"), + ]); + + return redirect() + ->back() + ->with("success", "Zaktualizowano ustawienia sekcji"); + } +} diff --git a/app/Http/Controllers/Public/HomeController.php b/app/Http/Controllers/Public/HomeController.php index b7d6384..43b101e 100644 --- a/app/Http/Controllers/Public/HomeController.php +++ b/app/Http/Controllers/Public/HomeController.php @@ -5,6 +5,8 @@ namespace App\Http\Controllers\Public; use App\Http\Controllers\Controller; +use App\Models\Section; +use App\Models\SectionSettings; use App\Models\Setting; use Inertia\Response; @@ -14,6 +16,8 @@ public function __invoke(): Response { /** @var Setting $settings */ $settings = Setting::query()->first(); + /** @var SectionSettings $sectionSettings */ + $sectionSettings = SectionSettings::query()->first(); return inertia("Public/Home", [ "title" => $settings->teacher_titles, @@ -22,35 +26,9 @@ public function __invoke(): Response "department" => $settings->department_name, "university" => $settings->university_name, "universityLogo" => "https://irg2023.collegiumwitelona.pl/assets/logos/cwup.png", - "sections" => [ - [ - "header" => "Naukowo", - "paragraphs" => [ - "Ukończyłem studia inżynierskie i magisterskie na kierunku Automatyka i Robotyka na Wydziale Elektroniki Politechniki Wrocławskiej. Specjalizowałem się, kolejno na studiach pierwszego i drugiego stopnia, w systemach informatycznych w automatyce oraz technologiach informacyjnych w systemach automatyki.", - "Od pierwszej połowy 2016 roku prowadzę zawodowe praktyki dla studentów i uczę praktycznie programowania w ramach różnych projektów. W 2017 można było usłyszeć mój wykład w ramach Otwartych Seminariów Naukowych. Od 2017 pracuję jako nauczyciel akademicki na Wydziale Nauk Technicznych i Ekonomicznych Collegium Witelona Uczelnia Państwowa, dawniej Państwowej Wyższej Szkoły Zawodowej im. Witelona w Legnicy.", - ], - ], - [ - "header" => "Zawodowo", - "paragraphs" => [ - "Od 2014 roku pracuję jako programista. Zaczynałem jako młodszy programista PHP w małym wrocławskim software housie, gdzie pod bacznym okiem specjalistów nauczyłem się realistycznego i praktycznego podejścia do zawodu. Później pracowałem w projektach dla międzynarodowych korporacji, firm z sektora MŚP oraz startupów. Od 2020 roku jestem dyrektorem ds. technologii w software housie Blumilk.", - "Zawsze gdy moge wybrać, programuję w PHP - najchętniej w najnowszej wersji oraz przede wszystkim w Laravelu. Mam zawodowe doświadczenie w pracy z przeróżnymi frameworkami JavaScriptu, Pythonem oraz C#.", - ], - ], - [ - "header" => "Prywatnie", - "paragraphs" => [ - "Urodziłem się i mieszkam od urodzenia w Legnicy (choć miałem w tym pięcioletnią przerwę na mieszkanie i studiowanie we Wrocławiu). W wolnym czasie lubię siedzieć nad książkami wszelkiego gatunku, śledzić przeróżne seriale oraz jeździć po legnickich i okolicznych drogach rowerowych.", - "Ponadto jestem piwowarem domowym, więc warzę w domu własnoręcznie piwo, jestem zapalonym fanem Gwiezdnych wojen, a także - a jakżeby inaczej! - programuję najróżniejsze rzeczy w najróżniejszych technologiach w najróżniejszym celu.", - ], - ], - ], - "counters" => [ - ["id" => 1, "name" => "lat prowadzenia zajęć dydaktycznych na uczelni", "value" => 7], - ["id" => 2, "name" => "kursów prowadzonych w tym semestrze", "value" => 5], - ["id" => 3, "name" => "studentów na wykładach i innych zajęciach", "value" => 345], - ["id" => 4, "name" => "średnia ocena wystawiona przez studentów w ankiecie nt. jakości kształcenia", "value" => 4.93], - ], + "sectionSettings" => $sectionSettings, + "about" => $sectionSettings->about_enabled ? Section::query()->about()->orderBy("created_at")->get() : [], + "counters" => $sectionSettings->counters_enabled ? Section::query()->counter()->orderBy("created_at")->get() : [], ]); } } diff --git a/app/Http/Requests/SectionRequest.php b/app/Http/Requests/SectionRequest.php new file mode 100644 index 0000000..254ae58 --- /dev/null +++ b/app/Http/Requests/SectionRequest.php @@ -0,0 +1,21 @@ + ["required", "max:255"], + "value" => ["required", "max:65000"], + "type" => ["required", new Enum(SectionType::class)], + ]; + } +} diff --git a/app/Models/Section.php b/app/Models/Section.php new file mode 100644 index 0000000..1ddb79c --- /dev/null +++ b/app/Models/Section.php @@ -0,0 +1,54 @@ + SectionType::class, + ]; + + public function scopeCounter(Builder $query): Builder + { + return $query->where("type", SectionType::Counter->value); + } + + public function scopeAbout(Builder $query): Builder + { + return $query->where("type", SectionType::About->value); + } + + protected function value(): Attribute + { + return Attribute::make( + set: fn(?string $value): string => Purify::clean($value), + ); + } +} diff --git a/app/Models/SectionSettings.php b/app/Models/SectionSettings.php new file mode 100644 index 0000000..362975c --- /dev/null +++ b/app/Models/SectionSettings.php @@ -0,0 +1,38 @@ + "boolean", + "about_enabled" => "boolean", + "counters_enabled" => "boolean", + "contact_enabled" => "boolean", + ]; +} diff --git a/database/factories/SectionFactory.php b/database/factories/SectionFactory.php new file mode 100644 index 0000000..c66e6b1 --- /dev/null +++ b/database/factories/SectionFactory.php @@ -0,0 +1,38 @@ + fake()->text(20), + "value" => fake()->text(300), + "type" => fake()->randomElement(SectionType::cases())->value, + ]; + } + + public function counter(): static + { + return $this->state(fn(array $attributes): array => [ + "title" => fake()->numberBetween(1, 100), + "value" => fake()->text(30), + "type" => SectionType::Counter->value, + ]); + } + + public function about(): static + { + return $this->state(fn(array $attributes): array => [ + "title" => fake()->text(30), + "value" => fake()->text(300), + "type" => SectionType::About->value, + ]); + } +} diff --git a/database/factories/SectionSettingsFactory.php b/database/factories/SectionSettingsFactory.php new file mode 100644 index 0000000..79e254d --- /dev/null +++ b/database/factories/SectionSettingsFactory.php @@ -0,0 +1,20 @@ + fake()->boolean, + "about_enabled" => fake()->boolean, + "counters_enabled" => fake()->boolean, + "contact_enabled" => fake()->boolean, + ]; + } +} diff --git a/database/migrations/2023_10_18_193906_create_section_settings_table.php b/database/migrations/2023_10_18_193906_create_section_settings_table.php new file mode 100644 index 0000000..b995a11 --- /dev/null +++ b/database/migrations/2023_10_18_193906_create_section_settings_table.php @@ -0,0 +1,26 @@ +ulid("id")->primary(); + $table->boolean("banner_enabled"); + $table->boolean("about_enabled"); + $table->boolean("counters_enabled"); + $table->boolean("contact_enabled"); + $table->timestamps(); + }); + } + + public function down(): void + { + Schema::dropIfExists("section_settings"); + } +}; diff --git a/database/migrations/2023_10_18_193906_create_sections_table.php b/database/migrations/2023_10_18_193906_create_sections_table.php new file mode 100644 index 0000000..f641b62 --- /dev/null +++ b/database/migrations/2023_10_18_193906_create_sections_table.php @@ -0,0 +1,25 @@ +ulid("id")->primary(); + $table->string("title"); + $table->text("value"); + $table->string("type"); + $table->timestamps(); + }); + } + + public function down(): void + { + Schema::dropIfExists("sections"); + } +}; diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index c79e71d..2d3fd79 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -4,6 +4,8 @@ namespace Database\Seeders; +use App\Models\Section; +use App\Models\SectionSettings; use App\Models\Setting; use App\Models\User; use Illuminate\Database\Seeder; @@ -14,5 +16,13 @@ public function run(): void { User::factory()->create(["email" => "admin@example.com"]); Setting::factory()->create(); + Section::factory(4)->counter()->create(); + Section::factory(3)->about()->create(); + SectionSettings::query()->create([ + "banner_enabled" => true, + "about_enabled" => true, + "counters_enabled" => true, + "contact_enabled" => true, + ]); } } diff --git a/lang/pl.json b/lang/pl.json index a7ad0e7..9441967 100644 --- a/lang/pl.json +++ b/lang/pl.json @@ -7,5 +7,7 @@ "project": "projekt", "stationary": "stacjonarny", "part-time": "niestacjonarny", - "study and work": "ucz się i pracuj" + "study and work": "ucz się i pracuj", + "About": "O mnie", + "Counters": "Liczniki" } diff --git a/lang/pl/validation.php b/lang/pl/validation.php index b066404..b6a0194 100644 --- a/lang/pl/validation.php +++ b/lang/pl/validation.php @@ -123,11 +123,13 @@ "index_number" => "numer indeksu", "abbreviation" => "skrótowiec", "form" => "tryb studiów", - "type" => "typ zajęć", + "type" => "typ", "teacher_name" => "imię i nazwisko nauczyciela", "teacher_email" => "adres e-mail nauczyciela", "teacher_titles" => "tytuły/stopnie naukowe nauczyciela", "university_name" => "nazwa uczelni", "department_name" => "nazwa wydziału", + "title" => "tytuł", + "value" => "opis", ], ]; diff --git a/package-lock.json b/package-lock.json index afa685a..af83d13 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "@tailwindcss/forms": "^0.5.6", "@tailwindcss/typography": "^0.5.10", "axios": "^1.5.0", + "dompurify": "^3.0.6", "laravel-vite-plugin": "^0.8.0", "lodash": "^4.17.21", "primevue": "^3.35.0", @@ -1477,6 +1478,11 @@ "node": ">=6.0.0" } }, + "node_modules/dompurify": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.6.tgz", + "integrity": "sha512-ilkD8YEnnGh1zJ240uJsW7AzE+2qpbOUYjacomn3AvJ6J4JhKGSZ2nh4wUIXPZrEPppaCLx5jFe8T89Rk8tQ7w==" + }, "node_modules/electron-to-chromium": { "version": "1.4.539", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.539.tgz", diff --git a/package.json b/package.json index fb6a45c..78a4d97 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "@tailwindcss/forms": "^0.5.6", "@tailwindcss/typography": "^0.5.10", "axios": "^1.5.0", + "dompurify": "^3.0.6", "laravel-vite-plugin": "^0.8.0", "lodash": "^4.17.21", "primevue": "^3.35.0", diff --git a/resources/js/Layouts/DashboardLayout.vue b/resources/js/Layouts/DashboardLayout.vue index 3bc0eb4..c4c219f 100644 --- a/resources/js/Layouts/DashboardLayout.vue +++ b/resources/js/Layouts/DashboardLayout.vue @@ -15,6 +15,7 @@ import { ClipboardIcon, Cog6ToothIcon, LockOpenIcon, + Square2StackIcon, } from '@heroicons/vue/24/outline' import { ref, watch } from 'vue' import { useToast } from 'vue-toastification' @@ -30,6 +31,7 @@ const navigation = [ elements: [ { name: 'Dashboard', href: '/dashboard', icon: HomeIcon, current: true }, { name: 'Ustawienia', href: '/dashboard/settings', icon: Cog6ToothIcon, current: false }, + { name: 'Sekcje', href: '/dashboard/sections', icon: Square2StackIcon, current: false }, { name: 'Aktualizacja hasła', href: '/dashboard/password', icon: LockOpenIcon, current: false }, { name: 'Aktualności', href: '/dashboard/news', icon: NewspaperIcon, current: false }, { name: 'FAQ', href: '/dashboard/faqs', icon: QuestionMarkCircleIcon, current: false }, diff --git a/resources/js/Pages/Dashboard/Section/Show.vue b/resources/js/Pages/Dashboard/Section/Show.vue new file mode 100644 index 0000000..e041342 --- /dev/null +++ b/resources/js/Pages/Dashboard/Section/Show.vue @@ -0,0 +1,311 @@ + + + diff --git a/resources/js/Pages/Public/Home.vue b/resources/js/Pages/Public/Home.vue index 174218a..a37c381 100644 --- a/resources/js/Pages/Public/Home.vue +++ b/resources/js/Pages/Public/Home.vue @@ -5,6 +5,7 @@ import { } from '@heroicons/vue/24/outline' import BackgroundGrid from '@/Components/BackgroundGrid.vue' import SectionHeader from '@/Components/SectionHeader.vue' +import DOMPurify from 'dompurify' defineProps({ title: String, @@ -13,14 +14,15 @@ defineProps({ department: String, university: String, universityLogo: String, - sections: Array, + sectionSettings: Object, + about: Array, counters: Array, })