diff --git a/app/Http/Controllers/Fo/NewsController.php b/app/Http/Controllers/Fo/NewsController.php new file mode 100644 index 0000000..acec46b --- /dev/null +++ b/app/Http/Controllers/Fo/NewsController.php @@ -0,0 +1,45 @@ +where('type', StaticPageType::news) + ->firstOrFail(); + $rangeModelList = Range::query()->limit(3)->inRandomOrder()->get(); + + $newsModelList = News::query()->get(); + return view('fo.pages.news.index', compact( + 'staticPageModel', + 'newsModelList', + 'rangeModelList' + )); + } + + /** + * Show a specific resource. + * + * @param \App\Models\News $frontNews + * @return \Illuminate\Contracts\View\View + */ + public function show(News $frontNews): \Illuminate\Contracts\View\View + { + return view('fo.pages.news.show', [ + 'newsModel' => $frontNews + ]); + } +} diff --git a/app/Http/Requests/Bo/News/StoreNewsRequest.php b/app/Http/Requests/Bo/News/StoreNewsRequest.php index ea86d7e..20dd7bc 100644 --- a/app/Http/Requests/Bo/News/StoreNewsRequest.php +++ b/app/Http/Requests/Bo/News/StoreNewsRequest.php @@ -42,6 +42,9 @@ class StoreNewsRequest extends FormRequest public function rules(): array { $rules = [ + 'seo_title' => 'required|between:45,70', + 'seo_description' => 'required|between:50,160', + 'title' => 'required|string|between:3,100', 'text' => [ 'required', 'string', @@ -78,12 +81,15 @@ class StoreNewsRequest extends FormRequest public function attributes(): array { return [ - 'text' => trans('validation.attributes.text'), - 'picture_alt' => trans('validation.custom.picture_alt'), - 'picture_title' => trans('validation.custom.picture_title'), - 'published' => trans('validation.custom.activated'), - 'publish_at' => trans('validation.custom.publish_at'), - 'unpublish_at' => trans('validation.custom.unpublish_at'), + 'seo_title' => trans('validation.custom.seo_title'), + 'seo_description' => trans('validation.custom.seo_description'), + 'title' => trans('validation.attributes.title'), + 'text' => trans('validation.attributes.text'), + 'picture_alt' => trans('validation.custom.picture_alt'), + 'picture_title' => trans('validation.custom.picture_title'), + 'published' => trans('validation.custom.activated'), + 'publish_at' => trans('validation.custom.publish_at'), + 'unpublish_at' => trans('validation.custom.unpublish_at'), ]; } } diff --git a/app/Models/News.php b/app/Models/News.php index 4f13a79..86cfa32 100644 --- a/app/Models/News.php +++ b/app/Models/News.php @@ -3,11 +3,18 @@ namespace App\Models; use App\Lib\Helpers\FileStorageHelper; +use App\Lib\Helpers\ToolboxHelper; use App\Traits\Models\ActivityLog; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +use Illuminate\Support\Str; /** + * @property string $seo_title Utilisé pour la balise title de la page. + * @property string $seo_description Utilisé pour la balise meta description + * de la page. + * @property string $title Titre de la news. + * @property string $slug Slug à partir du titre de la news. * @property string $picture Path de l'image (308px * 223px). * @property string $picture_alt Attribut alt de l'image. * @property string $picture_title Attribut title de l'image. @@ -29,6 +36,10 @@ class News extends Model * @var array */ protected $fillable = [ + 'seo_title', + 'seo_description', + 'title', + 'slug', 'picture', 'picture_alt', 'picture_title', @@ -81,10 +92,30 @@ class News extends Model */ private static function manageFields(self $model): void { + // * ASSERTIONS + self::assertFieldIsUnique('title', $model->title, $model->id); + self::assertFieldIsUnique('slug', Str::slug($model->title), $model->id); + + // * TRANSFORMATIONS + $model->slug = Str::slug($model->title); self::makeStatusSense($model); $model->picture = FileStorageHelper::storeFile($model, $model->picture, true); } + /** + * Asserts using validation that the field is unique. + * + * @param string $nameField + * @param string $valueField + * @param integer|null $id + * @return void + */ + private static function assertFieldIsUnique(string $nameField, string $valueField, ?int $id = null): void + { + $table = (new self())->getTable(); + ToolboxHelper::assertFieldIsUnique($table, $nameField, $valueField, $id); + } + /** * Adjusts model status using current status and publish dates * diff --git a/app/Models/Product.php b/app/Models/Product.php index e868ea3..c9dbb3a 100644 --- a/app/Models/Product.php +++ b/app/Models/Product.php @@ -117,7 +117,7 @@ class Product extends Model self::assertFieldIsUnique('slug', Str::slug($model->label), $model->id); // * TRANSFORMATIONS - self::setSlug($model, $model->label); + $model->slug = Str::slug($model->label); $model->picture = FileStorageHelper::storeFile($model, $model->picture, true); $model->picture_thumbnail = FileStorageHelper::storeFile($model, $model->picture_thumbnail, true); } @@ -147,19 +147,6 @@ class Product extends Model ToolboxHelper::assertFieldIsUnique($table, $nameField, $valueField, $id); } - /** - * Set the slug. - * - * @param self $model - * @param string $original - * - * @return void - */ - private static function setSlug(self $model, string $original): void - { - $model->slug = Str::slug($original); - } - // * RELATIONS /** diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index 9554dc5..d71b31d 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -48,6 +48,7 @@ class RouteServiceProvider extends ServiceProvider ->group(base_path('routes/web-fo.php')); }); + Route::bind('frontNews', fn ($value) => News::query()->where('slug', $value)->firstOrFail()); Route::model('user', User::class); Route::model('picto', Picto::class); Route::model('range', Range::class); diff --git a/database/factories/NewsFactory.php b/database/factories/NewsFactory.php index 0de2d69..50cdc47 100644 --- a/database/factories/NewsFactory.php +++ b/database/factories/NewsFactory.php @@ -3,6 +3,7 @@ namespace Database\Factories; use Illuminate\Database\Eloquent\Factories\Factory; +use Illuminate\Support\Str; class NewsFactory extends Factory { @@ -17,13 +18,18 @@ class NewsFactory extends Factory $unpublish_at = $publish_at->copy()->addDays(rand(1, 30)); $published = now() > $publish_at && now() < $unpublish_at ? true : false; + $title = fake()->unique()->slug(3); return [ - 'picture_alt' => fake()->sentence(), - 'picture_title' => fake()->sentence(), - 'text' => \clean(\fake()->randomHtml(1, 1)), - 'published' => $published, - 'publish_at' => $publish_at, - 'unpublish_at' => $unpublish_at, + 'title' => Str::of($title)->replace('-', ' ')->title(), + 'slug' => Str::slug($title), + 'seo_title' => fake()->sentence(), + 'seo_description' => fake()->sentence(), + 'picture_alt' => fake()->sentence(), + 'picture_title' => fake()->sentence(), + 'text' => \clean(\fake()->randomHtml(1, 1)), + 'published' => $published, + 'publish_at' => $publish_at, + 'unpublish_at' => $unpublish_at, ]; } } diff --git a/database/migrations/2023_12_22_154506_update_news_table.php b/database/migrations/2023_12_22_154506_update_news_table.php new file mode 100644 index 0000000..15c0e58 --- /dev/null +++ b/database/migrations/2023_12_22_154506_update_news_table.php @@ -0,0 +1,62 @@ +get(); + Schema::table('news', function (Blueprint $table) { + $table->string('seo_title')->after('id') + ->comment('Utilisé pour la balise title de la page produit'); + $table->string('seo_description')->after('seo_title') + ->comment('Utilisé pour la balise meta description de la page produit'); + $table->string('title')->after('seo_description') + ->comment('Titre de la news'); + $table->string('slug')->after('title') + ->comment('Slug à partir du titre de la news'); + $table->text('text')->change() + ->comment('WYSIWYG - Texte de l\'actualité.'); + }); + $news->each(function (object $news) { + $title = fake()->unique()->jobTitle; + DB::table('news')->where('id', $news->id) + ->update([ + 'seo_title' => fake()->words(3, true), + 'seo_description' => fake()->words(7, true), + 'title' => $title, + 'slug' => Str::slug($title), + ]); + }); + Schema::table('news', function (Blueprint $table) { + $table->unique('slug'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('news', function (Blueprint $table) { + $table->dropColumn('seo_title'); + $table->dropColumn('seo_description'); + $table->dropColumn('title'); + $table->dropColumn('slug'); + $table->text('text')->change() + ->comment('WYSIWYG - Texte de l\'actualité. Max 80 mots.'); + }); + } +}; diff --git a/resources/assets/images/news/news-bg.jpg b/resources/assets/images/news/news-bg.jpg new file mode 100644 index 0000000..6686522 Binary files /dev/null and b/resources/assets/images/news/news-bg.jpg differ diff --git a/resources/sass/fo/base/_common.scss b/resources/sass/fo/base/_common.scss index cef3f4b..d307d63 100644 --- a/resources/sass/fo/base/_common.scss +++ b/resources/sass/fo/base/_common.scss @@ -12,7 +12,7 @@ } // N'as rien à faire ici !! -.products, .history, .news { +.products, .history { h1, h2 { &::after { display: block; @@ -39,3 +39,7 @@ opacity: 1 !important; visibility: visible !important; } + +.color-primary { + color: $color-primary !important; +} diff --git a/resources/sass/fo/fo.scss b/resources/sass/fo/fo.scss index 47f7cea..dd0a55e 100644 --- a/resources/sass/fo/fo.scss +++ b/resources/sass/fo/fo.scss @@ -25,6 +25,7 @@ @import './pages/product'; @import './pages/ranges'; @import './pages/range'; +@import './pages/news-index'; // * PARTIALS @import './partials/news-section'; diff --git a/resources/sass/fo/pages/_historypage.scss b/resources/sass/fo/pages/_historypage.scss index 184dcad..3851187 100644 --- a/resources/sass/fo/pages/_historypage.scss +++ b/resources/sass/fo/pages/_historypage.scss @@ -1,4 +1,4 @@ -.historypage { +.historypage,.newspage { .ourproducts { &::before { position: relative; @@ -129,7 +129,7 @@ // X:::::X X:::::XL::::::::::::::::::::::L // XXXXXXX XXXXXXXLLLLLLLLLLLLLLLLLLLLLLLL @include media-breakpoint-up(xl) { - .historypage { + .historypage,.newspage { .ourproducts { .seagull { top: 2.5rem; diff --git a/resources/sass/fo/pages/_news-index.scss b/resources/sass/fo/pages/_news-index.scss new file mode 100644 index 0000000..79990e6 --- /dev/null +++ b/resources/sass/fo/pages/_news-index.scss @@ -0,0 +1,28 @@ +.newspage { + #swiper-news-index { + clip-path: inset(-2rem -50vw 0 0); + + // .swiper-slide { + // max-width: 22rem; + // } + + .swiper-button-prev-custom, .swiper-button-next-custom { + &:hover { + cursor: pointer; + } + } + } + + .link-news::after { + position: relative; + top: -0.5rem; + display: block; + width: 100%; + height: 1rem; + background-image: url('@/images/global/underline-yellow.svg'); + background-position: center; + background-repeat: no-repeat; + background-size: 3rem; + content: ""; + } +} diff --git a/resources/ts/fo/base/common.ts b/resources/ts/fo/base/common.ts index c040eb4..10ba014 100644 --- a/resources/ts/fo/base/common.ts +++ b/resources/ts/fo/base/common.ts @@ -47,6 +47,26 @@ document.addEventListener("DOMContentLoaded", function () { } // SWIPER const swiperConfig = { + "swiper-news-index": { + modules: [Navigation], + grabCursor: true, + slidesPerView: 1, + navigation: { + nextEl: ".swiper-button-next-custom", + prevEl: ".swiper-button-prev-custom", + }, + breakpoints: { + 500: { + slidesPerView: 2, + }, + 768: { + slidesPerView: 3, + }, + 1200: { + slidesPerView: 4, + }, + }, + }, "swiper-news": { modules: [Navigation], grabCursor: true, diff --git a/resources/views/bo/pages/news/form.blade.php b/resources/views/bo/pages/news/form.blade.php index 936ed73..575e992 100644 --- a/resources/views/bo/pages/news/form.blade.php +++ b/resources/views/bo/pages/news/form.blade.php @@ -1,29 +1,103 @@
- {{-- TEXT --}} -
+ {{-- SEO --}} +
- {{ Str::ucfirst(__('validation.attributes.text')) }} -
-
+ {{ Str::ucfirst(__('validation.custom.seo')) }} +
+ {{-- SEO_TITLE --}} +
@include('bo.partials.inputs.varchar', [ - 'id' => 'newsText', + 'id' => 'newsSeoTitle', 'label' => __(':field :inter:model', [ - 'field' => Str::ucfirst(__('validation.attributes.text')), - 'inter' => __('validation.custom.of_the.vowel'), + 'field' => Str::ucfirst(__('validation.custom.seo_title')), + 'inter' => __('validation.custom.of_the.male'), 'model' => __('models.classes.news'), ]), 'hasWordCounter' => true, - 'name' => 'text', + 'name' => 'seo_title', 'required' => true, 'model' => $newsModel ?? null, - 'helper' => __('validation.string', [ + 'helper' => sprintf( + '%s %s', + __('crud.helpers.seo_title'), + __('validation.between.string', [ + 'attribute' => __(':field :inter:model', [ + 'field' => __('validation.custom.seo_title'), + 'inter' => __('validation.custom.of_the.female'), + 'model' => __('models.classes.news'), + ]), + 'min' => 45, + 'max' => 70, + ])), + 'placeholder' => __(':field :inter:model', [ + 'field' => Str::ucfirst(__('validation.custom.seo_title')), + 'inter' => __('validation.custom.of_the.male'), + 'model' => __('models.classes.news'), + ]), + ]) +
+ {{-- SEO_DESCRIPTION --}} +
+ @include('bo.partials.inputs.varchar-multiline', [ + 'id' => 'newsSeoDescription', + 'label' => __(':field :inter:model', [ + 'field' => Str::ucfirst(__('validation.custom.seo_description')), + 'inter' => __('validation.custom.of_the.male'), + 'model' => __('models.classes.news'), + ]), + 'hasWordCounter' => true, + 'name' => 'seo_description', + 'required' => true, + 'model' => $newsModel ?? null, + 'helper' => sprintf( + '%s %s', + __('crud.helpers.seo_description'), + __('validation.between.string', [ + 'attribute' => __(':field :inter:model', [ + 'field' => __('validation.custom.seo_description'), + 'inter' => __('validation.custom.of_the.female'), + 'model' => __('models.classes.news'), + ]), + 'min' => 50, + 'max' => 160, + ])), + 'placeholder' => __(':field :inter:model', [ + 'field' => Str::ucfirst(__('validation.custom.seo_description')), + 'inter' => __('validation.custom.of_the.male'), + 'model' => __('models.classes.news'), + ]), + ]) +
+
+
+
+ {{-- TITLE --}} +
+
+ {{ Str::ucfirst(__('validation.attributes.title')) }} +
+
+ @include('bo.partials.inputs.varchar', [ + 'id' => 'newsTitle', + 'label' => __(':field :inter:model', [ + 'field' => Str::ucfirst(__('validation.attributes.title')), + 'inter' => __('validation.custom.of_the.male'), + 'model' => __('models.classes.news'), + ]), + 'hasWordCounter' => true, + 'name' => 'title', + 'required' => true, + 'model' => $newsModel ?? null, + 'helper' => __('validation.between.string', [ 'attribute' => __(':field :inter:model', [ - 'field' => Str::ucfirst(__('validation.attributes.text')), - 'inter' => __('validation.custom.of_the.vowel'), + 'field' => __('validation.attributes.title'), + 'inter' => __('validation.custom.of_the.male'), 'model' => __('models.classes.news'), ]), + 'min' => 3, + 'max' => 100, ]), - 'placeholder' => Str::ucfirst(__('validation.attributes.text')), + 'placeholder' => __('Un titre clair et concis'), ])
@@ -103,6 +177,28 @@
+ {{-- TEXT --}} +
+
+ {{ Str::ucfirst(__('validation.attributes.text')) }} +
+
+ @include('bo.partials.inputs.text-html', [ + 'id' => 'newsDescription', + 'label' => __(':field :inter:model', [ + 'field' => Str::ucfirst(__('validation.attributes.text')), + 'inter' => __('validation.custom.of_the.male'), + 'model' => __('models.classes.news'), + ]), + 'name' => 'text', + 'model' => $newsModel ?? null, + 'required' => true, + 'helper' => __('crud.minicrud.text_warning'), + ]) +
+
+
+
{{-- PUBLISHED --}}
diff --git a/resources/views/fo/layouts/footer.blade.php b/resources/views/fo/layouts/footer.blade.php index af21570..1f4608c 100644 --- a/resources/views/fo/layouts/footer.blade.php +++ b/resources/views/fo/layouts/footer.blade.php @@ -5,11 +5,14 @@ {{-- Logo + Social --}} @@ -19,22 +22,28 @@ {{ __('Tre Galette') }}
@@ -44,15 +53,22 @@
- \ No newline at end of file + diff --git a/resources/views/fo/layouts/nav-list.blade.php b/resources/views/fo/layouts/nav-list.blade.php index 912e576..a39c516 100644 --- a/resources/views/fo/layouts/nav-list.blade.php +++ b/resources/views/fo/layouts/nav-list.blade.php @@ -22,7 +22,7 @@ 'url' => route('fo.ranges.index'), 'text' => __('Nos produits'), 'title' => __('Nos produits'), - 'is_active' => request()->routeIs('fo.ranges.index'), + 'is_active' => request()->routeIs('fo.ranges.*'), 'sub_menu_list' => $ranges, ]) @@ -42,6 +42,14 @@ 'is_active' => request()->routeIs('fo.commitments.show'), ]) +
{{-- SWIPER NEWS --}}
@php - $cardClasses = ['btn-rotate-left', 'btn-rotate-right', '']; + $cardClasses = ['btn-rotate-left', 'btn-rotate-right', '']; @endphp @foreach ($newsModels as $news) -
-
- {{ $news->picture_alt }} -
-

- {{ $news->text }} -

-
+
+
+ {{ $news->picture_alt }} +
+

+ {{ Str::limit(strip_tags($news->text), 150, '...') }} +

+
@endforeach
- {{ __('flèche vers la gauche') }} + {{ __('flèche vers la gauche') }}
- {{ __('flèche vers la droite') }} + {{ __('flèche vers la droite') }}
@@ -48,4 +44,4 @@
- + \ No newline at end of file diff --git a/resources/views/fo/partials/our-products.blade.php b/resources/views/fo/partials/our-products.blade.php new file mode 100644 index 0000000..53489e0 --- /dev/null +++ b/resources/views/fo/partials/our-products.blade.php @@ -0,0 +1,33 @@ +
+
+
+
+

{{ __('Découvrez nos produits') }}

+
+
+ @foreach ($rangeModels as $key => $rangeModel) + + @endforeach +
+
+ {{ __('Ombre de mouettes') }} +
+
+
+
diff --git a/routes/web-fo.php b/routes/web-fo.php index 5642bba..3fe31a4 100644 --- a/routes/web-fo.php +++ b/routes/web-fo.php @@ -1,5 +1,6 @@ name('fo.')->group(function () { }); }); + // * NEWS + Route::get('/actualites', [NewsController::class, 'index'])->name('news.index'); + Route::get('/actualites/{frontNews:slug}', [NewsController::class, 'show'])->name('news.show'); + // * RANGES Route::get('/gammes', [RangeController::class, 'index'])->name('ranges.index'); Route::get('/gamme/{slug}', [RangeController::class, 'show'])->name('ranges.show');