4 Commits

Author SHA1 Message Date
Valentin Lab
6fee40c55c new: make all forms have a cancel/save button on the top also 2025-12-13 21:17:07 +01:00
Valentin Lab
f5d21fe083 fix: prevent error 500 upon displaying empty 'Rayons' 2025-12-13 21:05:08 +01:00
Valentin Lab
2fe818e8b4 fix: make 'Rayons' title adaptable to screen width 2025-12-13 20:55:07 +01:00
Valentin Lab
0ec13e802e new: make the menu visible on mobile 2025-12-13 20:10:42 +01:00
43 changed files with 98 additions and 678 deletions

View File

@@ -47,10 +47,6 @@ class CustomerOrdersDataTable extends DataTable
{
$datatables
->editColumn('status', function (Order $order) {
if ($order->status == 0 && in_array($order->payment_type, [2, 3])) {
return 'En attente de règlement';
}
return Orders::getStatus($order->status);
})
->editColumn('created_at', function (Order $order) {

View File

@@ -63,17 +63,6 @@ class ArticleController extends Controller
return view('Admin.Shop.Articles.edit', $data);
}
public function duplicate($id)
{
$data = Articles::getFull($id);
// Prepare for creation: blank id/slug, tweak name to indicate copy
$data['article']['id'] = null;
$data['article']['slug'] = null;
$data['article']['name'] = ($data['article']['name'] ?? '').' (copie)';
return view('Admin.Shop.Articles.create', $data);
}
public function destroy($id)
{
return Articles::destroy($id);

View File

@@ -68,10 +68,8 @@ class BasketController extends Controller
public function getBasketTotal($deliveryId = false, $deliveryTypeId = false)
{
$basket = Baskets::getBasketTotal($deliveryId, $deliveryTypeId);
$data = [
'basket' => $basket,
'sale_channel' => $basket['sale_channel'] ?? null,
'basket' => Baskets::getBasketTotal($deliveryId, $deliveryTypeId),
];
return view('Shop.Baskets.partials.basketTotal', $data);

View File

@@ -8,7 +8,6 @@ use App\Repositories\Shop\CustomerAddresses;
use App\Repositories\Shop\Customers;
use App\Repositories\Shop\Offers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\Validator;
@@ -118,82 +117,9 @@ class CustomerController extends Controller
public function store(Request $request)
{
$data = $request->all();
$validator = Validator::make($data, [
'phone' => 'required|max:30',
], [
'phone.required' => __('Le numéro de téléphone est obligatoire.'),
]);
if ($validator->fails()) {
return redirect()->route('Shop.Customers.edit')
->withInput()
->withErrors($validator->errors(), 'registration');
}
$passwordError = $this->handlePasswordChange($request);
if ($passwordError) {
return redirect()->route('Shop.Customers.edit')
->with('growl', [$passwordError, 'danger']);
}
unset($data['current-password'], $data['new-password'], $data['new-password_confirmation']);
$customer = Customers::storeFull($data);
$growl = $request->filled('new-password')
? [__('Profil et mot de passe mis à jour.'), 'success']
: [__('Profil mis à jour.'), 'success'];
return redirect()->route('Shop.Customers.edit')->with('growl', $growl);
}
protected function handlePasswordChange(Request $request)
{
if (! $request->filled('new-password')) {
return null;
}
$customer = Customers::get(Customers::getId());
if (! $customer) {
return __('Impossible de modifier le mot de passe.');
}
if (! Hash::check($request->input('current-password'), $customer->password)) {
return __('Le mot de passe actuel est incorrect.');
}
if ($request->input('new-password') !== $request->input('new-password_confirmation')) {
return __('Les mots de passe ne correspondent pas.');
}
$newPassword = $request->input('new-password');
if (strlen($newPassword) < 8) {
return __('Le mot de passe doit contenir au moins 8 caractères.');
}
if (! preg_match('/[a-z]/', $newPassword)) {
return __('Le mot de passe doit contenir au moins une lettre minuscule.');
}
if (! preg_match('/[A-Z]/', $newPassword)) {
return __('Le mot de passe doit contenir au moins une lettre majuscule.');
}
if (! preg_match('/[0-9]/', $newPassword)) {
return __('Le mot de passe doit contenir au moins un chiffre.');
}
if (! preg_match('/[^A-Za-z0-9]/', $newPassword)) {
return __('Le mot de passe doit contenir au moins un caractère spécial.');
}
$customer->password = Hash::make($request->input('new-password'));
$customer->save();
return null;
return redirect()->route('Shop.Customers.edit');
}
public function storeAddress(Request $request)
@@ -245,7 +171,6 @@ class CustomerController extends Controller
$html = view('Shop.Customers.partials.address_item', [
'address' => $address->toArray(),
'prefix' => $prefix,
'inputName' => $request->input('input_name'),
'with_name' => true,
'selected' => $address->id,
])->render();

View File

@@ -9,7 +9,6 @@ use App\Repositories\Core\User\ShopCart;
use App\Repositories\Shop\Baskets;
use App\Repositories\Shop\Contents;
use App\Repositories\Shop\Customers;
use App\Repositories\Shop\CustomerAddresses;
use App\Repositories\Shop\Deliveries;
use App\Repositories\Shop\DeliveryTypes;
use App\Repositories\Shop\OrderMails;
@@ -58,21 +57,8 @@ class OrderController extends Controller
$deliveries = $deliveries ? $deliveries->values() : collect();
$customerData = $customer ? $customer->toArray() : false;
if ($customerData) {
$customerData['delivery_address_id'] = optional(CustomerAddresses::getDeliveryAddress($customerId))->id;
$customerData['invoice_address_id'] = optional(CustomerAddresses::getInvoiceAddress($customerId))->id;
if (! $customerData['delivery_address_id'] && ! empty($customerData['delivery_addresses'])) {
$customerData['delivery_address_id'] = $customerData['delivery_addresses'][0]['id'] ?? null;
}
if (! $customerData['invoice_address_id'] && ! empty($customerData['invoice_addresses'])) {
$customerData['invoice_address_id'] = $customerData['invoice_addresses'][0]['id'] ?? null;
}
if ($defaultSaleChannelId) {
$customerData['default_sale_channel_id'] = $defaultSaleChannelId;
}
if ($customerData && $defaultSaleChannelId) {
$customerData['default_sale_channel_id'] = $defaultSaleChannelId;
}
$data = [
@@ -102,9 +88,7 @@ class OrderController extends Controller
}
OrderMails::sendOrderConfirmed($order->id);
return redirect()->route('Shop.Orders.confirmed', [
'payment_type' => $data['payment_type'],
]);
return redirect()->route('Shop.Orders.confirmed');
}
return view('Shop.Orders.order');
@@ -113,18 +97,9 @@ class OrderController extends Controller
public function confirmed()
{
ShopCart::clear();
$paymentType = request('payment_type');
$content = Contents::getOrderConfirmedContent();
$paymentLabel = match ($paymentType) {
'2' => 'chèque',
'3' => 'virement',
default => null,
};
return view('Shop.Orders.confirmed', [
'content' => $content,
'payment_label' => $paymentLabel,
]);
return view('Shop.Orders.confirmed', ['content' => $content]);
}
public function getPdf($uuid)

View File

@@ -20,7 +20,6 @@ class RegisterCustomer extends FormRequest
'last_name' => 'required|max:255',
'first_name' => 'required|max:255',
'email' => 'required|email|max:255|unique:shop_customers,email,NULL,id,deleted_at,NULL',
'phone' => 'required|max:30',
'password' => ['required', 'confirmed', new Password()],
];
}

View File

@@ -3,7 +3,6 @@
namespace App\Mail;
use App\Models\Core\Mail\MailTemplate;
use App\Repositories\Shop\Orders;
use App\Repositories\Shop\Traits\MailCustomers;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
@@ -33,44 +32,29 @@ class ConfirmationCommande extends TemplateMailable
public $facturation_ville;
public $livraison_nom;
public $livraison_adresse;
public $livraison_adresse2;
public $livraison_cp;
public $livraison_ville;
public $facturation_nom;
public $facturation_adresse2;
public $mode_paiement;
protected static $templateModelClass = MailTemplate::class;
public function __construct($order)
{
$facturation_address = $order->invoice->address;
$facturation_address = $order->invoice->address;
$delivery_address = $order->delivery_address;
$this->prenom = $order->customer->first_name;
$this->nom = $order->customer->last_name;
$this->facturation_nom = $facturation_address->name;
$this->facturation_adresse = $facturation_address->address;
$this->facturation_adresse2 = $facturation_address->address2;
$this->facturation_cp = $facturation_address->zipcode;
$this->facturation_ville = $facturation_address->city;
$this->livraison_nom = $delivery_address->name;
$this->livraison_adresse = $delivery_address->address;
$this->livraison_adresse2 = $delivery_address->address2;
$this->livraison_cp = $delivery_address->zipcode;
$this->livraison_ville = $delivery_address->city;
$this->societe = $order->customer->company;
$this->email = $order->customer->email;
$this->numero_commande = $order->ref;
$this->date_commande = $order->created_at;
$this->mode_paiement = Orders::getPaymentType($order->payment_type);
}
}

View File

@@ -31,11 +31,7 @@ class ArticleImages
public static function getFullImagesByArticle($article)
{
if (! $article) {
return collect([]);
}
$images = count($article->images ?? []) ? $article->images : collect([]);
$images = count($article->images) ? $article->images : collect([]);
switch ($article->product_type) {
case 'App\Models\Botanic\Variety':
$variety = $article->product ?? false;

View File

@@ -204,7 +204,7 @@ class Articles
ksort($data);
}
return $data ?? [];
return $data ?? false;
}
public static function getDataForSale($article)

View File

@@ -41,16 +41,6 @@ class Contents
return self::get(5)->text ?? 'Votre commande a été confirmée';
}
public static function getOrderConfirmedByCheckContent()
{
return self::get(10)->text ?? 'Votre commande a bien été enregistrée, elle vous sera expédiée dès réception de votre chèque.';
}
public static function getOrderConfirmedByWireContent()
{
return self::get(11)->text ?? 'Votre commande a bien été enregistrée, elle vous sera expédiée dès réception de votre virement.';
}
public static function getPayboxConfirmedContent()
{
return self::get(6)->text ?? 'Merci pour votre règlement. Votre commande sera traitée sous peu.';

View File

@@ -19,7 +19,7 @@ class InvoicePDF
$invoice = Invoices::getFull($id);
$customFields = [];
if ($orderRef = optional($invoice->order)->ref) {
$customFields['Numéro de commande'] = $orderRef;
$customFields['order number'] = $orderRef;
}
$customer = new Party([
@@ -61,7 +61,7 @@ class InvoicePDF
trim(($address->zipcode ?? '').' '.($address->city ?? '')),
]);
return implode("\n", $lines);
return implode('<br>', $lines);
}
public static function makeItems($details)

