feat: Added SSO for BO

This commit is contained in:
Noah Lancien 2025-03-07 11:29:42 +01:00
parent 1bb3c146b3
commit 4324dd1e8a
57 changed files with 1700 additions and 845 deletions

View file

@ -45,3 +45,10 @@ GOOGLE_RECAPTCHA_VERSION=
# LDC Web Admin Secure
LDC_WEB_API_KEY="MY_KEY"
# SAML
SAML2_DEFAULT_TENANT_UUID=
SAML2_SP_CERT_x509=
SAML2_SP_CERT_PRIVATEKEY=
SAML2_CONTACT_TECHNICAL_EMAIL=
SAML2_CONTACT_SUPPORT_EMAIL=

View file

@ -1,177 +0,0 @@
<?php
namespace App\Console\Commands;
use App\Models\User;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
class CreateAdmin extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'user:create';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Create a backend admin user';
/**
* The firstname of the new user.
*
* @var string|null
*/
protected $firstname;
/**
* The lastname of the new user.
*
* @var string|null
*/
protected $lastname;
/**
* The email of the new user.
*
* @var string|null
*/
protected $email;
/**
* The password of the new user.
*
* @var string|null
*/
protected $password;
/**
* Execute the console command.
*
* @return integer
*/
public function handle(): int
{
$this->firstname = null;
$this->addFirstname();
$this->lastname = null;
$this->addLastname();
$this->email = null;
$this->addEmail();
$this->password = null;
$this->addPassword();
$user = new User([
'firstname' => $this->firstname,
'lastname' => $this->lastname,
'email' => $this->email,
'password' => $this->password,
]);
$user->saveOrFail();
$this->info('User created 👌');
return 0;
}
/**
* Add a firstname to the user.
*
* @return void
*/
private function addFirstname(): void
{
while (is_null($this->firstname)) {
try {
$tmp = $this->ask('Type the wanted user firstname', 'Admin');
$validator = Validator::make(['firstname' => $tmp], [
'firstname' => 'required|min:3|max:255'
]);
$validator->validate();
$this->firstname = $tmp;
} catch (ValidationException $e) {
$this->error(sprintf(
'Please provide a valid firstname %s',
\implode(',', collect($e->errors())->flatten()->all())
));
continue;
} //end try
}; //end while
}
/**
* Add a lastname to the user.
*
* @return void
*/
private function addLastname(): void
{
while (is_null($this->lastname)) {
try {
$tmp = $this->ask('Type the wanted user lastname', 'Saïtama');
$validator = Validator::make(['lastname' => $tmp], [
'lastname' => 'required|min:3|max:255'
]);
$validator->validate();
$this->lastname = $tmp;
} catch (ValidationException $e) {
$this->error(sprintf(
'Please provide a valid lastname %s',
\implode(',', collect($e->errors())->flatten()->all())
));
continue;
} //end try
}; //end while
}
/**
* Add an email to the user.
*
* @return void
*/
private function addEmail(): void
{
while (is_null($this->email)) {
try {
$tmp = $this->ask('Type the wanted user email', 'equipe.web@ldc.fr');
$validator = Validator::make(['email' => $tmp], [
'email' => 'required|unique:users,email|email:rfc,dns'
]);
$validator->validate();
$this->email = $tmp;
} catch (ValidationException $e) {
$this->error(sprintf(
'Please provide a valid email %s',
\implode(',', collect($e->errors())->flatten()->all())
));
continue;
} //end try
}; //end while
}
/**
* Add a password to the user.
*
* @return void
*/
private function addPassword(): void
{
while (
($this->password = $this->secret(
(!\is_null($this->password) ? 'Failed to confirm, ' : '') . 'Type the wanted user password'
)) !== $this->secret('Confirm the password')
) {
continue;
}; //end while
}
}

View file

@ -0,0 +1,204 @@
<?php
namespace App\Console\Commands;
use App\Enums\Users\RoleEnum;
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
use Illuminate\Support\Str;
class UserCreate extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'uwil:user:create';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Create a backend admin user';
/**
* The first name of the new user.
*
* @var string|null
*/
protected $first_name;
/**
* The last name of the new user.
*
* @var string|null
*/
protected $last_name;
/**
* The email of the new user.
*
* @var string|null
*/
protected $email;
/**
* The role of the new user.
*
* @var string|null
*/
protected $role;
/**
* Execute the console command.
*
* @return void
*/
public function handle(): void
{
$this->first_name = null;
$this->addFirstName();
$this->last_name = null;
$this->addLastName();
$this->email = null;
$this->addEmail();
$this->role = null;
$this->addRole();
$user = new User([
'first_name' => $this->first_name,
'last_name' => $this->last_name,
'email' => $this->email,
'role' => $this->role,
'published' => true,
]);
$user->saveOrFail();
$this->info('User created 👌');
}
/**
* Add a first name to the user.
*
* @return void
*/
private function addFirstName(): void
{
while (is_null($this->first_name)) {
try {
$tmp = $this->ask('Type the wanted user first name', 'Admin');
$validator = Validator::make(['first_name' => $tmp], [
'first_name' => 'required|min:3|max:255'
]);
$validator->validate();
$this->first_name = $tmp;
} catch (ValidationException $e) {
$this->error(sprintf(
'Please provide a valid first name %s',
\implode(',', collect($e->errors())->flatten()->all())
));
continue;
} //end try
}; //end while
}
/**
* Add a last name to the user.
*
* @return void
*/
private function addLastName(): void
{
while (is_null($this->last_name)) {
try {
$tmp = $this->ask('Type the wanted user last name', 'Saïtama');
$validator = Validator::make(['last_name' => $tmp], [
'last_name' => 'required|min:3|max:255'
]);
$validator->validate();
$this->last_name = $tmp;
} catch (ValidationException $e) {
$this->error(sprintf(
'Please provide a valid last_name %s',
\implode(',', collect($e->errors())->flatten()->all())
));
continue;
} //end try
}; //end while
}
/**
* Add an email to the user.
*
* @return void
*/
private function addEmail(): void
{
while (is_null($this->email)) {
try {
$tmp = $this->ask('Type the wanted user email', 'equipe.web@ldc.fr');
$validator = Validator::make(['email' => $tmp], [
'email' => 'required|unique:users,email|email:rfc,dns'
]);
$validator->validate();
$this->email = $tmp;
} catch (ValidationException $e) {
$this->error(sprintf(
'Please provide a valid email %s',
\implode(',', collect($e->errors())->flatten()->all())
));
continue;
} //end try
}; //end while
}
/**
* Add a role to the user.
*
* @return void
*/
private function addRole(): void
{
while (is_null($this->role)) {
try {
$tmp = $this->choice(
'Select his role',
[
Str::ucFirst(RoleEnum::conceptor->label()),
Str::ucFirst(RoleEnum::admin->label()),
Str::ucFirst(RoleEnum::visitor->label())
],
Str::ucFirst(RoleEnum::visitor->label()),
);
if ($tmp === Str::ucFirst(RoleEnum::conceptor->label())) {
$tmp = RoleEnum::conceptor;
}
if ($tmp === Str::ucFirst(RoleEnum::admin->label())) {
$tmp = RoleEnum::admin;
}
if ($tmp === Str::ucFirst(RoleEnum::visitor->label())) {
$tmp = RoleEnum::visitor;
}
$validator = Validator::make(['role' => $tmp], [
'role' => 'required'
]);
$validator->validate();
$this->role = $tmp;
} catch (ValidationException $e) {
$this->error(sprintf(
'Please choose from the selection %s',
\implode(',', collect($e->errors())->flatten()->all())
));
continue;
} //end try
}; //end while
}
}

View file

@ -0,0 +1,25 @@
<?php
namespace App\Enums\Users;
use App\Traits\Enums\BaseEnum;
enum RoleEnum: int
{
use BaseEnum;
case conceptor = 1;
case admin = 2;
case visitor = 99;
/**
* Optionnal labels definition.
*
* @phpstan-ignore-next-line
*/
private const LABELS = [
self::conceptor->name => 'enums.user-role.conceptor',
self::admin->name => 'enums.user-role.admin',
self::visitor->name => 'enums.user-role.visitor',
];
}

View file

