Merge branch 'trunk'

This commit is contained in:
Noah Lancien 2024-10-07 14:36:56 +02:00
commit a9b36ba2a0
58 changed files with 2060 additions and 280 deletions

View file

@ -68,3 +68,8 @@ SAML2_SP_CERT_x509=
SAML2_SP_CERT_PRIVATEKEY=
SAML2_CONTACT_TECHNICAL_EMAIL=
SAML2_CONTACT_SUPPORT_EMAIL=
# RECAPTCHA
GOOGLE_RECAPTCHA_PUBLIC=
GOOGLE_RECAPTCHA_PRIVATE=
GOOGLE_RECAPTCHA_VERSION=

View file

@ -0,0 +1,23 @@
<?php
namespace App\Enums;
use App\Traits\Enums\BaseEnum;
enum ContactFormCivility: int
{
use BaseEnum;
case m = 1;
case mme = 2;
/**
* Optionnal labels definition.
*
* @phpstan-ignore-next-line
*/
private const LABELS = [
self::m->name => 'M',
self::mme->name => 'Mme',
];
}

View file

@ -0,0 +1,90 @@
<?php
namespace App\Enums;
use App\Traits\Enums\BaseEnum;
enum ContactFormType: int
{
use BaseEnum;
case none = 1;
case complain = 2;
case club = 3;
case games = 4;
case others = 5;
case nl = 6;
/**
* Optionnal labels definition.
*
* @phpstan-ignore-next-line
*/
private const LABELS = [
self::none->name => 'Aucun',
self::complain->name => 'Une réclamation',
self::club->name => 'Le Club des Gaulois',
self::games->name => 'Jeux Concours',
self::others->name => 'Autres',
self::nl->name => 'Formulaire en NL',
];
/**
* Get the description.
*
* @return string
*/
public function description(): string
{
return match ($this) {
self::none => 'Aucun objet',
self::complain => 'Je souhaite obtenir de l\'aide car je désire envoyer une réclamation sur un produit',
self::club => 'Je rencontre des problèmes au sujet des magnets départ\'aimants ou des bons de
réductions Le Gaulois.',
self::games => 'Je souhaite faire une réclamation sur un jeu Le Gaulois ou je rencontre un problème
sur le site.',
self::others => 'Je souhaite faire une réclamation sur un jeu Le Gaulois ou je rencontre un problème
sur le site.',
};
}
/**
* Get the picto src/alt.
*
* @return string
*/
public function picto(): array
{
return match ($this) {
self::complain => [
'src' => 'resources/assets/images/contact/exclamation.svg',
'alt' => 'Point d\'exclamation'
],
self::club,
self::games,
self::others => [
'src' => 'resources/assets/images/contact/question-mark.svg',
'alt' => 'Point d\'interrogation'
],
default => [
'src' => '',
'alt' => '',
],
};
}
/**
* Get all contact types except 'none'.
*
* @return array
*/
public static function getContactTypes(): array
{
return [
self::complain,
self::club,
self::games,
self::others,
];
}
}

View file

@ -0,0 +1,7 @@
<?php
namespace App\Exceptions;
use Exception;
class AbortContactMailer extends Exception {}

View file

@ -0,0 +1,74 @@
<?php
namespace App\Http\Controllers\Fo;
use App\Enums\ContactFormType;
use App\Enums\Listen\ObjectEnum;
use App\Enums\StaticPageType;
use App\Exceptions\AbortContactMailer;
use App\Http\Controllers\Controller;
use App\Http\Requests\Fo\ContactFormRequest;
use App\Jobs\Mails\SendArcadiaMail;
use App\Jobs\Mails\SendPasserelleMail;
use App\Lib\Helpers\FileStorageHelper;
use App\Models\MailContactData;
use App\Models\StaticPage;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Storage;
class ContactController extends Controller
{
/**
* Contact page.
*
* @param \App\Models\StaticPage $staticPageModel
* @return \Illuminate\Contracts\View\View
*/
public function contact(StaticPage $staticPageModel): \Illuminate\Contracts\View\View
{
$staticPageModel = StaticPage::getCachedStaticPage(StaticPageType::contact);
return view('fo.pages.contact', ['staticPageModel' => $staticPageModel]);
}
/**
* Send contact form data.
*
* @param \App\Http\Requests\Fo\ContactFormRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function contactSubmit(ContactFormRequest $request): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request) {
try {
$data = new MailContactData($request->safe()->except(['g-recaptcha-response', 'joinFile']));
// Send to Passerelle if it is a compalin about a product.
if ($request->type == ContactFormType::complain->value) {
$joinFilePath = null;
$joinFile = '';
$uniqIdDirectory = \uniqid();
if ($request->file('joinFile')) {
$joinFile = Storage::disk('local')->put(
sprintf('public/tmp/%s', $uniqIdDirectory),
$request->file('joinFile')
);
$joinFilePath = storage_path('app/' . $joinFile);
}
\dispatch(new SendPasserelleMail($data, $joinFilePath));
} else {
\dispatch(new SendArcadiaMail($data));
}
return back()->with('success', __('Votre message a été envoyé avec succès'));
} catch (AbortContactMailer $e) {
Log::error('Fail to build mail from complain page ', ['exception' => $e]);
}
return back()->withInput()->with('error', 'Une erreur est survenue durant l\'envoi de votre message. Veuillez réessayer ultèrieurement.');
});
}
}

View file

@ -98,15 +98,18 @@ class ProductController extends Controller
/** @var \App\Models\Video $videoModel */
$videoModel = $productModel->videos->sortByDesc('created_at')->first();
$nutritionnalValueModel = $productModel->getNutritionnalValue();
return view('fo.pages.products.show', [
'productModel' => $productModel,
'prevProductModel' => $productModelSibling[0],
'nextProductModel' => $productModelSibling[1],
'productModels' => $this->getProductsFiltered($productModel),
'recipeModels' => $this->getRecipesFiltered($productModel),
'recipeModel' => $recipeModel,
'videoModel' => $videoModel,
'staticPageModel' => $staticPageModel,
'productModel' => $productModel,
'nutritionnalValueModel' => $nutritionnalValueModel,
'prevProductModel' => $productModelSibling[0],
'nextProductModel' => $productModelSibling[1],
'productModels' => $this->getProductsFiltered($productModel),
'recipeModels' => $this->getRecipesFiltered($productModel),
'recipeModel' => $recipeModel,
'videoModel' => $videoModel,
'staticPageModel' => $staticPageModel,
]);
}

View file

@ -20,18 +20,6 @@ use Detection\MobileDetect;
class StaticPageController extends Controller
{
/**
* Contact page.
*
* @param \App\Models\StaticPage $staticPageModel
* @return \Illuminate\Contracts\View\View
*/
public function contact(StaticPage $staticPageModel): \Illuminate\Contracts\View\View
{
$staticPageModel = StaticPage::getCachedStaticPage(StaticPageType::contact);
return view('fo.pages.legals', ['staticPageModel' => $staticPageModel]);
}
/**
* Legals page.
*
@ -94,7 +82,7 @@ class StaticPageController extends Controller
config('services.digital_recruiter.url'),
fn() => Offer::query()->where('published', true)->orderBy('publish_at', 'DESC')->take(6)->get()
);
$professionModels = Profession::get5Published();
$professionModels = Profession::getRandomPublished();
$brandAdvantageModels = BrandAdvantage::getPublishedOrdered();
$newsCorporateModels = NewsCorporate::get9PublishedOrderedByUpdatedAt();
if ($detect->isMobile()) {

View file

@ -49,7 +49,7 @@ class StoreNewsBreedingRequest extends FormRequest
'seo_description' => 'required|string|between:50,160',
'title' => 'required|string|between:3,255',
'subtitle' => 'required|string|between:3,255',
'content' => ['required', 'string', new WysiwygLength(15, 5000)],
'content' => ['required', 'string', new WysiwygLength(15, 15000)],
'associated_page' => [
'required',
Rule::enum(StaticPageType::class)->only(StaticPageType::getBreedingPages())

View file

@ -3,6 +3,7 @@
namespace App\Http\Requests\Bo\NewsCategories;
use App\Models\NewsCategory;
use App\Rules\Hex;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Gate;
@ -41,6 +42,7 @@ class StoreNewsCategoryRequest extends FormRequest
'seo_title' => 'required|string|between:45,70',
'seo_description' => 'required|string|between:50,160',
'title' => 'required|string|between:3,255',
'color' => ['required', new Hex()],
'published' => 'required|boolean',
];
}
@ -56,6 +58,7 @@ class StoreNewsCategoryRequest extends FormRequest
'seo_title' => trans('validation.attributes.title'),
'seo_description' => trans('validation.attributes.title'),
'title' => trans('validation.attributes.title'),
'color' => trans('validation.custom.color'),
'published' => trans('validation.attributes.published'),
];
}

View file

@ -0,0 +1,103 @@
<?php
namespace App\Http\Requests\Fo;
use App\Enums\ContactFormCivility;
use App\Enums\ContactFormType;
use App\Lib\Helpers\ToolboxHelper;
use Carbon\Carbon;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
use Illuminate\Validation\Rules\Enum;
class ContactFormRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
ToolboxHelper::validateRecaptcha($this, \config('services.google.recaptcha.version'));
$rules = [
'type' => [
'required',
Rule::enum(ContactFormType::class)->only(ContactFormType::getContactTypes())
],
'civility' => [
'required',
Rule::enum(ContactFormCivility::class)
],
// * max arcadia 50 chars
'lastname' => 'required|min:2|max:50',
// * max arcadia 50 chars
'firstname' => 'required|min:2|max:50',
'address' => 'required|min:4',
'email' => 'required|email:rfc,dns,spoof',
'postal' => 'required|postal_code:FR',
// * max arcadia 10 chars
'town' => 'required|min:3|max:90',
'phone' => 'required|phone:FR',
// 'onepagesubject' => 'sometimes|min:10|max:250',
'message' => 'required|min:20|max:1900'
];
if (ContactFormType::make($this->input('type')) === ContactFormType::complain) {
$rules = array_merge(
$rules,
[
'productName' => 'required|min:2|max:100',
'productCode' => 'required|digits_between:8,13|min:1|max:9999999999999',
'buyPlace' => 'required|min:2|max:100',
'lotNumberName' => 'required|min:2|max:50',
'limitDateDay' => 'required|numeric|min:1|max:31',
'limitDateMonth' => 'required|numeric|min:1|max:31',
'limitDateYear' => 'required|numeric|min:2010|max:2050',
'consumptionDateDay' => 'required|numeric|min:1|max:31',
'consumptionDateMonth' => 'required|numeric|min:1|max:31',
'consumptionDateYear' => 'required|numeric|min:2010|max:2050',
'joinFile' => 'nullable|mimes:jpg,jpeg,png,pdf|max:2048'
],
);
}
return $rules;
}
/**
* Get custom attributes for validator errors.
*
* @return array
*/
public function attributes(): array
{
return [
'lastname' => trans('validation.attributes.last_name'),
'firstname' => trans('validation.attributes.first_name'),
'town' => trans('validation.custom.town'),
'productName' => trans('validation.custom.product_name'),
'productCode' => trans('validation.custom.barcode'),
'buyPlace' => trans('validation.custom.place_of_purchase'),
'lotNumberName' => trans('validation.custom.batch_number'),
'limitDateDay' => sprintf('%s - %s', trans('validation.custom.use_by_date'), trans('day')),
'limitDateMonth' => sprintf('%s - %s', trans('validation.custom.use_by_date'), trans('month')),
'limitDateYear' => sprintf('%s - %s', trans('validation.custom.use_by_date'), trans('year')),
'consumptionDateDay' => sprintf('%s - %s', trans('validation.custom.consumption_date'), trans('day')),
'consumptionDateMonth' => sprintf('%s - %s', trans('validation.custom.consumption_date'), trans('month')),
'consumptionDateYear' => sprintf('%s - %s', trans('validation.custom.consumption_date'), trans('year')),
'joinFile' => trans('validation.custom.attachment')
];
}
}