View File

@@ -1,36 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
DB::table('shop_contents')->insert([
[
'id' => 10,
'text' => '<p>Votre commande a bien été enregistrée, elle vous sera expédiée dès réception de votre chèque.</p><p class="mt-3 text-warning"><i class="fa fa-exclamation-triangle mr-1"></i> Sans réception de votre paiement au bout de 30 jours, votre commande sera annulée.</p>',
'created_at' => now(),
'updated_at' => now(),
],
[
'id' => 11,
'text' => '<p>Votre commande a bien été enregistrée, elle vous sera expédiée dès réception de votre virement.</p><p class="mt-3 text-warning"><i class="fa fa-exclamation-triangle mr-1"></i> Sans réception de votre paiement au bout de 30 jours, votre commande sera annulée.</p>',
'created_at' => now(),
'updated_at' => now(),
],
]);
}
/**
* Reverse the migrations.
*/
public function down(): void
{
DB::table('shop_contents')->whereIn('id', [10, 11])->delete();
}
};

View File

@@ -1,87 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
$this->transformTemplate(function ($html) {
// Replace hardcoded "Carte de crédit" with the template variable
$html = str_replace(
'Carte de cr&eacute;dit',
'{{mode_paiement}}',
$html
);
// Add address2 to delivery address
$html = str_replace(
'{{livraison_adresse}}<br />{{livraison_cp}} {{livraison_ville}}',
'{{livraison_adresse}}{{#livraison_adresse2}}<br />{{livraison_adresse2}}{{/livraison_adresse2}}<br />{{livraison_cp}} {{livraison_ville}}',
$html
);
// Add address2 to billing address
$html = str_replace(
'{{facturation_adresse}}<br />{{facturation_cp}} {{facturation_ville}}',
'{{facturation_adresse}}{{#facturation_adresse2}}<br />{{facturation_adresse2}}{{/facturation_adresse2}}<br />{{facturation_cp}} {{facturation_ville}}',
$html
);
return $html;
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
$this->transformTemplate(function ($html) {
$html = str_replace(
'{{mode_paiement}}',
'Carte de cr&eacute;dit',
$html
);
$html = str_replace(
'{{livraison_adresse}}{{#livraison_adresse2}}<br />{{livraison_adresse2}}{{/livraison_adresse2}}<br />{{livraison_cp}} {{livraison_ville}}',
'{{livraison_adresse}}<br />{{livraison_cp}} {{livraison_ville}}',
$html
);
$html = str_replace(
'{{facturation_adresse}}{{#facturation_adresse2}}<br />{{facturation_adresse2}}{{/facturation_adresse2}}<br />{{facturation_cp}} {{facturation_ville}}',
'{{facturation_adresse}}<br />{{facturation_cp}} {{facturation_ville}}',
$html
);
return $html;
});
}
private function transformTemplate(callable $transform): void
{
$template = DB::table('mail_templates')
->where('mailable', 'App\\Mail\\ConfirmationCommande')
->first();
if (! $template) {
return;
}
$translations = json_decode($template->html_template, true);
foreach ($translations as $lang => $html) {
$translations[$lang] = $transform($html);
}
DB::table('mail_templates')
->where('id', $template->id)
->update(['html_template' => json_encode($translations, JSON_UNESCAPED_UNICODE)]);
}
};