@ -40,6 +40,12 @@ class Kernel extends HttpKernel
\App\Http\Middleware\SecureHeaders::class
],
'saml' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
],
'api' => [
\Illuminate\Routing\Middleware\ThrottleRequests::class . ':api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,

View file

@ -2,14 +2,29 @@
namespace App\Models;
use App\Enums\Users\RoleEnum;
use App\Traits\Models\ActivityLog;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
/**
* @property string $first_name Firstname.
* @property string $last_name Lastname.
* @property string $email Email.
* @property string $remember_token Remember_token.
* @property \App\Enums\Users\RoleEnum $role Role.
* @property \Illuminate\Support\Carbon $last_connection Last_connection.
* @property \Illuminate\Support\Carbon $published Published status.
* @property-read \Illuminate\Support\Carbon $created_at Created date.
* @property-read \Illuminate\Support\Carbon $updated_at Updated date.
*
* @property-read \App\Models\ActivityLog $activityLogs Activities logs HasMany relationship.
*/
class User extends Authenticatable
{
use ActivityLog;
use HasFactory;
use Notifiable;
@ -19,10 +34,11 @@ class User extends Authenticatable
* @var array<int, string>
*/
protected $fillable = [
'firstname',
'lastname',
'first_name',
'last_name',
'email',
'password',
'role',
'published',
];
/**
@ -31,7 +47,6 @@ class User extends Authenticatable
* @var array<int, string>
*/
protected $hidden = [
'password',
'remember_token',
];
@ -41,8 +56,9 @@ class User extends Authenticatable
* @var array<string, string>
*/
protected $casts = [
'email_verified_at' => 'datetime',
'last_connection' => 'datetime',
'role' => RoleEnum::class,
'last_connection' => 'date',
'published' => 'boolean',
];
/**
@ -50,20 +66,28 @@ class User extends Authenticatable
*
* @return void
*/
protected static function booted()
protected static function booted(): void
{
// * EVENT CLOSURES.
static::creating(function (self $user) {
if (Hash::needsRehash($user->password)) {
$user->password = Hash::make($user->password);
}
$user->remember_token = Str::random(10);
static::creating(function (self $model) {
// * Prevent user elevation privileges
throw_if(
auth('backend')->user() and auth('backend')->user()->role->value() > $model->role->value(),
AuthorizationException::class
);
});
static::updating(function (self $user) {
if (Hash::needsRehash($user->password)) {
$user->password = Hash::make($user->password);
static::updating(function (self $model) {
if (optional(auth('backend')->user())->getKey() === $model->getKey()) {
\validator(
['published' => $model->published],
['published' => 'required|boolean|accepted'],
['published.accepted' => trans('crud.messages.user_unpublished')],
)->validate();
}
$user->remember_token = Str::random(10);
// * Prevent user elevation privileges
throw_if(
auth('backend')->user() and auth('backend')->user()->role->value() > $model->role->value(),
AuthorizationException::class
);
});
}
}

View file

@ -5,6 +5,13 @@ namespace App\Providers;
use Illuminate\Auth\Events\Registered;
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Event;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Session;
use Slides\Saml2\Events\SignedIn;
use Slides\Saml2\Events\SignedOut;
class EventServiceProvider extends ServiceProvider
{
@ -19,15 +26,6 @@ class EventServiceProvider extends ServiceProvider
],
];
/**
* Register any events for your application.
*
* @return void
*/
public function boot(): void
{
}
/**
* Determine if events and listeners should be automatically discovered.
*
@ -37,4 +35,49 @@ class EventServiceProvider extends ServiceProvider
{
return false;
}
/**
* Register any events for your application.
*
* @return void
*/
public function boot()
{
Event::listen('Slides\Saml2\Events\SignedIn', function (SignedIn $event) {
$messageId = $event->getAuth()->getLastMessageId();
$fromCache = Cache::get(config('cache.prefix') . "_saml_message_id_" . $messageId);
if ($fromCache) {
logger()->error(
'SAML MESSAGE ID HANDLER',
['error' => 'Message ID : ' . $messageId . " replayed !"]
);
abort(403);
} else {
Cache::put(config('cache.prefix') . "_saml_message_id_" . $messageId, $messageId, 28800);
// Cache for 8h.
}
$user = $event->getSaml2User();
$email = $user->getNameId();
$lUser = User::where('email', '=', $email)->first();
if (!$lUser) {
abort(403);
}
Auth::guard('backend')->login($lUser);
session(['sessionIndex' => $user->getSessionIndex()]);
$lUser->last_connection = now();
$lUser->saveQuietly();
});
//phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found
Event::listen('Slides\Saml2\Events\SignedOut', function (SignedOut $event) {
Auth::guard('backend')->logout();
Session::save();
});
}
}

View file

@ -4,6 +4,7 @@
"description": "Site de la marque Lionor",
"require": {
"php": "^8.3",
"24slides/laravel-saml2": "^2.4",
"axlon/laravel-postal-code-validation": "^3.3",
"carlos-meneses/laravel-mpdf": "^2.1",
"diglactic/laravel-breadcrumbs": "^9.0",

189
composer.lock generated
View file

@ -4,8 +4,83 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "5fa930c4320067f6816f00faa0a3a5f8",
"content-hash": "5808ffd0b5badfc5737bf3ff38631a77",
"packages": [
{
"name": "24slides/laravel-saml2",
"version": "2.4.1",
"source": {
"type": "git",
"url": "https://github.com/24Slides/laravel-saml2.git",
"reference": "d3d8ac6cc456a2bd3e24e0d05c2dc3303c2938c4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/24Slides/laravel-saml2/zipball/d3d8ac6cc456a2bd3e24e0d05c2dc3303c2938c4",
"reference": "d3d8ac6cc456a2bd3e24e0d05c2dc3303c2938c4",
"shasum": ""
},
"require": {
"ext-openssl": "*",
"illuminate/console": "~5.5|^6.0|^7.0|^8.0|^9.0|^10.0|^11.0",
"illuminate/database": "~5.5|^6.0|^7.0|^8.0|^9.0|^10.0|^11.0",
"illuminate/support": "~5.4|^6.0|^7.0|^8.0|^9.0|^10.0|^11.0",
"onelogin/php-saml": "^3.0|^4.0",
"php": ">=7.1",
"ramsey/uuid": "^3.8|^4.0"
},
"require-dev": {
"mockery/mockery": "^0.9.9",
"phpunit/phpunit": "^7.5|^9.0",
"squizlabs/php_codesniffer": "^2.3"
},
"type": "library",
"extra": {
"laravel": {
"aliases": {
"Saml2": "Slides\\Saml2\\Facades\\Auth"
},
"providers": [
"Slides\\Saml2\\ServiceProvider"
]
},
"branch-aliases": {
"dev-master": "2.0.8-dev"
}
},
"autoload": {
"files": [
"src/helpers.php"
],
"psr-4": {
"Slides\\Saml2\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Artem Brezhnev",
"email": "brezzhnev@gmail.com"
}
],
"description": "SAML2 Service Provider integration to your Laravel 5.4+ application, based on OneLogin toolkit",
"homepage": "https://github.com/24slides/laravel-saml2",
"keywords": [
"SAML2",
"SSO",
"laravel",
"onelogin",
"saml"
],
"support": {
"issues": "https://github.com/24Slides/laravel-saml2/issues",
"source": "https://github.com/24Slides/laravel-saml2/tree/2.4.1"
},
"time": "2025-02-04T13:32:39+00:00"
},
{
"name": "axlon/laravel-postal-code-validation",
"version": "v3.8.0",
@ -3897,6 +3972,70 @@
],
"time": "2024-09-09T07:06:30+00:00"
},
{
"name": "onelogin/php-saml",
"version": "4.2.0",
"source": {
"type": "git",
"url": "https://github.com/SAML-Toolkits/php-saml.git",
"reference": "d3b5172f137db2f412239432d77253ceaaa1e939"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/SAML-Toolkits/php-saml/zipball/d3b5172f137db2f412239432d77253ceaaa1e939",
"reference": "d3b5172f137db2f412239432d77253ceaaa1e939",
"shasum": ""
},
"require": {
"php": ">=7.3",
"robrichards/xmlseclibs": "^3.1"
},
"require-dev": {
"pdepend/pdepend": "^2.8.0",
"php-coveralls/php-coveralls": "^2.0",
"phploc/phploc": "^4.0 || ^5.0 || ^6.0 || ^7.0",
"phpunit/phpunit": "^9.5",
"sebastian/phpcpd": "^4.0 || ^5.0 || ^6.0 ",
"squizlabs/php_codesniffer": "^3.5.8"
},
"suggest": {
"ext-curl": "Install curl lib to be able to use the IdPMetadataParser for parsing remote XMLs",
"ext-dom": "Install xml lib",
"ext-openssl": "Install openssl lib in order to handle with x509 certs (require to support sign and encryption)",
"ext-zlib": "Install zlib"
},
"type": "library",
"autoload": {
"psr-4": {
"OneLogin\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "PHP SAML Toolkit",
"homepage": "https://github.com/SAML-Toolkits/php-saml",
"keywords": [
"Federation",
"SAML2",
"SSO",
"identity",
"saml"
],
"support": {
"email": "sixto.martin.garcia@gmail.com",
"issues": "https://github.com/onelogin/SAML-Toolkits/issues",
"source": "https://github.com/onelogin/SAML-Toolkits/"
},
"funding": [
{
"url": "https://github.com/SAML-Toolkits",
"type": "github"
}
],
"time": "2024-05-30T15:10:40+00:00"
},
{
"name": "paragonie/random_compat",
"version": "v9.99.100",
@ -4906,6 +5045,48 @@
],
"time": "2024-04-27T21:32:50+00:00"
},
{
"name": "robrichards/xmlseclibs",
"version": "3.1.3",
"source": {
"type": "git",
"url": "https://github.com/robrichards/xmlseclibs.git",
"reference": "2bdfd742624d739dfadbd415f00181b4a77aaf07"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/robrichards/xmlseclibs/zipball/2bdfd742624d739dfadbd415f00181b4a77aaf07",
"reference": "2bdfd742624d739dfadbd415f00181b4a77aaf07",
"shasum": ""
},
"require": {
"ext-openssl": "*",
"php": ">= 5.4"
},
"type": "library",
"autoload": {
"psr-4": {
"RobRichards\\XMLSecLibs\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"description": "A PHP library for XML Security",
"homepage": "https://github.com/robrichards/xmlseclibs",
"keywords": [
"security",
"signature",
"xml",
"xmldsig"
],
"support": {
"issues": "https://github.com/robrichards/xmlseclibs/issues",
"source": "https://github.com/robrichards/xmlseclibs/tree/3.1.3"
},
"time": "2024-11-20T21:13:56+00:00"
},
{
"name": "sentry/sentry",
"version": "4.10.0",
@ -12050,12 +12231,12 @@
],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": {},
"stability-flags": [],
"prefer-stable": true,
"prefer-lowest": false,
"platform": {
"php": "^8.3"
},
"platform-dev": {},
"plugin-api-version": "2.6.0"
"platform-dev": [],
"plugin-api-version": "2.3.0"
}

View file

@ -56,8 +56,6 @@ return [
'url' => env('APP_URL', 'http://localhost'),
'asset_url' => env('ASSET_URL'),
/*
|--------------------------------------------------------------------------
| Application Timezone

View file

@ -23,7 +23,7 @@ return [
|
*/
'view' => 'modules/breadcrumb/breadcrumb-layout',
'view' => 'modules.breadcrumbs.breadcrumb-layout',
/*
|--------------------------------------------------------------------------

397
config/saml2.php Normal file
View file

@ -0,0 +1,397 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Tenant Model
|--------------------------------------------------------------------------
|
| This will allow you to override the tenant model with your own.
|
*/
'tenantModel' => \Slides\Saml2\Models\Tenant::class,
/*
|--------------------------------------------------------------------------
| Use built-in routes
|--------------------------------------------------------------------------
|
| If "useRoutes" set to true, the package defines five new routes:
|
| Method | URI | Name
| -------|---------------------------------|------------------
| POST | {routesPrefix}/{uuid}/acs | saml.acs
| GET | {routesPrefix}/{uuid}/login | saml.login
| GET | {routesPrefix}/{uuid}/logout | saml.logout
| GET | {routesPrefix}/{uuid}/metadata | saml.metadata
| GET | {routesPrefix}/{uuid}/sls | saml.sls
|
*/
'useRoutes' => true,
/*
|--------------------------------------------------------------------------
| Built-in routes prefix
|--------------------------------------------------------------------------
|
| Here you may define the prefix for built-in routes.
|
*/
'routesPrefix' => '/saml2',
/*
|--------------------------------------------------------------------------
| Middle groups to use for the SAML routes
|--------------------------------------------------------------------------
|
| Note, Laravel 5.2 requires a group which includes StartSession
|
*/
'routesMiddleware' => ['saml'],
/*
|--------------------------------------------------------------------------
| Signature validation
|--------------------------------------------------------------------------
|
| Set to true if you want to use parameters from $_SERVER to validate the signature.
|
*/
'retrieveParametersFromServer' => false,
/*
|--------------------------------------------------------------------------
| Login redirection URL.
|--------------------------------------------------------------------------
|
| The redirection URL after successful login.
|
*/
'loginRoute' => '/bo',
/*
|--------------------------------------------------------------------------
| Logout redirection URL.
|--------------------------------------------------------------------------
|
| The redirection URL after successful logout.
|
*/
'logoutRoute' => '/bo',
/*
|--------------------------------------------------------------------------
| Login error redirection URL.
|--------------------------------------------------------------------------
|
| The redirection URL after login failing.
|
*/
'errorRoute' => '/bo/login-error',
/*
|--------------------------------------------------------------------------
| Strict mode.
|--------------------------------------------------------------------------
|
| If 'strict' is True, then the PHP Toolkit will reject unsigned
| or unencrypted messages if it expects them signed or encrypted
| Also will reject the messages if not strictly follow the SAML
| standard: Destination, NameId, Conditions... are validated too.
|
*/
'strict' => true,
/*
|--------------------------------------------------------------------------
| Debug mode.
|--------------------------------------------------------------------------
|
| When enabled, errors must be printed.
|
*/
'debug' => env('SAML2_DEBUG', env('APP_DEBUG', false)),
/*
|--------------------------------------------------------------------------
| Whether to use `X-Forwarded-*` headers to determine port/domain/protocol.
|--------------------------------------------------------------------------
|
| If 'proxyVars' is True, then the Saml lib will trust proxy headers
| e.g X-Forwarded-Proto / HTTP_X_FORWARDED_PROTO. This is useful if
| your application is running behind a load balancer which terminates SSL.
|
*/
'proxyVars' => true,
/*
|--------------------------------------------------------------------------
| Service Provider configuration.
|--------------------------------------------------------------------------
|
| General setting of the service provider.
|
*/
'sp' => [
/*
|--------------------------------------------------------------------------
| NameID format.
|--------------------------------------------------------------------------
|
| Specifies constraints on the name identifier to be used to
| represent the requested subject.
|
*/
'NameIDFormat' => 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress',
/*
|--------------------------------------------------------------------------
| SP Certificates.
|--------------------------------------------------------------------------
|
| Usually x509cert and privateKey of the SP are provided by files placed at
| the certs folder. But we can also provide them with the following parameters.
|
*/
'x509cert' => env('SAML2_SP_CERT_x509', ''),
'privateKey' => env('SAML2_SP_CERT_PRIVATEKEY', ''),
/*
|--------------------------------------------------------------------------
| Identifier (URI) of the SP entity.
|--------------------------------------------------------------------------
|
| Leave blank to use the 'saml.metadata' route.
|
*/
'entityId' => env('SAML2_SP_ENTITYID', ''),
/*
|--------------------------------------------------------------------------
| The Assertion Consumer Service (ACS) URL.
|--------------------------------------------------------------------------
|
| URL Location where the <Response> from the IdP will be returned, using HTTP-POST binding.
| Leave blank to use the 'saml.acs' route.
|
*/
'assertionConsumerService' => [
'url' => '',
],
/*
|--------------------------------------------------------------------------
| The Single Logout Service URL.
|--------------------------------------------------------------------------
|
| Specifies info about where and how the <Logout Response> message MUST be
| returned to the requester, in this case our SP.
|
| URL Location where the <Response> from the IdP will be returned, using HTTP-Redirect binding.
| Leave blank to use the 'saml.sls' route.
|
*/
'singleLogoutService' => [
'url' => ''
],
],
/*
|--------------------------------------------------------------------------
| OneLogin security settings.
|--------------------------------------------------------------------------
|
|
|
*/
'security' => [
'signatureAlgorithm' => 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256',
'digestAlgorithm' => 'http://www.w3.org/2001/04/xmlenc#sha256',
/*
|--------------------------------------------------------------------------
| NameId encryption
|--------------------------------------------------------------------------
|
| Indicates that the nameID of the <samlp:logoutRequest> sent by this SP
| will be encrypted.
|
*/
'nameIdEncrypted' => true,
/*
|--------------------------------------------------------------------------
| AuthnRequest signage
|--------------------------------------------------------------------------
|
| Indicates whether the <samlp:AuthnRequest> messages sent by
| this SP will be signed. The Metadata of the SP will offer this info
|
*/
'authnRequestsSigned' => true,
/*
|--------------------------------------------------------------------------
| Logout request signage
|--------------------------------------------------------------------------
|
| Indicates whether the <samlp:logoutRequest> messages sent by this SP
| will be signed.
|
*/
'logoutRequestSigned' => true,
/*
|--------------------------------------------------------------------------
| Logout response signage
|--------------------------------------------------------------------------
|
| Indicates whether the <samlp:logoutResponse> messages sent by this SP
| will be signed.
|
*/
'logoutResponseSigned' => true,
/*
|--------------------------------------------------------------------------
| Whether need to sign metadata.
|--------------------------------------------------------------------------
|
| The possible values:
| - false
| - true (use certs)
| - array:
| ```
| [
| 'keyFileName' => 'metadata.key',
| 'certFileName' => 'metadata.crt'
| ]
| ```
|
*/
'signMetadata' => true,
/*
|--------------------------------------------------------------------------
| Requirement to sign messages.
|--------------------------------------------------------------------------
|
| Indicates a requirement for the <samlp:Response>, <samlp:LogoutRequest> and
| <samlp:LogoutResponse> elements received by this SP to be signed.
|
*/
'wantMessagesSigned' => true,
/*
|--------------------------------------------------------------------------
| Requirement to sign assertion elements.
|--------------------------------------------------------------------------
|
| Indicates a requirement for the <saml:Assertion> elements received by
| this SP to be signed.
|
*/
'wantAssertionsSigned' => true,
/*
|--------------------------------------------------------------------------
| Requirement to encrypt NameID.
|--------------------------------------------------------------------------
|
| Indicates a requirement for the NameID received by this SP to be encrypted.
|
*/
'wantNameIdEncrypted' => false,
/*
|--------------------------------------------------------------------------
| Authentication context.
|--------------------------------------------------------------------------
|
| Set to false and no AuthContext will be sent in the AuthNRequest,
|
| Set true or don't present this parameter and you will get an
| AuthContext 'exact' 'urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport'
|
| Set an array with the possible auth context values:
| ['urn:oasis:names:tc:SAML:2.0:ac:classes:Password', 'urn:oasis:names:tc:SAML:2.0:ac:classes:X509']
|
*/
'requestedAuthnContext' => true,
],
/*
|--------------------------------------------------------------------------
| Contact information.
|--------------------------------------------------------------------------
|
| It is recommended to supply a technical and support contacts.
|
*/
'contactPerson' => [
'technical' => [
'givenName' => env('SAML2_CONTACT_TECHNICAL_NAME', env('APP_NAME', 'Technical')),
'emailAddress' => env('SAML2_CONTACT_TECHNICAL_EMAIL', 'no@reply.com')
],
'support' => [
'givenName' => env('SAML2_CONTACT_SUPPORT_NAME', env('APP_NAME', 'Support')),
'emailAddress' => env('SAML2_CONTACT_SUPPORT_EMAIL', 'no@reply.com')
],
],
/*
|--------------------------------------------------------------------------
| Organization information.
|--------------------------------------------------------------------------
|
| The info in en_US lang is recommended, add more if required.
|
*/
'organization' => [
'en-US' => [
'name' => env('SAML2_ORGANIZATION_NAME', env('APP_NAME', 'Name')),
'displayname' => env('SAML2_ORGANIZATION_NAME', env('APP_NAME', 'Name')),
'url' => env('SAML2_ORGANIZATION_URL', env('APP_URL', 'http://url'))
],
],
/**
* Default tenant UUID
*/
'default_tenant_uuid' => env('SAML2_DEFAULT_TENANT_UUID', null)
];

View file

@ -15,8 +15,8 @@ class UserFactory extends Factory
public function definition()
{
return [
'firstname' => $this->faker->firstName(),
'lastname' => $this->faker->lastName(),
'first_name' => $this->faker->firstName(),
'last_name' => $this->faker->lastName(),
'email' => $this->faker->unique()->safeEmail(),
'email_verified_at' => now(),
'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi',

View file

@ -0,0 +1,50 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
// Drop.
$table->dropColumn('email_verified_at');
$table->dropColumn('password');
// Rename.
$table->renameColumn('firstname', 'first_name');
$table->renameColumn('lastname', 'last_name');
// New columns.
$table->unsignedTinyInteger('role')->after('remember_token')
->comment('Role of the user who defines the rights/access.');
$table->boolean('published')->after('role')
->comment('Is the user published ?');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
// New columns.
$table->timestamp('email_verified_at')->nullable()->after('email');
$table->string('password')->after('email_verified_at');
// Rename.
$table->renameColumn('first_name', 'firstname');
$table->renameColumn('last_name', 'lastname');
// Drop.
$table->dropColumn('role');
$table->dropColumn('published');
});
}
};

View file

@ -28,6 +28,12 @@ data:
GOOGLE_RECAPTCHA_VERSION: "${APP_GOOGLE_RECAPTCHA_VERSION}"
SENTRY_LARAVEL_DSN: "${APP_SENTRY_LARAVEL_DSN}"
MATOMO_SITE_ID: "${APP_MATOMO_SITE_ID}"
SAML2_SP_CERT_x509: "${APP_SAML2_SP_CERT_x509}"
SAML2_SP_CERT_PRIVATEKEY: "${APP_SAML2_SP_CERT_PRIVATEKEY}"
SAML2_CONTACT_TECHNICAL_EMAIL: "${APP_SAML2_CONTACT_TECHNICAL_EMAIL}"
SAML2_CONTACT_SUPPORT_EMAIL: "${APP_SAML2_CONTACT_SUPPORT_EMAIL}"
SAML2_DEFAULT_TENANT_UUID: "${APP_SAML2_DEFAULT_TENANT_UUID}"
---
apiVersion: v1

View file

@ -1,3 +1,9 @@
<?php
return [];
return [,
'user-role' => [
"conceptor" => "conceptor",
"admin" => "admin",
"visitor" => "visitor",
]
];

View file

@ -1,3 +1,9 @@
<?php
return [];
return [
'user-role' => [
"conceptor" => "concepteur",
"admin" => "administrateur",
"visitor" => "visiteur",
],
];

View file

@ -8,6 +8,7 @@ import {
faArrowRight,
faArrowRightFromBracket,
faArrowUp,
faArrowUpRightFromSquare,
faArrowsAltH,
faArrowsAltV,
faBackspace,
@ -81,6 +82,7 @@ library.add(faBan);
library.add(faCircleExclamation);
library.add(faCircleInfo);
library.add(faPaintRoller);
library.add(faArrowUpRightFromSquare);
// Front
library.add(faChevronLeft);

View file

@ -3,65 +3,23 @@
@section('description', __('Formulaire d\'authentification au back-office.'))
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8 p-0">
<div class="card">
<div class="card-header">{{ __('Connexion') }}</div>
<div class="card-body">
<form method="POST" action="{{ route('bo.login') }}">
@csrf
<div class="row mb-3">
<label for="email" class="col-md-4 col-form-label text-md-end">{{ __('Adresse Email') }}</label>
<div class="col-md-6">
<input id="email" type="email" class="form-control @error('email') is-invalid @enderror" name="email"
value="{{ old('email') }}" autocomplete="email" autofocus required>
@error('email')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="row mb-3">
<label for="password" class="col-md-4 col-form-label text-md-end">{{ __('Mot de passe') }}</label>
<div class="col-md-6">
<input id="password" type="password" class="form-control @error('password') is-invalid @enderror"
name="password" autocomplete="current-password" required>
@error('password')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="row mb-3">
<div class="col-md-6 offset-md-4">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="remember" id="remember" {{ old('remember') ? 'checked' : '' }}>
<label class="form-check-label user-select-none" for="remember">
{{ __('Se souvenir de moi') }}
</label>
</div>
</div>
</div>
<div class="row mb-0">
<div class="col-md-8 offset-md-4">
<button type="submit" class="btn btn-primary">
<span>{{ __('Connexion') }}</span>
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8 p-0">
<div class="card">
<div class="card-header">{{ __('Connexion') }}</div>
<div class="card-body">
<div class="row">
<div class="col text-center">
<a class="btn btn-primary" href="{{ route('saml.login', ['uuid' => config('saml2.default_tenant_uuid')]) }}">
<span>{{ __('Connexion avec UWIL Support') }}</span>
<i class="fa-solid fa-arrow-right-from-bracket"></i>
</button>
@if (Route::has('bo.password.request'))
<a class="btn btn-link" href="{{ route('bo.password.request') }}">
{{ __('Mot de passe oublié ?') }}
</a>
@endif
</a>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
@endsection

View file

@ -40,7 +40,7 @@
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" id="navbarDropdown" data-bs-toggle="dropdown" href="#" role="button"
aria-haspopup="true" aria-expanded="false">
{{ auth('backend')->user()->firstname }}&nbsp;{{ auth('backend')->user()->lastname }} <span
{{ auth('backend')->user()->first_name }}&nbsp;{{ auth('backend')->user()->last_name }} <span
class="caret"></span>
</a>
@ -63,11 +63,6 @@
@include('modules.flash-messages')
</div>
</div>
<div class="row justify-content-center">
<div class="col-12 col-md-10 col-lg-8 mt-3">
@include('modules.breadcrumb.breadcrumb-body')
</div>
</div>
<div class="row p-md-3 justify-content-center">
<div class="col-12 col-md-10 col-lg-8 p-md-0 pt-3">
@yield('content')

View file

@ -9,34 +9,36 @@
<meta name="csrf-token" content="{{ csrf_token() }}">
@hasSection('canonical')
<link rel="canonical" href="@yield('canonical')" />
<link href="@yield('canonical')" rel="canonical" />
@else
<link rel="canonical" href="{{ url()->current() }}" />
<link href="{{ url()->current() }}" rel="canonical" />
@endif
<!-- META INDEX, FOLLOW -->
@hasSection('metaIndex')
<meta name="robots" content="@yield('metaIndex')">
<meta name="robots" content="@yield('metaIndex')">
@else
<meta name="robots" content="index, follow">
<meta name="robots" content="index, follow">
@endif
<!-- Favicon -->
{{-- GENERER favicon avec un outil https://realfavicongenerator.net/ --}}
<link rel="apple-touch-icon" sizes="180x180" href="{{ asset('apple-touch-icon-bo.png') }}">
<link rel="icon" href="{{ asset('favicon-bo.ico') }}">
<link rel="icon" type="image/png" sizes="32x32" href="{{ asset('favicon-bo-32x32.png') }}">
<link rel="icon" type="image/png" sizes="16x16" href="{{ asset('favicon-bo-16x16.png') }}">
<link rel="manifest" href="{{ asset('site.webmanifest') }}">
<link rel="mask-icon" href="{{ asset('safari-pinned-tab.svg') }}" color="#5bbad5">
<link href="{{ asset('apple-touch-icon-bo.png') }}" rel="apple-touch-icon" sizes="180x180">
<link href="{{ asset('favicon-bo.ico') }}" rel="icon">
<link type="image/png" href="{{ asset('favicon-bo-32x32.png') }}" rel="icon" sizes="32x32">
<link type="image/png" href="{{ asset('favicon-bo-16x16.png') }}" rel="icon" sizes="16x16">
<link href="{{ asset('site.webmanifest') }}" rel="manifest">
<link href="{{ asset('safari-pinned-tab.svg') }}" rel="mask-icon" color="#5bbad5">
<meta name="msapplication-TileColor" content="#da532c">
<meta name="theme-color" content="#ffffff">
@include('modules.breadcrumb.breadcrumb-head')
@include('modules.breadcrumbs.breadcrumb-head')
<!-- STYLES -->
@vite(['resources/sass/bo/bo.scss'])
@if(config('app.debug'))
<script nonce="{{ $nonce }}">window.vueDebug = true;</script>
@if (config('app.debug'))
<script nonce="{{ $nonce ?? '' }}">
window.vueDebug = true;
</script>
@endif

View file

@ -1,7 +1,7 @@
<div class="row">
<div class="col-12 col-md-6">
{{-- TITLE --}}
<fieldset class="border p-3 mb-3">
<fieldset class="mb-3 border p-3">
<legend>{{ __('Libellé *') }}</legend>
<div class="row">
<div class="col-12 mb-3">
@ -19,7 +19,7 @@
</div>
{{-- PUBLICATION --}}
<div class="col">
<fieldset class="border p-3 mb-3">
<fieldset class="mb-3 border p-3">
<legend>{{ __('Publication') }}</legend>
<div class="row mb-3">
<div class="col">
@ -39,7 +39,7 @@
{{-- STICKER --}}
<div class="row">
<div class="col">
<fieldset class="border p-3 mb-3">
<fieldset class="mb-3 border p-3">
<legend>{{ __('Vignette') }}</legend>
<div class="row mb-3">
<div class="col-12 col-md-6">
@ -51,7 +51,8 @@
'required' => true,
'model' => $certificationModel ?? null,
'placeholder' => __('Une description de l\'image pour les moteurs de recherche'),
'helper' => __('Doit décrire votre image pour les moteurs de recherches et les non voyants (5 à 6 mots / 3 caractères minimum, 255 maximum)'),
'helper' => __(
'Doit décrire votre image pour les moteurs de recherches et les non voyants (5 à 6 mots / 3 caractères minimum, 255 maximum)'),
])
</div>
<div class="col-12 col-md-6">
@ -63,7 +64,8 @@
'required' => true,
'model' => $certificationModel ?? null,
'placeholder' => __('Texte d\'info-bulle au survol de la souris'),
'helper' => __('Utile pour les utilisateurs, s\'affiche en passant la souris sur la bannière (3 caractères minimum, 255 maximum).'),
'helper' => __(
'Utile pour les utilisateurs, s\'affiche en passant la souris sur la bannière (3 caractères minimum, 255 maximum).'),
])
</div>
</div>
@ -85,3 +87,5 @@
</fieldset>
</div>
</div>
@include('bo.partials.script-button-clone')

View file

@ -1,15 +1,18 @@
<div class="table-responsive">
<table class="table fix-action">
<table class="fix-action table">
<thead>
@php
$cols = [
'name' => __('Nom'),
'sticker' => __('Vignette'),
'updated_at' => __('Mis à jour le')];
'updated_at' => __('Mis à jour le'),
];
$routeName = request()->route()->getName();
$rst = !is_null(request()->rst) || !Session::has("$routeName.sorted");
$noOrder = Session::get("$routeName.sort_col") !== 'order' and (Session::has("$routeName.sort_col") or Session::has("$routeName.sort_way"));
$noOrder = Session::get("$routeName.sort_col") !== '' and (Session::has("$routeName.sort_col") or Session::has("$routeName.sort_way"));
($noOrder = Session::get("$routeName.sort_col") !== 'order') and
(Session::has("$routeName.sort_col") or Session::has("$routeName.sort_way"));
($noOrder = Session::get("$routeName.sort_col") !== '') and
(Session::has("$routeName.sort_col") or Session::has("$routeName.sort_way"));
@endphp
@include('modules.table-col-sorter', [
'cols' => $cols,
@ -24,32 +27,28 @@
<span>{{ $certificationModel->name }}</span>
</td>
<td>
<img
class="img-fluid img-thumbnail"
src="{{ asset(resize($certificationModel->sticker, 'boStickers')) }}"
title="{{ $certificationModel->sticker_title }}"
alt="{{ $certificationModel->sticker_alt }}">
<img class="img-fluid img-thumbnail" src="{{ asset(resize($certificationModel->sticker, 'boStickers')) }}"
title="{{ $certificationModel->sticker_title }}" alt="{{ $certificationModel->sticker_alt }}">
</td>
<td>
<span>{{ $certificationModel->updated_at->formatLocalized('%d/%m/%Y à %I:%M') }}</span>
<span>{{ $certificationModel->updated_at->isoFormat('LLLL') }}</span>
</td>
<td>
<div class="btn-group">
<a class="btn btn-sm btn-primary"
<a class="btn btn-sm btn-primary" data-bs-toggle="tooltip"
href="{{ route('bo.certifications.edit', $certificationModel) }}"
title="{{ __('Modifier :model', [
'model' => Str::singular(__('models.classes.certifications')),
]) }}"
data-bs-toggle="tooltip"><i class="fa-solid fa-pencil"></i></a>
]) }}"><i
class="fa-solid fa-pencil"></i></a>
</div>
</td>
</tr>
@endforeach
@else
<tr>
<td colspan="5" class="text-center">
<span
class="h3">{{ __('Aucun résultat pour les :models', ['models' => __('models.classes.certifications')]) }}
<td class="text-center" colspan="5">
<span class="h3">{{ __('Aucun résultat pour les :models', ['models' => __('models.classes.certifications')]) }}
</span>
</td>
</tr>

