From fcd30ee23b3a04c4a34fc59aca2deb6daf0ef353 Mon Sep 17 00:00:00 2001 From: Alexis Gousseau Date: Tue, 6 Jan 2026 09:53:08 +0100 Subject: [PATCH] feat: :sparkles: set home in dynamic page --- app/Http/Controllers/Front/HomeController.php | 11 - .../DynamicPages/StoreDynamicPageRequest.php | 13 +- app/Models/DynamicPage.php | 31 +- .../DynamicPageRouteServiceProvider.php | 2 +- ...for_racine_page_in_dynamic_pages_table.php | 32 + lang/en/back.php | 3 + lang/en/front.php | 1 + lang/en/validation.php | 1 + lang/fr/back.php | 3 + lang/fr/front.php | 1 + lang/fr/validation.php | 1 + .../ts/back/Components/Inputs/ModelSelect.tsx | 10 + .../back/Components/Inputs/Slugifycator.tsx | 5 +- .../ts/back/Components/Layout/AppSidebar.tsx | 2 +- resources/ts/back/Pages/DynamicPages/Form.tsx | 34 +- .../ts/back/Pages/DynamicPages/Index.tsx | 23 +- resources/ts/back/Pages/DynamicPages/Show.tsx | 13 + resources/ts/back/Pages/Errors/Default.tsx | 6 +- resources/ts/back/types/global.d.ts | 2 + .../front/brands-icons/brands-icons.blade.php | 6 +- .../breadcrumbs/breadcrumb-layout.blade.php | 2 +- resources/views/front/pages/accueil.blade.php | 130 ++++ resources/views/front/pages/error.blade.php | 4 +- resources/views/front/pages/home.blade.php | 674 ------------------ .../front/pages/notre-engagement.blade.php | 1 + routes/breadcrumbs-front.php | 122 ++-- routes/web-front.php | 4 - 27 files changed, 368 insertions(+), 769 deletions(-) create mode 100644 database/migrations/2026_01_08_100318_add_boolean_for_racine_page_in_dynamic_pages_table.php create mode 100644 resources/views/front/pages/accueil.blade.php delete mode 100644 resources/views/front/pages/home.blade.php diff --git a/app/Http/Controllers/Front/HomeController.php b/app/Http/Controllers/Front/HomeController.php index 669c8d94..c35a8f46 100644 --- a/app/Http/Controllers/Front/HomeController.php +++ b/app/Http/Controllers/Front/HomeController.php @@ -14,17 +14,6 @@ use Uwil\LaravelCommon\Controllers\Controller; class HomeController extends Controller { - /** - * Show home page. - * - * @return \Illuminate\Contracts\View\View - */ - public function homepage(): \Illuminate\Contracts\View\View - { - return view('front.pages.home'); - } - - /** * unsubscribeNewsletter method * diff --git a/app/Http/Requests/Back/DynamicPages/StoreDynamicPageRequest.php b/app/Http/Requests/Back/DynamicPages/StoreDynamicPageRequest.php index f8c27af2..aaa3faea 100644 --- a/app/Http/Requests/Back/DynamicPages/StoreDynamicPageRequest.php +++ b/app/Http/Requests/Back/DynamicPages/StoreDynamicPageRequest.php @@ -34,6 +34,8 @@ class StoreDynamicPageRequest extends UpdateDynamicFieldRequest $this->merge([ 'config_id' => $this->config_id != 0 ? $this->config_id : null, 'isLocked' => $this->boolean('isLocked'), + 'isRoot' => $this->boolean('isRoot'), + 'slug' => $this->boolean('isRoot') ? "" : $this->slug, ]); if ($this->config_id) { parent::prepareForValidation(); @@ -50,12 +52,19 @@ class StoreDynamicPageRequest extends UpdateDynamicFieldRequest { $rules = [ 'title' => 'required|string|between:3,255', - 'slug' => 'required|string|regex:/^[-A-Za-z0-9]{3,255}$/', 'page_id' => 'sometimes|nullable|numeric|exists:dynamic_pages,id', 'config_id' => 'sometimes|nullable|numeric|exists:page_configs,id', 'status' => ['required', new Enum(PageStatusEnum::class)], 'isLocked' => 'required|boolean', + 'isRoot' => 'required|boolean', ]; + + if ($this->boolean('isRoot')) { + $rules['slug'] = ['present', 'nullable']; + } else { + $rules['slug'] = ['required', 'string', 'regex:/^[-A-Za-z0-9]{3,255}$/']; + } + if ($this->config_id) { $rules = \array_merge( ['content' => 'required|array|min:1'], @@ -67,6 +76,7 @@ class StoreDynamicPageRequest extends UpdateDynamicFieldRequest 'content' => ['required', 'string', new WysiwygLength()], ], $rules); } + return $rules; } @@ -83,6 +93,7 @@ class StoreDynamicPageRequest extends UpdateDynamicFieldRequest 'config_id' => trans_choice('models.classes.page-config', 1), 'status' => trans('validation.custom.status'), 'isLocked' => trans('validation.custom.locked'), + 'isRoot' => trans('validation.custom.root'), ]; if ($this->config_id) { $attributes = \array_merge( diff --git a/app/Models/DynamicPage.php b/app/Models/DynamicPage.php index a6377033..f7476247 100644 --- a/app/Models/DynamicPage.php +++ b/app/Models/DynamicPage.php @@ -31,6 +31,7 @@ use Uwil\LaravelCommon\Helpers\FileHelper; * @property integer|null $page_id Page associated to the dynamic page. * @property \App\Enums\DynamicPages\PageStatusEnum $status Status of the dynamic page. * @property boolean $isLocked Locked status of dynamic page. + * @property boolean $isRoot Root status of dynamic page. * @property-read \Illuminate\Support\Carbon $created_at Date on which the dynamic page was created. * @property-read \Illuminate\Support\Carbon $updated_at Date on which the dynamic page was updated. * @property-read \Illuminate\Support\Carbon $deleted_at Date on which the dynamic page was updated. @@ -58,6 +59,7 @@ class DynamicPage extends Model implements TranslatableModel 'config_id', 'status', 'isLocked', + 'isRoot', ]; /** @@ -69,6 +71,7 @@ class DynamicPage extends Model implements TranslatableModel 'content' => 'array', 'status' => PageStatusEnum::class, 'isLocked' => 'boolean', + 'isRoot' => 'boolean', ]; /** @@ -122,6 +125,7 @@ class DynamicPage extends Model implements TranslatableModel ]); } $model->status = PageStatusEnum::draft; + $model->isRoot = false; }); static::forceDeleted(function (self $model) { // TODO: update this after recursive deleting images. @@ -223,14 +227,33 @@ class DynamicPage extends Model implements TranslatableModel private static function manageFields(self $model): void { // Check unique fields. - $slug = $model->slug ?? Str::slug($model->title); - TranslationHelper::assertTranslationFieldIsUnique($model, $model->title, 'title'); - TranslationHelper::assertTranslationFieldIsUnique($model, $slug, 'slug'); - $model->setAttribute('slug', $slug); + if($model->isRoot) { + self::assertRootIsUnique($model); + } else { + $slug = $model->slug ?? Str::slug($model->title); + TranslationHelper::assertTranslationFieldIsUnique($model, $model->title, 'title'); + TranslationHelper::assertTranslationFieldIsUnique($model, $slug, 'slug'); + $model->setAttribute('slug', $slug); + } // Check translations. self::validateFallbackTranslations($model); } + /** + * Assert there is only one root page. + * + * @param self $model + * @return void + */ + public static function assertRootIsUnique(self $model): void + { + $oldDynamicPageRoot = self::getModelsCached()->where('isRoot', true)->first(); + if ($oldDynamicPageRoot) { + $oldDynamicPageRoot->isRoot = false; + $oldDynamicPageRoot->saveQuietly(); + } + } + /** * Assert fallback locale translation is present. * diff --git a/app/Providers/DynamicPageRouteServiceProvider.php b/app/Providers/DynamicPageRouteServiceProvider.php index b0ded6c9..9ba17f7c 100644 --- a/app/Providers/DynamicPageRouteServiceProvider.php +++ b/app/Providers/DynamicPageRouteServiceProvider.php @@ -53,7 +53,7 @@ class DynamicPageRouteServiceProvider extends ServiceProvider $slug = $dynamicPageModel->getTranslation('slug', $locale); // If no slug for the locale → skip. - if (!$slug) continue; + if (!empty($slug) && !$slug) continue; // Retrieves the parent segments for this locale. $segments = DynamicPageHelper::getAllDynamicPageSegments( diff --git a/database/migrations/2026_01_08_100318_add_boolean_for_racine_page_in_dynamic_pages_table.php b/database/migrations/2026_01_08_100318_add_boolean_for_racine_page_in_dynamic_pages_table.php new file mode 100644 index 00000000..2c1f6b51 --- /dev/null +++ b/database/migrations/2026_01_08_100318_add_boolean_for_racine_page_in_dynamic_pages_table.php @@ -0,0 +1,32 @@ +boolean('isRoot')->default(false)->comment('Page root status.')->after('isLocked'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down(): void + { + Schema::table('dynamic_pages', function (Blueprint $table) { + $table->dropColumn('isRoot'); + }); + } +}; diff --git a/lang/en/back.php b/lang/en/back.php index 60eb7d48..0aac6f7e 100644 --- a/lang/en/back.php +++ b/lang/en/back.php @@ -86,6 +86,8 @@ return [ "form_subtitle_visibility" => "Manage account display.", "form_title_isLocked" => "Restriction", "form_subtitle_isLocked" => "Manage page locking.", + "form_title_page_accessibility" => "Accessibility", + "form_subtitle_page_accessibility" => "Manage access the dynamic page of the site.", "form_associate_to" => ":attribute associated to this model|:attribute associated to this model", "form_associate_to_plural" => ":attribute associated to this model|:attribute associated to this model", "form_title_config" => "Setup", @@ -124,6 +126,7 @@ return [ "list_children_needed" => "Children are required for this type :enumLabel", "empty_warning" => "No data recorded", "page_locked" => "This page is currently locked", + "page_root" => "This page is currently home", "unsupported_type" => "Unsupported type:", "missing_translations" => "The translation of the :field for the language :locales is missing.|The translation of the :field for :locales languages are missing.", diff --git a/lang/en/front.php b/lang/en/front.php index fea40ca0..89d1079b 100644 --- a/lang/en/front.php +++ b/lang/en/front.php @@ -4,6 +4,7 @@ return [ "multiple_choice" => 'multiple choice', "required_field" => 'compulsory fields', "go_to_the_back_end" => "access the back-office", + "back_to_homepage" => "Back to the homepage", "homepage" => "LDC", "nos-engagements" => [ 'key' => "Presentation of our commitments", diff --git a/lang/en/validation.php b/lang/en/validation.php index 46d99792..ced66178 100644 --- a/lang/en/validation.php +++ b/lang/en/validation.php @@ -240,6 +240,7 @@ return [ "validation" => "validation", "reference" => "reference", "locked" => "locked", + "root" => "root", "field" => "field", "key" => "unique key", "slug" => "slug", diff --git a/lang/fr/back.php b/lang/fr/back.php index c0807425..c55edf1f 100644 --- a/lang/fr/back.php +++ b/lang/fr/back.php @@ -85,6 +85,8 @@ return [ "form_subtitle_visibility" => "Gérer l'affichage du compte.", "form_title_isLocked" => "Restriction", "form_subtitle_isLocked" => "Gérer le vérouillage de la page.", + "form_title_page_accessibility" => "Accessibilité", + "form_subtitle_page_accessibility" => "Gérer l’accès à la page dynamique du site.", "form_associate_to" => ":attribute associé au modèle|:attribute associée au modèle", "form_associate_to_plural" => ":attribute associés au modèle|:attribute associées au modèle", "form_title_config" => "Réglage", @@ -124,6 +126,7 @@ return [ "list_children_needed" => "Des enfants sont nécessaires pour ce type :enumLabel", "empty_warning" => "Aucune donnée enregistrée", "page_locked" => "Cette page est actuellement vérouillée", + "page_root" => "Cette page est actuellement l'accueil", "unsupported_type" => "Type non supporté:", "missing_translations" => "La traduction du champs :field pour la langue :locales est manquante.|La traduction du champs :field pour l es langues :locales sont manquantes.", diff --git a/lang/fr/front.php b/lang/fr/front.php index 61354488..ab9de292 100644 --- a/lang/fr/front.php +++ b/lang/fr/front.php @@ -4,6 +4,7 @@ return [ "multiple_choice" => 'choix multiple', "required_field" => 'champs obligatoires', "go_to_the_back_end" => "accéder au back-office", + "back_to_homepage" => "Revenir à la page d'accueil", "homepage" => "LDC", "nos-engagements" => "nos engagements", "nos-engagements" => [ diff --git a/lang/fr/validation.php b/lang/fr/validation.php index 81e87aac..b13eed7c 100644 --- a/lang/fr/validation.php +++ b/lang/fr/validation.php @@ -240,6 +240,7 @@ return [ "validation" => "validation", "reference" => "référence", "locked" => "vérouillé", + "root" => "page racine (accueil)", "field" => "champs", "key" => "clé", "slug" => "chemin d'accès", diff --git a/resources/ts/back/Components/Inputs/ModelSelect.tsx b/resources/ts/back/Components/Inputs/ModelSelect.tsx index 7952d4c9..db9d5012 100644 --- a/resources/ts/back/Components/Inputs/ModelSelect.tsx +++ b/resources/ts/back/Components/Inputs/ModelSelect.tsx @@ -244,6 +244,16 @@ export default function ModelSelect({ return () => clearTimeout(handler); }, [search, targetModel, field]); + // Update when selected is updated. + useEffect(() => { + if (selected !== null) { + handleSelect(selected); + } else { + setSelectedId(null); + setSelectedValue(null); + } + }, [selected]); + return (