View File

@@ -1,74 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
$this->transformTemplate(function ($html) {
// Add name before delivery address
$html = str_replace(
'{{livraison_adresse}}',
'{{#livraison_nom}}{{livraison_nom}}<br />{{/livraison_nom}}{{livraison_adresse}}',
$html
);
// Add name before billing address
$html = str_replace(
'{{facturation_adresse}}',
'{{#facturation_nom}}{{facturation_nom}}<br />{{/facturation_nom}}{{facturation_adresse}}',
$html
);
return $html;
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
$this->transformTemplate(function ($html) {
$html = str_replace(
'{{#livraison_nom}}{{livraison_nom}}<br />{{/livraison_nom}}{{livraison_adresse}}',
'{{livraison_adresse}}',
$html
);
$html = str_replace(
'{{#facturation_nom}}{{facturation_nom}}<br />{{/facturation_nom}}{{facturation_adresse}}',
'{{facturation_adresse}}',
$html
);
return $html;
});
}
private function transformTemplate(callable $transform): void
{
$template = DB::table('mail_templates')
->where('mailable', 'App\\Mail\\ConfirmationCommande')
->first();
if (! $template) {
return;
}
$translations = json_decode($template->html_template, true);
foreach ($translations as $lang => $html) {
$translations[$lang] = $transform($html);
}
DB::table('mail_templates')
->where('id', $template->id)
->update(['html_template' => json_encode($translations, JSON_UNESCAPED_UNICODE)]);
}
};