View file

@ -1,15 +1,36 @@
@extends('bo.layout')
@section('title', __('Accueil'))
@section('description', __('Description site'))
@section('breadcrumb', 'no')
@section('title', __('Accueil back-office'))
@section('description', __('Page d\'accueil de la partie administration du site :site', ['site' => config('app.name')]))
@section('content')
<div class="container">
<div class="row">
<div class="col">
<h1>{{ __('Panneau de bord') }}</h1>
</div>
</div>
<div class="d-flex justify-content-between flex-md-nowrap align-items-center flex-wrap">
@include('modules.breadcrumbs.breadcrumb-body')
</div>
<div class="border-start-0 border-end-0 border-bottom-0 border-secondary mt-2 border border-2">
<h5 class="card-title fw-bold py-2">
{{ __('Bonjour :first_name :last_name !', [
'first_name' => auth('backend')->user()->first_name,
'last_name' => auth('backend')->user()->last_name,
]) }}
</h5>
<p class="card-text">
{{ __('Bienvenue dans le back-office, où vous pouvez gérer votre application web.') }}
</p>
<p class="card-text">
{{ __('Pour accéder à vos statistiques') }} :
<a class="btn btn-primary btn-sm" data-bs-toggle="tooltip" href="https://report.uwil.fr"
title="{{ __('Accéder à notre outil de ticketing') }}" target="blank">
{{ __('UWIL Report') }}
<i class="fa-solid fa-arrow-up-right-from-square ms-1"></i>
</a>
<br>
{{ __('Pour tout problème ou évolution souhaitée, veuillez nous contacter via notre outil : ') }}
<a class="btn btn-primary btn-sm" data-bs-toggle="tooltip" href="https://support.uwil.fr/"
title="{{ __('Accéder à notre outil de ticketing') }}" target="blank">
{{ __('UWIL Support') }}
<i class="fa-solid fa-arrow-up-right-from-square ms-1"></i>
</a>
</p>
</div>
@endsection

View file

@ -0,0 +1,22 @@
@extends('fo.layout')
@section('title', __('Une erreur est survenue lors de la connexion.'))
@section('description', __('Il semble qu\'il y ait des erreurs pour répondre à votre demande.'))
@section('breadcrumb', 'no')
@section('bodyClass', 'bg-white-wood')
@section('content')
<div class="container my-5 py-5">
<div class="row">
<div class="col text-center">
<h1>{{ str(__('Une erreur est survenue lors de la connexion.'))->upper() }}</h1>
<h4 class="h4 my-4">
{{ __('Veuillez contacter le support.') }}
</h4>
<a class="btn btn-secondary" href="{{ route('fo.home.show') }}" title="{{ __('Accéder à la page d\'accueil') }}">
{{ str(__('retour à la page d\'accueil'))->upper() }}
</a>
</div>
</div>
</div>
@endsection

View file

@ -371,3 +371,5 @@
</fieldset>
</div>
</div>
@include('bo.partials.script-button-clone')

View file

@ -1,5 +1,5 @@
<div class="table-responsive">
<table class="table fix-action">
<table class="fix-action table">
<thead>
@php
$cols = [
@ -8,11 +8,14 @@
'sticker' => __('Vignette'),
'nutriscore' => __('Nutriscore'),
'status' => __('Publication'),
'updated_at' => __('Mis à jour le')];
'updated_at' => __('Mis à jour le'),
];
$routeName = request()->route()->getName();
$rst = !is_null(request()->rst) || !Session::has("$routeName.sorted");
$noOrder = Session::get("$routeName.sort_col") !== 'order' and (Session::has("$routeName.sort_col") or Session::has("$routeName.sort_way"));
$noOrder = Session::get("$routeName.sort_col") !== '' and (Session::has("$routeName.sort_col") or Session::has("$routeName.sort_way"));
($noOrder = Session::get("$routeName.sort_col") !== 'order') and
(Session::has("$routeName.sort_col") or Session::has("$routeName.sort_way"));
($noOrder = Session::get("$routeName.sort_col") !== '') and
(Session::has("$routeName.sort_col") or Session::has("$routeName.sort_way"));
@endphp
@include('modules.table-col-sorter', [
'cols' => $cols,
@ -30,57 +33,50 @@
<span>{{ Str::limit(strip_tags($productModel->description), 25) }}</span>
</td>
<td>
<img
class="img-fluid img-thumbnail"
src="{{ asset(resize($productModel->sticker, 'boStickers')) }}"
title="{{ $productModel->sticker_title }}"
alt="{{ $productModel->sticker_alt }}">
</td>
<img class="img-fluid img-thumbnail" src="{{ asset(resize($productModel->sticker, 'boStickers')) }}"
title="{{ $productModel->sticker_title }}" alt="{{ $productModel->sticker_alt }}">
</td>
<td class="text-center">
<span class="badge rounded-pill text-bg-primary">{{ $productModel->nutriscore->label() }}</span>
</td>
<td class="d-none d-md-table-cell text-center align-middle">
<form action="{{ route('bo.products.change-published', $productModel->id) }}" method="POST">
@csrf
<button type="submit"
class="btn btn-sm"
title="{{ __($productModel->status ? __('Dépublier') : __('Publier')) }}"
data-bs-toggle="tooltip">
@if($productModel->status)
@include('bo.partials/svg/round-check')
<button class="btn btn-sm" data-bs-toggle="tooltip" type="submit"
title="{{ __($productModel->status ? __('Dépublier') : __('Publier')) }}">
@if ($productModel->status)
@include('bo.partials/svg/round-check')
@else
@include('bo.partials/svg/round-cross')
@include('bo.partials/svg/round-cross')
@endif
</button>
</form>
</td>
<td>
<span>{{ $productModel->updated_at->formatLocalized('%d/%m/%Y à %I:%M') }}</span>
<span>{{ $productModel->updated_at->isoFormat('LLLL') }}</span>
</td>
<td>
<div class="btn-group">
<a class="btn btn-sm btn-primary"
<a class="btn btn-sm btn-primary" data-bs-toggle="tooltip"
href="{{ route('bo.products.edit', $productModel) }}"
title="{{ __('Modifier :model', [
'model' => Str::singular(__('models.classes.products')),
]) }}"
data-bs-toggle="tooltip"><i class="fa-solid fa-pencil"></i></a>
<a class="btn btn-sm btn-secondary"
]) }}"><i
class="fa-solid fa-pencil"></i></a>
<a class="btn btn-sm btn-secondary" data-bs-toggle="tooltip"
href="{{ route('products.show', $productModel) }}"
title="{{ __('Voir :model', [
'model' => Str::singular(__('models.classes.products')),
]) }}"
target="_blank"
data-bs-toggle="tooltip"><i class="fa-solid fa-eye"></i></a>
target="_blank"><i class="fa-solid fa-eye"></i></a>
</div>
</td>
</tr>
@endforeach
@else
<tr>
<td colspan="5" class="text-center">
<span
class="h3">{{ __('Aucun résultat pour les :models', ['models' => __('models.classes.products')]) }}
<td class="text-center" colspan="5">
<span class="h3">{{ __('Aucun résultat pour les :models', ['models' => __('models.classes.products')]) }}
</span>
</td>
</tr>

View file

@ -1,7 +1,7 @@
<div class="row">
<div class="col-12 col-md-6 order-md-0">
{{-- TITLE --}}
<fieldset class="border p-3 mb-3">
<fieldset class="mb-3 border p-3">
<legend>{{ __('Libellé *') }}</legend>
<div class="row">
<div class="col-12 mb-3">
@ -19,7 +19,7 @@
</div>
<div class="col-12 col-md-6 order-md-2">
{{-- SUBTITLE --}}
<fieldset class="border p-3 mb-3">
<fieldset class="mb-3 border p-3">
<legend>{{ __('Sous-titre *') }}</legend>
<div class="row">
<div class="col-12 mb-3">
@ -37,7 +37,7 @@
</div>
{{-- PUBLICATION --}}
<div class="col">
<fieldset class="border p-3 mb-3 order-md-1">
<fieldset class="order-md-1 mb-3 border p-3">
<legend>{{ __('Publication') }}</legend>
<div class="row mb-3">
<div class="col">
@ -57,7 +57,7 @@
{{-- SEO --}}
<div class="row">
<div class="col">
<fieldset class="border p-3 mb-3">
<fieldset class="mb-3 border p-3">
<legend>{{ __('SEO') }}</legend>
<div class="row mb-3">
<div class="col-12 col-md-6">
@ -69,7 +69,8 @@
'required' => true,
'model' => $rangeModel ?? null,
'placeholder' => __('Un titre en 70 caractères maximum'),
'helper' => __('Doit décrire précisément la page, recommandation pour un positionnement optimal dans les moteurs de recherche : entre 45 et 70 caractères, balise <title>.'),
'helper' => __(
'Doit décrire précisément la page, recommandation pour un positionnement optimal dans les moteurs de recherche : entre 45 et 70 caractères, balise <title>.'),
])
</div>
<div class="col-12 col-md-6">
@ -79,7 +80,8 @@
'name' => 'seo_description',
'model' => $rangeModel ?? null,
'required' => true,
'helper' => __(' Doit décrire précisément le contenu de la gamme, requiert entre 50 et 160 caractères, balise <meta> description.'),
'helper' => __(
' Doit décrire précisément le contenu de la gamme, requiert entre 50 et 160 caractères, balise <meta> description.'),
'placeholder' => __('Une description du contenu de la page pour les moteurs de recherche.'),
])
</div>
@ -91,7 +93,7 @@
{{-- BANNER --}}
<div class="row">
<div class="col">
<fieldset class="border p-3 mb-3">
<fieldset class="mb-3 border p-3">
<legend>{{ __('Bannière') }}</legend>
<div class="row mb-3">
<div class="col-12 col-md-6">
@ -103,7 +105,8 @@
'required' => true,
'model' => $rangeModel ?? null,
'placeholder' => __('Une description de l\'image pour les moteurs de recherche'),
'helper' => __('Doit décrire votre image pour les moteurs de recherches et les non voyants (5 à 6 mots / 3 caractères minimum, 255 maximum)'),
'helper' => __(
'Doit décrire votre image pour les moteurs de recherches et les non voyants (5 à 6 mots / 3 caractères minimum, 255 maximum)'),
])
</div>
<div class="col-12 col-md-6">
@ -115,7 +118,8 @@
'required' => true,
'model' => $rangeModel ?? null,
'placeholder' => __('Texte d\'info-bulle au survol de la souris'),
'helper' => __('Utile pour les utilisateurs, s\'affiche en passant la souris sur la bannière (3 caractères minimum, 255 maximum).'),
'helper' => __(
'Utile pour les utilisateurs, s\'affiche en passant la souris sur la bannière (3 caractères minimum, 255 maximum).'),
])
</div>
</div>
@ -128,7 +132,8 @@
'height' => 312,
'required' => true,
'model' => $rangeModel ?? null,
'helper' => __('La bannière qui servir à l\'affichage de la gamme, doit être de 1600px (largeur) par 312px (hauteur)'),
'helper' => __(
'La bannière qui servir à l\'affichage de la gamme, doit être de 1600px (largeur) par 312px (hauteur)'),
'required' => true,
'preview' => true,
])
@ -141,7 +146,7 @@
{{-- PICTURE --}}
<div class="row">
<div class="col">
<fieldset class="border p-3 mb-3">
<fieldset class="mb-3 border p-3">
<legend>{{ __('Illustration') }}</legend>
<div class="row mb-3">
<div class="col-12 col-md-6">
@ -153,7 +158,8 @@
'required' => true,
'model' => $rangeModel ?? null,
'placeholder' => __('Une description de l\'image pour les moteurs de recherche'),
'helper' => __('Doit décrire votre image pour les moteurs de recherches et les non voyants (5 à 6 mots / 3 caractères minimum, 255 maximum)'),
'helper' => __(
'Doit décrire votre image pour les moteurs de recherches et les non voyants (5 à 6 mots / 3 caractères minimum, 255 maximum)'),
])
</div>
<div class="col-12 col-md-6">
@ -165,7 +171,8 @@
'required' => true,
'model' => $rangeModel ?? null,
'placeholder' => __('Texte d\'info-bulle au survol de la souris'),
'helper' => __('Utile pour les utilisateurs, s\'affiche en passant la souris sur la bannière (3 caractères minimum, 255 maximum).'),
'helper' => __(
'Utile pour les utilisateurs, s\'affiche en passant la souris sur la bannière (3 caractères minimum, 255 maximum).'),
])
</div>
</div>
@ -178,7 +185,8 @@
'height' => 415,
'required' => true,
'model' => $rangeModel ?? null,
'helper' => __('L\'image qui va servir d\'illustration a la gamme, doit être de 650px (largeur) par 415px (hauteur)'),
'helper' => __(
'L\'image qui va servir d\'illustration a la gamme, doit être de 650px (largeur) par 415px (hauteur)'),
'required' => true,
'preview' => true,
])
@ -191,7 +199,7 @@
{{-- STICKER --}}
<div class="row">
<div class="col">
<fieldset class="border p-3 mb-3">
<fieldset class="mb-3 border p-3">
<legend>{{ __('Vignette') }}</legend>
<div class="row mb-3">
<div class="col-12 col-md-6">
@ -203,7 +211,8 @@
'required' => true,
'model' => $rangeModel ?? null,
'placeholder' => __('Une description de l\'image pour les moteurs de recherche'),
'helper' => __('Doit décrire votre image pour les moteurs de recherches et les non voyants (5 à 6 mots / 3 caractères minimum, 255 maximum)'),
'helper' => __(
'Doit décrire votre image pour les moteurs de recherches et les non voyants (5 à 6 mots / 3 caractères minimum, 255 maximum)'),
])
</div>
<div class="col-12 col-md-6">
@ -215,7 +224,8 @@
'required' => true,
'model' => $rangeModel ?? null,
'placeholder' => __('Texte d\'info-bulle au survol de la souris'),
'helper' => __('Utile pour les utilisateurs, s\'affiche en passant la souris sur la bannière (3 caractères minimum, 255 maximum).'),
'helper' => __(
'Utile pour les utilisateurs, s\'affiche en passant la souris sur la bannière (3 caractères minimum, 255 maximum).'),
])
</div>
</div>
@ -237,3 +247,5 @@
</fieldset>
</div>
</div>
@include('bo.partials.script-button-clone')