View file

@ -0,0 +1,46 @@
<?php
namespace App\Jobs\Mails;
use App\Mail\ContactMail;
use App\Models\MailContactData;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
class SendArcadiaMail implements ShouldQueue
{
use Dispatchable;
use InteractsWithQueue;
/**
* To be serialized by jobs system
*
* @var \App\Models\MailContactData
* */
private $data;
/**
* Create a new job instance.
*
* @param \App\Models\MailContactData $data
* @return void
*/
public function __construct(MailContactData $data)
{
$this->data = $data;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
Mail::to(config('mail.contactsList.contact'))->send(new ContactMail($this->data));
Log::debug(sprintf('Sent %s to %s', ContactMail::class, implode(',', config('mail.contactsList.contact'))));
}
}

View file

@ -0,0 +1,54 @@
<?php
namespace App\Jobs\Mails;
use App\Mail\ContactComplainMail;
use App\Models\MailContactData;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Storage;
class SendPasserelleMail implements ShouldQueue
{
use Dispatchable;
use InteractsWithQueue;
/**
* To be serialized by jobs system
*
* @var MailContactData
* */
private $data;
private $file;
/**
* Create a new job instance.
*
* @param \App\Models\MailContactData $data
* @return void
*/
public function __construct(MailContactData $data, mixed $file)
{
$this->data = $data;
$this->file = $file;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
Mail::to(config('mail.contactsList.passerelle'))->send(new ContactComplainMail($this->data, $this->file));
if (file_exists($this->file)) {
File::deleteDirectory(File::dirname($this->file));
}
Log::debug(sprintf('Sent %s to %s', ContactComplainMail::class, config('mail.contactsList.passerelle')));
}
}

View file

@ -0,0 +1,82 @@
<?php
namespace App\Mail;
use App\Models\MailContactData;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Attachment;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Storage;
class ContactComplainMail extends ContactMail
{
use Queueable;
use SerializesModels;
/**
* The order instance.
*
* @var MailContactData
*/
protected $data;
protected $file;
/**
* Create a new message instance.
*
* @param \App\Models\MailContactData $data
* @param string|null $file
* @return void
*/
public function __construct(MailContactData $data, string|null $file)
{
$this->data = $data;
$this->file = $file;
}
/**
* Build the message.
*
* @return $this
*/
public function build()
{
if ($this->file) {
$this->joinedFileName = sprintf(
'LEGAULOIS_PASS_MESSAGE-PJ-%s.%s',
date("YmdHis"),
File::extension($this->file)
);
}
return $this->view('emails.contact')
->subject(sprintf('Nouveau message consommateur Le Gaulois (%s)', $this->data->type->label()))
->text('emails.contact-plain')
->with('data', $this->data)
->attachData(
view('emails.passerelle-xml', ['data' => $this->data->exportToPasserelleDataArray()])->render(),
sprintf('LEGAULOIS_PASS_MESSAGE_%d.xml', date("YmdHis")),
['mime' => 'application/xml']
);
}
/**
* Get the attachments for the message.
*
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
*/
public function attachments(): array
{
if (!$this->file) {
return [];
}
return [
Attachment::fromPath($this->file)->as(sprintf(
'LEGAULOIS_PASS_MESSAGE-PJ-%s.%s',
date("YmdHis"),
File::extension($this->file)
)),
];
}
}

96
app/Mail/ContactMail.php Normal file
View file

@ -0,0 +1,96 @@
<?php
namespace App\Mail;
use App\Models\MailContactData;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use League\Csv\Bom;
use League\Csv\Writer;
use SplTempFileObject;
class ContactMail extends Mailable
{
use Queueable;
use SerializesModels;
/**
* The order instance.
*
* @var MailContactData
*/
protected $data;
/**
* The joined file name
*
* @var string
*/
protected $joinedFileName;
/**
* Create a new message instance.
*
* @return void
*/
public function __construct(MailContactData $data)
{
$this->data = $data;
}
/**
* Build the message.
*
* @return $this
*/
public function build()
{
return $this
->subject(sprintf('Nouveau message consommateur Le Gaulois (%s)', $this->data->type->label()))
->view('emails.contact')
->text('emails.contact-plain')
->with('data', $this->data)
->attachData(
$this->makeCsvFile(),
sprintf('contact_le_gaulois_%d.csv', time()),
['mime' => 'text/csv']
);
}
/**
* @return string
*/
protected function makeCsvFile(): string
{
// we create the CSV into memory
// FILENAME
$csv = Writer::createFromFileObject(new SplTempFileObject());
$csv->setDelimiter(';');
$csv->setEnclosure('"');
$csv->setOutputBOM(Bom::Utf8);
// we insert the CSV header
$csv->insertOne([
'EMETTEUR_DATE',
'EMETTEUR_HEURE',
'EMETTEUR_CIVILITE',
'EMETTEUR_NOM',
'EMETTEUR_PRENOM',
'EMETTEUR_ADRESSE',
'EMETTEUR_CP',
'EMETTEUR_VILLE',
'EMETTEUR_EMAIL',
'EMETTEUR_OBJETDEMANDE',
'EMETTEUR_MESSAGE',
'EMETTEUR_CATEGORIE',
'EMETTEUR_ACTIVITE',
'REPONSE_ETAT',
'TYPE_REPONSE'
]);
$csv->insertOne($this->data->exportToArcadiaDataArray());
return $csv->toString();
}
}

View file

@ -0,0 +1,174 @@
<?php
namespace App\Models;
use App\Enums\ContactFormCivility;
use App\Enums\ContactFormType;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
/**
* This class is used to store data in memory only, not in databases
*/
class MailContactData extends Model
{
/**
* The database connection that should be used by the model.
*
* @var string
*/
protected $connection = null;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'civility',
'lastname',
'firstname',
'address',
'email',
'postal',
'town',
'phone',
'type',
'onepagesubject',
'message',
'joinFile',
'productName',
'productCode',
'buyPlace',
'lotNumberName',
'limitDateDay',
'limitDateMonth',
'limitDateYear',
'consumptionDateDay',
'consumptionDateMonth',
'consumptionDateYear'
];
/**
* The attributes that should be cast.
*
* @var array<string, string>
*/
protected $casts = [
'type' => ContactFormType::class,
'civility' => ContactFormCivility::class
];
/**
* The model's attributes.
*
* @var array
*/
protected $attributes = [
'productName' => '',
'productCode' => '',
'buyPlace' => '',
'lotNumberName' => 0,
'limitDateDay' => '00',
'limitDateMonth' => '00',
'limitDateYear' => '0000',
'consumptionDateDay' => '00',
'consumptionDateMonth' => '00',
'consumptionDateYear' => '0000',
];
/**
* Export the model data to a Formatted Arcadia Data array
*
* @return array
*/
public function exportToArcadiaDataArray(): array
{
return [
date("Ymd"),
date("Gis"),
$this->civility->value,
$this->lastname,
$this->firstname,
$this->address,
$this->postal,
$this->town,
$this->email,
$this->type->equals(ContactFormType::nl) ? $this->onepagesubject : $this->type->value,
$this->message,
'0',
'0',
'0',
'0'
];
}
/**
* Export the model data to a Formatted Passerelle Data array
*
* * Removed
* 'EMETTEUR_OBJETDEMANDE' => "objet de la reclamation string",
* 'EMETTEUR_OPT_IN' => '',
* 'REPONSE_ETAT' => 0,
* 'TYPE_REPONSE' => 'mail',
*
* @return array
*/
public function exportToPasserelleDataArray(): array
{
// * CF PAsserrelle fields .csv
return [
// * Marque le Gaulois (Obligatoire)
'EMETTEUR_MARQUE' => 1,
// * Obligatoire
'EMETTEUR_DATE' => date("Y-m-d"),
// * Obligatoire
'EMETTEUR_HEURE' => date("h:i:s"),
'EMETTEUR_CIVILITE' => $this->civility->value,
// * Obligatoire Limité a 50
'EMETTEUR_NOM' => Str::limit($this->lastname, 50, ''),
// * Obligatoire
'EMETTEUR_PRENOM' => $this->firstname,
'EMETTEUR_ADRESSE1' => $this->address,
'EMETTEUR_ADRESSE2' => '',
'EMETTEUR_CP' => $this->postal,
// * Obligatoire
'EMETTEUR_VILLE' => $this->town,
'EMETTEUR_EMAIL' => $this->email,
'EMETTEUR_TELEPHONE' => $this->phone,
'EMETTEUR_PAYS' => 'France',
'EMETTEUR_NOM_PRODUIT' => $this->productName,
// * Obligatoire
'EMETTEUR_EAN13' => $this->productCode,
'EMETTEUR_DATE_LIMITE_CONSO' => sprintf(
'%s/%s/%s',
$this->limitDateDay ?? '00',
$this->limitDateMonth ?? '00',
$this->limitDateYear ?? '0000'
),
'EMETTEUR_NUM_LOT' => $this->lotNumberName,
'EMETTEUR_MESSAGE' => $this->type->equals(ContactFormType::nl) ?
sprintf('%s \n %s', $this->onepagesubject, $this->message) :
$this->message,
'EMETTEUR_PJ' => $this->joinFile,
'DATE_CONSO' => sprintf(
'%s/%s/%s',
$this->consumptionDateDay ?? '00',
$this->consumptionDateMonth ?? '00',
$this->consumptionDateYear ?? '0000'
),
'LIEU_ACHAT' => $this->buyPlace,
// * FUCK passerelle et leur DTD qui n'existe pas cf: resources/documents
// * Recla conso ?
'EMETTEUR_OBJETDEMANDE' => 3,
// * doit être vide ?
'EMETTEUR_OPT_IN' => '',
// * c'est quoi ça ?
'REPONSE_ETAT' => 0,
// * ok
'TYPE_REPONSE' => 'mail'
];
}
}