View File

@@ -1,56 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
return new class extends Migration
{
private $oldSrc = '/storage/photos/shares/logo.png';
private $newSrc = 'https://boutique.jardinenvie.com/img/logo.png';
/**
* Run the migrations.
*/
public function up(): void
{
$this->replaceLogoInAllTemplates($this->oldSrc, $this->newSrc);
}
/**
* Reverse the migrations.
*/
public function down(): void
{
$this->replaceLogoInAllTemplates($this->newSrc, $this->oldSrc);
}
private function replaceLogoInAllTemplates(string $from, string $to): void
{
$templates = DB::table('mail_templates')->get();
foreach ($templates as $template) {
$translations = json_decode($template->html_template, true);
if (! $translations) {
continue;
}
$changed = false;
foreach ($translations as $lang => $html) {
$updated = str_replace($from, $to, $html);
if ($updated !== $html) {
$translations[$lang] = $updated;
$changed = true;
}
}
if ($changed) {
DB::table('mail_templates')
->where('id', $template->id)
->update(['html_template' => json_encode($translations, JSON_UNESCAPED_UNICODE)]);
}
}
}
};

View File

@@ -1,9 +0,0 @@
<?php
return [
'confirmdelete' => 'Do you confirm the deletion?',
'deletesuccess' => 'Deleted successfully.',
'mail_the_selection' => 'Mail the selection',
'mail_the_complete_list' => 'Mail the complete list',
];

View File

@@ -1,9 +0,0 @@
<?php
return [
'confirmdelete' => 'Confirmez-vous la suppression ?',
'deletesuccess' => 'La suppression a été effectuée.',
'mail_the_selection' => 'Envoyer la sélection',
'mail_the_complete_list' => 'Envoyer toute la liste',
];

View File

@@ -126,19 +126,3 @@ body {
padding-bottom: 4px;
line-height: 1.1;
}
@media (max-width: 575.98px) {
.content-header .form-buttons {
margin-left: 0;
margin-top: 8px;
}
.content-header .form-buttons .btn {
height: 28px;
padding-top: 2px;
padding-bottom: 2px;
padding-left: 8px;
padding-right: 8px;
font-size: 0.75rem;
}
}

View File

@@ -397,11 +397,6 @@ div.megamenu ul.megamenu li.megamenu.level1
}
@media (max-width: 991.98px){
#navbarContentMobile {
max-height: calc(100vh - 60px);
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
#navbarContentMobile .navbar-nav {
flex-direction: column;
}

View File

@@ -5,7 +5,5 @@
])
@section('content')
@include('Admin.Shop.Articles.form', [
'cancel_url' => route('Admin.Shop.Articles.index'),
])
@include('Admin.Shop.Articles.form')
@endsection

View File

@@ -5,13 +5,5 @@
])
@section('content')
@php
$duplicateUrl = \Route::has('Admin.Shop.Articles.duplicate')
? route('Admin.Shop.Articles.duplicate', $article['id'] ?? null)
: null;
@endphp
@include('Admin.Shop.Articles.form', [
'duplicate_url' => $duplicateUrl,
'cancel_url' => route('Admin.Shop.Articles.index'),
])
@include('Admin.Shop.Articles.form')
@endsection

View File