View file

@ -1,5 +1,5 @@
<div class="table-responsive">
<table class="table fix-action">
<table class="fix-action table">
<thead>
@php
$cols = [
@ -7,11 +7,14 @@
'subtitle' => __('Sous-titre'),
'sticker' => __('Vignette'),
'status' => __('Publication'),
'updated_at' => __('Mis à jour le')];
'updated_at' => __('Mis à jour le'),
];
$routeName = request()->route()->getName();
$rst = !is_null(request()->rst) || !Session::has("$routeName.sorted");
$noOrder = Session::get("$routeName.sort_col") !== 'order' and (Session::has("$routeName.sort_col") or Session::has("$routeName.sort_way"));
$noOrder = Session::get("$routeName.sort_col") !== '' and (Session::has("$routeName.sort_col") or Session::has("$routeName.sort_way"));
($noOrder = Session::get("$routeName.sort_col") !== 'order') and
(Session::has("$routeName.sort_col") or Session::has("$routeName.sort_way"));
($noOrder = Session::get("$routeName.sort_col") !== '') and
(Session::has("$routeName.sort_col") or Session::has("$routeName.sort_way"));
@endphp
@include('modules.table-col-sorter', [
'cols' => $cols,
@ -29,54 +32,46 @@
<span>{{ $rangeModel->subtitle }}</span>
</td>
<td>
<img
class="img-fluid img-thumbnail"
src="{{ asset(resize($rangeModel->sticker, 'boStickers')) }}"
title="{{ $rangeModel->sticker_title }}"
alt="{{ $rangeModel->sticker_alt }}">
<img class="img-fluid img-thumbnail" src="{{ asset(resize($rangeModel->sticker, 'boStickers')) }}"
title="{{ $rangeModel->sticker_title }}" alt="{{ $rangeModel->sticker_alt }}">
</td>
<td class="d-none d-lg-table-cell text-center align-middle">
<form action="{{ route('bo.ranges.change-published', $rangeModel->id) }}" method="POST">
@csrf
<button type="submit"
class="btn btn-sm"
title="{{ __($rangeModel->status ? __('Dépublier') : __('Publier')) }}"
data-bs-toggle="tooltip">
@if($rangeModel->status)
@include('bo.partials/svg/round-check')
<button class="btn btn-sm" data-bs-toggle="tooltip" type="submit"
title="{{ __($rangeModel->status ? __('Dépublier') : __('Publier')) }}">
@if ($rangeModel->status)
@include('bo.partials/svg/round-check')
@else
@include('bo.partials/svg/round-cross')
@include('bo.partials/svg/round-cross')
@endif
</button>
</form>
</td>
<td>
<span>{{ $rangeModel->updated_at->formatLocalized('%d/%m/%Y à %I:%M') }}</span>
<span>{{ $rangeModel->updated_at->isoFormat('LLLL') }}</span>
</td>
<td>
<div class="btn-group">
<a class="btn btn-sm btn-primary"
href="{{ route('bo.ranges.edit', $rangeModel) }}"
<a class="btn btn-sm btn-primary" data-bs-toggle="tooltip" href="{{ route('bo.ranges.edit', $rangeModel) }}"
title="{{ __('Modifier :model', [
'model' => Str::singular(__('models.classes.ranges')),
]) }}"
data-bs-toggle="tooltip"><i class="fa-solid fa-pencil"></i></a>
<a class="btn btn-sm btn-secondary"
]) }}"><i
class="fa-solid fa-pencil"></i></a>
<a class="btn btn-sm btn-secondary" data-bs-toggle="tooltip"
href="{{ route('ranges.products', $rangeModel) }}"
title="{{ __('Voir :model', [
'model' => Str::singular(__('models.classes.ranges')),
]) }}"
target="_blank"
data-bs-toggle="tooltip"><i class="fa-solid fa-eye"></i></a>
target="_blank"><i class="fa-solid fa-eye"></i></a>
</div>
</td>
</tr>
@endforeach
@else
<tr>
<td colspan="5" class="text-center">
<span
class="h3">{{ __('Aucun résultat pour les :models', ['models' => __('models.classes.ranges')]) }}
<td class="text-center" colspan="5">
<span class="h3">{{ __('Aucun résultat pour les :models', ['models' => __('models.classes.ranges')]) }}
</span>
</td>
</tr>

View file

@ -1,7 +1,7 @@
<div class="row">
{{-- TITLE --}}
<div class="col-12 col-md-6">
<fieldset class="border p-3 mb-3">
<fieldset class="mb-3 border p-3">
<legend>{{ __('Libellé *') }}</legend>
<div class="row">
<div class="col-12 mb-3">
@ -19,7 +19,7 @@
</div>
{{-- PUBLICATION --}}
<div class="col">
<fieldset class="border p-3 mb-3">
<fieldset class="mb-3 border p-3">
<legend>{{ __('Publication') }}</legend>
<div class="row mb-3">
<div class="col">
@ -39,7 +39,7 @@
{{-- SEO --}}
<div class="row">
<div class="col">
<fieldset class="border p-3 mb-3">
<fieldset class="mb-3 border p-3">
<legend>{{ __('SEO') }}</legend>
<div class="row mb-3">
<div class="col-12 col-md-6">
@ -51,7 +51,8 @@
'model' => $recipeModel ?? null,
'required' => true,
'placeholder' => __('Un titre en 70 caractères maximum'),
'helper' => __('Doit décrire précisément la page, recommandation pour un positionnement optimal dans les moteurs de recherche : entre 45 et 70 caractères, balise <title>.'),
'helper' => __(
'Doit décrire précisément la page, recommandation pour un positionnement optimal dans les moteurs de recherche : entre 45 et 70 caractères, balise <title>.'),
])
</div>
<div class="col-12 col-md-6">
@ -61,7 +62,8 @@
'name' => 'seo_description',
'model' => $recipeModel ?? null,
'required' => true,
'helper' => __(' Doit décrire précisément le contenu de la recette, requiert entre 50 et 160 caractères, balise <meta> description.'),
'helper' => __(
' Doit décrire précisément le contenu de la recette, requiert entre 50 et 160 caractères, balise <meta> description.'),
'placeholder' => __('Une description du contenu de la page pour les moteurs de recherche.'),
])
</div>
@ -73,7 +75,7 @@
{{-- CARACTERISTIQUES --}}
<div class="row">
<div class="col">
<fieldset class="border p-3 mb-3">
<fieldset class="mb-3 border p-3">
<legend>{{ __('Caractéristiques') }}</legend>
{{-- DESCRIPTION --}}
<div class="row">
@ -162,7 +164,7 @@
{{-- INGREDIENTS --}}
<div class="row">
<div class="col-12">
<fieldset class="border p-3 mb-3">
<fieldset class="mb-3 border p-3">
<div class="row">
<div class="col">
@include('bo.partials.inputs.text-list-items', [
@ -196,11 +198,10 @@
</div>
</div>
{{-- PICTURE --}}
<div class="row">
<div class="col">
<fieldset class="border p-3 mb-3">
<fieldset class="mb-3 border p-3">
<legend>{{ __('Illustration') }}</legend>
<div class="row mb-3">
<div class="col-12 col-md-6">
@ -212,7 +213,8 @@
'required' => true,
'model' => $recipeModel ?? null,
'placeholder' => __('Une description de l\'image pour les moteurs de recherche'),
'helper' => __('Doit décrire votre image pour les moteurs de recherches et les non voyants (5 à 6 mots / 3 caractères minimum, 255 maximum)'),
'helper' => __(
'Doit décrire votre image pour les moteurs de recherches et les non voyants (5 à 6 mots / 3 caractères minimum, 255 maximum)'),
])
</div>
<div class="col-12 col-md-6">
@ -224,7 +226,8 @@
'required' => true,
'model' => $recipeModel ?? null,
'placeholder' => __('Texte d\'info-bulle au survol de la souris'),
'helper' => __('Utile pour les utilisateurs, s\'affiche en passant la souris sur la bannière (3 caractères minimum, 255 maximum).'),
'helper' => __(
'Utile pour les utilisateurs, s\'affiche en passant la souris sur la bannière (3 caractères minimum, 255 maximum).'),
])
</div>
</div>
@ -237,7 +240,8 @@
'height' => 488,
'required' => true,
'model' => $recipeModel ?? null,
'helper' => __('L\'image qui va servir d\'illustration a la gamme, doit être de 650px (largeur) par 488px (hauteur)'),
'helper' => __(
'L\'image qui va servir d\'illustration a la gamme, doit être de 650px (largeur) par 488px (hauteur)'),
'required' => true,
'preview' => true,
])
@ -250,7 +254,7 @@
{{-- STICKER --}}
<div class="row">
<div class="col">
<fieldset class="border p-3 mb-3">
<fieldset class="mb-3 border p-3">
<legend>{{ __('Vignette') }}</legend>
<div class="row mb-3">
<div class="col-12 col-md-6">
@ -262,7 +266,8 @@
'required' => true,
'model' => $recipeModel ?? null,
'placeholder' => __('Une description de l\'image pour les moteurs de recherche'),
'helper' => __('Doit décrire votre image pour les moteurs de recherches et les non voyants (5 à 6 mots / 3 caractères minimum, 255 maximum)'),
'helper' => __(
'Doit décrire votre image pour les moteurs de recherches et les non voyants (5 à 6 mots / 3 caractères minimum, 255 maximum)'),
])
</div>
<div class="col-12 col-md-6">
@ -274,7 +279,8 @@
'required' => true,
'model' => $recipeModel ?? null,
'placeholder' => __('Texte d\'info-bulle au survol de la souris'),
'helper' => __('Utile pour les utilisateurs, s\'affiche en passant la souris sur la bannière (3 caractères minimum, 255 maximum).'),
'helper' => __(
'Utile pour les utilisateurs, s\'affiche en passant la souris sur la bannière (3 caractères minimum, 255 maximum).'),
])
</div>
</div>
@ -300,7 +306,7 @@
{{-- VIDEO --}}
<div class="row">
<div class="col">
<fieldset class="border p-3 mb-3">
<fieldset class="mb-3 border p-3">
<legend>{{ __('Identificateur Youtube de la vidéo') }}</legend>
<div class="row">
<div class="col col-md-6 mb-3">
@ -322,7 +328,7 @@
<div class="row">
{{-- PRODUITS ASSOCIES --}}
<div class="col">
<fieldset class="border p-3 mb-3">
<fieldset class="mb-3 border p-3">
<legend>{{ __('Produits associés *') }}</legend>
<div class="row">
<div class="col">
@ -342,7 +348,7 @@
</div>
{{-- RECETTES ASSOCIEES --}}
<div class="col">
<fieldset class="border p-3 mb-3">
<fieldset class="mb-3 border p-3">
<legend>{{ __('Recettes associées') }}</legend>
<div class="row">
<div class="col">
@ -361,3 +367,5 @@
</fieldset>
</div>
</div>
@include('bo.partials.script-button-clone')

View file

@ -1,16 +1,19 @@
<div class="table-responsive">
<table class="table fix-action">
<table class="fix-action table">
<thead>
@php
$cols = [
'name' => __('Nom'),
'sticker' => __('Vignette'),
'status' => __('Publication'),
'updated_at' => __('Mis à jour le')];
'updated_at' => __('Mis à jour le'),
];
$routeName = request()->route()->getName();
$rst = !is_null(request()->rst) || !Session::has("$routeName.sorted");
$noOrder = Session::get("$routeName.sort_col") !== 'order' and (Session::has("$routeName.sort_col") or Session::has("$routeName.sort_way"));
$noOrder = Session::get("$routeName.sort_col") !== '' and (Session::has("$routeName.sort_col") or Session::has("$routeName.sort_way"));
($noOrder = Session::get("$routeName.sort_col") !== 'order') and
(Session::has("$routeName.sort_col") or Session::has("$routeName.sort_way"));
($noOrder = Session::get("$routeName.sort_col") !== '') and
(Session::has("$routeName.sort_col") or Session::has("$routeName.sort_way"));
@endphp
@include('modules.table-col-sorter', [
'cols' => $cols,
@ -25,45 +28,39 @@
<span>{{ $recipeModel->name }}</span>
</td>
<td>
<img
class="img-fluid img-thumbnail"
src="{{ asset(resize($recipeModel->sticker, 'boStickers')) }}"
title="{{ $recipeModel->sticker_title }}"
alt="{{ $recipeModel->sticker_alt }}">
<img class="img-fluid img-thumbnail" src="{{ asset(resize($recipeModel->sticker, 'boStickers')) }}"
title="{{ $recipeModel->sticker_title }}" alt="{{ $recipeModel->sticker_alt }}">
</td>
<td class="d-none d-md-table-cell text-center align-middle">
<form action="{{ route('bo.recipes.change-published', $recipeModel->id) }}" method="POST">
@csrf
<button type="submit"
class="btn btn-sm"
title="{{ __($recipeModel->status ? __('Dépublier') : __('Publier')) }}"
data-bs-toggle="tooltip">
@if($recipeModel->status)
@include('bo.partials/svg/round-check')
<button class="btn btn-sm" data-bs-toggle="tooltip" type="submit"
title="{{ __($recipeModel->status ? __('Dépublier') : __('Publier')) }}">
@if ($recipeModel->status)
@include('bo.partials/svg/round-check')
@else
@include('bo.partials/svg/round-cross')
@include('bo.partials/svg/round-cross')
@endif
</button>
</form>
</td>
<td>
<span>{{ $recipeModel->updated_at->formatLocalized('%d/%m/%Y à %I:%M') }}</span>
<span>{{ $recipeModel->updated_at->isoFormat('LLLL') }}</span>
</td>
<td>
<div class="btn-group">
<a class="btn btn-sm btn-primary"
<a class="btn btn-sm btn-primary" data-bs-toggle="tooltip"
href="{{ route('bo.recipes.edit', $recipeModel->id) }}"
title="{{ __('Modifier :model', [
'model' => Str::singular(__('models.classes.recipes')),
]) }}"><i
class="fa-solid fa-pencil"></i></a>
<a class="btn btn-sm btn-secondary" data-bs-toggle="tooltip"
href="{{ route('recipes.show', $recipeModel) }}"
title="{{ __('Voir :model', [
'model' => Str::singular(__('models.classes.ranges')),
]) }}"
data-bs-toggle="tooltip"><i class="fa-solid fa-pencil"></i></a>
<a class="btn btn-sm btn-secondary"
href="{{ route('recipes.show', $recipeModel) }}"
title="{{ __('Voir :model', [
'model' => Str::singular(__('models.classes.ranges')),
]) }}"
target="_blank"
data-bs-toggle="tooltip"><i class="fa-solid fa-eye"></i></a>
target="_blank"><i class="fa-solid fa-eye"></i></a>
</div>
</td>
@ -71,9 +68,8 @@
@endforeach
@else
<tr>
<td colspan="5" class="text-center">
<span
class="h3">{{ __('Aucun résultat pour les :models', ['models' => __('models.classes.recipes')]) }}
<td class="text-center" colspan="5">
<span class="h3">{{ __('Aucun résultat pour les :models', ['models' => __('models.classes.recipes')]) }}
</span>
</td>
</tr>

View file