View file

@ -2,6 +2,7 @@
namespace App\Models;
use App\Casts\HtmlColor;
use App\Traits\Models\ActivityLog;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
@ -13,6 +14,7 @@ use Illuminate\Support\Facades\Cache;
* @property string $title The title of the news.
* @property string $seo_title Seo title about 70 chars as recommended.
* @property string $seo_description Seo description about 160 chars as recommended.
* @property int $color Range color.
* @property string $slug The slug url of the category.
* @property boolean $published Category publish status.
* @property-read \Illuminate\Support\Carbon $created_at Created date.
@ -32,6 +34,7 @@ class NewsCategory extends Model
'title',
'seo_title',
'seo_description',
'color',
'slug',
'published',
];
@ -42,6 +45,7 @@ class NewsCategory extends Model
* @var array<string, string>
*/
protected $casts = [
'color' => HtmlColor::class,
'published' => 'boolean'
];

View file

@ -85,7 +85,7 @@ class Profession extends Model
private static function resetCache()
{
Cache::forget('5publishedProfessions');
self::get5Published();
self::getRandomPublished();
}
/**
@ -93,10 +93,10 @@ class Profession extends Model
*
* @return \Illuminate\Database\Eloquent\Collection
*/
public static function get5Published(): \Illuminate\Database\Eloquent\Collection
public static function getRandomPublished(): \Illuminate\Database\Eloquent\Collection
{
return Cache::rememberForever('5publishedProfessions', function () {
return self::query()->where('published', true)->take(5)->get();
return self::query()->where('published', true)->inRandomOrder()->get();
});
}
}

View file

@ -24,6 +24,7 @@
"laravel/tinker": "^2.8",
"laravel/ui": "^4.2",
"ldc/ldccrmv4client": "^4.0",
"league/csv": "^9.16",
"mews/purifier": "^3.4",
"mobiledetect/mobiledetectlib": "^4.8",
"moneyphp/money": "^4.5",

