diff --git a/app/Http/Controllers/Bo/StickyController.php b/app/Http/Controllers/Bo/StickyController.php new file mode 100644 index 0000000..79c08da --- /dev/null +++ b/app/Http/Controllers/Bo/StickyController.php @@ -0,0 +1,157 @@ +authorizeResource(Sticky::class); + } + + /** + * Display a listing of the resource. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Contracts\View\View + */ + public function index(Request $request): \Illuminate\Contracts\View\View + { + /** @var \Illuminate\Database\Eloquent\Builder $stickyModels */ + $stickyModels = Sticky::query(); + + /** @var string $search Search field */ + $search = $request->input('search'); + if ($search) { + $this->searchQuery( + $stickyModels, + $search, + null, + 'title', + ); + } + $searchFields = \implode(', ', [ + Str::ucfirst(trans('validation.attributes.title')), + ]); + + /** Sort columns with a query */ + $this->sortQuery($stickyModels); + + /** Custom pagination */ + $stickyModels = $this->paginate($stickyModels); + + return view('bo.pages.stickies.index', compact('stickyModels', 'search', 'searchFields')); + } + + + /** + * Show the form for creating a new resource. + * + * @param \App\Models\Sticky $sticky + * @return \Illuminate\Contracts\View\View + */ + public function create(Sticky $sticky): \Illuminate\Contracts\View\View + { + return view('bo.pages.stickies.create', ['stickyModel' => $sticky]); + } + + /** + * Store a newly created resource in storage. + * + * @param \App\Http\Requests\Bo\Stickies\StoreStickyRequest $request + * @return \Illuminate\Http\RedirectResponse + */ + public function store(StoreStickyRequest $request): \Illuminate\Http\RedirectResponse + { + return DB::transaction(function () use ($request) { + $sticky = new Sticky(); + + if ($sticky->fill($request->validated())->saveOrFail()) { + return redirect()->route('bo.stickies.edit', $sticky) + ->with('success', trans('crud.messages.has_been_created', [ + 'model' => Str::ucfirst(trans_choice('models.classes.sticky', 0)) + ])); + } + return redirect()->back() + ->with('error', trans('crud.messages.cannot_be_created', [ + 'model' => Str::ucfirst(trans_choice('models.classes.sticky', 0)) + ])); + }); + } + + /** + * Show the form for editing the specified resource. + * + * @param \App\Models\Sticky $sticky + * @return \Illuminate\Contracts\View\View + */ + public function edit(Sticky $sticky): \Illuminate\Contracts\View\View + { + return view('bo.pages.stickies.edit', ['stickyModel' => $sticky]); + } + + /** + * Update the specified resource in storage. + * + * @param \App\Http\Requests\Bo\Stickies\UpdateStickyRequest $request + * @param \App\Models\Sticky $sticky + * @return \Illuminate\Http\RedirectResponse + */ + public function update(UpdateStickyRequest $request, Sticky $sticky): \Illuminate\Http\RedirectResponse + { + return DB::transaction(function () use ($request, $sticky) { + if ($sticky->fill($request->validated())->saveOrFail()) { + return redirect()->route('bo.stickies.edit', $sticky) + ->with('success', trans('crud.messages.has_been_updated', [ + 'model' => Str::ucfirst(trans_choice('models.classes.sticky', 0)) + ])); + } + return redirect()->route('bo.stickies.edit', $sticky) + ->with('error', trans('crud.messages.cannot_be_updated', [ + 'model' => Str::ucfirst(trans_choice('models.classes.sticky', 0)) + ])); + }); + } + + /** + * Remove the specified resource from storage. + * + * @param \App\Models\Sticky $sticky + * @return \Illuminate\Http\RedirectResponse + */ + public function destroy(Sticky $sticky): \Illuminate\Http\RedirectResponse + { + if ($sticky->deleteOrFail()) { + return redirect()->route('bo.stickies.index') + ->with('success', trans('crud.messages.has_been_deleted', [ + 'model' => Str::ucfirst(trans_choice('models.classes.sticky', 0)) + ])); + } + return redirect()->back() + ->with('error', trans('crud.messages.cannot_be_deleted', [ + 'model' => Str::ucfirst(trans_choice('models.classes.sticky', 0)) + ])); + } + + /** + * Duplicate the specified resource. + * + * @param \App\Models\Sticky $sticky + * @return \Illuminate\Contracts\View\View + */ + public function duplicate(Sticky $sticky): \Illuminate\Contracts\View\View + { + return $this->create($sticky->replicate()); + } +} diff --git a/app/Http/Controllers/Fo/StaticPageController.php b/app/Http/Controllers/Fo/StaticPageController.php index 37bbcd0..a97eaae 100644 --- a/app/Http/Controllers/Fo/StaticPageController.php +++ b/app/Http/Controllers/Fo/StaticPageController.php @@ -19,6 +19,7 @@ use App\Models\Section; use App\Models\Site; use App\Models\Slide; use App\Models\StaticPage; +use App\Models\Sticky; use App\Models\Strip; use App\Models\Testimonial; use Illuminate\Database\Eloquent\Builder; @@ -274,6 +275,7 @@ class StaticPageController extends Controller $testimonialSection = $sectionModels->where('section', PageSectionEnum::hire_grow)->first(); $paritySection = $sectionModels->where('section', PageSectionEnum::hire_parity)->first(); $findSection = $sectionModels->where('section', PageSectionEnum::hire_find)->first(); + $navLinks = Sticky::query()->where('type', StaticPageType::we_hire)->get(); return view('fo.pages.hire', \compact( 'staticPageModel', @@ -284,7 +286,8 @@ class StaticPageController extends Controller 'formationSection', 'testimonialSection', 'paritySection', - 'findSection' + 'findSection', + 'navLinks' )); } diff --git a/app/Http/Requests/Bo/Stickies/StoreStickyRequest.php b/app/Http/Requests/Bo/Stickies/StoreStickyRequest.php new file mode 100644 index 0000000..b749c3b --- /dev/null +++ b/app/Http/Requests/Bo/Stickies/StoreStickyRequest.php @@ -0,0 +1,56 @@ +check(); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules(): array + { + return [ + 'type' => [ + Rule::requiredIf($this->user()->can('create', $this->sticky)), + new Enum(StaticPageType::class) + ], + 'title' => 'required|string|between:3,255', + 'anchor' => [ + Rule::requiredIf($this->user()->can('create', $this->sticky)), + 'string', + 'between:3,255' + ], + ]; + } + + /** + * Get custom attributes for validator errors. + * + * @return array + */ + public function attributes(): array + { + return [ + 'type' => trans('validation.custom.type'), + 'title' => trans('validation.attributes.title'), + 'anchor' => trans('validation.custom.anchor'), + ]; + } +} diff --git a/app/Http/Requests/Bo/Stickies/UpdateStickyRequest.php b/app/Http/Requests/Bo/Stickies/UpdateStickyRequest.php new file mode 100644 index 0000000..869122c --- /dev/null +++ b/app/Http/Requests/Bo/Stickies/UpdateStickyRequest.php @@ -0,0 +1,7 @@ + + */ + protected $fillable = [ + 'type', + 'title', + 'anchor', + ]; + + /** + * The attributes that should be cast. + * + * @var array + */ + protected $casts = [ + 'type' => StaticPageType::class, + ]; + + /** + * Perform any actions required after the model boots. + * + * @return void + */ + protected static function booted(): void + { + static::creating(function (self $model) { + // * Prevent user elevation privileges + throw_if( + auth('backend')->user() and auth('backend')->user()->role->value() < RoleEnum::conceptor->value(), + AuthorizationException::class + ); + }); + static::deleting(function (self $model) { + // // * Prevent user elevation privileges + throw_if( + auth('backend')->user() and auth('backend')->user()->role->value() < RoleEnum::conceptor->value(), + AuthorizationException::class + ); + }); + } +} diff --git a/app/Policies/StickyPolicy.php b/app/Policies/StickyPolicy.php new file mode 100644 index 0000000..f4621ce --- /dev/null +++ b/app/Policies/StickyPolicy.php @@ -0,0 +1,98 @@ +id(); + $table->unsignedTinyInteger('type')->comment('Type of the static page (StaticPageEnum).'); + $table->string('title')->comment('Title of the sticky'); + $table->string('anchor')->comment('Anchor of the sticky'); + $table->timestamps(); + }); + + Artisan::call('db:seed', [ + 'class' => StickySeeder::class + ]); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('stickies'); + } +}; diff --git a/database/seeders/Mandatory/StickySeeder.php b/database/seeders/Mandatory/StickySeeder.php new file mode 100644 index 0000000..13c42dd --- /dev/null +++ b/database/seeders/Mandatory/StickySeeder.php @@ -0,0 +1,52 @@ +updateOrCreate( + [ + 'type' => StaticPageType::we_hire, + 'title' => 'La vie d\'entreprise', + 'anchor' => 'company-life' + ], + ); + Sticky::query()->updateOrCreate( + [ + 'type' => StaticPageType::we_hire, + 'title' => 'L\'alternance et la formation', + 'anchor' => 'formation' + ], + ); + Sticky::query()->updateOrCreate( + [ + 'type' => StaticPageType::we_hire, + 'title' => 'Nos collaborateurs témoignent', + 'anchor' => 'we-testimonials' + ], + ); + Sticky::query()->updateOrCreate( + [ + 'type' => StaticPageType::we_hire, + 'title' => 'La parité', + 'anchor' => 'parity' + ], + ); + Sticky::query()->updateOrCreate( + [ + 'type' => StaticPageType::we_hire, + 'title' => 'En savoir plus', + 'anchor' => 'on-se-retrouve' + ], + ); + } +} diff --git a/database/seeders/MandatorySeeder.php b/database/seeders/MandatorySeeder.php index 24e1d09..3447fc2 100644 --- a/database/seeders/MandatorySeeder.php +++ b/database/seeders/MandatorySeeder.php @@ -3,6 +3,7 @@ namespace Database\Seeders; use Database\Seeders\Mandatory\StaticPageSeeder; +use Database\Seeders\Mandatory\StickySeeder; use Illuminate\Database\Seeder; class MandatorySeeder extends Seeder @@ -15,7 +16,8 @@ class MandatorySeeder extends Seeder public function run(): void { $this->call([ - StaticPageSeeder::class + StaticPageSeeder::class, + StickySeeder::class ]); } } diff --git a/resources/lang/fr/models.php b/resources/lang/fr/models.php index b7ec1b0..c91a620 100644 --- a/resources/lang/fr/models.php +++ b/resources/lang/fr/models.php @@ -17,5 +17,6 @@ return [ 'network' => 'réseau|réseaux', 'aisle' => 'rayon', 'brand' => 'marque', + 'sticky' => 'menu d\'ancre|menu d\'ancres' ] ]; diff --git a/resources/lang/fr/validation.php b/resources/lang/fr/validation.php index a9d1fcc..a25b754 100644 --- a/resources/lang/fr/validation.php +++ b/resources/lang/fr/validation.php @@ -302,5 +302,6 @@ return [ 'title' => 'titre de l\'ancre', 'link' => 'lien de l\'ancre', ], + 'anchor' => 'ancre' ], ]; diff --git a/resources/sass/fo/base/_layout.scss b/resources/sass/fo/base/_layout.scss index 4bd0d9c..37776e1 100644 --- a/resources/sass/fo/base/_layout.scss +++ b/resources/sass/fo/base/_layout.scss @@ -281,6 +281,47 @@ body { > main { flex-grow: 1; flex-shrink: 0; + + .sticky-navigation { + @include media-breakpoint-up(md) { + position: fixed; + z-index: 10; + bottom: 4rem; + left: 4rem; + max-width: 15rem; + + &-wrapper { + height: 0; + opacity: 0; + transform: translateY(25%); + transition: .3s; + visibility: hidden; + + &.show { + height: auto; + opacity: 1; + transform: translateY(0); + visibility: visible; + } + } + } + + .col-md-12 { + @include media-breakpoint-up(md) { + width: fit-content; + } + + .badge { + width: .5rem; + height: .5rem; + + @include media-breakpoint-up(md) { + width: 1rem; + height: 1rem; + } + } + } + } } > footer { diff --git a/resources/sass/fo/pages/_commit.scss b/resources/sass/fo/pages/_commit.scss index ad1c09a..d73a418 100644 --- a/resources/sass/fo/pages/_commit.scss +++ b/resources/sass/fo/pages/_commit.scss @@ -56,44 +56,3 @@ } } } - -.commit-navigation { - @include media-breakpoint-up(md) { - position: fixed; - z-index: 10; - bottom: 4rem; - left: 4rem; - max-width: 15rem; - - &-wrapper { - height: 0; - opacity: 0; - transform: translateY(25%); - transition: .3s; - visibility: hidden; - - &.show { - height: 14rem; - opacity: 1; - transform: translateY(0); - visibility: visible; - } - } - } - - .col-md-12 { - @include media-breakpoint-up(md) { - width: fit-content; - } - - .badge { - width: .5rem; - height: .5rem; - - @include media-breakpoint-up(md) { - width: 1rem; - height: 1rem; - } - } - } -} diff --git a/resources/ts/fo/pages/commitments.ts b/resources/ts/fo/base/sticky.ts similarity index 55% rename from resources/ts/fo/pages/commitments.ts rename to resources/ts/fo/base/sticky.ts index d75a127..9a1f63b 100644 --- a/resources/ts/fo/pages/commitments.ts +++ b/resources/ts/fo/base/sticky.ts @@ -1,16 +1,16 @@ document.addEventListener("DOMContentLoaded", function () { - const commit = document.querySelector("section.commit"); - if (commit === null) { + const stickyNav = document.querySelector("div.sticky-navigation"); + if (stickyNav === null) { return; } - const button = document.querySelector(".commit-navigation .btn") as HTMLButtonElement; - const navWrapper = document.querySelectorAll(".commit-navigation-wrapper "); + const button = document.querySelector(".sticky-navigation .btn") as HTMLButtonElement; + const navWrapper = document.querySelectorAll(".sticky-navigation-wrapper "); button.addEventListener("click", () => { navWrapper.forEach((element: Element) => { element.classList.toggle("show"); }); - document.querySelectorAll(".commit-navigation .btn svg").forEach(icon => { + document.querySelectorAll(".sticky-navigation .btn svg").forEach(icon => { icon.classList.toggle("d-none"); }); }); diff --git a/resources/ts/fo/fo.ts b/resources/ts/fo/fo.ts index 0136a00..c1c5da6 100644 --- a/resources/ts/fo/fo.ts +++ b/resources/ts/fo/fo.ts @@ -17,9 +17,9 @@ import "./utilities/vue-fo"; // CUSTOM import "./base/common"; import "./base/layout"; +import "./base/sticky"; // PAGES -import "./pages/commitments"; import "./pages/unveil"; import "./pages/site"; diff --git a/resources/views/bo/layouts/nav-list.blade.php b/resources/views/bo/layouts/nav-list.blade.php index f89335e..e07a190 100644 --- a/resources/views/bo/layouts/nav-list.blade.php +++ b/resources/views/bo/layouts/nav-list.blade.php @@ -345,7 +345,7 @@ {{-- MORE --}} @endcan + {{-- STICKY --}} + @can('viewAny', \App\Models\Sticky::class) + + @endcan {{-- CONFIDENTIALITY --}} @can('viewAny', \App\Models\StaticPage::class)