@ -1,6 +1,6 @@
<div class="row">
<div class="col">
<fieldset class="border p-3 mb-3">
<fieldset class="mb-3 border p-3">
<legend>{{ __('Informations sur le magasin') }}</legend>
<div class="row mb-3">
<div class="col-12 col-md-6">
@ -23,7 +23,7 @@
'required' => true,
'associatedModels' => \App\Enums\SignsEnum::toArray(),
'model' => $shopModel ?? null,
'helper' => __('Choisir une chaîne de magasins')
'helper' => __('Choisir une chaîne de magasins'),
])
</div>
</div>
@ -33,7 +33,7 @@
<div class="row">
<div class="col">
<fieldset class="border p-3 mb-3">
<fieldset class="mb-3 border p-3">
<legend>{{ __('Coordonnés') }}</legend>
<div class="row mb-3">
<div class="col">
@ -79,7 +79,7 @@
<div class="row">
<div class="col">
<fieldset class="border p-3 mb-3">
<fieldset class="mb-3 border p-3">
<legend>{{ __('Coordonnées géographiques') }}</legend>
<div class="row mb-3">
<div class="col-12 col-md-6">
@ -108,3 +108,5 @@
</fieldset>
</div>
</div>
@include('bo.partials.script-button-clone')

View file

@ -1,16 +1,19 @@
<div class="table-responsive">
<table class="table fix-action">
<table class="fix-action table">
<thead>
@php
$cols = [
'name' => __('Nom'),
'signs' => __('Enseigne'),
'address' => __('Adresse'),
'updated_at' => __('Mis à jour le')];
'updated_at' => __('Mis à jour le'),
];
$routeName = request()->route()->getName();
$rst = !is_null(request()->rst) || !Session::has("$routeName.sorted");
$noOrder = Session::get("$routeName.sort_col") !== 'order' and (Session::has("$routeName.sort_col") or Session::has("$routeName.sort_way"));
$noOrder = Session::get("$routeName.sort_col") !== '' and (Session::has("$routeName.sort_col") or Session::has("$routeName.sort_way"));
($noOrder = Session::get("$routeName.sort_col") !== 'order') and
(Session::has("$routeName.sort_col") or Session::has("$routeName.sort_way"));
($noOrder = Session::get("$routeName.sort_col") !== '') and
(Session::has("$routeName.sort_col") or Session::has("$routeName.sort_way"));
@endphp
@include('modules.table-col-sorter', [
'cols' => $cols,
@ -29,16 +32,15 @@
</td>
<td>{{ $shopModel->address }}, {{ $shopModel->postcode }} {{ $shopModel->city }}</td>
<td>
<span>{{ $shopModel->updated_at->formatLocalized('%d/%m/%Y à %I:%M') }}</span>
<span>{{ $shopModel->updated_at->isoFormat('LLLL') }}</span>
</td>
<td>
<div class="btn-group">
<a class="btn btn-sm btn-primary"
href="{{ route('bo.shops.edit', $shopModel->id) }}"
<a class="btn btn-sm btn-primary" data-bs-toggle="tooltip" href="{{ route('bo.shops.edit', $shopModel->id) }}"
title="{{ __('Modifier :model', [
'model' => Str::singular(__('models.classes.shops')),
]) }}"
data-bs-toggle="tooltip"><i class="fa-solid fa-pencil"></i></a>
]) }}"><i
class="fa-solid fa-pencil"></i></a>
</div>
</td>
@ -46,9 +48,8 @@
@endforeach
@else
<tr>
<td colspan="5" class="text-center">
<span
class="h3">{{ __('Aucun résultat pour les :models', ['models' => __('models.classes.shops')]) }}
<td class="text-center" colspan="5">
<span class="h3">{{ __('Aucun résultat pour les :models', ['models' => __('models.classes.shops')]) }}
</span>
</td>
</tr>

View file

@ -156,16 +156,4 @@
</div>
</div>
@push('scripts')
<script nonce="{{ $nonce }}">
document.addEventListener("DOMContentLoaded", () => {
// Submit button clone.
const submit = document.getElementById('formSubmit')
submitClone = document.getElementById('formSubmitClone');
submitClone.addEventListener('click', (event) => {
event.preventDefault();
submit.click('');
})
});
</script>
@endpush
@include('bo.partials.script-button-clone')

View file

@ -0,0 +1,12 @@
@push('scripts')
<script nonce="{{ $nonce ?? '' }}">
document.addEventListener("DOMContentLoaded", () => {
const submit = document.getElementById('formSubmit')
submitClone = document.getElementById('formSubmitClone');
submitClone.addEventListener('click', (event) => {
event.preventDefault();
submit.click('');
})
});
</script>
@endpush

View file