502
composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -92,8 +92,14 @@ return [
*/
'from' => [
'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'),
'name' => env('MAIL_FROM_NAME', 'Example'),
'address' => env('MAIL_FROM_ADDRESS', env('MAIL_LIST_CONTACT', 'contact@legaulois.fr')),
'name' => env('MAIL_FROM_NAME', 'Contact Le Gaulois (legaulois.fr)')
],
// Contacts List.
'contactsList' => [
'contact' => [env('MAIL_LIST_CONTACT', 'contact.legaulois@ldc.fr')],
'passerelle' => env('MAIL_LIST_PASSERELLE', 'passerelle.ldc@ldc.fr'),
],
/*

View file

@ -34,4 +34,12 @@ return [
'digital_recruiter' => [
'url' => env('DIGITAL_RECRUITER_URL')
],
'google' => [
'recaptcha' => [
'public' => env('GOOGLE_RECAPTCHA_PUBLIC'),
'private' => env('GOOGLE_RECAPTCHA_PRIVATE'),
'version' => env('GOOGLE_RECAPTCHA_VERSION', "3") === "3" ? true : false
]
],
];

View file

@ -0,0 +1,34 @@
<?php
use App\Models\NewsCategory;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('news_categories', function (Blueprint $table) {
$table->unsignedBigInteger('color')->after('slug')->comment('News associated color.');
});
NewsCategory::query()->get()->map(function (NewsCategory $category) {
$category->color = fake()->unique()->safeHexColor();
$category->saveQuietly();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('news_categories', function (Blueprint $table) {
$table->dropColumn('color');
});
}
};

View file

@ -36,6 +36,9 @@ data:
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}"
GOOGLE_RECAPTCHA_PRIVATE: "${APP_GOOGLE_RECAPTCHA_PRIVATE}"
GOOGLE_RECAPTCHA_PUBLIC: "${APP_GOOGLE_RECAPTCHA_PUBLIC}"
GOOGLE_RECAPTCHA_VERSION: "${APP_GOOGLE_RECAPTCHA_VERSION}"
---
apiVersion: v1

View file

@ -0,0 +1,20 @@
<svg width="40" height="56" viewBox="0 0 40 56" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="point-exclamation">
<g id="Vector" filter="url(#filter0_d_7585_27118)">
<path d="M5.61791 1.46825H19.7192V3.67952L22.879 3.56332L34.1587 4.84149L30.4387 30.8908L28.1807 39.7288L20.7201 42.1478L16.7849 46.7077L10.5814 48.4506L5.50177 45.5422L3.58538 39.1478L5.73064 34.0316L3.47266 1.35205L5.61791 1.46825Z" fill="white"/>
</g>
<path id="Vector_2" d="M11.5638 35.3943C12.838 35.3943 13.9038 35.845 14.7612 36.7464C15.6152 37.6478 16.0456 38.757 16.0456 40.0703C16.0456 41.3837 15.6186 42.4365 14.7612 43.3379C13.9038 44.2393 12.838 44.6901 11.5638 44.6901C10.2896 44.6901 9.21359 44.2393 8.33909 43.3379C7.46459 42.4365 7.02734 41.3485 7.02734 40.0703C7.02734 38.7922 7.46459 37.6478 8.33909 36.7464C9.21359 35.845 10.2896 35.3943 11.5638 35.3943ZM7.19473 4.80273H15.8851L14.9559 33.3098L8.17854 30.8309L7.19473 4.80273Z" fill="#492B31"/>
<path id="Vector_3" d="M23.9798 30.9788C25.0183 31.0985 25.8449 31.5633 26.4632 32.3802C27.0815 33.1936 27.3309 34.1373 27.2182 35.2077C27.1054 36.2781 26.6614 37.095 25.8859 37.7464C25.1105 38.4014 24.2018 38.669 23.1634 38.5492C22.1249 38.4295 21.288 37.9612 20.656 37.1478C20.024 36.3309 19.7644 35.4049 19.8772 34.3661C19.9899 33.3274 20.4476 32.433 21.2367 31.7816C22.0258 31.1302 22.9413 30.8626 23.9798 30.9823V30.9788ZM22.9106 7.28516L29.9851 8.09854L26.9244 29.5985L21.6262 26.9471L22.914 7.28516H22.9106Z" fill="#492B31"/>
</g>
<defs>
<filter id="filter0_d_7585_27118" x="0.472656" y="0.352051" width="38.6865" height="55.0986" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dx="1" dy="3"/>
<feGaussianBlur stdDeviation="2"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.2 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_7585_27118"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_7585_27118" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path id="Vector" d="M10.0016 0C15.5253 0 20.0031 4.47784 20.0031 10.0016C20.0031 15.5253 15.5253 20.003 10.0016 20.003C4.47784 20.003 0 15.5253 0 10.0016C0 4.47784 4.47784 0 10.0016 0ZM9.99776 8.25055C9.48486 8.25095 9.06246 8.63725 9.00506 9.13465L8.99836 9.25125L9.00196 14.7529L9.00876 14.8695C9.06686 15.3668 9.48986 15.7526 10.0027 15.7522C10.5155 15.7519 10.9379 15.3656 10.9954 14.8681L11.002 14.7515L10.9984 9.24995L10.9916 9.13335C10.9335 8.63605 10.5106 8.25025 9.99776 8.25055ZM10.002 4.50103C9.31076 4.50103 8.75046 5.06135 8.75046 5.75255C8.75046 6.44375 9.31076 7.00407 10.002 7.00407C10.6932 7.00407 11.2536 6.44375 11.2536 5.75255C11.2536 5.06135 10.6932 4.50103 10.002 4.50103Z" fill="#4E3238"/>
</svg>

After

Width:  |  Height:  |  Size: 817 B

View file

@ -0,0 +1,23 @@
<svg width="44" height="62" viewBox="0 0 44 62" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Calque_1" clip-path="url(#clip0_7585_27127)">
<g id="Vector" filter="url(#filter0_d_7585_27127)">
<path d="M2.0957 7.06931L4.72094 21.5049L9.40126 24.1315L10.9032 40.4402L8.83963 47.0003L10.716 53.7477L19.5193 57.1235L25.5186 54.3096L27.5779 46.813L26.4547 41.1894L27.2035 34.442L33.0114 31.6281L38.8192 26.0046L40.5084 16.256L38.0704 6.32007L28.1396 0.696533L15.2134 1.07115L2.0957 7.06931Z" fill="white"/>
</g>
<path id="Vector_2" d="M20.4381 5.43896C25.0673 5.43896 28.7095 6.59688 31.3645 8.9127C34.0195 11.2285 35.347 14.2936 35.347 18.1079C35.347 20.743 34.3344 23.2504 32.3176 25.6343C30.2965 28.0183 27.4245 29.9808 23.7058 31.5261L22.7527 40.1082L14.7196 37.4518L14.1749 26.4176C17.3533 25.4215 19.9275 24.1486 21.9017 22.6033C23.876 21.0623 24.8631 19.5383 24.8631 18.0398C24.8631 16.8606 24.4334 15.9155 23.5696 15.2131C22.7059 14.5107 21.5954 14.1574 20.2338 14.1574C18.8723 14.1574 17.1363 14.4766 15.1621 15.1109C13.1878 15.7495 11.0434 16.7712 8.72874 18.176L7.36719 9.11704C11.4518 6.66499 15.8088 5.43896 20.4381 5.43896ZM18.1234 41.9473C19.7105 41.9473 21.0508 42.4922 22.14 43.582C23.2292 44.6718 23.7739 46.0127 23.7739 47.6006C23.7739 49.1885 23.2292 50.5209 22.14 51.5852C21.0508 52.6537 19.7105 53.1858 18.1234 53.1858C16.5364 53.1858 15.1961 52.6409 14.1069 51.5511C13.0176 50.4613 12.473 49.1459 12.473 47.6006C12.473 46.0553 13.0176 44.6718 14.1069 43.582C15.1961 42.4922 16.5321 41.9473 18.1234 41.9473Z" fill="#492B31"/>
</g>
<defs>
<filter id="filter0_d_7585_27127" x="-0.904297" y="-0.303467" width="46.4131" height="64.427" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dx="1" dy="3"/>
<feGaussianBlur stdDeviation="2"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.2 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_7585_27127"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_7585_27127" result="shape"/>
</filter>
<clipPath id="clip0_7585_27127">
<rect width="44" height="62" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View file

@ -222,6 +222,8 @@ return [
'vimeo_id' => ':attribute doit être un ID de vidéo Vimeo valide',
'list_items' => 'doit décrire la valeur :field (minimum :min caractères, :lineMin à :lineMax lignes).',
'highlight_boolean' => 'cocher pour mettre en avant cette recette et décocher l\'ancienne.',
'postal_code' => 'Le code postal doit être un code postal valide',
'phone' => 'Le numéro de téléphone doit être un numéro de téléphone valide',
// Custom attributes.
'custom' => [
'of_the' => [
@ -232,6 +234,7 @@ return [
],
'activated' => 'activé',
'advice' => 'conseil',
'associated_page' => 'page associée|pages associées',
'attribute_alt' => 'attribut alt',
'attribute_title' => 'attribut titre',
'background' => 'image de fond',
@ -247,9 +250,10 @@ return [
'button' => 'bouton',
'catchline' => 'phrase d\'accroche|phrases d\'accroche',
'category' => 'catégorie',
'claim' => 'allégation',
'claim_title' => 'titre des allégations',
'claim' => 'allégation',
'class' => 'classe',
'code' => 'code',
'color_block' => 'couleur du block',
'color' => 'couleur',
'contract' => 'contrat',
@ -298,26 +302,43 @@ return [
'sustitle' => 'sous-titre',
'target' => 'cible',
'testimonial_identity' => 'identité',
'tips' => 'astuce',
'thumbnail' => 'vignette',
'tips' => 'astuce',
'type' => 'type',
'unit' => 'unité',
'unpublish_at' => 'date de fin de publication',
'unpublished' => 'non publié',
'url' => 'lien',
'used' => 'utilisé',
'value' => 'valeur',
'video_id' => 'ID de la vidéo',
'vimeo_video_id' => 'ID de la vidéo viméo',
'youtube_video_id' => 'ID de la vidéo youtube',
'testimonial_identity' => 'identité',
'catchline' => 'phrase d\'accroche|phrases d\'accroche',
'button_visibility' => 'visibilité du bouton|visibilité des boutons',
'associated_page' => 'page associée|pages associées',
'product_name' => 'nom du produit',
'place_of_purchase' => 'lieu d\'achat',
'barcode' => 'code barre du produit',
'batch_number' => 'numéro de lot',
'use_by_date' => 'date limite de consommation',
'consumption_date' => 'date de consommation',
'message' => 'message',
'attachment' => 'pièce jointe',
'town' => 'ville',
'civility' => 'civilité',
'breeder_map_component' => [
'main_text' => 'texte principal',
'picto_text' => 'texte du pictogramme',
],
'code' => 'code',
'used' => 'utilisé',
'nutritionnal_values' => [
'energy' => 'énergie',
'proteins' => 'protéines',
'carbohydrates' => 'glucides',
'sugar' => 'dont sucres',
'fat' => 'matières grasses',
'saturated_fatty_acids' => 'dont Acides Gras Saturés',
'dietary_fiber' => 'fibres alimentaires',
'salt' => 'sel',
]
],
];

View file

@ -33,6 +33,7 @@
@import "./pages/page-des-40-ans";
@import "./pages/export-40-ans";
@import "./pages/news";
@import "./pages/contact";
// * PARTIALS
@import "./partials/breeder-card";

View file

@ -0,0 +1,40 @@
#contact-page {
.radio-subject {
box-shadow: 0 3px 10px -2px hsl(150deg 5% 65% / 50%);
input {
display: none;
cursor: pointer;
}
label {
position: relative;
display: block;
overflow: hidden;
height: 100%;
padding: 1rem 2rem 1rem 1rem;
border: 4px solid;
border-color: $color-tertiary;
border-radius: 10px;
background: $color-tertiary;
}
input:checked+label {
border: 4px solid #fff;
background: $color-secondary;
box-shadow: 0 0 10px 0 #0000004d;
color: #fff;
&::after {
position: absolute;
top: 50%;
right: 0;
width: 35px;
height: 35px;
background: #fff;
content: "";
transform: translate(50%, -50%) rotate(45deg);
}
}
}
}

View file

@ -6,11 +6,11 @@
border: 0;
border-radius: 100%;
margin: 0 0.5rem;
background-color: #fff;
background-color: $color-tertiary;
transition: .3s;
&-active {
background-color: $color-success !important;
background-color: $color-secondary !important;
transform: scale(1.1);
}
}
@ -178,7 +178,19 @@
left: 1rem;
@include media-breakpoint-up(md) {
left: 8rem;
left: 2rem;
}
@include media-breakpoint-up(lg) {
left: 3rem;
}
@include media-breakpoint-up(xl) {
left: 4rem;
}
@include media-breakpoint-up(xxl) {
left: 6rem;
}
}
@ -186,7 +198,19 @@
right: 1rem;
@include media-breakpoint-up(md) {
right: 80rem;
left: 41rem;
}
@include media-breakpoint-up(lg) {
left: 43rem;
}
@include media-breakpoint-up(xl) {
left: 46rem;
}
@include media-breakpoint-up(xxl) {
left: 49rem;
}
}
}
@ -310,6 +334,27 @@
}
}
#swiper-recipes{
.swiper-pagination-bullet {
width: 1rem;
height: 1rem;
border: 0;
border-radius: 100%;
margin: 0 0.5rem;
background-color: $color-white;
transition: .3s;
&-active {
background-color: $color-secondary !important;
transform: scale(1.1);
}
&:hover {
cursor: pointer;
}
}
}
#news {
.news-wrapper {
@include media-breakpoint-up(xl) {
@ -348,9 +393,6 @@
}
}
.swiper-videos {
.swiper-slide {
transition: all .2s ease;

View file

@ -78,7 +78,7 @@
right: 30%;
width: 20rem;
transform: rotate(-2deg);
transition: top .5s,bottom .5s,transform .5s;
transition: top .5s, bottom .5s, transform .5s;
@include media-breakpoint-up(xxl) {
width: 24rem;
@ -159,6 +159,30 @@
left: 9rem;
}
}
.nut-val-left-col {
border-right: 1rem solid #fff;
}
.nut-val-row {
border-bottom: 0.4rem solid #fff;
&-odd {
--bs-table-bg: var(--bs-light) !important;
background-color: $color-light !important;
}
&-even {
--bs-table-bg: var(--bs-tertiary) !important;
background-color: $color-tertiary !important;
}
}
.nut-val-header {
--bs-table-bg: #fff !important;
}
}
&-commitments {

View file

@ -203,8 +203,9 @@ document.addEventListener("DOMContentLoaded", function () {
},
992: {
slidesPerView: 3,
initialSlide: 1,
slidesPerGroup: 3
initialSlide: 2,
slidesPerGroup: 3,
centeredSlides: false,
},
},
},
@ -470,10 +471,12 @@ document.addEventListener("DOMContentLoaded", function () {
spaceBetween: 40,
slidesPerView: 2,
centeredSlides: false,
slidesPerGroup: 2,
},
992: {
slidesPerView: 3,
centeredSlides: false,
slidesPerGroup: 3,
},
},

View file

@ -14,6 +14,7 @@ import "./partials/layout";
// CUSTOM
import "./base/common";
import "./pages/contact";
import "./pages/home";
// COOKIES

View file

@ -0,0 +1,54 @@
document.addEventListener("DOMContentLoaded", function () {
const checkedSubject = document.querySelector("input[name=\"type\"]:checked") as HTMLInputElement;
const typeRadioInputs = document.querySelectorAll("input[name=\"type\"]") as NodeListOf<HTMLInputElement>;
if (!document.querySelector("#contact-page") || !checkedSubject || !typeRadioInputs) {
return;
}
checkRadioChecked(checkedSubject.value);
for (const typeRadioInput of typeRadioInputs) {
typeRadioInput.addEventListener("change", (e: Event) => {
if (e.currentTarget instanceof HTMLInputElement) {
checkRadioChecked(e.currentTarget.value);
}
});
}
});
function checkRadioChecked(value: String) {
// Check if the current checked radio is equal to ContactFormType::complain.
displayComplainForm(value === "2");
}
function displayComplainForm(shouldDisplay: boolean) {
const complainForm = document.querySelector("#complain-part");
if (!complainForm) {
return;
}
const fileInputContainer = document.querySelector("#file-input") as HTMLInputElement;
if (!fileInputContainer) {
return;
}
const complainFormInputs = complainForm.querySelectorAll(".required-field") as NodeListOf<HTMLInputElement>;
const fileInputs = fileInputContainer.querySelectorAll(".required-field") as NodeListOf<HTMLInputElement>;
if (shouldDisplay) {
complainForm.classList.remove("d-none");
for (const complainFormInput of complainFormInputs) {
complainFormInput.required = true;
}
fileInputContainer.classList.remove("d-none");
} else {
complainForm.classList.add("d-none");
for (const complainFormInput of complainFormInputs) {
complainFormInput.required = false;
complainFormInput.value = "";
}
fileInputContainer.classList.add("d-none");
for (const fileInput of fileInputs) {
fileInput.value = "";
}
}
}

View file

@ -112,6 +112,32 @@
</div>
</fieldset>
</div>
{{-- COLOR --}}
<div class="col-12 col-lg-6 mb-3">
<fieldset class="border p-3">
<legend>{{ str(__('validation.custom.color'))->ucFirst() }}</legend>
<div class="row mb-3">
<div class="col-12">
@include('bo.partials.inputs.int-color', [
'id' => 'newsCategoryColor',
'label' => str(
__(':field :inter:model', [
'field' => __('validation.custom.color'),
'inter' => __('validation.custom.of_the.female'),
'model' => __('models.classes.news_category'),
]))->ucfirst(),
'name' => 'color',
'value' => "#268DC6",
'presetColors' => ["#268DC6", "#EE7C28", "#C6725C", "#0F437E", "#AC283D", "#FDC331", "#FFDB9A", "#063"],
'model' => $newsCategoryModel ?? null,
'required' => true,
'disabled' => false,
'helper' => __('crud.minicrud.text_warning'),
])
</div>
</div>
</fieldset>
</div>
{{-- PUBLICATION --}}
<div class="col-12 pb-3">
<fieldset class="h-100 border p-3">

View file

@ -6,6 +6,7 @@
'title' => str(__('validation.attributes.title'))->ucfirst(),
'slug' => str(__('validation.custom.slug'))->ucfirst(),
'published' => str(__('validation.custom.publication'))->ucfirst(),
'color' => str(__('validation.custom.color'))->ucfirst(),
'updated_at' => str(__('validation.attributes.updated_at'))->ucfirst(),
],
])
@ -19,6 +20,9 @@
'routeName' => 'news_categories',
'model' => $newsCategoryModel,
])
<td class="text-center align-middle">
<input type="color" value="{{ $newsCategoryModel->color }}" disabled>
</td>
<td class="text-center align-middle">
<span class="badge text-bg-secondary">{{ $newsCategoryModel->updated_at->isoFormat('LLLL') }}</span>
</td>

View file

@ -10,7 +10,7 @@
$data = [
'id' => $id,
'name' => $name,
'value' => $value ?? old($name, $model->{$name} ?? ''),
'value' => old($name, $model->{$name} ?? '') ?? $value,
'nullable' => !$required,
'disabled' => $disabled ?? false,
'presetColors' => $presetColors ?? ["#0f437e", "#4e3238", "#f3ebe4", "#1175b7", "#c7d400", "#fab7d8", "#063", "#e30513", "#ce6118", "#212425", "#fbf8f5"],

View file

@ -17,7 +17,7 @@
{{ $badge->label }}
</div>
@endif
<div class="d-flex justify-content-center align-items-center">
<div class="d-flex align-items-center">
@if (isset($badge) && !empty($picto))
<img class="img-fluid my-3 me-2" id="header-banner-picto" src="{{ $picto }}" title="{{ $picto_title }}"
alt="{{ $picto_alt }}">

View file

@ -124,7 +124,7 @@
</div>
<x-fo.breeding.sortant-a-l-exterieur-card href="{{ route('fo.breeding.oui-c-est-bon') }}" title="La démarche Oui c'est bon"
containerTitle="Découvrez aussi" btnText="Découvrez la démarche" :icon="[
containerTitle="Découvrez aussi" btnText="En savoir plus" :icon="[
'path' => 'resources/assets/images/breeding/oui-c-est-bon/logo-oui-c-est-bon.png',
'alt' => 'pictogramme oui c\'est bon',
'description' => 'pictogramme oui c\'est bon',

View file

@ -0,0 +1,23 @@
Cette personne à utilisé le formulaire de contact du site legaulois.fr
Personne :
{{ $data->civility->label() }}
{{ $data->lastname }} {{ $data->firstname }}
Adresse :
{{ $data->address }}
{{ $data->town }} {{ $data->postal }}
Contact :
{{ $data->phone }}
{{ $data->email }}
Sujet :
@if ($data->type->equals(\App\Enums\ContactFormType::nl))
{{ $data->onepagesubject }}
@else
{{ $data->type->label() }}
@endif
Message :
{!! $data->message !!}

View file

@ -0,0 +1,29 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Nouveau Contact Le Gaulois</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<h2>Cette personne à utilisé le formulaire de contact du site <a href="{{ URL::to('/') }}" title="Visiter legaulois.fr">legaulois.fr</a>
</h2>
<p><u>Personne</u>&nbsp;:&nbsp;</p>
<p>
{{ $data->civility->label() }}
{{ $data->lastname }} {{ $data->firstname }}
</p>
<p><u>Adresse</u>&nbsp;:&nbsp;</p>
<p>{{ $data->address }}</p>
<p>{{ $data->town }} {{ $data->postal }}</p>
<p><u>Contact</u>&nbsp;:&nbsp;</p>
<p><a href="tel:{{ $data->phone }}" title="{{ $data->phone }}">{{ $data->phone }}</a></p>
<p><a href="mailto:{{ $data->email }}" title="{{ $data->email }}">{{ $data->email }}</a></p>
<p><u>Sujet</u>&nbsp;:&nbsp;</p>
<p>{{ $data->type->equals(\App\Enums\ContactFormType::nl) ? $data->onepagesubject : $data->type->label() }}</p>
<p><u>Message</u>&nbsp;:&nbsp;</p>
<p>{!! nl2br(e($data->message)) !!}</p>
</body>
</html>

View file

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<PASS_MESSAGE>
<MESSAGE>
<EMETTEUR_MARQUE>{{ $data ['EMETTEUR_MARQUE'] }}</EMETTEUR_MARQUE>
<EMETTEUR_DATE>{{ $data ['EMETTEUR_DATE'] }}</EMETTEUR_DATE>
<EMETTEUR_HEURE>{{ $data ['EMETTEUR_HEURE'] }}</EMETTEUR_HEURE>
<EMETTEUR_CIVILITE>{{ $data ['EMETTEUR_CIVILITE'] }}</EMETTEUR_CIVILITE>
<EMETTEUR_NOM><![CDATA[{{ $data ['EMETTEUR_NOM'] }}]]></EMETTEUR_NOM>
<EMETTEUR_PRENOM><![CDATA[{{ $data ['EMETTEUR_PRENOM'] }}]]></EMETTEUR_PRENOM>
<EMETTEUR_ADRESSE1><![CDATA[{{ $data ['EMETTEUR_ADRESSE1'] }}]]></EMETTEUR_ADRESSE1>
<EMETTEUR_ADRESSE2><![CDATA[{{ $data ['EMETTEUR_ADRESSE2'] }}]]></EMETTEUR_ADRESSE2>
<EMETTEUR_CP><![CDATA[{{ $data ['EMETTEUR_CP'] }}]]></EMETTEUR_CP>
<EMETTEUR_VILLE><![CDATA[{{ $data ['EMETTEUR_VILLE'] }}]]></EMETTEUR_VILLE>
<EMETTEUR_PAYS>{{ $data ['EMETTEUR_PAYS'] }}</EMETTEUR_PAYS>
<EMETTEUR_EMAIL>{{ $data ['EMETTEUR_EMAIL'] }}</EMETTEUR_EMAIL>
<EMETTEUR_TELEPHONE><![CDATA[{{ $data ['EMETTEUR_TELEPHONE'] }}]]></EMETTEUR_TELEPHONE>
<EMETTEUR_OBJETDEMANDE>{{ $data ['EMETTEUR_OBJETDEMANDE'] }}</EMETTEUR_OBJETDEMANDE>
<EMETTEUR_NOM_PRODUIT><![CDATA[{{ $data ['EMETTEUR_NOM_PRODUIT'] }}]]></EMETTEUR_NOM_PRODUIT>
<EMETTEUR_EAN13><![CDATA[{{ $data ['EMETTEUR_EAN13'] }}]]></EMETTEUR_EAN13>
<EMETTEUR_DATE_LIMITE_CONSO>{{ $data ['EMETTEUR_DATE_LIMITE_CONSO'] }}</EMETTEUR_DATE_LIMITE_CONSO>
<EMETTEUR_NUM_LOT><![CDATA[{{ $data ['EMETTEUR_NUM_LOT'] }}]]></EMETTEUR_NUM_LOT>
<EMETTEUR_OPT_IN>{{ $data ['EMETTEUR_OPT_IN'] }}</EMETTEUR_OPT_IN>
<EMETTEUR_MESSAGE><![CDATA[{{ $data ['EMETTEUR_MESSAGE'] }}]]></EMETTEUR_MESSAGE>
<EMETTEUR_PJ><![CDATA[{{ $data ['EMETTEUR_PJ'] }}]]></EMETTEUR_PJ>
<REPONSE_ETAT>{{ $data ['REPONSE_ETAT'] }}</REPONSE_ETAT>
<TYPE_REPONSE>{{ $data ['TYPE_REPONSE'] }}</TYPE_REPONSE>
<DATE_CONSO>{{ $data ['DATE_CONSO'] }}</DATE_CONSO>
<LIEU_ACHAT><![CDATA[{{ $data ['LIEU_ACHAT'] }}]]></LIEU_ACHAT>
</MESSAGE>
</PASS_MESSAGE>

View file

@ -11,11 +11,6 @@
@include('fo.layouts.header')
</header>
<main @class([$__env->yieldContent('bg-color'), 'no-offset' => $__env->yieldContent('bg-offset')])>
<div class="row justify-content-center">
<div class="col col-md-6">
@include('modules.flash-messages')
</div>
</div>
@yield('content')
</main>
<footer>

View file

@ -13,7 +13,7 @@
{{ $badge->label }}
</div>
@endif
<div class="d-flex justify-content-center align-items-center">
<div class="d-flex align-items-center">
@if (isset($badge) && !empty($picto))
<img class="img-fluid my-3 me-2" id="header-banner-picto" src="{{ $picto }}" title="{{ $picto_title }}"
alt="{{ $picto_alt }}">

View file

@ -146,7 +146,6 @@
</div>
@include('fo.partials.commitments.paragraph-with-visual', [
'title' => __('2021, nos collaborateurs ont du cœur'),
'isVideoModal' => true,
'text' => nl2br(
__(
'Depuis 2021, Le Gaulois soutient l’:strongStartassociation Mécénat Chirurgie Cardiaque:strongEnd pour sauver des enfants en finançant des opérations d’enfants atteint de malformation cardiaque. :strongStartCourse à pied, vélo, marche, randonnée,:strongEnd les kilomètres parcourus sont compilés par une application sportive tout au long de l’année pour récolter un maximum de fonds pour l’association.',

View file

@ -0,0 +1,466 @@
@extends('fo.layout', ['brParam' => $staticPageModel])
@section('title', $staticPageModel->seo_title)
@section('description', $staticPageModel->seo_description)
@section('breadcrumb', request()->route()->getName())
@section('bg-offset', true)
@section('content')
{{-- HEADER --}}
@include('fo.layouts.header-banner')
<section class="container mb-3 mt-5 pt-5" id="contact-page">
<div class="row mb-5">
<div class="col ck-content">
{!! clean($staticPageModel->content) !!}
</div>
</div>
<div class="border-3 border-primary fw-bold rounded-4 cn-cookie-activate d-flex flex-column flex-md-row mx-3 mb-3 mt-4 border bg-white p-3"
data-cookie-name="recaptcha">
<span class="d-flex flex-column w-md-75 w-100">
<strong class="text-underline text-bold text-primary fs-5 mb-2">
{{ __('cookie-notice.custom.recaptcha.warning') }}
</strong>
<br><br>
<small class="text-success fw-normal fs-5">
{{ __('Acceptez ce cookie pour utiliser les formulaires') }}
</small>
</span>
<button class="btn btn-primary w-lg-25 h-50 fs-5 rounded-4 mt-md-0 mt-3" id="recaptcha-accept">{{ __('AUTORISER') }}</button>
</div>
{{-- CONTACT FORM --}}
<form id="contact-form" method="POST" action="{{ route('fo.contact.submit') }}" enctype="multipart/form-data" inert>
@csrf
<div class="row">
<div class="col-12 col-xl-4">
<div class="row">
<div class="col-12">
<h2 class="fs-3">{{ __('Objet de votre demande') }}&nbsp;*</h2>
</div>
{{-- SUBJECT --}}
@foreach (\App\Enums\ContactFormType::getContactTypes() as $contactType)
<div class="col-12 col-sm-6 col-xl-12 mb-3 h-auto">
<div class="radio-subject position-relative h-100">
<input id="{{ $contactType->name }}" name="type" type="radio" value="{{ $contactType->value }}"
@checked($loop->first || $contactType->value === intval(old('type'))) />
<label class="d-flex align-items-center" for="{{ $contactType->name }}">
<img class="me-3" src="{{ Vite::asset($contactType->picto()['src']) }}"
alt="{{ $contactType->picto()['alt'] }}">
<p class="mb-0 h-fit">
<span class="text-uppercase f-bourton-base fs-3">{{ $contactType->label() }}&nbsp;?</span><br>
{{ $contactType->description() }}
</p>
</label>
</div>
</div>
@endforeach
<div class="col-12">
<p class="opacity-50">
{{ __('Les informations qui vous concernent sont destinées à un usage interne et ne seront en aucun cas transmises à des tiers. Vous disposez d\'un droit d\'accès, de modification, de rectification et de suppression des données qui vous concernent (art. 34 de la loi "Informatique et Libertés").') }}
</p>
</div>
</div>
</div>
<div class="col-12 col-xl-8">
<div class="row">
<div class="col-12">
<h2 class="my-3">{{ __('Je souhaite rédiger une réclamation') }}</h2>
{{-- FLASH MESSAGES --}}
<div class="row justify-content-center">
<div class="col">
@include('modules.flash-messages')
</div>
</div>
<h3>{{ __('Mes coordonnées') }}</h3>
<p class="fw-bold">*{{ __('Champs obligatoires') }}</p>
</div>
<div class="col-12 d-flex">
<p class="col-form-label ms-3 py-0 pe-3">{{ __('validation.custom.civility') }}</p>
@foreach (\App\Enums\ContactFormCivility::toArray() as $contactCivility)
<div class="form-check form-check-inline">
<input class="form-check-input" id="{{ $contactCivility->name }}" name="civility" type="radio"
value="{{ $contactCivility->value }}" @checked($loop->first || $contactCivility->value === intval(old('civility'))) />
<label class="form-check-label" for="{{ $contactCivility->name }}">
{{ $contactCivility->label }}
</label>
</div>
@endforeach
</div>
{{-- LAST NAME --}}
<div class="col-12 col-sm-6">
<label class="col-form-label ms-3 pb-1" for="lastname">
{{ __('validation.attributes.last_name') }}&nbsp;*
</label>
<input id="lastname" name="lastname" type="text" value="{{ old('lastname', '') }}"
aria-describedby="lastnameHelp" @class(['form-control', 'is-invalid' => $errors->has('lastname')]) placeholder="{{ __('ex : Dupont') }}" required>
@include('modules.input-error', [
'inputName' => 'lastname',
'helperId' => 'lastnameHelp',
])
</div>
{{-- FIRST NAME --}}
<div class="col-12 col-sm-6">
<label class="col-form-label ms-3 pb-1" for="firstname">
{{ __('validation.attributes.first_name') }}&nbsp;*
</label>
<input id="firstname" name="firstname" type="text" value="{{ old('firstname', '') }}"
aria-describedby="firstnameHelp" @class(['form-control', 'is-invalid' => $errors->has('firstname')]) placeholder="{{ __('ex : Martin') }}" required>
@include('modules.input-error', [
'inputName' => 'firstname',
'helperId' => 'firstnameHelp',
])
</div>
{{-- ADDRESS --}}
<div class="col-12">
<label class="col-form-label ms-3 pb-1" for="address">
{{ __('validation.attributes.address') }}&nbsp;*
</label>
<input id="address" name="address" type="text" value="{{ old('address', '') }}" aria-describedby="addressHelp"
@class(['form-control', 'is-invalid' => $errors->has('address')]) placeholder="{{ __('ex : 1 rue du poulet') }}" required>
@include('modules.input-error', [
'inputName' => 'address',
'helperId' => 'addressHelp',
])
</div>
{{-- POSTCODE --}}
<div class="col-12 col-sm-6">
<label class="col-form-label ms-3 pb-1" for="postal">
{{ __('validation.attributes.postal_code') }}&nbsp;*
</label>
<input id="postal" name="postal" type="text" value="{{ old('postal', '') }}" aria-describedby="postalHelp"
@class(['form-control', 'is-invalid' => $errors->has('postal')]) placeholder="{{ __('ex : 44 100') }}" required>
@include('modules.input-error', [
'inputName' => 'postal',
'helperId' => 'postalHelp',
])
</div>
{{-- CITY --}}
<div class="col-12 col-sm-6">
<label class="col-form-label ms-3 pb-1" for="town">
{{ __('validation.custom.town') }}&nbsp;*
</label>
<input id="town" name="town" type="text" value="{{ old('town', '') }}" aria-describedby="townHelp"
@class(['form-control', 'is-invalid' => $errors->has('town')]) placeholder="{{ __('ex : Nantes') }}" required>
@include('modules.input-error', [
'inputName' => 'town',
'helperId' => 'townHelp',
])
</div>
{{-- EMAIL --}}
<div class="col-12">
<label class="col-form-label ms-3 pb-1" for="email">
{{ __('validation.attributes.email') }}&nbsp;*
</label>
<input id="email" name="email" type="email" value="{{ old('email', '') }}"
aria-describedby="emailHelp" @class(['form-control', 'is-invalid' => $errors->has('email')]) placeholder="{{ __('ex : monadresse@mail.com') }}"
required>
@include('modules.input-error', [
'inputName' => 'email',
'helperId' => 'emailHelp',
])
</div>
{{-- PHONE --}}
<div class="col-12">
<label class="col-form-label ms-3 pb-1" for="phone">
{{ __('validation.attributes.phone') }}&nbsp;*
</label>
<input id="phone" name="phone" type="tel" value="{{ old('phone', '') }}"
aria-describedby="phoneHelp" @class(['form-control', 'is-invalid' => $errors->has('phone')]) placeholder="{{ __('ex : 06 00 00 00 00') }}"
required>
@include('modules.input-error', [
'inputName' => 'phone',
'helperId' => 'phoneHelp',
])
</div>
</div>
{{-- PRODUCT COMPLAIN (displayed only if subject 'complain' is checked) --}}
<div class="row d-none" id="complain-part">
<div class="col-12">
<h3 class="mt-4">{{ __('Réclamation produit') }}</h3>
</div>
{{-- PRODUCT NAME --}}
<div class="col-12 col-sm-6">
<label class="col-form-label ms-3 pb-1" for="productName">
{{ __('validation.custom.product_name') }}&nbsp;*
</label>
<input id="productName" name="productName" type="text" value="{{ old('productName', '') }}"
aria-describedby="productNameHelp" @class([
'form-control required-field',
'is-invalid' => $errors->has('productName'),
])
placeholder="{{ __('ex : Escalope de poulet') }}" required>
@include('modules.input-error', [
'inputName' => 'productName',
'helperId' => 'productNameHelp',
])
</div>
{{-- PLACE OF PURCHASE --}}
<div class="col-12 col-sm-6">
<label class="col-form-label ms-3 pb-1" for="buyPlace">
{{ __('validation.custom.place_of_purchase') }}&nbsp;*
</label>
<input id="buyPlace" name="buyPlace" type="text" value="{{ old('buyPlace', '') }}"
aria-describedby="buyPlaceHelp" @class([
'form-control required-field',
'is-invalid' => $errors->has('buyPlace'),
]) placeholder="{{ __('ex : Nantes') }}" required>
@include('modules.input-error', [
'inputName' => 'buyPlace',
'helperId' => 'buyPlaceHelp',
])
</div>
{{-- BARCODE --}}
<div class="col-12 col-sm-6">
<label class="col-form-label ms-3 pb-1" for="productCode">
{{ __('validation.custom.barcode') }}&nbsp;*
</label>
<input id="productCode" name="productCode" type="text" value="{{ old('productCode', '') }}"
aria-describedby="productCodeHelp" @class([
'form-control required-field',
'is-invalid' => $errors->has('productCode'),
]) placeholder="{{ __('ex : 0000000000000') }}"
required>
@include('modules.input-error', [
'inputName' => 'productCode',
'helperId' => 'productCodeHelp',
])
</div>
{{-- BATCH NUMBER --}}
<div class="col-12 col-sm-6">
<label class="col-form-label ms-3 pb-1" for="lotNumberName">
{{ __('validation.custom.batch_number') }}&nbsp;*
</label>
<input id="lotNumberName" name="lotNumberName" type="text" value="{{ old('lotNumberName', '') }}"
aria-describedby="lotNumberNameHelp" @class([
'form-control required-field',
'is-invalid' => $errors->has('lotNumberName'),
])
placeholder="{{ __('ex : 0000000000000') }}" required>
@include('modules.input-error', [
'inputName' => 'lotNumberName',
'helperId' => 'lotNumberNameHelp',
])
</div>
<div class="col-12 d-flex align-items-center">
<img class="me-3" src="{{ Vite::asset('resources/assets/images/contact/info.svg') }}"
alt="{{ __('Pictogramme "information"') }}">
<p class="my-3">
{{ __('Vous pouvez également trouver le n° de lot sous la dénomination "n° de traçabilité" ou "n° trac" ou à côté de la date.') }}
</p>
</div>
{{-- USE-BY DATE --}}
<div class="col-12 col-sm-6">
<p class="col-form-label pb-0">{{ __('validation.custom.use_by_date') }}&nbsp;*</p>
<div class="row">
{{-- DAY --}}
<div class="col-4">
<label class="col-form-label ms-3 pb-1" for="limitDateDay">
{{ __('validation.attributes.day') }}&nbsp;*
</label>
<input id="limitDateDay" name="limitDateDay" type="number" value="{{ old('limitDateDay', '') }}"
aria-describedby="limitDateDayHelp" @class([
'form-control required-field',
'is-invalid' => $errors->has('limitDateDay'),
]) min="1" max="31"
placeholder="{{ __('jj') }}" required>
@include('modules.input-error', [
'inputName' => 'limitDateDay',
'helperId' => 'limitDateDayHelp',
])
</div>
{{-- MONTH --}}
<div class="col-4">
<label class="col-form-label ms-3 pb-1" for="limitDateMonth">
{{ __('validation.attributes.month') }}&nbsp;*
</label>
<input id="limitDateMonth" name="limitDateMonth" type="number" value="{{ old('limitDateMonth', '') }}"
aria-describedby="limitDateMonthHelp" @class([
'form-control required-field',
'is-invalid' => $errors->has('limitDateMonth'),
]) min="1" max="12"
placeholder="{{ __('mm') }}" required>
@include('modules.input-error', [
'inputName' => 'limitDateMonth',
'helperId' => 'limitDateMonthHelp',
])
</div>
{{-- YEAR --}}
<div class="col-4">
<label class="col-form-label ms-3 pb-1" for="limitDateYear">
{{ __('validation.attributes.year') }}&nbsp;*
</label>
<input id="limitDateYear" name="limitDateYear" type="number" value="{{ old('limitDateYear', '') }}"
aria-describedby="limitDateYearHelp" @class([
'form-control required-field',
'is-invalid' => $errors->has('limitDateYear'),
]) min="2010" max="2050"
placeholder="{{ __('aaaa') }}" required>
@include('modules.input-error', [
'inputName' => 'limitDateYear',
'helperId' => 'limitDateYearHelp',
])
</div>
</div>
</div>
{{-- CONSUMPTION DATE --}}
<div class="col-12 col-sm-6">
<p class="col-form-label pb-0">{{ __('validation.custom.consumption_date') }}&nbsp;*</p>
<div class="row">
{{-- DAY --}}
<div class="col-4">
<label class="col-form-label ms-3 pb-1" for="consumptionDateDay">
{{ __('validation.attributes.day') }}&nbsp;*
</label>
<input id="consumptionDateDay" name="consumptionDateDay" type="number"
value="{{ old('consumptionDateDay', '') }}" aria-describedby="consumptionDateDayHelp"
@class([
'form-control required-field',
'is-invalid' => $errors->has('consumptionDateDay'),
]) min="1" max="31" placeholder="{{ __('jj') }}"
required>
@include('modules.input-error', [
'inputName' => 'consumptionDateDay',
'helperId' => 'consumptionDateDayHelp',
])
</div>
{{-- MONTH --}}
<div class="col-4">
<label class="col-form-label ms-3 pb-1" for="consumptionDateMonth">
{{ __('validation.attributes.month') }}&nbsp;*
</label>
<input id="consumptionDateMonth" name="consumptionDateMonth" type="number"
value="{{ old('consumptionDateMonth', '') }}" aria-describedby="consumptionDateMonthHelp"
@class([
'form-control required-field',
'is-invalid' => $errors->has('consumptionDateMonth'),
]) min="1" max="12" placeholder="{{ __('mm') }}"
required>
@include('modules.input-error', [
'inputName' => 'consumptionDateMonth',
'helperId' => 'consumptionDateMonthHelp',
])
</div>
{{-- YEAR --}}
<div class="col-4">
<label class="col-form-label ms-3 pb-1" for="consumptionDateYear">
{{ __('validation.attributes.year') }}&nbsp;*
</label>
<input id="consumptionDateYear" name="consumptionDateYear" type="number"
value="{{ old('consumptionDateYear', '') }}" aria-describedby="consumptionDateYearHelp"
@class([
'form-control required-field',
'is-invalid' => $errors->has('consumptionDateYear'),
]) min="2010" max="2050" placeholder="{{ __('aaaa') }}"
required>
@include('modules.input-error', [
'inputName' => 'consumptionDateYear',
'helperId' => 'consumptionDateYearHelp',
])
</div>
</div>
</div>
</div>
<div class="row">
{{-- MESSAGE --}}
<div class="col-12">
<label class="col-form-label ms-3 pb-1" for="message">
{{ __('validation.custom.message') }}&nbsp;*
</label>
<textarea id="message" name="message" type="date" value="{{ old('message', '') }}" aria-describedby="messageHelp"
@class(['form-control', 'is-invalid' => $errors->has('message')]) placeholder="{{ __('Votre message ...') }}" required> </textarea>
@include('modules.input-error', [
'inputName' => 'message',
'helperId' => 'messageHelp',
])
</div>
{{-- FILE --}}
<div class="col-12 d-none" id="file-input">
<label class="col-form-label ms-3 pb-1" for="joinFile">
{{ __('validation.custom.attachment') }}
</label>
<input id="joinFile" name="joinFile" type="file" value="{{ old('joinFile', '') }}"
aria-describedby="joinFileHelp" @class([
'form-control required-field',
'is-invalid' => $errors->has('joinFile'),
])
accept=".jpg,.jpeg,.png,.pdf, image/jpg,image/png,application/pdf">
@include('modules.input-error', [
'inputName' => 'joinFile',
'helperId' => 'joinFileHelp',
'helper' => clean(__('Formats acceptés : jpg, png, pdf<br>Taille max : 2 Mo')),
])
</div>
</div>
<div class="row">
<div class="col-12">
{{-- RECAPTCHA --}}
<div class="d-flex flex-column justify-content-center align-items-center my-3">
<div id="r_captcha"></div>
@if ($errors->has('g-recaptcha-response'))
<div class="mb-6">
<span class="text-danger">{{ $errors->first('g-recaptcha-response') }}</span>
</div>
@endif
</div>
{{-- SUBMIT BUTTON --}}
<div class="d-flex justify-content-end">
<button class="g-recaptcha btn btn-secondary ms-auto"
data-sitekey="{{ config('services.google.recaptcha.public') }}" type="submit">
{{ __('Envoyer') }}
</button>
</div>
<p class="my-3 text-end">
{{ __('Pour envoyer votre formulaire, vous devez accepter les') }}
<a class="cn-cookie-link" href="#">{{ __('cookies.') }}</a><br>
{{ __('Les informations recueillies à partir de ce formulaire sont traitées par le Responsable de Traitement pour donner suite à votre demande de contact. Pour connaître vos droits, vous pouvez consulter la') }}
<a href="{{ route('fo.policies.show') }}">{{ __('Charte de confidentialité.') }}</a>
</p>
</div>
</div>
</div>
</div>
</form>
</section>
@endsection
@push('scripts')
<script nonce="{{ $nonce }}">
document.addEventListener('DOMContentLoaded', function() {
const initFormCaptcha = () => {
const newDiv = document.createElement("div");
newDiv.classList.add('g-recaptcha');
document.getElementById('r_captcha').appendChild(newDiv);
document.getElementById('contact-form').inert = true;
};
window.__CN_Recaptcha = function() {
initFormCaptcha();
(function(d, t) {
let g = d.createElement(t),
s = d.getElementsByTagName(t)[0];
g.src = 'https://www.google.com/recaptcha/api.js?render=explicit&onload=__CN_Recaptcha_Loaded';
s.parentNode.insertBefore(g, s)
}(document, 'script'));
};
window.__CN_Recaptcha_Reset = function() {
document.querySelector('#r_captcha > div')?.remove();
};
window.__CN_Recaptcha_Loaded = function() {
document.getElementById('contact-form').inert = false;
grecaptcha.render(document.querySelector('#r_captcha > div'), {
'sitekey': '{{ config('services.google.recaptcha.public') }}'
});
};
document.getElementById("recaptcha-accept").addEventListener('click', function(e) {
e.preventDefault();
window.WebCookieNotice.acceptCookie('recaptcha');
});
});
</script>
@endpush

View file

@ -248,6 +248,7 @@
@include('fo.partials.news-card', ['newsModel' => $newsModels->first()])
</div>
</div>
@if (count($newsModels->slice(1, 3)))
{{-- 3 horizontal cards (only for devices with width > md size) --}}
<div class="d-none d-md-flex flex-column justify-content-around col-md-6 col-xl-4">

View file

@ -159,7 +159,6 @@
</div>
{{-- PRODUCTTYPE/DEMARCHE MOBILE --}}
@if (count($productModel->productTypes))
{{-- CHANGEME: quand la relation product<->productType aura été changé, adapter cette partie --}}
<div class="col-12 d-xl-none product-paper-demarche">
<button
class="product-collapse-btn btn btn-white position-relative fs-4 w-100 collapsed border-0 bg-transparent px-0 text-start shadow-none"
@ -170,14 +169,15 @@
title="{{ $productModel->productTypes[0]->picture_title }}"
alt="{{ $productModel->productTypes[0]->picture_alt }}">
</button>
<div class="collapse" id="productDemarcheMobile">
{{-- Not displaying the button before we know more about it --}}
{{-- <div class="collapse" id="productDemarcheMobile">
{!! clean($productModel->productTypes[0]->description) !!}
@if ($productModel->productTypes[0]->button_status)
<a class="btn btn-secondary" href="#FIXME:wait feeback JM">
{{ __('En savoir plus') }}
</a>
@endif
</div>
</div> --}}
</div>
@elseif ($productModel->approach)
<div class="col-12 d-xl-none product-paper-demarche">
@ -223,11 +223,12 @@
<div class="mb-3">
{!! clean($productModel->productTypes[0]->description) !!}
</div>
@if ($productModel->productTypes[0]->button_status)
{{-- Not displaying the button before we know more about it --}}
{{-- @if ($productModel->productTypes[0]->button_status)
<a class="btn btn-secondary" href="#FIXME:wait feeback JM">
{{ __('En savoir plus') }}
</a>
@endif
@endif --}}
</div>
</div>
</div>
@ -244,9 +245,11 @@
{{ $productModel->approach->name }}
</div>
@if ($productModel->approach->button_url)
<div class="text-center">
<a class="btn btn-secondary" href="{{ $productModel->approach->button_url }}">
{{ __('En savoir plus') }}
</a>
</div>
@endif
</div>
</div>
@ -255,7 +258,6 @@
</div>
</div>
{{-- PRODUCT INGREDIENT MODAL --}}
{{-- FIXME: add nutritionnal values when it's ready --}}
<div class="modal fade" id="productIngredients" aria-labelledby="productIngredientsLabel" aria-hidden="true" tabindex="-1">
<div class="modal-dialog modal-dialog-centered modal-lg px-4">
<div class="modal-content border-0 bg-transparent">
@ -267,8 +269,72 @@
<div class="modal-body line-border-small bg-white">
<p class="f-bourton-base fs-5 mb-2">{{ __('ingrédients') }}</p>
{!! clean($productModel->ingredients) !!}
{{-- FIXME: show nutritionnal values of the product --}}
{{-- <p class="f-bourton-base fs-5 mb-2">{{ __('valeurs nutritionnelles') }}</p> --}}
@if ($nutritionnalValueModel)
<p class="f-bourton-base fs-5 mb-2 mt-3">{{ __('valeurs nutritionnelles') }}</p>
<table class="table-borderless table">
<thead class="nut-val-header bg-transparent">
<tr>
<th></th>
<th class="f-bourton-base text-secondary text-center">{{ __('Pour 100g') }}</th>
</tr>
</thead>
<tbody>
<tr class="nut-val-row nut-val-row-odd">
<td class="text-secondary fw-bold nut-val-left-col align-middle">
{{ str(__('validation.custom.nutritionnal_values.energy'))->ucfirst() }}</td>
<td class="text-secondary text-center">
<p class="mb-0">{{ $nutritionnalValueModel->energy_percent }} {{ __('Kcal') }}</p>
<p class="mb-0">{{ $nutritionnalValueModel->energy_kj_percent }} {{ __('Kj') }}</p>
</td>
</tr>
<tr class="nut-val-row nut-val-row-even">
<td class="text-secondary fw-bold nut-val-left-col align-middle">
{{ str(__('validation.custom.nutritionnal_values.proteins'))->ucfirst() }}</td>
<td class="text-secondary text-center">
<p class="mb-0">{{ $nutritionnalValueModel->protein_percent }} {{ __('g') }}</p>
</td>
</tr>
<tr class="nut-val-row nut-val-row-odd">
<td class="text-secondary nut-val-left-col align-middle">
<p class="fw-bold mb-0">
{{ str(__('validation.custom.nutritionnal_values.carbohydrates'))->ucfirst() }}</p>
<p class="mb-0">{{ __('validation.custom.nutritionnal_values.sugar') }}</p>
</td>
<td class="text-secondary text-center">
<p class="mb-0">{{ $nutritionnalValueModel->carbohydrates_percent }} {{ __('g') }}
</p>
<p class="mb-0">{{ $nutritionnalValueModel->sugar_percent }} {{ __('g') }}</p>
</td>
</tr>
<tr class="nut-val-row nut-val-row-even">
<td class="text-secondary nut-val-left-col align-middle">
<p class="fw-bold mb-0">
{{ str(__('validation.custom.nutritionnal_values.fat'))->ucfirst() }}</p>
<p class="mb-0">
{{ __('validation.custom.nutritionnal_values.saturated_fatty_acids') }}</p>
</td>
<td class="text-secondary text-center">
<p class="mb-0">{{ $nutritionnalValueModel->fat_percent }} {{ __('g') }}</p>
<p class="mb-0">{{ $nutritionnalValueModel->saturated_fatty_acids_percent }}
{{ __('g') }}</p>
</td>
</tr>
<tr class="nut-val-row nut-val-row-odd">
<td class="text-secondary fw-bold nut-val-left-col align-middle">
{{ str(__('validation.custom.nutritionnal_values.dietary_fiber'))->ucfirst() }}</td>
<td class="text-secondary text-center">{{ $nutritionnalValueModel->dietary_fiber_percent }}
{{ __('g') }}
</td>
</tr>
<tr class="nut-val-row-even">
<td class="text-secondary fw-bold nut-val-left-col align-middle">
{{ str(__('validation.custom.nutritionnal_values.salt'))->ucfirst() }}</td>
<td class="text-secondary text-center">{{ $nutritionnalValueModel->salt_percent }}
{{ __('g') }}</td>
</tr>
</tbody>
</table>
@endif
</div>
</div>
</div>
@ -307,7 +373,8 @@
data-aos-delay="1250" data-aos-once="true" data-aos-anchor-placement="bottom-bottom">
<div class="part-left rotate-left">
<img class="img-fluid rotate-right" src="{{ asset($recipeModel->consumptionMoment->picture) }}"
title="{{ $recipeModel->consumptionMoment->picture_title }}" alt="{{ $recipeModel->consumptionMoment->picture_alt }}">
title="{{ $recipeModel->consumptionMoment->picture_title }}"
alt="{{ $recipeModel->consumptionMoment->picture_alt }}">
<div class="rotate-custom ps-4 pt-5">
<p class="h3 fs-5 text-center">
{{ $staticPageModel->data['animation_text_1']['value'] }}

View file

@ -175,7 +175,7 @@
'btnRouteName' => $recipeModel->products->first()->meat
? route('fo.recipes.search.meat', $recipeModel->products->first()->meat->slug)
: route('fo.recipes.search.submeat', [
'model_slug' => $recipeModel->products->first()->submeat->meat->slug,
'model_slug' => $recipeModel->products->first()->submeat->first()->meat->slug,
'child_slug' => $recipeModel->products->first()->submeat->slug,
]),
'btnRouteTitle' => 'nos recettes',

View file

@ -196,7 +196,7 @@
</div>
<div class="col-7">
<div @class([
'bottom-left-shadow w-fit',
'bottom-left-shadow w-100',
$loop->iteration % 2 ? 'rotate-right' : 'rotate-left',
])>
@if (isset($historicalMilestoneModel->picto) && $historicalMilestoneModel->picto !== null)

View file

@ -51,7 +51,7 @@
@endif
<div class="bottom-left-shadow">
<div @class([
'bg-white line-border-small w-100 h-fit mx-auto m-lg-0 p-2',
'bg-white line-border-small w-100 mx-auto m-lg-0 p-2',
'rotate-left' => isset($reversed) && $reversed,
'rotate-right' => !isset($reversed),
'h-100' => isset($video_id),
@ -68,7 +68,7 @@
</div>
@endif
<div class="position-relative w-100 h-fit">
<div class="position-relative w-100 h-100">
@if (isset($video_id) && \App\Rules\IsValidYoutubeId::validates($video_id))
@if (isset($isVideoModal) && isset($video_id) && \App\Rules\IsValidYoutubeId::validates($video_id))
<div class="position-absolute z-1 w-100 h-100 start-0 top-0 bg-transparent" data-bs-toggle="modal"

View file

@ -14,7 +14,7 @@
<div class="d-flex align-items-center mb-2">
{{-- TAG --}}
<div class="news-tag text-uppercase f-bourton-base fs-5 me-3 px-2 py-1"
style="background-color: #1175b7; color: #fff">
style="background-color: {{ $newsModel->newsCategory->color }}; color: #fff">
{{ $newsModel->newsCategory->title }}
</div>
{{-- DATE --}}

View file

@ -14,7 +14,10 @@
<img class="w-100 line-border-small" src="{{ asset($newsModel->thumbnail) }}" title="{{ $newsModel->thumbnail_title }}"
alt="{{ $newsModel->thumbnail_alt }}">
<div class="position-absolute tag">
<p class="text-uppercase rotate-left text-bg-quad f-bourton-base fs-5 px-2 py-1">{{ $newsModel->newsCategory->title }}</p>
<p class="text-uppercase rotate-left f-bourton-base fs-5 px-2 py-1"
style="background-color: {{ $newsModel->newsCategory->color }}; color: #fff">
{{ $newsModel->newsCategory->title }}
</p>
</div>
</div>
<div class="card-body p-4">

View file

@ -5,5 +5,5 @@
@endif
@if (isset($helper))
<small class="form-text text-body-secondary"
@if (isset($helperId)) id="{{ $helperId }}" @endif>{{ $helper }}</small>
@if (isset($helperId)) id="{{ $helperId }}" @endif>{!! clean($helper) !!}</small>
@endif

View file

@ -185,7 +185,7 @@ function () {
[...document.getElementsByClassName('cn-cookie-activate')].forEach(function(el) {
if (el.dataset.cookieName === 'recaptcha') {
el.classList.add('d-none');
document.getElementById('listen-form').inert = false;
document.getElementById('contact-form').inert = false;
}
});
}
@ -198,7 +198,7 @@ function () {
[...document.getElementsByClassName('cn-cookie-activate')].forEach(function(el) {
if (el.dataset.cookieName === 'recaptcha') {
el.classList.remove('d-none');
document.getElementById('listen-form').inert = true;
document.getElementById('contact-form').inert = true;
}
});
}

View file

@ -177,3 +177,9 @@ Breadcrumbs::for('fo.40-ans', function (Generator $trail) {
$trail->parent('fo.home.show');
$trail->push(Str::of(trans('40years.breadcrumb'))->ucfirst(), route('fo.40-ans'));
});
// * CONTACT
Breadcrumbs::for('fo.contact.show', function (Generator $trail) {
$trail->parent('fo.home.show');
$trail->push(Str::of(__('Contactez-nous'))->ucfirst(), route('fo.contact.show'));
});

View file

@ -2,6 +2,7 @@
use App\Http\Controllers\Fo\BreedingController;
use App\Http\Controllers\Fo\BreederController;
use App\Http\Controllers\Fo\ContactController;
use App\Http\Controllers\Fo\ExportController;
use App\Http\Controllers\Fo\HomeController;
use App\Http\Controllers\Fo\NewsController;
@ -30,7 +31,10 @@ Route::middleware(['throttle:web'])->name('fo.')->group(function () {
Route::get('/charte-de-confidentialite', [StaticPageController::class, 'policies'])->name('policies.show');
Route::get('/conditions-generales-de-vente', [StaticPageController::class, 'selling'])->name('conditions.show');
Route::get('/accessibilite', [StaticPageController::class, 'accessibility'])->name('accessibility.show');
Route::get('/contact', [StaticPageController::class, 'contact'])->name('contact.show');
// * CONTACT
Route::get('/contact', [ContactController::class, 'contact'])->name('contact.show');
Route::post('/contactSubmit', [ContactController::class, 'contactSubmit'])->name('contact.submit');
// * BRAND
Route::prefix('marque')