@@ -5,29 +5,10 @@
'files' => true,
]) }}
<input type="hidden" name="id" id="id" value="{{ $article['id'] ?? null }}">
@php
$articlePublicUrl = null;
if (!empty($article['slug'] ?? null)) {
$articlePublicUrl = route('Shop.Articles.slug', ['slug' => $article['slug']]);
} elseif (!empty($article['id'] ?? null)) {
$articlePublicUrl = route('Shop.Articles.show', ['id' => $article['id']]);
}
@endphp
@if ($articlePublicUrl)
<div class="d-flex justify-content-end mb-3">
<a href="{{ $articlePublicUrl }}" class="btn btn-outline-primary" target="_blank" rel="noopener">
Voir la page publique
<i class="fa fa-external-link"></i>
</a>
</div>
@endif
@include('Admin.Shop.Articles.partials.characteristics')
{{ Form::close() }}
<x-save :cancel-url="$cancel_url ?? null" :duplicate-url="$duplicate_url ?? null" />
<x-save />
@include('load.form.appender')
@include('load.form.editor')

View File

@@ -2,7 +2,7 @@
@component('components.layout.box-collapse', [
'id' => 'product_description_box',
'title' => 'Informations héritées',
'collapsed' => $collapsed ?? true,
'collapsed' => $collapsed ?? false,
])
@foreach ($article['inherited'] as $inherited)
@component('components.card', ['title' => $inherited['name'], 'class' => 'mb-3'])

View File

