add hot news on home page

This commit is contained in:
Nicolas Demangeat 2024-04-19 17:03:25 +02:00
parent 50f6f68f79
commit d18427f299
15 changed files with 222 additions and 50 deletions

View file

@ -5,9 +5,11 @@ namespace App\Http\Controllers\Bo;
use App\Enums\Settings\SettingsType;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\GeneralSettings\UpdateGeneralSettingRequest;
use App\Lib\Helpers\FileStorageHelper;
use App\Models\GeneralSetting;
use Illuminate\Database\Query\Builder;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
@ -80,7 +82,20 @@ class GeneralSettingController extends Controller
GeneralSetting $general_setting
): \Illuminate\Http\RedirectResponse {
return DB::transaction(function () use ($request, $general_setting) {
$general_setting->fill($request->only('value'));
if ($general_setting->type === SettingsType::hotNews) {
collect($request->only(['value->picture', 'value->text', 'value->picture_alt', 'value->picture_title']))
->each(fn (string|\Illuminate\Http\UploadedFile $value, string $attribute) =>
$general_setting->setAttribute(
$attribute,
$value instanceof \Illuminate\Http\UploadedFile ?
FileStorageHelper::storeFile($general_setting, $value, true):
$value
)
);
} else {
$general_setting->fill($request->only('value'));
}
if ($general_setting->saveOrFail()) {
return redirect()->route('bo.general_settings.edit', $general_setting)
->with('success', __('crud.messages.has_been_updated', [

View file

@ -2,11 +2,14 @@
namespace App\Http\Controllers\Fo;
use App\Enums\Settings\SettingsType;
use App\Http\Controllers\Controller;
use App\Http\Requests\Fo\SingleOptinRequest;
use App\Models\DemarcheSlide;
use App\Models\MainSlide;
use App\Models\Product;
use App\Models\GeneralSetting;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Vite;
use LDC\LDCCRM\Enums\ApiError;
use LDC\LDCCRM\Exceptions\ApiErrorException;
@ -26,6 +29,7 @@ class HomeController extends Controller
->with('approach')->get();
$demarcheSlideModels = DemarcheSlide::query()->where('published', true)->orderBy('order', 'ASC')->get();
$productViews = Product::getProductsViewsByRange();
$hotNewsSettings = GeneralSetting::query()->where('type', SettingsType::hotNews)->first();
// FIXME: replace 'for loop' by eloquent orm on news publicated.
for ($i = 0; $i < 5; $i++) {
$newsModels[] = (object)['title' => "lorem ipsum"];
@ -47,7 +51,8 @@ class HomeController extends Controller
'demarcheSlideModels',
'productViews',
'newsModels',
'recipeModels'
'recipeModels',
'hotNewsSettings'
));
}
@ -76,4 +81,15 @@ class HomeController extends Controller
return \response(trans('Une erreur est survenue, merci de réessayer ultérieurement'), 404);
}
}
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\JsonResponse
*/
public function updateHotNewsSatus(): \Illuminate\Http\JsonResponse
{
Cache::remember("hotNewsIsDisplayed", 60 * 60 * 24, fn() => true);
return response()->json();
}
}

View file

@ -33,12 +33,10 @@ class UpdateGeneralSettingRequest extends FormRequest
if ($generalSettingModel->type === SettingsType::hotNews) {
$this->mergePicture('picture');
$this->merge([
'value' => [
'picture_alt' => $this->input('picture_alt'),
'picture_title' => $this->input('picture_title'),
'picture' => $this->input('picture_title'),
'text' => $this->input('text')
]
'value->picture' => $this->file('picture'),
'value->text' => $this->input('text'),
'value->picture_alt' => $this->input('picture_alt'),
'value->picture_title' => $this->input('picture_title'),
]);
return;
}
@ -64,7 +62,7 @@ class UpdateGeneralSettingRequest extends FormRequest
];
return \array_merge(
$rules,
$this->pictureRules('picture', 200, 150, 200, 150, false),
$this->pictureRules('picture', 200, 100, 200, 100, false),
);
} else {
return [

View file

@ -103,7 +103,7 @@ class FileStorageHelper
*/
public static function storeFile(
Model $model,
$file,
\Illuminate\Http\UploadedFile|\SplFileInfo|string|null $file,
bool $slugify = false,
string $filename = null,
bool $private = false

View file

@ -10,6 +10,8 @@ use App\Lib\Link;
use App\Traits\Models\IsCachedAsCollection;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Cache;
/**
* @property-read \App\Enums\Settings\SettingsType $type Enum SettingsType.
@ -49,7 +51,7 @@ class GeneralSetting extends Model
* @var array<int, string>
*/
protected $fillable = [
'value',
'value'
];
/**
@ -71,15 +73,12 @@ class GeneralSetting extends Model
protected static function booted(): void
{
static::creating(fn () => throw new \ErrorException('Not allowed to create an instance of this'));
static::updating(function (self $model) {
if ($model->type === SettingsType::hotNews) {
// @phpstan-ignore-next-line
$model->value = FileStorageHelper::storeFile($model, $model->value['picture'], true);
}
});
static::updated(function (self $model) {
if ($model->type === SettingsType::hotNews) {
FileStorageHelper::removeOldFile($model, $model->value['picture']);
FileStorageHelper::removeOldFiles(
Arr::wrap(Arr::get($model->getOriginal('value'), 'picture')),
Arr::wrap($model->value['picture'])
);
}
self::removeCached();
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 KiB

View file

@ -1,4 +1,42 @@
#home {
#hot-news-desktop{
.wood-background{
height: 22rem;
background-image: url('@/images/home/hot-news-wood.png');
background-size: cover;
}
.hot-news-position{
position: absolute;
z-index: 10;
top: -7%;
right: 13%;
}
}
#hot-news{
transition: .3s;
hr {
border-top: 2px dashed;
}
&.hot-news-hide {
transform: translate(100%, -50%) !important;
}
&-loader {
opacity: 0;
transition: .3s;
visibility: hidden;
&.hot-news-loader-active {
opacity: 1 !important;
visibility: visible !important;
}
}
}
#main-slides {
#swiper-main-slides {
.swiper-background {

View file

@ -12,3 +12,4 @@ import "./partials/layout";
// CUSTOM
import "./base/common";
import "./pages/home";

View file

@ -0,0 +1,75 @@
import parseValidationErrors from "../../modules/parseLaravelErrorMessages";
import route from "../../modules/route";
import trans from "../../modules/trans";
document.addEventListener("DOMContentLoaded", function () {
if (!document.querySelector("#home #hot-news")) {
return;
}
// addClassIfViewportLessThan600();
// * HOT NEWS
const hotNews = document.getElementById("hot-news") as HTMLDivElement|null;
const hotNewsLoader = hotNews?.querySelector("#hot-news-loader") as HTMLDivElement|null;
const closeButton = hotNews?.querySelector("#close-hot-news") as HTMLDivElement|null;
if (closeButton && hotNews && hotNewsLoader) {
closeButton.addEventListener("click", function() {
hotNews.setAttribute("inert", "true");
hotNewsLoader?.classList.add("hot-news-loader-active");
updateHotNewsCache(hotNews, hotNewsLoader);
});
}
});
/** Call ajax to update cache about hot news content */
function updateHotNewsCache(hotNews: HTMLDivElement, hotNewsLoader: HTMLDivElement): void {
// call ajax pour modif hot news status en false
window.axios.post(getHomeHotNewsRoute()).then(function () {
hotNews.classList.add("hot-news-hide");
hotNews.removeAttribute("inert");
setTimeout(() => {
hotNewsLoader?.classList.remove("hot-news-loader-active");
}, 300);
}).catch((ajaxErrorHandler));
}
/** Get home hot news route */
function getHomeHotNewsRoute(): string {
const homeRoute = route.methods.route("fo.home.hotnews");
if (!homeRoute) {
throw new Error("Undefined route fo.home.hotnews");
}
return homeRoute;
}
/** Return error message after the ajax call */
function ajaxErrorHandler(e: any): void {
let message = trans.methods.__("Une erreur est survenue");
if (e.response.status === 422) {
message = parseValidationErrors.methods.parseValidationErrors(
e.response?.data?.errors ?? {}
) || message;
if (window.vueDebug) {
console.warn(e.response.data.errors);
}
} else if (e.response.status === 419) {
console.log(trans.methods.__("Votre session a expiré, la page va être rechargée"));
setTimeout(() => window.location.reload(), 2000);
} else if (e.response.status === 403) {
console.log(trans.methods.__("Vous n'êtes pas autorisé a effectuer cette action"));
setTimeout(() => window.location.reload(), 2000);
} else {
if (window.vueDebug) {
console.error(e);
}
}
console.log(message);
}
// Définir une fonction pour ajouter une classe à une div si la taille du viewport est inférieure à 600px
// function addClassIfViewportLessThan600() {
// const viewportWidth = window.innerWidth;
// const targetDiv = document.querySelector("#home #hot-news-desktop");
// if (viewportWidth < 600 && targetDiv) {
// targetDiv.classList.add("wood-background");
// }
// }

View file

@ -55,7 +55,7 @@
'id' => 'hotNewsPicture',
'name' => 'picture',
'width' => 200,
'height' => 150,
'height' => 100,
'model' => null,
'value' => $generalSettingModel->value['picture'],
'required' => true,
@ -109,16 +109,3 @@
</fieldset>
</div>
</div>
{{--
<div>
{{ __($generalSettingModel->value['picture_alt']) }}
</div>
<div>
{{ __($generalSettingModel->value['picture_title']) }}
</div>
<div>
{{ __($generalSettingModel->value['picture']) }}
</div>
<div>
{{ __($generalSettingModel->value['text']) }}
</div> --}}

View file

@ -13,7 +13,7 @@
<div class="nav-collapse bg-light collapse shadow-sm" id="{{ $collapseId }}">
<div class="row w-100 mb-xl-0 mx-auto mb-3">
@include('fo.layouts.main-menu.partials.collapse-left', [
'btnLabel' => str($menuNodeData->sub_label)->ucFirst(),
'btnLabel' => str($menuNodeData->type->label())->ucFirst(),
'btnLink' => route($menuNode->link->getRouteName()),
])
@include('fo.layouts.main-menu.partials.collapse-middle', [

View file

@ -7,21 +7,19 @@
@section('content')
<section id="home">
{{-- HOT NEWS --}}
<div class="top-50 position-fixed z-3 end-0 bg-white">
<div class="row p-4">
<div class="col-6">
<img src="" alt="">
</div>
<div class="col-6">
</div>
</div>
<div class="row p-4">
<div class="col-6">
<img src="" alt="">
</div>
<div class="col-6">
<div @class([
'top-50 end-0 translate-middle-y position-fixed z-3 bg-white d-none d-xl-block',
'd-none' => cache()->get('hotNewsIsDisplayed')
]) id="hot-news">
<div id="hot-news-loader" class="bg-primary bg-opacity-50 position-absolute w-100 h-100 top-0 start-0 z-2">
<div class="position-absolute start-50 top-50 translate-middle">
<div class="spinner-border" role="status">
<span class="visually-hidden">Chargement...</span>
</div>
</div>
</div>
<button id="close-hot-news" class="btn-close position-absolute start-0 top-0" aria-label="Close"></button>
@include('fo.partials.hot-news')
</div>
{{-- MAIN SLIDES --}}
@if ($mainSlideModels->isNotEmpty())
@ -88,15 +86,25 @@
<img src="{{ Vite::asset('resources/assets/images/common/arrow.svg') }}" title="{{ __('Navigation du slider (gauche)') }}"
alt="{{ __('flèche vers la gauche') }}">
</button>
<button
class="swiper-button swiper-button-next-custom swiper-main-slides-button-next rounded-circle z-1 mx-3 border-0 bg-white">
<button class="swiper-button swiper-button-prev-custom swiper-main-slides-button-prev rounded-circle z-1 mx-3 border-0 bg-white">
<img src="{{ Vite::asset('resources/assets/images/common/arrow.svg') }}" title="{{ __('Navigation du slider (gauche)') }}"
alt="{{ __('flèche vers la gauche') }}">
</button>
<button class="swiper-button swiper-button-next-custom swiper-main-slides-button-next rounded-circle z-1 mx-3 border-0 bg-white">
<img src="{{ Vite::asset('resources/assets/images/common/arrow.svg') }}" title="{{ __('Navigation du slider (droite)') }}"
alt="{{ __('flèche vers la droite') }}">
</button>
<img class="d-none d-md-block w-100" src="{{ Vite::asset('resources/assets/images/home/bg-slider-wood.png') }}"
title="{{ __('Image imitation bois') }}" alt="{{ __('Footer imitation bois du slider') }}">
<img class="w-100 d-none d-xl-block" src="{{ Vite::asset('resources/assets/images/home/bg-slider-wood.png') }}"
alt="{{ __('Footer imitation bois du slider') }}">
</section>
@endif
{{-- HOT NEWS DESKTOP --}}
<div id="hot-news-desktop" class="position-relative">
<div class="wood-background w-100"></div>
<div class="w-75 mx-auto d-block d-xl-none bg-white hot-news-position line-border-medium">
@include('fo.partials.hot-news')
</div>
</div>
{{-- RANGES/PRODUCTS --}}
{{-- FIXME: uncomment when CRUD range is ready @if ($rangeModels->isNotEmpty()) --}}
<section class="position-relative bg-light gx-0 py-5" id="products">

View file

@ -0,0 +1,32 @@
<div class="container-fluid">
<div class="row p-4">
<div class="col-6">
{{-- FIXME: changer l'image pour l'image du model news ainsi que le texte --}}
<img class="img-fluid" src="{{ Vite::asset('resources/assets/images/tmp/nuggets.png') }}" alt="">
</div>
<div class="col-6 text-center">
LOREM IPSUM
</div>
</div>
<hr>
<div class="row p-4">
<div class="col-6">
{{-- FIXME: changer l'image pour l'image du model news ainsi que le texte --}}
<img class="img-fluid" src="{{ Vite::asset('resources/assets/images/tmp/nuggets.png') }}" alt="">
</div>
<div class="col-6 text-center">
LOREM IPSUM
</div>
</div>
<hr>
<div class="row">
<div class="col-8 rotate-left-5 f-bakerie-rough fs-4 fw-bolder">
{{ $hotNewsSettings->value['text'] }}
</div>
</div>
<div class="row ps-4 pe-4 pb-4 pt-2">
<div class="col-12 text-center">
<img class="img-fluid" src="{{ asset($hotNewsSettings->value['picture']) }}" alt="{{ $hotNewsSettings->value['picture_alt'] }}" title="{{ $hotNewsSettings->value['picture_title'] }}">
</div>
</div>
</div>

View file

@ -8,6 +8,7 @@
// FIXME: Add missing routes for RangesSliderComponent.vue
window.__SYSTEM._routes['fo.home.products'] = '{{ route('fo.home.products', ['range_slug' => 'RANGESLUG']) }}';
window.__SYSTEM._routes['fo.contact.show'] = '{{ route('fo.contact.show') }}';
window.__SYSTEM._routes['fo.home.hotnews'] = '{{ route('fo.home.hotnews') }}';
window.__SYSTEM._routes['fo.add-single-optin'] = '{{ route('fo.add-single-optin') }}';
window.__SYSTEM._routes['bo.menu_node_data.index'] = '{{ route('bo.menu_node_data.index') }}';
window.__SYSTEM._routes['bo.menu_node_data.update'] = '{{ route('bo.menu_node_data.update', ['menu_node_data' => 'MENU_NODE_DATA']) }}';

View file

@ -20,6 +20,8 @@ Route::middleware(['throttle:credentials'])
'/accueil/produits-par-gamme/{range_slug}',
[ProductController::class, 'jsonGetProductsViewsByRange']
)->name('home.products');
Route::post('/accueil/actualites_chaudes', [HomeController::class, 'updateHotNewsSatus'])
->name('home.hotnews');
Route::get('/recrutement', [StaticPageController::class, 'recruitment'])->name('recruitment.show');
Route::get('/mentions-legales', [StaticPageController::class, 'legals'])->name('legals.show');
Route::get('/charte-de-confidentialite', [StaticPageController::class, 'policies'])->name('policies.show');