@ -1,7 +1,11 @@
<!-- Meta -->
<meta charset="utf-8">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
@hasSection('metaIndex')<meta name="robots" content="@yield('metaIndex')">@else<meta name="robots" content="index, follow">@endif
@hasSection('metaIndex')
<meta name="robots" content="@yield('metaIndex')">
@else
<meta name="robots" content="index, follow">
@endif
<meta name="csrf-token" content="{{ csrf_token() }}">
<meta name="description" content="@yield('description')">
<meta name="viewport" content="width=device-width, initial-scale=1">
@ -12,35 +16,36 @@
<meta property="og:url" content="{{ config('app.url') }}" />
<meta property="og:site_name" content="{{ config('app.name') }}" />
@hasSection('ogPicture')
<meta property="og:image" content="@yield('ogPicture')" />
<meta property="og:image" content="@yield('ogPicture')" />
@else
<meta property="og:image" content="{{ Vite::asset('resources/assets/images/opengraph-website.png') }}" />
<meta property="og:image" content="{{ Vite::asset('resources/assets/images/opengraph-website.png') }}" />
@endif
<meta property="og:description" content="@yield('description')" />
<!-- Favicon -->
{{-- GENERER favicon avec un outil https://realfavicongenerator.net/ --}}
<link rel="apple-touch-icon" sizes="180x180" href="{{ asset('apple-touch-icon.png') }}">
<link rel="icon" href="{{ asset('favicon.ico') }}">
<link rel="icon" type="image/png" sizes="32x32" href="{{ asset('favicon-32x32.png') }}">
<link rel="icon" type="image/png" sizes="16x16" href="{{ asset('favicon-16x16.png') }}">
<link rel="manifest" href="{{ asset('site.webmanifest') }}">
<link rel="mask-icon" href="{{ asset('safari-pinned-tab.svg') }}" color="#5bbad5">
<link href="{{ asset('apple-touch-icon.png') }}" rel="apple-touch-icon" sizes="180x180">
<link href="{{ asset('favicon.ico') }}" rel="icon">
<link type="image/png" href="{{ asset('favicon-32x32.png') }}" rel="icon" sizes="32x32">
<link type="image/png" href="{{ asset('favicon-16x16.png') }}" rel="icon" sizes="16x16">
<link href="{{ asset('site.webmanifest') }}" rel="manifest">
<link href="{{ asset('safari-pinned-tab.svg') }}" rel="mask-icon" color="#5bbad5">
<meta name="msapplication-TileColor" content="#da532c">
<meta name="theme-color" content="#ffffff">
<!-- Others -->
<title>@yield('title') - {{ config('app.name') }}</title>
@if(config('app.debug'))
<script nonce="{{ $nonce }}">window.vueDebug = true;</script>
@if (config('app.debug'))
<script nonce="{{ $nonce ?? '' }}">
window.vueDebug = true;
</script>
@endif
@hasSection('canonical')
<link rel="canonical" href="@yield('canonical')" />
<link href="@yield('canonical')" rel="canonical" />
@else
<link rel="canonical" href="{{ \config('app.url') }}/{{ \request()->path() }}" />
<link href="{{ \config('app.url') }}/{{ \request()->path() }}" rel="canonical" />
@endif
<!-- STYLES -->
@vite(['resources/sass/fo/fo.scss'])
@stack('styles')

View file

@ -15,7 +15,7 @@
<div class="row">
<div class="col-12 d-flex">
<p class="fst-italic me-2">Vous êtes ici</p>
@include('modules/breadcrumb/breadcrumb-body')
@include('modules.breadcrumbs.breadcrumb-body')
</div>
</div>
</div>
@ -157,7 +157,7 @@
@endsection
@push('scripts')
<script nonce="{{ $nonce }}">
<script nonce="{{ $nonce ?? '' }}">
document.addEventListener('DOMContentLoaded', function() {
document.getElementById("recaptcha-accept").addEventListener('click', function(e) {
e.preventDefault();

View file

@ -6,53 +6,57 @@
@section('content')
<div class="g-0 pb-md-5 position-relative hero">
<img src="{{ Vite::asset('resources/assets/images/100LOCAL-V1.png') }}" alt="Logo 100% local" class="img-fluid">
<img class="img-fluid" src="{{ Vite::asset('resources/assets/images/100LOCAL-V1.png') }}" alt="Logo 100% local">
<div class="position-relative hero-fix-height">
<img src="{{ Vite::asset('resources/assets/images/Panoramique6.jpg') }}" alt="Un paysage de campagne" class="img-fluid">
<img class="img-fluid" src="{{ Vite::asset('resources/assets/images/Panoramique6.jpg') }}" alt="Un paysage de campagne">
</div>
<!-- breadcrumb -->
<div class="container breadcrumb-obj small mt-3">
<div class="breadcrumb-obj small container mt-3">
<div class="row">
<div class="col-12 d-flex ">
<div class="col-12 d-flex">
<p class="fst-italic me-2">Vous êtes ici</p>
@include('modules/breadcrumb/breadcrumb-body')
@include('modules.breadcrumbs.breadcrumb-body')
</div>
</div>
</div>
<!-- END breadcrumb -->
</div>
<div class="container pt-5 pb-5 position-relative">
<div class="position-relative container pb-5 pt-5">
<div class="row">
<section class="col-md-8 offset-md-2 stripes">
<h1 class="text-uppercase text-center font-amatic font-amatic-48">{{ __('Les engagements') }}<span class="font-amatic-red">{{ __('Lionor') }}</span></h1>
<h1 class="text-uppercase font-amatic font-amatic-48 text-center">{{ __('Les engagements') }}<span
class="font-amatic-red">{{ __('Lionor') }}</span></h1>
</section>
</div>
<!-- GRID -->
<div class="mt-4 text-center about">
<div class="row mb-5 about-one position-relative">
<div class="col-lg-8 offset-lg-2 bckg-red opacity p-5 position-relative">
<div class="about mt-4 text-center">
<div class="row about-one position-relative mb-5">
<div class="col-lg-8 offset-lg-2 bckg-red opacity position-relative p-5">
<h2 class="fw-bold text-uppercase">{{ __('Des produits de qualité bons et accessibles') }}</h2>
<h3 class="text-uppercase">{{ __('Garantis 100% Hauts-De-France') }}</h3>
<p>{{ __('Avec notre label 100% LOCAL, certifié et contrôlé par un cahier des charges précis, nous garantissons l\'origine de nos produits issus de poulets nés, élevés et préparés en région des Hauts de France.') }}</p>
<p>{{ __('Avec notre label 100% LOCAL, certifié et contrôlé par un cahier des charges précis, nous garantissons l\'origine de nos produits issus de poulets nés, élevés et préparés en région des Hauts de France.') }}
</p>
</div>
</div>
<div class="row mb-5 about-two position-relative">
<div class="col-lg-8 offset-lg-2 bckg-yellow opacity pt-5 pe-5 ps-5 position-relative">
<div class="row about-two position-relative mb-5">
<div class="col-lg-8 offset-lg-2 bckg-yellow opacity position-relative pe-5 ps-5 pt-5">
<h2 class="fw-bold text-uppercase">{{ __('Une filière soucieuse de la santé') }}</h2>
<h3 class="text-uppercase">{{ __('Des hommes, des animaux et de la terre') }}</h3>
<p>{{ __('LIONOR, c\'est une filière qui s\'engage pour élever des volailles dans le respect du vivant et qui favorise l\'économie locale. Fiers de nos 70 éleveurs et de nos 235 salariés, unis par la passion de leur métier, nous participons activement à la vie sociale et économique de notre région.') }}</p>
<p>{{ __('LIONOR, c\'est une filière qui s\'engage pour élever des volailles dans le respect du vivant et qui favorise l\'économie locale. Fiers de nos 70 éleveurs et de nos 235 salariés, unis par la passion de leur métier, nous participons activement à la vie sociale et économique de notre région.') }}
</p>
</div>
</div>
<div class="row mb-5 about-three position-relative">
<div class="col-lg-8 offset-lg-2 bckg-red opacity pt-5 pe-5 ps-5 position-relative">
<div class="row about-three position-relative mb-5">
<div class="col-lg-8 offset-lg-2 bckg-red opacity position-relative pe-5 ps-5 pt-5">
<h2 class="fw-bold text-uppercase">{{ __('Des femmes et des hommes') }}</h2>
<h3 class="text-uppercase">{{ __('Qui maîtrisent la filière du grain à l\'assiette') }}</h3>
<p>{{ __('Grâce à la maitrise totale de notre filière, allant de l\'alimentation aux produits régionaux de qualité, en passant pas nos élevages se situant dans un rayon de 50kms autour de notre entreprise, nous garantissons aux consommateurs finaux, une qualité et une traçabilité irréprochables.
Notre crédo : "élever bien pour manger sain en région des Hauts de France."') }}</p>
Notre crédo : "élever bien pour manger sain en région des Hauts de France."') }}
</p>
</div>
</div>
</div>

View file

@ -5,32 +5,32 @@
@section('breadcrumb', request()->route()->getName())
@section('content')
<div class="g-0 pb-md-5 position-relative hero">
<div class="position-relative hero-fix-height">
<img src="{{ Vite::asset('resources/assets/images/Panoramique6.jpg') }}" alt="Un paysage de campagne" class="img-fluid">
<div class="g-0 pb-md-5 position-relative hero">
<div class="position-relative hero-fix-height">
<img class="img-fluid" src="{{ Vite::asset('resources/assets/images/Panoramique6.jpg') }}" alt="Un paysage de campagne">
</div>
<!-- breadcrumb -->
<div class="breadcrumb-obj small container mt-3">
<div class="row">
<div class="col-12 d-flex">
<p class="fst-italic me-2">Vous êtes ici</p>
@include('modules.breadcrumbs.breadcrumb-body')
</div>
</div>
</div>
<!-- END breadcrumb -->
</div>
<!-- breadcrumb -->
<div class="container breadcrumb-obj small mt-3">
<div class="row">
<div class="col-12 d-flex ">
<p class="fst-italic me-2">Vous êtes ici</p>
@include('modules/breadcrumb/breadcrumb-body')
<div class="pt-lg-5 container">
<div class="row justify-content-center">
<section class="col-md-10 stripes px-5">
<h1 class="text-uppercase font-amatic font-amatic-48 text-center">{{ __($staticPageModel->title) }}</h1>
</section>
</div>
<div class="contact-wrapper position-relative bg-ardoise-radial pt-sm-6 pt-5">
<div class="row wrapper-xl mb-3">
{!! clean($staticPageModel->content) !!}
</div>
</div>
</div>
<!-- END breadcrumb -->
</div>
<div class="container pt-lg-5">
<div class="row justify-content-center">
<section class="col-md-10 stripes px-5">
<h1 class="text-uppercase text-center font-amatic font-amatic-48">{{ __($staticPageModel->title) }}</h1>
</section>
</div>
<div class="contact-wrapper position-relative bg-ardoise-radial pt-5 pt-sm-6">
<div class="row wrapper-xl mb-3">
{!! clean($staticPageModel->content) !!}
</div>
</div>
</div>
@endsection

View file

@ -6,87 +6,109 @@
@section('content')
<div class="g-0 pb-md-5 position-relative hero">
<img src="{{ Vite::asset('resources/assets/images/100LOCAL-V1.png') }}" alt="Logo 100% local" class="img-fluid">
<img class="img-fluid" src="{{ Vite::asset('resources/assets/images/100LOCAL-V1.png') }}" alt="Logo 100% local">
<div class="position-relative hero-fix-height">
<img src="{{ Vite::asset('resources/assets/images/Panoramique6.jpg') }}" alt="Un paysage de campagne" class="img-fluid">
<img class="img-fluid" src="{{ Vite::asset('resources/assets/images/Panoramique6.jpg') }}" alt="Un paysage de campagne">
</div>
<!-- breadcrumb -->
<div class="container breadcrumb-obj small mt-3">
<div class="breadcrumb-obj small container mt-3">
<div class="row">
<div class="col-12 d-flex ">
<div class="col-12 d-flex">
<p class="fst-italic me-2">Vous êtes ici</p>
@include('modules/breadcrumb/breadcrumb-body')
@include('modules.breadcrumbs.breadcrumb-body')
</div>
</div>
</div>
<!-- END breadcrumb -->
</div>
<div class="container pt-5 position-relative">
<div class="position-relative container pt-5">
<div class="row">
<section class="col-md-8 offset-md-2 stripes">
<h1 class="text-uppercase text-center font-amatic font-amatic-48">{{ __('Pour nous, produire bien') }}<span class="font-amatic-red">{{ __('c\'est produire 100% local') }}</span></h1>
<h1 class="text-uppercase font-amatic font-amatic-48 text-center">{{ __('Pour nous, produire bien') }}<span
class="font-amatic-red">{{ __('c\'est produire 100% local') }}</span></h1>
</section>
</div>
<!-- GRID -->
<div class="row mt-4 g-3">
<div class="row g-3 mt-4">
<div class="col-md-6">
<img src="{{ Vite::asset('resources/assets/images/Frise-images-issue-22.jpg') }}" alt="Une frise d'images avec des poussins, un éleveur, un camion de transport, des cuisses de poulets cuites" class="img-fluid">
<img class="img-fluid" src="{{ Vite::asset('resources/assets/images/Frise-images-issue-22.jpg') }}"
alt="Une frise d'images avec des poussins, un éleveur, un camion de transport, des cuisses de poulets cuites">
</div>
<div class="col-md-6">
<ul class="list-group commitments">
<li class="list-group-item d-flex align-items-center">
<img src="{{ Vite::asset('resources/assets/images/Couvoir.svg') }}" alt="Une illustration d'un poussin sortant de sa coquille">
<img src="{{ Vite::asset('resources/assets/images/Couvoir.svg') }}"
alt="Une illustration d'un poussin sortant de sa coquille">
<div class="ms-4">
<h2 class="font-amatic font-amatic-48 font-amatic-red">{{ __('Couvoirs') }}</h2>
<p>{{ __('Nos poussins naissent dans des couvoirs situés en région ') }}<strong>{{ __('Hauts-de-France où nous sélectionnons des races reconnues pour la qualité de leur chair') }}</strong>.</p>
<p>{{ __('Nos poussins naissent dans des couvoirs situés en région ') }}<strong>{{ __('Hauts-de-France où nous sélectionnons des races reconnues pour la qualité de leur chair') }}</strong>.
</p>
</div>
</li>
<li class="list-group-item d-flex align-items-center">
<img src="{{ Vite::asset('resources/assets/images/Aliments.svg') }}" alt="Une illustration d'un sac de blé">
<div class="ms-4">
<h2 class="font-amatic font-amatic-48 font-amatic-red">{{ __('Aliments') }}</h2>
<p>{{ __('Nos volailles sont nourries avec des matières premières végétales minérales et vitaminiques : avec un minimum de') }} <strong>{{ __('70% de céréales françaises provenant en majorité des Hauts-de-France') }}</strong>. {{ __('Les aliments sont fabriqués par notre ') }}<strong>{{ __('partenaire local situé à Arques') }}</strong>.</p>
<p>{{ __('Nos volailles sont nourries avec des matières premières végétales minérales et vitaminiques : avec un minimum de') }}
<strong>{{ __('70% de céréales françaises provenant en majorité des Hauts-de-France') }}</strong>.
{{ __('Les aliments sont fabriqués par notre ') }}<strong>{{ __('partenaire local situé à Arques') }}</strong>.
</p>
</div>
</li>
<li class="list-group-item d-flex align-items-center">
<img src="{{ Vite::asset('resources/assets/images/Region.svg') }}" alt="Une illustration d'une tour dans un soleil">
<div class="ms-4">
<h2 class="font-amatic font-amatic-48 font-amatic-red">{{ __('Poulaillers') }}</h2>
<p><strong>{{ __('Nos poulaillers sont situés dans un rayon de 50 km') }}</strong> {{ __('autour de notre atelier de préparation, pour le respect des animaux lors du transport. ') }}<strong>{{ __('Nos éleveurs élèvent les volailles dans des conditions optimales') }}</strong> {{ __('(maîtrise de l\'eau, de l\'air, de la température et de la lumière). Ils bénificient d\'un accompagnement individuel par nos techniciens qui garantissent des conditions d\'élevage favorables. Nos vétérinaires partenaires encadrent la santé des volailles.') }}
{{ __('Nos éleveurs sont engagés dans ') }}<strong>{{ __('des actions en faveur de l\'environnement') }}</strong> {{ __('(implantation paysagère et biométhanisation)') }}.
<strong>{{ __('Notre filière permet d\'assurer le revenu des éleveurs en garantissant leur rémunération') }}</strong>.</p>
<p><strong>{{ __('Nos poulaillers sont situés dans un rayon de 50 km') }}</strong>
{{ __('autour de notre atelier de préparation, pour le respect des animaux lors du transport. ') }}<strong>{{ __('Nos éleveurs élèvent les volailles dans des conditions optimales') }}</strong>
{{ __('(maîtrise de l\'eau, de l\'air, de la température et de la lumière). Ils bénificient d\'un accompagnement individuel par nos techniciens qui garantissent des conditions d\'élevage favorables. Nos vétérinaires partenaires encadrent la santé des volailles.') }}
{{ __('Nos éleveurs sont engagés dans ') }}<strong>{{ __('des actions en faveur de l\'environnement') }}</strong>
{{ __('(implantation paysagère et biométhanisation)') }}.
<strong>{{ __('Notre filière permet d\'assurer le revenu des éleveurs en garantissant leur rémunération') }}</strong>.
</p>
</div>
</li>
<li class="list-group-item d-flex align-items-center">
<img src="{{ Vite::asset('resources/assets/images/Poule-main.svg') }}" alt="Une illustration d'une main portant un poulet">
<img src="{{ Vite::asset('resources/assets/images/Poule-main.svg') }}"
alt="Une illustration d'une main portant un poulet">
<div class="ms-4">
<h2 class="font-amatic font-amatic-48 font-amatic-red">{{ __('Transport de volailles') }}</h2>
<p>{{ __('Le transport des volailles est réalisé par ') }}<strong>{{ __('nos équipes') }}</strong>, {{ __('formées au bien-être animal. ') }}<strong>{{ __('Des contrôles sont réalisés à la réception des volailles') }}</strong>.</p>
<p>{{ __('Le transport des volailles est réalisé par ') }}<strong>{{ __('nos équipes') }}</strong>,
{{ __('formées au bien-être animal. ') }}<strong>{{ __('Des contrôles sont réalisés à la réception des volailles') }}</strong>.
</p>
</div>
</li>
<li class="list-group-item d-flex align-items-center">
<img src="{{ Vite::asset('resources/assets/images/Atelier.svg') }}" alt="Une illustration d'un poulet nu">
<div class="ms-4">
<h2 class="font-amatic font-amatic-48 font-amatic-red">{{ __('Atelier de préparation') }}</h2>
<p>{{ __('Les procédures misent en place nous permettent ') }}<strong>{{ __('d\'assurer la qualité et la traçabilité de nos produits') }}</strong>. {{ __('Notre savoir-faire est appuyé par du matériel innovant.') }}</p>
<p>{{ __('Les procédures misent en place nous permettent ') }}<strong>{{ __('d\'assurer la qualité et la traçabilité de nos produits') }}</strong>.
{{ __('Notre savoir-faire est appuyé par du matériel innovant.') }}</p>
</div>
</li>
<li class="list-group-item d-flex align-items-center">
<img src="{{ Vite::asset('resources/assets/images/Ecoute.svg') }}" alt="Une illustration de deux info-bulles avec un smiley">
<img src="{{ Vite::asset('resources/assets/images/Ecoute.svg') }}"
alt="Une illustration de deux info-bulles avec un smiley">
<div class="ms-4">
<h2 class="font-amatic font-amatic-48 font-amatic-red">{{ __('Écoute client') }}</h2>
<p>{{ __('Notre équipe de télévente est à l\'écoute de nos clients du lundi au samedi pour la prise de commandes. ') }}<strong>{{ __('Des visites sur les points de ventes sont réalisées régulièrement par notre équipe commerciale') }}</strong> {{ __('pour assurer un lien direct avec nos clients, et réaliser si besoin des animations avec nos éleveurs.') }}</p>
<p>{{ __('Notre équipe de télévente est à l\'écoute de nos clients du lundi au samedi pour la prise de commandes. ') }}<strong>{{ __('Des visites sur les points de ventes sont réalisées régulièrement par notre équipe commerciale') }}</strong>
{{ __('pour assurer un lien direct avec nos clients, et réaliser si besoin des animations avec nos éleveurs.') }}
</p>
</div>
</li>
<li class="list-group-item d-flex align-items-center">
<img src="{{ Vite::asset('resources/assets/images/Livraison.svg') }}" alt="Une illustration d'une camionette à l'éffigie de Lionor">
<img src="{{ Vite::asset('resources/assets/images/Livraison.svg') }}"
alt="Une illustration d'une camionette à l'éffigie de Lionor">
<div class="ms-4">
<h2 class="font-amatic font-amatic-48 font-amatic-red">{{ __('Transport et livraison') }}</h2>
<p><strong>{{ __('Pour garantir la satisfaction de nos clients') }}</strong>, {{ __('nous livrons un maximum de commandes avec') }} <strong>{{ __('nos propres véhicules') }}</strong>.</p>
<p><u><a href="{{ route('recipes.index') }}" title="{{ __('Nos recettes') }}" class="fw-bold">> {{ __('Voir toutes nos recettes') }}</a></u></p>
<p><u><a href="{{ route('map') }}" title="Où nous trouver" class="fw-bold">> {{ __('Où acheter les produits Lionor ?') }}</a></u></p>
<p><strong>{{ __('Pour garantir la satisfaction de nos clients') }}</strong>,
{{ __('nous livrons un maximum de commandes avec') }} <strong>{{ __('nos propres véhicules') }}</strong>.</p>
<p><u><a class="fw-bold" href="{{ route('recipes.index') }}" title="{{ __('Nos recettes') }}">>
{{ __('Voir toutes nos recettes') }}</a></u></p>
<p><u><a class="fw-bold" href="{{ route('map') }}" title="Où nous trouver">>
{{ __('Où acheter les produits Lionor ?') }}</a></u></p>
</div>
</li>
</ul>

View file

@ -21,7 +21,7 @@
<div class="row">
<div class="col-12 d-flex">
<p class="fst-italic me-2">Vous êtes ici</p>
@include('modules/breadcrumb/breadcrumb-body')
@include('modules.breadcrumbs.breadcrumb-body')
</div>
</div>
</div>
@ -109,6 +109,6 @@
@endsection
@push('scripts')
<script nonce="{{ $nonce }}" src="https://unpkg.com/leaflet@1.9.3/dist/leaflet.js"
<script nonce="{{ $nonce ?? '' }}" src="https://unpkg.com/leaflet@1.9.3/dist/leaflet.js"
integrity="sha256-WBkoXOwTeyKclOHuWtc+i2uENFpDZ9YPdf5Hf+D7ewM=" crossorigin=""></script>
@endpush

View file

@ -1,32 +1,32 @@
@extends('fo.layout')
@section('title', "Politique des données personnelles")
@section('description', "Lionor vous informe sur les données que nous collectons, comment nous les traitons, dans quelles finalités.")
@section('title', 'Politique des données personnelles')
@section('description', 'Lionor vous informe sur les données que nous collectons, comment nous les traitons, dans quelles finalités.')
@section('breadcrumb', request()->route()->getName())
@section('content')
<div class="g-0 pb-md-5 position-relative hero">
<div class="position-relative hero-fix-height">
<img src="{{ Vite::asset('resources/assets/images/Panoramique6.jpg') }}" alt="Paysage de campagne" class="img-fluid">
<img class="img-fluid" src="{{ Vite::asset('resources/assets/images/Panoramique6.jpg') }}" alt="Paysage de campagne">
</div>
<!-- breadcrumb -->
<div class="container breadcrumb-obj small mt-3">
<div class="breadcrumb-obj small container mt-3">
<div class="row">
<div class="col-12 d-flex ">
<div class="col-12 d-flex">
<p class="fst-italic me-2">Vous êtes ici</p>
@include('modules/breadcrumb/breadcrumb-body')
@include('modules.breadcrumbs.breadcrumb-body')
</div>
</div>
</div>
<!-- END breadcrumb -->
</div>
<div class="container pt-lg-5">
<div class="pt-lg-5 container">
<div class="row justify-content-center">
<section class="col-md-8 stripes px-5">
<h1 class="text-uppercase text-center font-amatic font-amatic-48">{{ __($staticPageModel->title) }}</h1>
<h1 class="text-uppercase font-amatic font-amatic-48 text-center">{{ __($staticPageModel->title) }}</h1>
</section>
</div>
<div class="contact-wrapper position-relative bg-ardoise-radial pt-5 pt-sm-6">
<div class="contact-wrapper position-relative bg-ardoise-radial pt-sm-6 pt-5">
<div class="row wrapper-xl mb-3">
{!! clean($staticPageModel->content) !!}
</div>

View file

@ -11,56 +11,64 @@
@section('content')
<div class="g-0 pb-md-5 position-relative hero">
<img src="{{ Vite::asset('resources/assets/images/100LOCAL-V1.png') }}" alt="Logo 100% local" class="img-fluid">
<img class="img-fluid" src="{{ Vite::asset('resources/assets/images/100LOCAL-V1.png') }}" alt="Logo 100% local">
<div class="position-relative hero-fix-height">
<img src="{{ Vite::asset('resources/assets/images/Panoramique1.jpg') }}" alt="Des poulets dans un élevage" class="img-fluid">
<img class="img-fluid" src="{{ Vite::asset('resources/assets/images/Panoramique1.jpg') }}" alt="Des poulets dans un élevage">
</div>
<!-- breadcrumb -->
<div class="container breadcrumb-obj small mt-3">
<div class="breadcrumb-obj small container mt-3">
<div class="row">
<div class="col-12 d-flex ">
<div class="col-12 d-flex">
<p class="fst-italic me-2">Vous êtes ici</p>
@include('modules/breadcrumb/breadcrumb-body')
@include('modules.breadcrumbs.breadcrumb-body')
</div>
</div>
</div>
<!-- END breadcrumb -->
</div>
<div class="container pt-5 position-relative">
<div class="position-relative container pt-5">
<div class="row">
<section class="col-md-8 offset-md-2 stripes">
<h1 class="text-uppercase font-amatic font-amatic-48 ">{{ __('Nos volailles bien élevées') }}<span class="font-amatic-red">{{ __('qui ont le goût de la qualité') }}</span></h1>
<h1 class="text-uppercase font-amatic font-amatic-48">{{ __('Nos volailles bien élevées') }}<span
class="font-amatic-red">{{ __('qui ont le goût de la qualité') }}</span></h1>
</section>
</div>
<!-- LES GAMMES -->
<div class="row mt-4 g-3">
@foreach($ranges as $range)
<div class="col-md-6 mb-5">
<img src="{{ asset($range->picture) }}" alt="{{ $range->picture_alt }}" title="{{ $range->picture_title }}" class="img-fluid">
<div class="d-flex flex-wrap align-items-center justify-content-between">
<div class="w-75">
<h2 class="text-uppercase font-amatic font-amatic-46 mt-4">{{ $range->name }}</h2>
<p>{{ $range->subtitle }}</p>
<div class="row g-3 mt-4">
@foreach ($ranges as $range)
<div class="col-md-6 mb-5">
<img class="img-fluid" src="{{ asset($range->picture) }}" title="{{ $range->picture_title }}"
alt="{{ $range->picture_alt }}">
<div class="d-flex align-items-center justify-content-between flex-wrap">
<div class="w-75">
<h2 class="text-uppercase font-amatic font-amatic-46 mt-4">{{ $range->name }}</h2>
<p>{{ $range->subtitle }}</p>
</div>
<a class="btn btn-primary text-uppercase" href="{{ route('ranges.products', ['publishedRange' => $range]) }}"
title="{{ __('Découvrez la gamme') }} {{ $range->name }}">{{ __('Découvrir >') }}</a>
</div>
<a href="{{ route('ranges.products', ['publishedRange' => $range]) }}" title="{{ __('Découvrez la gamme') }} {{ $range->name }}" class="btn btn-primary text-uppercase">{{ __('Découvrir >') }}</a>
</div>
</div>
@endforeach
</div>
<div class="row text-center img-110">
<div class="row img-110 text-center">
<section class="col-md-5">
<img src="{{ Vite::asset('resources/assets/images/Toque.svg') }}" alt="Une illustration d'une toque de cuisinier">
<h2 class="text-uppercase font-amatic font-amatic-60">{{ __('Trouvez l\'inspiration') }}<br>{{ __('avec nos recettes') }}<span class="font-amatic-red">{{ __('de volailles bonnes') }}<br>{{ __('et accessibles') }}</span></h2>
<a href="{{ route('recipes.index') }}" title="{{ __('Voir toutes nos recettes') }}" class="btn btn-primary text-uppercase mt-3">{{ __('Voir les recettes >') }}</a>
<h2 class="text-uppercase font-amatic font-amatic-60">{{ __('Trouvez l\'inspiration') }}<br>{{ __('avec nos recettes') }}<span
class="font-amatic-red">{{ __('de volailles bonnes') }}<br>{{ __('et accessibles') }}</span></h2>
<a class="btn btn-primary text-uppercase mt-3" href="{{ route('recipes.index') }}"
title="{{ __('Voir toutes nos recettes') }}">{{ __('Voir les recettes >') }}</a>
</section>
<span class="col-md-2 vertical"></span>
<section class="col-md-5">
<img src="{{ Vite::asset('resources/assets/images/Region.svg') }}" alt="Une illustration d'une tour dans un soleil">
<h2 class="text-uppercase font-amatic font-amatic-60">{{ __('Découvrez les engagements') }}<br>{{ __('d\'une filière engagée') }}<span class="font-amatic-red">{{ __('pour manger bon, sain') }}<br>{{ __('et 100% local') }}</span></h2>
<a href="{{ route('local') }}" title="{{ __('En savoir plus sur le 100% local') }}" class="btn btn-primary text-uppercase mt-3">{{ __('En savoir plus >') }}</a>
<h2 class="text-uppercase font-amatic font-amatic-60">
{{ __('Découvrez les engagements') }}<br>{{ __('d\'une filière engagée') }}<span
class="font-amatic-red">{{ __('pour manger bon, sain') }}<br>{{ __('et 100% local') }}</span></h2>
<a class="btn btn-primary text-uppercase mt-3" href="{{ route('local') }}"
title="{{ __('En savoir plus sur le 100% local') }}">{{ __('En savoir plus >') }}</a>
</section>
</div>
</div>

View file

@ -6,8 +6,7 @@
@section('canonical', route('products.show', ['publishedProduct' => $productModel]))
@section('ogPicture', asset($productModel->picture))
@section('breadcrumb',
request()->route()->getName())
@section('breadcrumb', request()->route()->getName())
@section('content')
@ -21,7 +20,7 @@
<div class="row">
<div class="col-12 d-flex">
<p class="fst-italic me-2">Vous êtes ici</p>
@include('modules/breadcrumb/breadcrumb-body', ['brParam' => $productModel])
@include('modules.breadcrumbs.breadcrumb-body', ['brParam' => $productModel])
</div>
</div>
</div>

View file

@ -6,8 +6,7 @@
@section('canonical', route('ranges.products', ['publishedRange' => $publishedRange]))
@section('breadcrumb',
request()->route()->getName())
@section('breadcrumb', request()->route()->getName())
@section('content')
@ -22,7 +21,7 @@
<div class="row">
<div class="col-12 d-flex">
<p class="fst-italic me-2">Vous êtes ici</p>
@include('modules/breadcrumb/breadcrumb-body', ['brParam' => $publishedRange])
@include('modules.breadcrumbs.breadcrumb-body', ['brParam' => $publishedRange])
</div>
</div>
</div>

View file

@ -11,14 +11,15 @@
<div class="g-0 pb-md-5 position-relative hero">
<div class="position-relative hero-fix-height">
<img src="{{ Vite::asset('resources/assets/images/Panoramique4.jpg') }}" alt="Plat de cuisses de poulet cuites avec des dés de carottes et un bouquet garni" class="img-fluid">
<img class="img-fluid" src="{{ Vite::asset('resources/assets/images/Panoramique4.jpg') }}"
alt="Plat de cuisses de poulet cuites avec des dés de carottes et un bouquet garni">
</div>
<!-- breadcrumb -->
<div class="container breadcrumb-obj small mt-3">
<div class="breadcrumb-obj small container mt-3">
<div class="row">
<div class="col-12 d-flex ">
<div class="col-12 d-flex">
<p class="fst-italic me-2">Vous êtes ici</p>
@include('modules/breadcrumb/breadcrumb-body')
@include('modules.breadcrumbs.breadcrumb-body')
</div>
</div>
</div>
@ -29,43 +30,45 @@
<div class="row">
<div class="col-lg-8 offset-lg-2">
<section class="stripes">
<h1 class="text-center text-uppercase font-amatic font-amatic-48">{{ __('Trouver l\'inspiration avec nos recettes') }}<span class="font-amatic-red">{{ __('bonnes et accessibles') }}</span></h1>
<h1 class="text-uppercase font-amatic font-amatic-48 text-center">{{ __('Trouver l\'inspiration avec nos recettes') }}<span
class="font-amatic-red">{{ __('bonnes et accessibles') }}</span></h1>
</section>
<!-- SEARCH BLOCK -->
<form id="recipes-filters" class="row position-relative d-md-flex justify-content-between recipes-search" action="{{ route('recipes.index', request()->query()) }}" name="recipes-search" enctype="multipart/form-data" method="get">
<form class="row position-relative d-md-flex justify-content-between recipes-search" id="recipes-filters" name="recipes-search"
action="{{ route('recipes.index', request()->query()) }}" enctype="multipart/form-data" method="get">
<div class="d-md-flex align-items-center col-md-4">
<label for="inputPassword6" class="col-form-label me-3">{{ __('Difficulté') }}</label>
<select id="difficulty" class="form-select" name="difficulty_enum" aria-label="Default select example">
<label class="col-form-label me-3" for="inputPassword6">{{ __('Difficulté') }}</label>
<select class="form-select" id="difficulty" name="difficulty_enum" aria-label="Default select example">
<option value="-1">{{ __('Tous') }}</option>
@foreach(App\Enums\DifficultyEnum::all() as $difficultyEnum)
<option value="{{ $difficultyEnum->value() }}" @selected(request()->get('difficulty_enum') == $difficultyEnum->value())>
{{ $difficultyEnum->label() }}
</option>
@foreach (App\Enums\DifficultyEnum::all() as $difficultyEnum)
<option value="{{ $difficultyEnum->value() }}" @selected(request()->get('difficulty_enum') == $difficultyEnum->value())>
{{ $difficultyEnum->label() }}
</option>
@endforeach
</select>
</div>
<div class="d-md-flex align-items-center col-md-4">
<label for="inputPassword6" class="col-form-label me-3">{{ __('Rapidité') }}</label>
<select id="speed" class="form-select" name="preparation_time_enum" aria-label="Default select example">
<label class="col-form-label me-3" for="inputPassword6">{{ __('Rapidité') }}</label>
<select class="form-select" id="speed" name="preparation_time_enum" aria-label="Default select example">
<option value="-1">{{ __('Tous') }}</option>
@foreach(App\Enums\PreparationTimeEnum::all() as $preparationEnum)
<option value="{{ $preparationEnum->value() }}" @selected(request()->get('preparation_time_enum') == $preparationEnum->value())>
{{ $preparationEnum->label() }}
</option>
@foreach (App\Enums\PreparationTimeEnum::all() as $preparationEnum)
<option value="{{ $preparationEnum->value() }}" @selected(request()->get('preparation_time_enum') == $preparationEnum->value())>
{{ $preparationEnum->label() }}
</option>
@endforeach
</select>
</div>
<div class="d-md-flex align-items-center col-md-4">
<label for="inputPassword6" class="col-form-label me-3">{{ __('Mode de cuisson') }}</label>
<select id="cooking" class="form-select" name="cooking_mode_enum" aria-label="Default select example">
<label class="col-form-label me-3" for="inputPassword6">{{ __('Mode de cuisson') }}</label>
<select class="form-select" id="cooking" name="cooking_mode_enum" aria-label="Default select example">
<option value="-1">{{ __('Tous') }}</option>
@foreach(App\Enums\CookingModeEnum::all() as $cookingModeEnum)
<option value="{{ $cookingModeEnum->value() }}" @selected(request()->get('cooking_mode_enum') == $cookingModeEnum->value())>
{{ $cookingModeEnum->label() }}
</option>
@foreach (App\Enums\CookingModeEnum::all() as $cookingModeEnum)
<option value="{{ $cookingModeEnum->value() }}" @selected(request()->get('cooking_mode_enum') == $cookingModeEnum->value())>
{{ $cookingModeEnum->label() }}
</option>
@endforeach
</select>
</div>
@ -73,12 +76,14 @@
@switch($publishedRecipes->total())
@case(0)
<p class="text-uppercase fw-bold text-center">{{ __('Aucune recette n\'a été trouvée') }}</p>
@break
@break
@case(1)
<p class="text-uppercase fw-bold text-center">{{ $publishedRecipes->total() }} {{ __('recette a été trouvée') }}</p>
@break
@break
@default
<p class="text-uppercase fw-bold text-center">{{ $publishedRecipes->total() }} {{ __('recettes ont été trouvées') }}</p>
<p class="text-uppercase fw-bold text-center">{{ $publishedRecipes->total() }} {{ __('recettes ont été trouvées') }}</p>
@endswitch
</form>
<!-- END SEARCH BLOCK -->
@ -98,25 +103,23 @@
@endsection
@push('scripts')
<script nonce="{{ $nonce }}">
document.addEventListener("DOMContentLoaded", () => {
// Submit button clone.
const form = document.getElementById('recipes-filters');
const selectDifficulty = document.getElementById('difficulty');
const selectSpeed = document.getElementById('speed');
const selectCooking = document.getElementById('cooking');
selectDifficulty.addEventListener('change', (event) => {
form.submit();
<script nonce="{{ $nonce ?? '' }}">
document.addEventListener("DOMContentLoaded", () => {
// Submit button clone.
const form = document.getElementById('recipes-filters');
const selectDifficulty = document.getElementById('difficulty');
const selectSpeed = document.getElementById('speed');
const selectCooking = document.getElementById('cooking');
selectDifficulty.addEventListener('change', (event) => {
form.submit();
});
selectSpeed.addEventListener('change', (event) => {
form.submit();
});
selectCooking.addEventListener('change', (event) => {
form.submit();
})
});
selectSpeed.addEventListener('change', (event) => {
form.submit();
});
selectCooking.addEventListener('change', (event) => {
form.submit();
})
});
</script>
</script>
@endpush

View file

@ -20,7 +20,7 @@
<div class="row">
<div class="col-12 d-flex">
<p class="fst-italic me-2">Vous êtes ici</p>
@include('modules/breadcrumb/breadcrumb-body', ['brParam' => $recipeModel])
@include('modules.breadcrumbs.breadcrumb-body', ['brParam' => $recipeModel])
</div>
</div>
</div>

View file

@ -1,6 +1,6 @@
{{-- MINI CRUD EDIT WHEN JSON IS TRANSLATABLE --}}
@php
$data = (isset($model->data)) ? $model->getEmptyData() : $conceptData;
$data = isset($model->data) ? $model->getEmptyData() : $conceptData;
@endphp
@foreach ($data as $k => $input)
@php
@ -9,9 +9,10 @@ $data = (isset($model->data)) ? $model->getEmptyData() : $conceptData;
@switch(intval($input['inputType']))
@case(intval(App\Enums\InputType::video->value))
@case(intval(App\Enums\InputType::videoVimeo->value))
@case(intval(App\Enums\InputType::videoYoutube->value))
<div class="col-12 col-md-6 mb-3">
<fieldset class="border h-100 bg-white p-3">
<fieldset class="h-100 border bg-white p-3">
<legend>{{ $input['label'] }} *</legend>
<div class="row">
<div class="col-12 mb-3">
@ -19,29 +20,29 @@ $data = (isset($model->data)) ? $model->getEmptyData() : $conceptData;
'id' => Str::slug($input['label']),
'label' => $input['label'],
'hasWordCounter' => true,
'name' => 'data['.$k.'][value]',
'name' => 'data[' . $k . '][value]',
'value' => old("data.{$k}.value", $model->data[$k]['value'] ?? null),
'required' => isset($input['required']) ? !empty($input['required']) : true,
'model' => $model ?? null,
'helper' => match(intval($input['inputType'])) {
'helper' => match (intval($input['inputType'])) {
intval(App\Enums\InputType::videoYoutube->value) => __('video id eg: keZURbOo4-M'),
intval(App\Enums\InputType::videoVimeo->value) => __('video id eg: 74918975'),
intval(App\Enums\InputType::video->value) => __('video id eg: keZURbOo4-M or 74918975'),
}
},
])
@include('modules.input-error', [
'inputName' => "data.{$k}.value"
'inputName' => "data.{$k}.value",
])
</div>
</div>
</fieldset>
</div>
@break
@break
@case(intval(App\Enums\InputType::url->value))
@case(intval(App\Enums\InputType::string->value))
<div class="col-12 col-md-6 mb-3">
<fieldset class="border h-100 bg-white p-3">
<fieldset class="h-100 border bg-white p-3">
<legend>{{ $input['label'] }} *</legend>
<div class="row">
<div class="col-12 mb-3">
@ -49,57 +50,56 @@ $data = (isset($model->data)) ? $model->getEmptyData() : $conceptData;
'id' => Str::slug($input['label']),
'label' => $input['label'],
'hasWordCounter' => true,
'name' => 'data['.$k.'][value]',
'name' => 'data[' . $k . '][value]',
'value' => old("data.{$k}.value", $model->data[$k]['value'] ?? null),
'required' => isset($input['required']) ? !empty($input['required']) : true,
'model' => $model ?? null,
'helper' => __('validation.between.string', [
'attribute' => $input['label'],
'min' => 3,
'max' => 255
])
'max' => 255,
]),
])
@include('modules.input-error', [
'inputName' => "data.{$k}.value"
'inputName' => "data.{$k}.value",
])
</div>
</div>
</fieldset>
</div>
@break
@break
@case(intval(App\Enums\InputType::number->value))
<div class="row">
<div class="col">
<fieldset class="border p-3 mb-3">
<div class="d-flex flex-row justify-content-start align-items-center mb-2">
<legend class="w-auto m-0">
<label for="number{{$k}}" class="col-form-label">
<b>{{ $input['label'] }}</b>
</label>
</legend>
@if(!empty($input['tooltip']))
<span class="ms-1" data-bs-toggle="tooltip" data-bs-html="true" title="{{ $input['tooltip'] }}">
<i class="fa fa-question-circle"></i>
</span>
@endif
</div>
<div class="row mb-3">
<div class="form-group">
<input id="number{{$k}}" type="number" step="0.01" class="form-control"
aria-describedby="NumberHelp{{$k}}"
name="data[{{ $k }}][value]" placeholder="{{ $input['label'] }}"
value="{{ old("data.{$k}.value") ?? ($input['value'] ?? '') }}" @if(!isset($input['required']) or $input['required']) required @endif>
@include('back::modules.input-error', [
'inputName' => "data.{$k}.value",
'helperId' => "NumberHelp{$k}",
'helper' => $input['helper'] ?? '',
])
<div class="row">
<div class="col">
<fieldset class="mb-3 border p-3">
<div class="d-flex justify-content-start align-items-center mb-2 flex-row">
<legend class="m-0 w-auto">
<label class="col-form-label" for="number{{ $k }}">
<b>{{ $input['label'] }}</b>
</label>
</legend>
@if (!empty($input['tooltip']))
<span class="ms-1" data-bs-toggle="tooltip" data-bs-html="true" title="{{ $input['tooltip'] }}">
<i class="fa fa-question-circle"></i>
</span>
@endif
</div>
</div>
</fieldset>
<div class="row mb-3">
<div class="form-group">
<input class="form-control" id="number{{ $k }}" name="data[{{ $k }}][value]" type="number"
value="{{ old("data.{$k}.value") ?? ($input['value'] ?? '') }}" aria-describedby="NumberHelp{{ $k }}"
step="0.01" placeholder="{{ $input['label'] }}" @if (!isset($input['required']) or $input['required']) required @endif>
@include('back::modules.input-error', [
'inputName' => "data.{$k}.value",
'helperId' => "NumberHelp{$k}",
'helper' => $input['helper'] ?? '',
])
</div>
</div>
</fieldset>
</div>
</div>
</div>
@break
@case(intval(App\Enums\InputType::text->value))
@ -111,15 +111,18 @@ $data = (isset($model->data)) ? $model->getEmptyData() : $conceptData;
@include('bo.partials.inputs.text-html', [
'id' => Str::slug($input['label']),
'name' => "data[{$k}][value]",
'value' =>old("data.{$k}.value", $model->data[$k]['value'] ?? null),
'value' => old("data.{$k}.value", $model->data[$k]['value'] ?? null),
'model' => $model ?? null,
'helper' => __('crud.minicrud.text_warning') . '. ' . __('validation.min.string', [
'attribute' => $input['label'],
'min' => 3
]),
'helper' =>
__('crud.minicrud.text_warning') .
'. ' .
__('validation.min.string', [
'attribute' => $input['label'],
'min' => 3,
]),
])
@include('modules.input-error', [
'inputName' => "data.{$k}.value"
'inputName' => "data.{$k}.value",
])
</div>
</div>
@ -128,33 +131,32 @@ $data = (isset($model->data)) ? $model->getEmptyData() : $conceptData;
@break
@case(intval(App\Enums\InputType::boolean->value))
<div class="col-12 col-md-6 mb-3">
<fieldset class="border bg-white p-3">
<legend>{{ $input['label'] }} *</legend>
<div class="row mb-3">
<div class="col-12">
<label class="col-form-label" for="{{ Str::slug($input['label']) }}"><b>{{ __('crud.minicrud.visibility_label') }}</b></label>
<div class="form-check form-switch ms-2">
<input
id="{{ Str::slug($input['label']) }}"
aria-describedby="{{ Str::slug($input['label']) }}Help"
name="data[{{ $k }}][value]"
type="checkbox"
class="ml-2 form-check-input"
value="1"
@if(old("data.{$k}.value", (old('_token') ? false : ($model->data[$k]['value'] ?? null ?? false)))) checked @endif>
<div class="col-12 col-md-6 mb-3">
<fieldset class="border bg-white p-3">
<legend>{{ $input['label'] }} *</legend>
<div class="row mb-3">
<div class="col-12">
<label class="col-form-label"
for="{{ Str::slug($input['label']) }}"><b>{{ __('crud.minicrud.visibility_label') }}</b></label>
<div class="form-check form-switch ms-2">
<input class="form-check-input ml-2" id="{{ Str::slug($input['label']) }}" name="data[{{ $k }}][value]"
type="checkbox" value="1" aria-describedby="{{ Str::slug($input['label']) }}Help"
@if (old("data.{$k}.value", old('_token') ? false : $model->data[$k]['value'] ?? (null ?? false))) checked @endif>
</div>
@include('modules.input-error', [
'inputName' => "data.{$k}.value",
'helperId' => Str::slug($input['label']) . 'Help',
'helper' => __('crud.minicrud.visibility_helper'),
])
</div>
@include('modules.input-error', [
'inputName' => "data.{$k}.value", 'helperId' => Str::slug($input['label']). 'Help' , 'helper' => __('crud.minicrud.visibility_helper')
])
</div>
</div>
</fieldset>
</div>
</fieldset>
</div>
@break
@case(intval(App\Enums\InputType::image->value))
<div class="col-12 mb-3">
<fieldset class="border h-100 bg-white p-3">
<fieldset class="h-100 border bg-white p-3">
<legend>{{ $input['label'] }} *</legend>
<div class="row">
{{-- PICTURE_ALT --}}
@ -163,17 +165,20 @@ $data = (isset($model->data)) ? $model->getEmptyData() : $conceptData;
'id' => Str::slug($input['label']) . '-alt',
'label' => __('crud.minicrud.picture_alt_label'),
'hasWordCounter' => true,
'name' => 'data['.$k.'][alt]',
'name' => 'data[' . $k . '][alt]',
'value' => old("data.{$k}.alt", $model->data[$k]['alt'] ?? null),
'required' => true,
'model' => $model ?? null,
'helper' => __('crud.minicrud.picture_alt_helper') . '. ' . __('validation.min.string', [
'attribute' => __('crud.minicrud.picture_alt_label'),
'min' => 3
]),
'helper' =>
__('crud.minicrud.picture_alt_helper') .
'. ' .
__('validation.min.string', [
'attribute' => __('crud.minicrud.picture_alt_label'),
'min' => 3,
]),
])
@include('modules.input-error', [
'inputName' => "data.{$k}.alt"
'inputName' => "data.{$k}.alt",
])
</div>
{{-- PICTURE_TITLE --}}
@ -182,17 +187,20 @@ $data = (isset($model->data)) ? $model->getEmptyData() : $conceptData;
'id' => Str::slug($input['label']) . '-title',
'label' => __('crud.minicrud.picture_title_label'),
'hasWordCounter' => true,
'name' => 'data['.$k.'][title]',
'name' => 'data[' . $k . '][title]',
'value' => old("data.{$k}.title", $model->data[$k]['title'] ?? null),
'required' => true,
'model' => $model ?? null,
'helper' => __('crud.minicrud.picture_title_helper') . '. ' . __('validation.min.string', [
'attribute' => __('crud.minicrud.picture_title_label'),
'min' => 3
]),
'helper' =>
__('crud.minicrud.picture_title_helper') .
'. ' .
__('validation.min.string', [
'attribute' => __('crud.minicrud.picture_title_label'),
'min' => 3,
]),
])
@include('modules.input-error', [
'inputName' => "data.{$k}.title"
'inputName' => "data.{$k}.title",
])
</div>
{{-- PICTURE --}}
@ -212,54 +220,58 @@ $data = (isset($model->data)) ? $model->getEmptyData() : $conceptData;
'preview' => true,
])
@include('modules.input-error', [
'inputName' => "data.{$k}.value"
'inputName' => "data.{$k}.value",
])
</div>
</div>
</fieldset>
</div>
@break
@case(App\Enums\InputType::email->value)
<div class="row">
<div class="col">
<fieldset class="border p-3 mb-3">
<div class="d-flex flex-row justify-content-start align-items-center mb-2">
<legend class="w-auto m-0">
<label for="title{{$k}}" class="col-form-label">
<b>{{ $input['label'] }}</b>
</label>
</legend>
@if(!empty($input['tooltip']))
<span class="ms-1" data-bs-toggle="tooltip" data-bs-html="true" title="{{ $input['tooltip'] }}">
<i class="fa fa-question-circle"></i>
</span>
@endif
</div>
<div class="row mb-3">
<div class="form-group">
<input id="title{{$k}}" type="email" class="form-control" name="data[{{$k}}][value]"
placeholder="{{ $input['label'] }}" value="{{ old("data.{$k}.value") ?? ($input['value'] ?? '') }}" @if(!isset($input['required']) or $input['required']) required @endif>
<div class="row">
<div class="col">
<fieldset class="mb-3 border p-3">
<div class="d-flex justify-content-start align-items-center mb-2 flex-row">
<legend class="m-0 w-auto">
<label class="col-form-label" for="title{{ $k }}">
<b>{{ $input['label'] }}</b>
</label>
</legend>
@if (!empty($input['tooltip']))
<span class="ms-1" data-bs-toggle="tooltip" data-bs-html="true" title="{{ $input['tooltip'] }}">
<i class="fa fa-question-circle"></i>
</span>
@endif
</div>
<div class="row mb-3">
<div class="form-group">
<input class="form-control" id="title{{ $k }}" name="data[{{ $k }}][value]" type="email"
value="{{ old("data.{$k}.value") ?? ($input['value'] ?? '') }}" placeholder="{{ $input['label'] }}"
@if (!isset($input['required']) or $input['required']) required @endif>
@include('modules.input-error', [
'inputName' => "data.{$k}.value",
'helperId' => "{$k}StringHelp",
'helper' => $input['helper'] ?? '',
])
</div>
</div>
</div>
</fieldset>
</fieldset>
</div>
</div>
</div>
@break
@case(intval(App\Enums\InputType::imageArray->value))
@case(intval(App\Enums\InputType::labelArray->value))
@case(intval(App\Enums\InputType::selectEnum->value))
<div class="col-12 col-md-6 mb-3">
<fieldset class="border h-100 bg-white p-3">
<fieldset class="h-100 border bg-white p-3">
<legend>{{ $input['label'] }} *</legend>
<div class="row">
<div class="col-12 mb-3">
@include('bo.partials.inputs.tinyint-enum', [
'id' => Str::slug($input["label"]),
'id' => Str::slug($input['label']),
'label' => __('crud.minicrud.associated_enum', ['enum' => $input['label']]),
'name' => "data[{$k}][value]",
'model' => $model ?? null,
@ -269,7 +281,7 @@ $data = (isset($model->data)) ? $model->getEmptyData() : $conceptData;
'helper' => __('crud.minicrud.enum_select', ['enum' => $input['label']]),
])
@include('modules.input-error', [
'inputName' => "data.{$k}.value"
'inputName' => "data.{$k}.value",
])
</div>
</div>
@ -283,7 +295,7 @@ $data = (isset($model->data)) ? $model->getEmptyData() : $conceptData;
@endforeach
@push('scripts')
<script nonce="{{ $nonce }}">
<script nonce="{{ $nonce ?? '' }}">
document.addEventListener('DOMContentLoaded', function() {
let minicrud = document.getElementById('minicrud').closest('form');
if (minicrud) {

View file

@ -1,4 +1,4 @@
<script @if(isset($nonce)) nonce="{{ $nonce }}" @endif>
<script @if (isset($nonce)) nonce="{{ $nonce ?? '' }}" @endif>
if (!window.__SYSTEM) {
window.__SYSTEM = {};
}

View file

@ -1,9 +1,8 @@
<script @if(isset($nonce)) nonce="{{ $nonce }}" @endif>
if (!window.__SYSTEM._routes) {
window.__SYSTEM._routes = {};
}
window.__SYSTEM._routes['shops.index'] = '{{ route('shops.index') }}';
window.__SYSTEM._routes['shops.nearest'] = '{{ route('shops.nearest') }}';
<script @if (isset($nonce)) nonce="{{ $nonce ?? '' }}" @endif>
if (!window.__SYSTEM._routes) {
window.__SYSTEM._routes = {};
}
window.__SYSTEM._routes['shops.index'] = '{{ route('shops.index') }}';
window.__SYSTEM._routes['shops.nearest'] = '{{ route('shops.nearest') }}';
</script>

View file

@ -1,10 +1,10 @@
@php
$iArgs = [];
if(request()->sort_col) {
if (request()->sort_col) {
$iArgs['sort_col'] = request()->sort_col;
}
if(request()->search) {
if (request()->search) {
$iArgs['search'] = request()->sort_way;
}
@endphp
@ -13,32 +13,33 @@
<form id="searchForm" action="/{{ request()->path() }}">
<div class="input-group">
<label class="input-group-text" for="searchField">{{ $searchFields }}</label>
@if(request()->sort_col)
<input class="d-none" type="text" name="sort_col" value="{{ request()->sort_col }}">
@if (request()->sort_col)
<input class="d-none" name="sort_col" type="text" value="{{ request()->sort_col }}">
@endif
@if(request()->sort_way)
<input class="d-none" type="text" name="sort_way" value="{{ request()->sort_way }}">
@if (request()->sort_way)
<input class="d-none" name="sort_way" type="text" value="{{ request()->sort_way }}">
@endif
@if(request()->rangefilter)
<input class="d-none" type="text" name="rangefilter" value="{{ request()->rangefilter }}">
@if (request()->rangefilter)
<input class="d-none" name="rangefilter" type="text" value="{{ request()->rangefilter }}">
@endif
@if(request()->group)
<input class="d-none" type="text" name="group" value="{{ request()->group }}">
@if (request()->group)
<input class="d-none" name="group" type="text" value="{{ request()->group }}">
@endif
<input type="search" id="searchField" name="search" class="form-control" value="{{ request()->search }}" placeholder="{{ __('Vos mots-clés ici...') }}"/>
<button type="submit" class="btn btn-primary" data-bs-toggle="tooltip" title="{{ __('Start search') }}">
<input class="form-control" id="searchField" name="search" type="search" value="{{ request()->search }}"
placeholder="{{ __('Vos mots-clés ici...') }}" />
<button class="btn btn-primary" data-bs-toggle="tooltip" type="submit" title="{{ __('Start search') }}">
<i class="fas fa-search"></i>
</button>
<button id="searchDelete" type="button" class="btn btn-danger" data-bs-toggle="tooltip" title="{{ __('Delete search') }}">
<button class="btn btn-danger" id="searchDelete" data-bs-toggle="tooltip" type="button" title="{{ __('Delete search') }}">
<i class="fas fa-backspace"></i>
</button>
@push('scripts')
<script nonce="{{ $nonce }}">
document.getElementById('searchDelete').addEventListener('click', function (e) {
document.getElementById('searchField').value = '';
document.getElementById('searchForm').submit();
});
</script>
<script nonce="{{ $nonce ?? '' }}">
document.getElementById('searchDelete').addEventListener('click', function(e) {
document.getElementById('searchField').value = '';
document.getElementById('searchForm').submit();
});
</script>
@endpush
</div>
</form>

View file

@ -1,85 +1,93 @@
@php $circle = isset($circle) ? '-circle' : ''; $hash = uniqid('', true); @endphp
@php
$circle = isset($circle) ? '-circle' : '';
$hash = uniqid('', true);
@endphp
@switch($socialNetwork)
@case('facebook')
<a data-share="facebook-{{ $hash }}" role="button" target="_blank" rel="me"
href="https://www.facebook.com/sharer/sharer.php?u={{ urlencode(str_replace("http://", "https://", request()->fullurl())) }}&t={{ urlencode($title) }}"
title="{{ __('Partager sur Facebook') }}">
<a data-share="facebook-{{ $hash }}"
href="https://www.facebook.com/sharer/sharer.php?u={{ urlencode(str_replace('http://', 'https://', request()->fullurl())) }}&t={{ urlencode($title) }}"
title="{{ __('Partager sur Facebook') }}" role="button" target="_blank" rel="me">
<img class="mx-2 my-1" src="{{ Vite::asset("resources/assets/images/facebook{$circle}.png") }}" alt="Facebook">
</a>
@break
@break
@case('twitter')
<a data-share="twitter-{{ $hash }}" role="button" target="_blank" rel="me"
href="https://twitter.com/share?url={{ urlencode(str_replace("http://", "https://", request()->fullurl())) }}&text={{ urlencode($title) }}"
title="{{ __('Partager sur Twitter') }}">
<a data-share="twitter-{{ $hash }}"
href="https://twitter.com/share?url={{ urlencode(str_replace('http://', 'https://', request()->fullurl())) }}&text={{ urlencode($title) }}"
title="{{ __('Partager sur Twitter') }}" role="button" target="_blank" rel="me">
<img class="mx-2 my-1" src="{{ Vite::asset("resources/assets/images/twitter{$circle}.png") }}" alt="Twitter">
</a>
@break
@break
@case('linkedin')
<a data-share="linkedin-{{ $hash }}" role="button" target="_blank" rel="me"
href="http://www.linkedin.com/shareArticle?mini=true&url={{ urlencode(str_replace("http://", "https://", request()->fullurl())) }}&title={{ urlencode($title) }}"
title="{{ __('Partager sur LinkedIn') }}">
<a data-share="linkedin-{{ $hash }}"
href="http://www.linkedin.com/shareArticle?mini=true&url={{ urlencode(str_replace('http://', 'https://', request()->fullurl())) }}&title={{ urlencode($title) }}"
title="{{ __('Partager sur LinkedIn') }}" role="button" target="_blank" rel="me">
<img class="mx-2 my-1" src="{{ Vite::asset("resources/assets/images/linkedin{$circle}.png") }}" alt="Linkedin">
</a>
@break
@break
@case('mail')
<a data-share="mail-{{ $hash }}" role="button" target="_blank"
<a data-share="mail-{{ $hash }}"
href="mailto:?{{ http_build_query([
'subject' => __('Je souhaite partager ceci que j\'ai trouvé sur :appName', [
'appName' => config('app.name')
'appName' => config('app.name'),
]),
'body' => $title
'body' => $title,
]) }}"
title="{{ __('Partager par E-mail') }}">
title="{{ __('Partager par E-mail') }}" role="button" target="_blank">
<img class="mx-2 my-1" src="{{ Vite::asset('resources/assets/images/mail.png') }}" alt="{{ __('E-mail') }}">
</a>
@break
@break
@case('whatsapp')
<a data-share="whatsapp-{{ $hash }}" role="button" target="_blank" rel="me"
href="https://wa.me?text={{ urlencode($title) }} : {{ urlencode(str_replace("http://", "https://", request()->fullurl())) }}"
title="{{ __('Partager sur Whatsapp') }}">
<img class="mx-2 my-1" src="{{ Vite::asset("resources/assets/images/whatsapp{$circle}.png") }}" alt="Whatsapp">
<a data-share="whatsapp-{{ $hash }}"
href="https://wa.me?text={{ urlencode($title) }} : {{ urlencode(str_replace('http://', 'https://', request()->fullurl())) }}"
title="{{ __('Partager sur Whatsapp') }}" role="button" target="_blank" rel="me">
<img class="mx-2 my-1" src="{{ Vite::asset("resources/assets/images/whatsapp{$circle}.png") }}" alt="Whatsapp">
</a>
@break
@break
@default
@dd('Unhandled share')
@endswitch
@push('scripts')
<script nonce="{{ $nonce }}">
document.addEventListener('DOMContentLoaded', function() {
/** @type {HTMLLinkElement|null} */
const facebook = document.querySelector('a[data-share="facebook-{{ $hash }}"]');
/** @type {HTMLLinkElement|null} */
const twitter = document.querySelector('a[data-share="twitter-{{ $hash }}"]');
/** @type {HTMLLinkElement|null} */
const linkedin = document.querySelector('a[data-share="linkedin-{{ $hash }}"]');
/** @type {HTMLLinkElement|null} */
const mail = document.querySelector('a[data-share="mail-{{ $hash }}"]');
<script nonce="{{ $nonce ?? '' }}">
document.addEventListener('DOMContentLoaded', function() {
/** @type {HTMLLinkElement|null} */
const facebook = document.querySelector('a[data-share="facebook-{{ $hash }}"]');
/** @type {HTMLLinkElement|null} */
const twitter = document.querySelector('a[data-share="twitter-{{ $hash }}"]');
/** @type {HTMLLinkElement|null} */
const linkedin = document.querySelector('a[data-share="linkedin-{{ $hash }}"]');
/** @type {HTMLLinkElement|null} */
const mail = document.querySelector('a[data-share="mail-{{ $hash }}"]');
if(facebook) {
facebook.addEventListener('click', function(e) {
e.preventDefault();
window.open(this.href, "Facebook", "height=285,width=550,resizable=1");
});
}
if(twitter) {
twitter.addEventListener('click', function(e) {
e.preventDefault();
window.open(this.href, "Twitter", "height=385,width=550,resizable=1");
});
}
if(linkedin) {
linkedin.addEventListener('click', function(e) {
e.preventDefault();
window.open(this.href, "LinkedIn", "height=485,width=550,resizable=1");
});
}
if(mail) {
mail.addEventListener('click', function(e) {
e.preventDefault();
window.open(this.href, "Mail", "height=485,width=550,resizable=1");
});
}
});
</script>
if (facebook) {
facebook.addEventListener('click', function(e) {
e.preventDefault();
window.open(this.href, "Facebook", "height=285,width=550,resizable=1");
});
}
if (twitter) {
twitter.addEventListener('click', function(e) {
e.preventDefault();
window.open(this.href, "Twitter", "height=385,width=550,resizable=1");
});
}
if (linkedin) {
linkedin.addEventListener('click', function(e) {
e.preventDefault();
window.open(this.href, "LinkedIn", "height=485,width=550,resizable=1");
});
}
if (mail) {
mail.addEventListener('click', function(e) {
e.preventDefault();
window.open(this.href, "Mail", "height=485,width=550,resizable=1");
});
}
});
</script>
@endpush

View file

@ -5,11 +5,11 @@
@section('message', __('Page Expired'))
@push('scripts')
<script nonce="{{ $nonce ?? null }}">
document.addEventListener('DOMContentLoaded', function () {
setTimeout(() => {
window.location = '{{ url()->previous() }}';
}, 3500);
});
</script>
<script nonce="{{ $nonce ?? '' }}">
document.addEventListener('DOMContentLoaded', function() {
setTimeout(() => {
window.location = '{{ url()->previous() }}';
}, 3500);
});
</script>
@endpush

View file

@ -20,6 +20,9 @@ Route::prefix('bo')->name('bo.')->middleware(['admin-secure', 'throttle:credenti
'confirm' => false,
'verify' => false
]);
Route::get('/login-error', function () {
return view('bo.pages.login-error');
})->name('login-error');
});
Route::middleware('auth:backend')->group(function () {
@ -31,12 +34,12 @@ Route::prefix('bo')->name('bo.')->middleware(['admin-secure', 'throttle:credenti
// * RANGES
Route::resource('ranges', RangeController::class)->except(['show', 'create', 'destroy']);
Route::post('/ranges/{range}/change-published', [RangeController::class, 'changePublished'])
->name('ranges.change-published');
->name('ranges.change-published');
// * PRODUCTS
Route::resource('products', ProductController::class)->except(['show']);
Route::post('/products/{product}/change-published', [ProductController::class, 'changePublished'])
->name('products.change-published');
->name('products.change-published');
// * SHOPS
Route::resource('shops', ShopController::class)->except(['show']);
@ -47,17 +50,17 @@ Route::prefix('bo')->name('bo.')->middleware(['admin-secure', 'throttle:credenti
->withoutMiddleware('htmlsanitize')
->middleware('htmlsanitize:description,preparation,ingredients');
Route::post('/recipes/{recipe}/change-published', [RecipeController::class, 'changePublished'])
->name('recipes.change-published');
->name('recipes.change-published');
// * STATIC PAGES
Route::resource('pages', StaticPageController::class)
->names([
'index' => 'static_pages.index',
'edit' => 'static_pages.edit',
'update' => 'static_pages.update',
])->parameters(['pages' => 'static_page'])
->only(['index', 'edit', 'update'])
->withoutMiddleware('htmlsanitize')
->middleware('htmlsanitize:content');
->names([
'index' => 'static_pages.index',
'edit' => 'static_pages.edit',
'update' => 'static_pages.update',
])->parameters(['pages' => 'static_page'])
->only(['index', 'edit', 'update'])
->withoutMiddleware('htmlsanitize')
->middleware('htmlsanitize:content');
});
});