@@ -1,17 +1,8 @@
{{ Form::open(['route' => 'Admin.Shop.Offers.store', 'id' => 'offer-form', 'autocomplete' => 'off']) }}
<input type="hidden" name="id" value="{{ $offer['id'] ?? false }}">
@if (($offer['id'] ?? false) && ($offer['article_id'] ?? false))
<div class="d-flex justify-content-end mb-3">
<a href="{{ route('Shop.Articles.show', ['id' => $offer['article_id']]) }}" class="btn btn-outline-primary" target="_blank" rel="noopener">
Voir la page publique de l'article
<i class="fa fa-external-link"></i>
</a>
</div>
@endif
<div class="row mb-3">
<div class="col-12">
<div class="col-8">
<div class="row mb-3">
<div class="col-12">
@include('components.form.select', [
@@ -105,6 +96,13 @@
</div>
@endcomponent
</div>
<div class="col-4">
@component('components.card', ['title' => 'Previsualisation'])
<div id="preview-article"></div>
<div id="preview-variation"></div>
<div id="preview-tariff"></div>
@endcomponent
</div>
</div>
@@ -119,8 +117,59 @@
{!! JsValidator::formRequest('App\Http\Requests\Admin\Shop\StoreOfferPost', '#offer-form') !!}
<script>
function handleArticle() {
$('.select_article').change(function() {
previewArticle($(this).val());
})
}
function previewArticle(id) {
var url = '{{ route('Admin.Shop.Offers.previewArticle') }}/' + id;
$('#preview-article').load(url, function() {
initChevron();
});
}
function handleVariation() {
$('.select_variation').change(function() {
previewVariation($(this).val());
})
}
function previewVariation(id) {
var url = '{{ route('Admin.Shop.Offers.previewVariation') }}/' + id;
$('#preview-variation').load(url, function() {
initChevron();
});
}
function handleTariff() {
$('.select_tariffs').change(function() {
previewTariff($(this).val());
})
}
function previewTariff(id) {
var url = '{{ route('Admin.Shop.Offers.previewTariff') }}/' + id;
$('#preview-tariff').load(url, function() {
initChevron();
});
}
function initPreview() {
previewArticle("{{ $offer['article_id'] ?? null }}");
previewVariation("{{ $offer['variation_id'] ?? null }}");
previewTariff("{{ $offer['tariff_id'] ?? null }}");
}
handleArticle();
handleVariation();
handleTariff();
initChevron();
initSaveForm('#offer-form');
initSelect2();
@if ($offer['id'] ?? false)
initPreview();
@endif
</script>
@endpush

View File

@@ -50,16 +50,7 @@
<div class="col-lg-3 col-xs-12">
@if (auth('web')->check() && !empty($article['available_sale_channels']))
<div id="article-admin-offers" class="alert alert-info p-2 mb-3">
<div class="d-flex justify-content-between align-items-center">
<strong class="d-block mb-0">Offres :</strong>
<a href="{{ route('Admin.Shop.Articles.edit', $article['id']) }}" class="text-dark d-inline-flex align-items-center gap-1" style="font-size: 0.95rem;" title="Ouvrir la fiche article en admin" target="_blank" rel="noopener">
<svg aria-hidden="true" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 20h9" />
<path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4Z" />
</svg>
<span class="sr-only">Éditer l'article</span>
</a>
</div>
<strong class="d-block">Offres :</strong>
<ul class="list-unstyled mb-0 small">
@php
$currentSaleChannelId = $article['current_sale_channel']['id'] ?? null;
@@ -95,7 +86,7 @@
$tariffId = $channel['tariff_id'] ?? null;
@endphp
@if ($tariffId)
<a href="{{ route('Admin.Shop.Tariffs.edit', $tariffId) }}" target="_blank" rel="noopener" title="Ouvrir le tarif" class="ml-2 text-nowrap text-right {{ $nameClass }} text-decoration-none text-reset d-inline-block admin-link-group admin-price-link">
<a href="{{ route('Admin.Shop.Tariffs.edit', $tariffId) }}" target="_blank" rel="noopener" class="ml-2 text-nowrap text-right {{ $nameClass }} text-decoration-none text-reset d-inline-block admin-link-group admin-price-link">
{{ number_format($priceTaxed, 2, ',', ' ') }} TTC
@if (! empty($quantity))
<span class="d-block text-muted" style="font-size: 0.85em;">Qté min. {{ $quantity }}</span>
@@ -122,7 +113,7 @@
$stockClass = $offer['stock_current'] > 0 ? 'text-success' : 'text-danger';
@endphp
<li class="small {{ $offerClass }}" style="font-size: 0.85em;">
<a href="{{ route('Admin.Shop.Offers.edit', $offer['id']) }}" target="_blank" rel="noopener" title="Ouvrir l'offre" class="text-decoration-none {{ $offerClass }} admin-link-group admin-offer-link">
<a href="{{ route('Admin.Shop.Offers.edit', $offer['id']) }}" target="_blank" rel="noopener" class="text-decoration-none {{ $offerClass }} admin-link-group admin-offer-link">
<div class="d-flex justify-content-between align-items-start">
<div>
<span style="opacity: 0.5;">{{ $isSelectedOffer ? '▸' : '○' }}</span>

View File

@@ -1,7 +1,7 @@
<div class="row mt-3 address-row" data-address-id="{{ $address['id'] }}">
<div class="col-1">
@php
$inputName = $inputName ?? (isset($prefix) && $prefix ? $prefix.'[address_id]' : 'address_id');
$inputName = isset($prefix) && $prefix ? $prefix.'[address_id]' : 'address_id';
$currentValue = $selected ?? null;
@endphp
<x-form.radios.icheck name="{{ $inputName }}" val="{{ $address['id'] }}"

View File

@@ -3,7 +3,6 @@
@include('Shop.Customers.partials.address_item', [
'address' => $address,
'prefix' => $prefix ?? null,
'inputName' => $inputName ?? null,
'with_name' => $with_name ?? false,
'selected' => $selected ?? null,
])
@@ -45,7 +44,6 @@
<script>
(function() {
var prefix = '{{ $prefix }}';
var inputName = '{{ $inputName ?? '' }}';
var $formContainer = $('#add_address_container_{{ $prefix }}');
var $list = $('#addresses_list_{{ $prefix }}');
var storeUrl = '{{ route('Shop.Customers.address.store') }}';
@@ -71,7 +69,7 @@
$.ajax({
url: storeUrl,
method: 'POST',
data: data + '&prefix=' + prefix + (inputName ? '&input_name=' + inputName : ''),
data: data + '&prefix=' + prefix,
success: function(response) {
if (response.html) {
$list.append(response.html);

View File

@@ -57,7 +57,6 @@
'name' => 'phone',
'value' => $customer['phone'] ?? (old('phone') ?? ''),
'label' => 'Téléphone',
'required' => true,
])
</div>
</div>

View File

@@ -4,20 +4,8 @@
@section('content')
<div class="row">
<div class="col-12 text-center py-5">
<i class="fa fa-check-circle text-success" style="font-size: 5rem;"></i>
<div class="mt-4" style="font-size: 1.2rem;">
{!! $content !!}
</div>
@if($payment_label ?? false)
<div class="mt-3" style="font-size: 1.1rem;">
Votre commande a bien été enregistrée, elle vous sera expédiée dès réception de votre {{ $payment_label }}.
</div>
<div class="mt-3" style="font-size: 1.1rem;">
<i class="fa fa-exclamation-triangle text-warning mr-1"></i>
Sans réception de votre paiement au bout de 30 jours, votre commande sera annulée.
</div>
@endif
<div class="col-12">
{!! $content !!}
</div>
</div>
@endsection

View File

@@ -40,10 +40,7 @@
<div class="col-sm-12 col-lg-4">
<x-card class='shadow'>
<div id="basketTotal">
@include('Shop.Baskets.partials.basketTotal', [
'basket' => $basket,
'sale_channel' => $basket['sale_channel'] ?? null,
])
@include('Shop.Baskets.partials.basketTotal', ['basket' => $basket])
</div>
</x-card>
</div>

View File

@@ -1,11 +1,9 @@
<div id="registred">
<x-layout.collapse id="invoice_addresses" title="Adresse de facturation" class="rounded-lg mb-3" uncollapsed=true>
@include('Shop.Customers.partials.addresses', [
'addresses' => $customer['invoice_addresses'] ?? [],
'prefix' => 'invoices',
'inputName' => 'invoice[invoice_address_id]',
'with_name' => true,
'selected' => $customer['invoice_address_id'] ?? null,
@include('Shop.Orders.partials.addresses', [
'addresses' => $customer['invoice_addresses'] ?? false,
'prefix' => 'invoice',
'name' => 'invoice[invoice_address_id]',
])
</x-layout.collapse>
@@ -15,12 +13,10 @@
<x-layout.collapse id="delivery_addresses" title="Adresse de livraison" class="rounded-lg mb-3 d-none"
uncollapsed=true>
@include('Shop.Customers.partials.addresses', [
'addresses' => $customer['delivery_addresses'] ?? [],
'prefix' => 'deliveries',
'inputName' => 'delivery_address_id',
'with_name' => true,
'selected' => $customer['delivery_address_id'] ?? null,
@include('Shop.Orders.partials.addresses', [
'addresses' => $customer['delivery_addresses'] ?? false,
'prefix' => 'delivery',
'name' => 'delivery_address_id',
])
@include('Shop.Orders.partials.shipping')
</x-layout.collapse>

View File

@@ -1,62 +0,0 @@
@php
$passwordInputId = $passwordInputId ?? 'password';
@endphp
<ul class="list-unstyled small mt-1 mb-0 password-rules" data-input="#{{ $passwordInputId }}" style="display: none;">
<li data-rule="length"><i class="fa fa-fw fa-times"></i> Au moins 8 caractères</li>
<li data-rule="lowercase"><i class="fa fa-fw fa-times"></i> Au moins une lettre minuscule</li>
<li data-rule="uppercase"><i class="fa fa-fw fa-times"></i> Au moins une lettre majuscule</li>
<li data-rule="number"><i class="fa fa-fw fa-times"></i> Au moins un chiffre</li>
<li data-rule="special"><i class="fa fa-fw fa-times"></i> Au moins un caractère spécial</li>
</ul>
@once
@push('css')
<style>
.password-rules li {
color: #dc3545;
transition: all 0.3s;
}
.password-rules li.valid {
display: none;
}
</style>
@endpush
@push('js')
<script>
$(function() {
$('.password-rules').each(function() {
var $rules = $(this);
var inputSelector = $rules.data('input');
var $input = $(inputSelector);
if (!$input.length) return;
var checks = {
length: function(v) { return v.length >= 8; },
lowercase: function(v) { return /[a-z]/.test(v); },
uppercase: function(v) { return /[A-Z]/.test(v); },
number: function(v) { return /[0-9]/.test(v); },
special: function(v) { return /[^A-Za-z0-9]/.test(v); }
};
$input.on('input keyup', function() {
var val = $(this).val();
if (val.length === 0) {
$rules.hide();
return;
}
$rules.find('li').each(function() {
var rule = $(this).data('rule');
if (checks[rule]) {
$(this).toggleClass('valid', checks[rule](val));
}
});
$rules.show();
});
});
});
</script>
@endpush
@endonce

View File

@@ -11,12 +11,10 @@
<label>Mot de passe *</label>
{{ Form::password('password', [
'class' => 'form-control',
'id' => 'password',
'placeholder' => __('boilerplate::auth.fields.password'),
'required',
]) }}
{!! $errors->registration->first('password', '<p class="text-danger"><strong>:message</strong></p>') !!}
@include('Shop.auth.partials.password_rules', ['passwordInputId' => 'password'])
</div>
</div>
<div class="col-6">

View File

@@ -13,8 +13,7 @@
<label for="new-password" class="col-md-6 control-label text-right">Nouveau mot de passe</label>
<div class="col-md-6">
<input id="new-password" type="password" class="form-control" name="new-password">
@include('Shop.auth.partials.password_rules', ['passwordInputId' => 'new-password'])
<input id="new-password" type="password" class="form-control" name="new-password" required>
</div>
</div>
@@ -22,6 +21,6 @@
<label for="new-password-confirm" class="col-md-6 control-label text-right">Confirmez votre mot de passe</label>
<div class="col-md-6">
<input id="new-password-confirm" type="password" class="form-control" name="new-password_confirmation">
<input id="new-password-confirm" type="password" class="form-control" name="new-password_confirmation" required>
</div>
</div>

View File

@@ -1,19 +0,0 @@
<ul class="navbar-nav w-100">
@foreach ($categories as $menu)
<li class="nav-item dropdown megamenu p-2 col
@if (in_array($menu['id'], [$category['id'] ?? false, $category['parent_id'] ?? false, $category['parent']['parent_id'] ?? false])) active @endif">
@if ($menu['children'] ?? false)
<a id="megamenu_{{ $menu['id'] }}" href="{{ route('Shop.Categories.show', ['id' => $menu['id']]) }}" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" class="nav-link dropdown-toggle text-uppercase">
{{ $menu['name'] }}
</a>
<div aria-labelledby="megamenu_{{ $menu['id'] }}" class="dropdown-menu border-0 p-0 m-0">
@include('Shop.layout.partials.megamenu')
</div>
@else
<a href="{{ route('Shop.Categories.show', ['id' => $menu['id']]) }}" class="nav-link text-uppercase text-white">
{{ $menu['name'] }}
</a>
@endif
</li>
@endforeach
</ul>

View File

@@ -15,20 +15,7 @@
@isset($trigger) data-trigger="{{ $trigger }}" @endisset
@isset($container) data-container="{{ $container }}" @endisset
@isset($html) data-html="true" @endisset
@isset($metadata) {{ $metadata }} @endisset
@isset($attr)
@if (is_array($attr))
@foreach ($attr as $key => $value)
@if ($value === true)
{{ $key }}
@elseif ($value !== false && $value !== null)
{{ $key }}="{{ $value }}"
@endif
@endforeach
@else
{{ $attr }}
@endif
@endisset>
@isset($metadata) {{ $metadata }} @endisset>
<i class="fa fa-fw {{ $icon ?? '' }}"></i>
{{ $txt ?? '' }}
</button>

View File

@@ -1,6 +0,0 @@
@include('components.form.button', [
'class' => 'btn-info duplicate ' . ($class ?? ''),
'icon' => 'fa-copy',
'txt' => __('Dupliquer'),
'attr' => ['data-url' => $duplicate_url ?? $duplicateUrl ?? null],
])

View File

@@ -1,6 +1,5 @@
@php
$cancelUrl = $cancel_url ?? $cancelUrl ?? null;
$duplicateUrl = $duplicate_url ?? $duplicateUrl ?? null;
@endphp
@push('header-actions')
@@ -9,12 +8,6 @@
'class' => 'btn-sm mr-2',
'url' => $cancelUrl,
])
@if($duplicateUrl)
@include('components.form.buttons.button-duplicate', [
'class' => 'btn-sm mr-2',
'duplicate_url' => $duplicateUrl,
])
@endif
@include('components.form.buttons.button-save', [
'class' => 'btn-sm',
])
@@ -25,9 +18,6 @@
<div class="col-12">
<div class="text-right form-buttons">
@include('components.form.buttons.button-cancel', ['url' => $cancelUrl])
@if($duplicateUrl)
@include('components.form.buttons.button-duplicate', ['duplicate_url' => $duplicateUrl])
@endif
@include('components.form.buttons.button-save')
</div>
</div>

View File

@@ -1,9 +1,9 @@
<div class="content-header pt-2 pb-1">
<div class="container-fluid">
<div class="row mb-2 align-items-center">
<div class="col-sm-8">
<div class="col-sm-6">
<div class="d-flex align-items-center flex-wrap">
<h1 class="m-0 text-dark d-flex align-items-center flex-grow-1">
<h1 class="m-0 text-dark d-flex align-items-center">
{{ $title ?? null}}
@isset($subtitle)
<small class="font-weight-light ml-1 text-md">{{ $subtitle }}</small>
@@ -12,7 +12,7 @@
@stack('header-actions')
</div>
</div>
<div class="col-sm-4">
<div class="col-sm-6">
<ol class="breadcrumb float-sm-right text-sm">
<li class="breadcrumb-item">
<a href="{{ route('boilerplate.dashboard') }}">

View File

@@ -94,18 +94,6 @@
@stack('scripts')
@stack('js')
<script>
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('.form-buttons .duplicate').forEach(function(btn) {
btn.addEventListener('click', function() {
var url = this.dataset.url || this.getAttribute('data-url');
if (url) {
window.location = url;
}
});
});
});
</script>
</body>

View File

@@ -14,5 +14,4 @@ Route::prefix('Articles')->name('Articles.')->group(function () {
Route::get('getProductImages/{product_id?}/{model?}', 'ArticleController@getProductImages')->name('getProductImages');
Route::post('toggleVisible', 'ArticleController@toggleVisible')->name('toggleVisible');
Route::post('toggleHomepage', 'ArticleController@toggleHomepage')->name('toggleHomepage');
Route::get('duplicate/{id}', 'ArticleController@duplicate')->name('duplicate');
});

View File

@@ -1,8 +1,8 @@
<?php
Route::middleware('auth')->prefix('Admin')->namespace('Admin')->name('Admin.')->group(function () {
Route::get('{period?}', 'HomeController@index')->name('home');
include __DIR__.'/Botanic/route.php';
include __DIR__.'/Core/route.php';
include __DIR__.'/Shop/route.php';
Route::get('{period?}', 'HomeController@index')->name('home');
});

View File

@@ -1,8 +1,5 @@
<?php
Route::prefix('Offres')->name('Offers.')->group(function () {
// Public offer pages are not exposed; keep the route returning 404 to avoid leaking data.
Route::get('show/{id}', function () {
abort(404);
})->name('show');
Route::get('show/{id}', 'OfferController@show')->name('show');
});