2026-04-10
- corvée: met à jour les deps - corvée: formate
This commit is contained in:
parent
00f87fedcd
commit
d50de6d534
85 changed files with 132090 additions and 31346 deletions
|
|
@ -22,11 +22,12 @@ $templates = ['404.twig'];
|
|||
*
|
||||
* @throws Exception une exception est levée s'il est impossible d'obtenir la date de modification du fichier à charger
|
||||
*/
|
||||
function load_page_resources(): void {
|
||||
Resource::enqueue_style_file(
|
||||
handle: 'haiku-atelier-2024-styles-page-a-propos',
|
||||
path: '/assets/css/pages/page-modele-simple.css',
|
||||
);
|
||||
function load_page_resources(): void
|
||||
{
|
||||
Resource::enqueue_style_file(
|
||||
handle: 'haiku-atelier-2024-styles-page-a-propos',
|
||||
path: '/assets/css/pages/page-modele-simple.css',
|
||||
);
|
||||
}
|
||||
|
||||
add_action('wp_enqueue_scripts', load_page_resources(...));
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
jQuery(document).ready(function ($) {
|
||||
jQuery(document).ready(function($) {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
|
|
@ -9,7 +9,7 @@ jQuery(document).ready(function ($) {
|
|||
* @link https://github.com/maddisondesigns
|
||||
*/
|
||||
|
||||
$(".customize-control-tinymce-editor").each(function () {
|
||||
$(".customize-control-tinymce-editor").each(function() {
|
||||
// Get the toolbar strings that were passed from the PHP Class
|
||||
const tinyMCEToolbar1String = _wpCustomizeSettings.controls[$(this).attr("id")].skyrockettinymcetoolbar1;
|
||||
const tinyMCEToolbar2String = _wpCustomizeSettings.controls[$(this).attr("id")].skyrockettinymcetoolbar2;
|
||||
|
|
@ -19,14 +19,14 @@ jQuery(document).ready(function ($) {
|
|||
mediaButtons: tinyMCEMediaButtons,
|
||||
quicktags: true,
|
||||
tinymce: {
|
||||
wpautop: true,
|
||||
toolbar1: tinyMCEToolbar1String,
|
||||
toolbar2: tinyMCEToolbar2String,
|
||||
wpautop: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
$(document).on("tinymce-editor-init", function (event, editor) {
|
||||
editor.on("change", function (e) {
|
||||
$(document).on("tinymce-editor-init", function(event, editor) {
|
||||
editor.on("change", function(e) {
|
||||
tinyMCE.triggerSave();
|
||||
$("#" + editor.id).trigger("change");
|
||||
});
|
||||
|
|
|
|||
|
|
@ -17,14 +17,14 @@ $context = Timber::context();
|
|||
$templates = ['accueil.twig'];
|
||||
|
||||
add_action('wp_enqueue_scripts', function (): void {
|
||||
Resource::enqueue_style_file(
|
||||
handle: 'haiku-atelier-2024-styles-page-accueil',
|
||||
path: '/assets/css/pages/page-accueil.css',
|
||||
);
|
||||
Resource::enqueue_script_module_file(
|
||||
id: 'haiku-atelier-2024-scripts-page-accueil',
|
||||
path: '/assets/js/scripts-page-accueil.js',
|
||||
);
|
||||
Resource::enqueue_style_file(
|
||||
handle: 'haiku-atelier-2024-styles-page-accueil',
|
||||
path: '/assets/css/pages/page-accueil.css',
|
||||
);
|
||||
Resource::enqueue_script_module_file(
|
||||
id: 'haiku-atelier-2024-scripts-page-accueil',
|
||||
path: '/assets/js/scripts-page-accueil.js',
|
||||
);
|
||||
});
|
||||
|
||||
Timber::render(data: $context, filenames: $templates);
|
||||
|
|
|
|||
|
|
@ -24,20 +24,20 @@ $templates = ['a-propos.twig'];
|
|||
$image_dimensions = getimagesize(filename: get_template_directory() . '/assets/img/about/haikuabout.png');
|
||||
|
||||
if (is_bool($image_dimensions)) {
|
||||
throw new Exception("Impossible d'obtenir les dimensions de l'image principale de la page.");
|
||||
throw new Exception("Impossible d'obtenir les dimensions de l'image principale de la page.");
|
||||
}
|
||||
|
||||
$context['image_dimensions'] = $image_dimensions;
|
||||
|
||||
add_action('wp_enqueue_scripts', function (): void {
|
||||
Resource::enqueue_style_file(
|
||||
handle: 'haiku-atelier-2024-styles-page-a-propos',
|
||||
path: '/assets/css/pages/page-a-propos.css',
|
||||
);
|
||||
Resource::enqueue_script_module_file(
|
||||
id: 'haiku-atelier-2024-scripts-page-a-propos',
|
||||
path: '/assets/js/scripts-page-a-propos.js',
|
||||
);
|
||||
Resource::enqueue_style_file(
|
||||
handle: 'haiku-atelier-2024-styles-page-a-propos',
|
||||
path: '/assets/css/pages/page-a-propos.css',
|
||||
);
|
||||
Resource::enqueue_script_module_file(
|
||||
id: 'haiku-atelier-2024-scripts-page-a-propos',
|
||||
path: '/assets/js/scripts-page-a-propos.js',
|
||||
);
|
||||
});
|
||||
|
||||
Timber::render(data: $context, filenames: $templates);
|
||||
|
|
|
|||
|
|
@ -22,20 +22,22 @@ use WC_Session_Handler;
|
|||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
// TODO: Appliquer le bon calcul pour les montants vs. percentages
|
||||
function get_discount_amount(WC_Coupon $coupon) {
|
||||
if ($coupon->get_discount_type() === 'fixed_cart') {
|
||||
return $coupon->get_amount() * 100;
|
||||
} else {
|
||||
return $coupon->get_amount();
|
||||
}
|
||||
function get_discount_amount(WC_Coupon $coupon)
|
||||
{
|
||||
if ($coupon->get_discount_type() === 'fixed_cart') {
|
||||
return $coupon->get_amount() * 100;
|
||||
} else {
|
||||
return $coupon->get_amount();
|
||||
}
|
||||
}
|
||||
|
||||
function get_discount_duration(WC_Coupon $coupon): string {
|
||||
if ($coupon->get_discount_type() === 'fixed_cart') {
|
||||
return 'once';
|
||||
} else {
|
||||
return 'forever';
|
||||
}
|
||||
function get_discount_duration(WC_Coupon $coupon): string
|
||||
{
|
||||
if ($coupon->get_discount_type() === 'fixed_cart') {
|
||||
return 'once';
|
||||
} else {
|
||||
return 'forever';
|
||||
}
|
||||
}
|
||||
|
||||
// Récupère les informations nécessaires
|
||||
|
|
@ -44,37 +46,37 @@ $session_wc = WC()->session;
|
|||
|
||||
/** @var array<string,string> $urls URLs utilisables pour rediriger l'Utilisateur. */
|
||||
$urls = [
|
||||
'accueil' => get_page_link(get_page_by_path('home')),
|
||||
'succes_commande' => get_page_link(get_page_by_path('successful-order')),
|
||||
'echec_commande' => get_page_link(get_page_by_path('failed-order')),
|
||||
'accueil' => get_page_link(get_page_by_path('home')),
|
||||
'succes_commande' => get_page_link(get_page_by_path('successful-order')),
|
||||
'echec_commande' => get_page_link(get_page_by_path('failed-order')),
|
||||
];
|
||||
|
||||
// Redirige à la page d'accueil si le Panier est vide
|
||||
if (WC()->cart->is_empty()) {
|
||||
header('Location: ' . $urls['accueil']);
|
||||
header('Location: ' . $urls['accueil']);
|
||||
|
||||
return;
|
||||
return;
|
||||
}
|
||||
|
||||
// Vérifie que les paramètres d'URLs nécessaires soient présents
|
||||
/** @var string $order_id */
|
||||
$order_id = $_GET['order_id'];
|
||||
if (!$order_id) {
|
||||
$reponse = ['succes' => false, 'status' => 'order_key is missing'];
|
||||
echo json_encode($reponse);
|
||||
http_response_code(400);
|
||||
$reponse = ['succes' => false, 'status' => 'order_key is missing'];
|
||||
echo json_encode($reponse);
|
||||
http_response_code(400);
|
||||
|
||||
return;
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var string $order_key */
|
||||
$order_key = $_GET['order_key'];
|
||||
if (!$order_key) {
|
||||
$reponse = ['succes' => false, 'status' => 'order_key is missing'];
|
||||
echo json_encode($reponse);
|
||||
http_response_code(400);
|
||||
$reponse = ['succes' => false, 'status' => 'order_key is missing'];
|
||||
echo json_encode($reponse);
|
||||
http_response_code(400);
|
||||
|
||||
return;
|
||||
return;
|
||||
}
|
||||
|
||||
// Récupère le Panier et l'Email du Client
|
||||
|
|
@ -86,29 +88,29 @@ $email_client = WC()->session->get('customer')['email'];
|
|||
|
||||
/** @var list<Product> $articles */
|
||||
$articles = collect($panier->get_cart())
|
||||
->map(static function ($article_panier) {
|
||||
$titre_produit = match ('variable' === $article_panier['data']?->get_type()) {
|
||||
true => $article_panier['data']?->get_title()
|
||||
. ' ('
|
||||
. explode(': ', (string) $article_panier['data']?->get_attribute_summary())[1]
|
||||
. ')',
|
||||
false => $article_panier['data']?->get_title(),
|
||||
};
|
||||
->map(static function ($article_panier) {
|
||||
$titre_produit = match ('variable' === $article_panier['data']?->get_type()) {
|
||||
true => $article_panier['data']?->get_title()
|
||||
. ' ('
|
||||
. explode(': ', (string) $article_panier['data']?->get_attribute_summary())[1]
|
||||
. ')',
|
||||
false => $article_panier['data']?->get_title(),
|
||||
};
|
||||
|
||||
return [
|
||||
'price_data' => [
|
||||
'currency' => 'EUR',
|
||||
'product_data' => [
|
||||
'name' => $titre_produit,
|
||||
'images' => [wp_get_attachment_image_url($article_panier['data']?->get_image_id())],
|
||||
],
|
||||
'unit_amount' => $article_panier['data']?->get_price() * 100,
|
||||
],
|
||||
'quantity' => $article_panier['quantity'],
|
||||
];
|
||||
})
|
||||
->values()
|
||||
->toArray();
|
||||
return [
|
||||
'price_data' => [
|
||||
'currency' => 'EUR',
|
||||
'product_data' => [
|
||||
'name' => $titre_produit,
|
||||
'images' => [wp_get_attachment_image_url($article_panier['data']?->get_image_id())],
|
||||
],
|
||||
'unit_amount' => $article_panier['data']?->get_price() * 100,
|
||||
],
|
||||
'quantity' => $article_panier['quantity'],
|
||||
];
|
||||
})
|
||||
->values()
|
||||
->toArray();
|
||||
|
||||
// Récupère la Commande et la Méthode de Livraison
|
||||
/** @var WC_Order $commande */
|
||||
|
|
@ -118,7 +120,7 @@ $methode_livraison = ['nom' => $commande->get_shipping_method(), 'cout' => $comm
|
|||
|
||||
// Le nom de la méthode de livraison ne peut être une chaîne vide.
|
||||
if (empty($methode_livraison['nom'])) {
|
||||
$methode_livraison['nom'] = 'Free';
|
||||
$methode_livraison['nom'] = 'Free';
|
||||
}
|
||||
|
||||
// Sélectionne la clé API Stripe
|
||||
|
|
@ -127,39 +129,39 @@ Stripe::setApiKey(Config::get('STRIPE_API_SECRET'));
|
|||
// Met à jour les Codes promos
|
||||
$coupons_stripe = collect(Coupon::all()->data);
|
||||
$coupons_wc = collect(WC()->cart->get_coupons())
|
||||
->map(static fn(WC_Coupon $coupon): array => [
|
||||
'currency' => 'EUR',
|
||||
'duration' => get_discount_duration($coupon),
|
||||
'fixed_cart' === $coupon->get_discount_type() ? 'amount_off' : 'percent_off' => get_discount_amount($coupon),
|
||||
'id' => $coupon->get_code(),
|
||||
'name' => $coupon->get_code(),
|
||||
])
|
||||
->each(static function (array $item) use ($coupons_stripe): void {
|
||||
// Si le code promo n'existe pas, le créer
|
||||
if (!$coupons_stripe->contains('name', $item['name'])) {
|
||||
Coupon::create($item);
|
||||
}
|
||||
});
|
||||
->map(static fn(WC_Coupon $coupon): array => [
|
||||
'currency' => 'EUR',
|
||||
'duration' => get_discount_duration($coupon),
|
||||
'fixed_cart' === $coupon->get_discount_type() ? 'amount_off' : 'percent_off' => get_discount_amount($coupon),
|
||||
'id' => $coupon->get_code(),
|
||||
'name' => $coupon->get_code(),
|
||||
])
|
||||
->each(static function (array $item) use ($coupons_stripe): void {
|
||||
// Si le code promo n'existe pas, le créer
|
||||
if (!$coupons_stripe->contains('name', $item['name'])) {
|
||||
Coupon::create($item);
|
||||
}
|
||||
});
|
||||
$reductions_stripe = $coupons_wc
|
||||
->map(static fn($coupon): array => ['coupon' => $coupon['name']])
|
||||
->values()
|
||||
->toArray();
|
||||
->map(static fn($coupon): array => ['coupon' => $coupon['name']])
|
||||
->values()
|
||||
->toArray();
|
||||
|
||||
/** @var Session $session_checkout_stripe */
|
||||
$session_checkout_stripe = Session::create([
|
||||
'cancel_url' => $urls['echec_commande'],
|
||||
'customer_email' => $email_client,
|
||||
'discounts' => $reductions_stripe,
|
||||
'line_items' => $articles,
|
||||
'mode' => 'payment',
|
||||
'success_url' => $urls['succes_commande'] . '?session_id={CHECKOUT_SESSION_ID}',
|
||||
'metadata' => ['order_id' => $order_id, 'order_key' => $order_key],
|
||||
'shipping_options' => [['shipping_rate_data' => [
|
||||
'display_name' => $methode_livraison['nom'],
|
||||
'fixed_amount' => ['amount' => $methode_livraison['cout'], 'currency' => 'EUR'],
|
||||
'tax_behavior' => 'inclusive',
|
||||
'type' => 'fixed_amount',
|
||||
]]],
|
||||
'cancel_url' => $urls['echec_commande'],
|
||||
'customer_email' => $email_client,
|
||||
'discounts' => $reductions_stripe,
|
||||
'line_items' => $articles,
|
||||
'mode' => 'payment',
|
||||
'success_url' => $urls['succes_commande'] . '?session_id={CHECKOUT_SESSION_ID}',
|
||||
'metadata' => ['order_id' => $order_id, 'order_key' => $order_key],
|
||||
'shipping_options' => [['shipping_rate_data' => [
|
||||
'display_name' => $methode_livraison['nom'],
|
||||
'fixed_amount' => ['amount' => $methode_livraison['cout'], 'currency' => 'EUR'],
|
||||
'tax_behavior' => 'inclusive',
|
||||
'type' => 'fixed_amount',
|
||||
]]],
|
||||
], ['idempotency_key' => Uuid::v4()]);
|
||||
// echo json_encode($session_checkout_stripe);
|
||||
header('HTTP/1.1 303 See Other');
|
||||
|
|
|
|||
|
|
@ -17,10 +17,10 @@ $context = Timber::context();
|
|||
$templates = ['echec-commande.twig'];
|
||||
|
||||
add_action('wp_enqueue_scripts', function (): void {
|
||||
Resource::enqueue_style_file(
|
||||
handle: 'haiku-atelier-2024-styles-page-modele-simple',
|
||||
path: '/assets/css/pages/page-modele-simple.css',
|
||||
);
|
||||
Resource::enqueue_style_file(
|
||||
handle: 'haiku-atelier-2024-styles-page-modele-simple',
|
||||
path: '/assets/css/pages/page-modele-simple.css',
|
||||
);
|
||||
});
|
||||
|
||||
// Rendu
|
||||
|
|
|
|||
|
|
@ -17,10 +17,10 @@ $context = Timber::context();
|
|||
$templates = ['cgv.twig'];
|
||||
|
||||
add_action('wp_enqueue_scripts', function (): void {
|
||||
Resource::enqueue_style_file(
|
||||
handle: '/assets/css/pages/page-modele-simple.css',
|
||||
path: '/assets/css/pages/page-modele-simple.css',
|
||||
);
|
||||
Resource::enqueue_style_file(
|
||||
handle: '/assets/css/pages/page-modele-simple.css',
|
||||
path: '/assets/css/pages/page-modele-simple.css',
|
||||
);
|
||||
});
|
||||
|
||||
// Rendu
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ $maximum_price = collect($product->variations)->max('price');
|
|||
$same_collection_products = Product::get_same_collection_products($product->collection)($product->id)
|
||||
|> function (/** @var list<WC_Product>|stdClass */ mixed $products): array {
|
||||
assert(is_array($products), 'Les Produits de la même collection doivent être un tableau.');
|
||||
|
||||
return $products;
|
||||
}
|
||||
|> (static fn(/** @var list<WC_Product> */ array $products): array => Arr::map(
|
||||
|
|
|
|||
|
|
@ -170,6 +170,7 @@ final class StarterSite extends Site {
|
|||
public function maj_environnement_twig(array $options): array {
|
||||
return $options;
|
||||
}
|
||||
|
||||
// public function charge_traductions_theme(): void {
|
||||
// load_theme_textdomain("haiku-atelier-2024", get_template_directory() . "/languages");
|
||||
// }
|
||||
|
|
|
|||
|
|
@ -9,36 +9,39 @@ declare(strict_types=1);
|
|||
use Carbon_Fields\Container;
|
||||
use Carbon_Fields\Field;
|
||||
|
||||
function cree_champs_personnalises_produit(): void {
|
||||
Container::make('post_meta', "Product's Details")
|
||||
->where('post_type', '=', 'product')
|
||||
->add_fields([
|
||||
// Galerie des photos Produit
|
||||
Field::make('media_gallery', 'photos_colonne_gauche', __('Left Column Photos'))
|
||||
->set_type(['image'])
|
||||
->set_duplicates_allowed(false),
|
||||
// Galerie des photos portées
|
||||
Field::make('media_gallery', 'photos_colonne_droite', __('Right Column Photos'))
|
||||
->set_type(['image'])
|
||||
->set_duplicates_allowed(false),
|
||||
// Texte des détails du Produit
|
||||
Field::make('rich_text', 'haiku_details_produit', __("Product's Details")),
|
||||
function cree_champs_personnalises_produit(): void
|
||||
{
|
||||
Container::make('post_meta', "Product's Details")
|
||||
->where('post_type', '=', 'product')
|
||||
->add_fields([
|
||||
// Galerie des photos Produit
|
||||
Field::make('media_gallery', 'photos_colonne_gauche', __('Left Column Photos'))
|
||||
->set_type(['image'])
|
||||
->set_duplicates_allowed(false),
|
||||
// Galerie des photos portées
|
||||
Field::make('media_gallery', 'photos_colonne_droite', __('Right Column Photos'))
|
||||
->set_type(['image'])
|
||||
->set_duplicates_allowed(false),
|
||||
// Texte des détails du Produit
|
||||
Field::make('rich_text', 'haiku_details_produit', __("Product's Details")),
|
||||
]);
|
||||
}
|
||||
|
||||
function cree_champ_personnalise_commande($order): void
|
||||
{
|
||||
woocommerce_wp_text_input([
|
||||
'id' => 'tracking_number',
|
||||
'label' => 'Tracking Number:',
|
||||
'value' => $order->get_meta('tracking_number'),
|
||||
'wrapper_class' => 'form-field-wide',
|
||||
]);
|
||||
}
|
||||
|
||||
function cree_champ_personnalise_commande($order): void {
|
||||
woocommerce_wp_text_input([
|
||||
'id' => 'tracking_number',
|
||||
'label' => 'Tracking Number:',
|
||||
'value' => $order->get_meta('tracking_number'),
|
||||
'wrapper_class' => 'form-field-wide',
|
||||
]);
|
||||
}
|
||||
|
||||
function maj_champ_personnalise_commande($order_id): void {
|
||||
$order = wc_get_order($order_id);
|
||||
$order->update_meta_data('tracking_number', wc_clean($_POST['tracking_number']));
|
||||
$order->save();
|
||||
function maj_champ_personnalise_commande($order_id): void
|
||||
{
|
||||
$order = wc_get_order($order_id);
|
||||
$order->update_meta_data('tracking_number', wc_clean($_POST['tracking_number']));
|
||||
$order->save();
|
||||
}
|
||||
|
||||
add_action('carbon_fields_register_fields', 'cree_champs_personnalises_produit');
|
||||
|
|
|
|||
|
|
@ -8,69 +8,74 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
function enregistre_controle_personnalise_tinymce(): void {
|
||||
/**
|
||||
* TinyMCE Custom Control.
|
||||
*
|
||||
* @author Anthony Hortin <http://maddisondesigns.com>
|
||||
* @license http://www.gnu.org/licenses/gpl-2.0.html
|
||||
*
|
||||
* @see https://github.com/maddisondesigns
|
||||
*/
|
||||
final class ControlesPersonnalises extends WP_Customize_Control {
|
||||
/** The type of control being rendered. */
|
||||
public $type = 'editeur_tinymce';
|
||||
|
||||
function enregistre_controle_personnalise_tinymce(): void
|
||||
{
|
||||
/**
|
||||
* Enqueue our scripts and styles.
|
||||
* TinyMCE Custom Control.
|
||||
*
|
||||
* @author Anthony Hortin <http://maddisondesigns.com>
|
||||
* @license http://www.gnu.org/licenses/gpl-2.0.html
|
||||
*
|
||||
* @see https://github.com/maddisondesigns
|
||||
*/
|
||||
public function enqueue(): void {
|
||||
wp_enqueue_script(
|
||||
handle: 'controle-personnalise-tinymce',
|
||||
src: get_template_directory_uri() . '/assets/vendor/controle-personnalise-tinymce.js',
|
||||
deps: ['jquery'],
|
||||
ver: '1.3',
|
||||
args: true,
|
||||
);
|
||||
wp_enqueue_editor();
|
||||
}
|
||||
final class ControlesPersonnalises extends WP_Customize_Control
|
||||
{
|
||||
/** The type of control being rendered. */
|
||||
public $type = 'editeur_tinymce';
|
||||
|
||||
/**
|
||||
* Render the control in the customizer.
|
||||
*/
|
||||
public function render_content(): void { ?>
|
||||
/**
|
||||
* Enqueue our scripts and styles.
|
||||
*/
|
||||
public function enqueue(): void
|
||||
{
|
||||
wp_enqueue_script(
|
||||
handle: 'controle-personnalise-tinymce',
|
||||
src: get_template_directory_uri() . '/assets/vendor/controle-personnalise-tinymce.js',
|
||||
deps: ['jquery'],
|
||||
ver: '1.3',
|
||||
args: true,
|
||||
);
|
||||
wp_enqueue_editor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the control in the customizer.
|
||||
*/
|
||||
public function render_content(): void
|
||||
{ ?>
|
||||
<div class="tinymce-control">
|
||||
<span class="customize-control-title"><?php echo esc_html($this->label); ?></span>
|
||||
<?php if (!empty($this->description)) { ?>
|
||||
<span class="customize-control-description"><?php echo esc_html($this->description); ?></span>
|
||||
<?php } ?>
|
||||
<textarea id="<?php echo
|
||||
esc_attr($this->id)
|
||||
esc_attr($this->id)
|
||||
; ?>" class="customize-control-tinymce-editor" <?php $this->link(); ?>><?php echo
|
||||
esc_html($this->value())
|
||||
esc_html($this->value())
|
||||
; ?></textarea>
|
||||
</div>
|
||||
<?php }
|
||||
|
||||
/**
|
||||
* Pass our TinyMCE toolbar string to JavaScript.
|
||||
*/
|
||||
public function to_json(): void {
|
||||
parent::to_json();
|
||||
/**
|
||||
* Pass our TinyMCE toolbar string to JavaScript.
|
||||
*/
|
||||
public function to_json(): void
|
||||
{
|
||||
parent::to_json();
|
||||
|
||||
$this->json['skyrockettinymcetoolbar1'] = isset($this->input_attrs['toolbar1'])
|
||||
? esc_attr($this->input_attrs['toolbar1'])
|
||||
: 'bold italic bullist numlist alignleft aligncenter alignright link';
|
||||
$this->json['skyrockettinymcetoolbar1'] = isset($this->input_attrs['toolbar1'])
|
||||
? esc_attr($this->input_attrs['toolbar1'])
|
||||
: 'bold italic bullist numlist alignleft aligncenter alignright link';
|
||||
|
||||
$this->json['skyrockettinymcetoolbar2'] = isset($this->input_attrs['toolbar2'])
|
||||
? esc_attr($this->input_attrs['toolbar2'])
|
||||
: '';
|
||||
$this->json['skyrocketmediabuttons'] = isset($this->input_attrs['mediaButtons'])
|
||||
&& $this->input_attrs['mediaButtons'] === true
|
||||
? true
|
||||
: false;
|
||||
$this->json['skyrockettinymcetoolbar2'] = isset($this->input_attrs['toolbar2'])
|
||||
? esc_attr($this->input_attrs['toolbar2'])
|
||||
: '';
|
||||
$this->json['skyrocketmediabuttons'] = isset($this->input_attrs['mediaButtons'])
|
||||
&& $this->input_attrs['mediaButtons'] === true
|
||||
? true
|
||||
: false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
add_action('customize_register', 'enregistre_controle_personnalise_tinymce');
|
||||
|
|
|
|||
|
|
@ -11,112 +11,116 @@ use function is_float;
|
|||
use function is_int;
|
||||
use function is_string;
|
||||
|
||||
final readonly class Cart {
|
||||
public function __construct() {}
|
||||
final readonly class Cart
|
||||
{
|
||||
public function __construct() {}
|
||||
|
||||
/** La valeur par défaut d'une donnée invalide du Panier. */
|
||||
private const string DEFAULT_VALUE = '0.00';
|
||||
/** La valeur par défaut d'une donnée invalide du Panier. */
|
||||
private const string DEFAULT_VALUE = '0.00';
|
||||
|
||||
/**
|
||||
* Retourne la liste des pays acceptés pour la livraison.
|
||||
*
|
||||
* @return array<int,string>
|
||||
*/
|
||||
public static function get_allowed_countries(): array {
|
||||
return [
|
||||
'AD',
|
||||
'AL',
|
||||
'AM',
|
||||
'AR',
|
||||
'AT',
|
||||
'AU',
|
||||
'BA',
|
||||
'BE',
|
||||
'BG',
|
||||
'BR',
|
||||
'CA',
|
||||
'CH',
|
||||
'CL',
|
||||
'CR',
|
||||
'CU',
|
||||
'CY',
|
||||
'CZ',
|
||||
'DE',
|
||||
'DK',
|
||||
'DZ',
|
||||
'EE',
|
||||
'EG',
|
||||
'ES',
|
||||
'FI',
|
||||
'FR',
|
||||
'GF',
|
||||
'GP',
|
||||
'GR',
|
||||
'HR',
|
||||
'HU',
|
||||
'IE',
|
||||
'IS',
|
||||
'IT',
|
||||
'JP',
|
||||
'KR',
|
||||
'LB',
|
||||
'LI',
|
||||
'LT',
|
||||
'LU',
|
||||
'LV',
|
||||
'MA',
|
||||
'MD',
|
||||
'ME',
|
||||
'MF',
|
||||
'MQ',
|
||||
'MT',
|
||||
'MX',
|
||||
'NC',
|
||||
'NL',
|
||||
'NO',
|
||||
'NZ',
|
||||
'PF',
|
||||
'PL',
|
||||
'PM',
|
||||
'PS',
|
||||
'PT',
|
||||
'RE',
|
||||
'RO',
|
||||
'SE',
|
||||
'SI',
|
||||
'SK',
|
||||
'SM',
|
||||
'TN',
|
||||
'TR',
|
||||
'TW',
|
||||
'US',
|
||||
'YT',
|
||||
'ZA',
|
||||
];
|
||||
}
|
||||
|
||||
public static function parse_cart_value(int|float|string|bool $cart_value): string {
|
||||
if (is_int($cart_value) || is_float($cart_value)) {
|
||||
return self::format_number($cart_value);
|
||||
/**
|
||||
* Retourne la liste des pays acceptés pour la livraison.
|
||||
*
|
||||
* @return array<int,string>
|
||||
*/
|
||||
public static function get_allowed_countries(): array
|
||||
{
|
||||
return [
|
||||
'AD',
|
||||
'AL',
|
||||
'AM',
|
||||
'AR',
|
||||
'AT',
|
||||
'AU',
|
||||
'BA',
|
||||
'BE',
|
||||
'BG',
|
||||
'BR',
|
||||
'CA',
|
||||
'CH',
|
||||
'CL',
|
||||
'CR',
|
||||
'CU',
|
||||
'CY',
|
||||
'CZ',
|
||||
'DE',
|
||||
'DK',
|
||||
'DZ',
|
||||
'EE',
|
||||
'EG',
|
||||
'ES',
|
||||
'FI',
|
||||
'FR',
|
||||
'GF',
|
||||
'GP',
|
||||
'GR',
|
||||
'HR',
|
||||
'HU',
|
||||
'IE',
|
||||
'IS',
|
||||
'IT',
|
||||
'JP',
|
||||
'KR',
|
||||
'LB',
|
||||
'LI',
|
||||
'LT',
|
||||
'LU',
|
||||
'LV',
|
||||
'MA',
|
||||
'MD',
|
||||
'ME',
|
||||
'MF',
|
||||
'MQ',
|
||||
'MT',
|
||||
'MX',
|
||||
'NC',
|
||||
'NL',
|
||||
'NO',
|
||||
'NZ',
|
||||
'PF',
|
||||
'PL',
|
||||
'PM',
|
||||
'PS',
|
||||
'PT',
|
||||
'RE',
|
||||
'RO',
|
||||
'SE',
|
||||
'SI',
|
||||
'SK',
|
||||
'SM',
|
||||
'TN',
|
||||
'TR',
|
||||
'TW',
|
||||
'US',
|
||||
'YT',
|
||||
'ZA',
|
||||
];
|
||||
}
|
||||
|
||||
if (is_string($cart_value)) {
|
||||
$number = Number::parseInt($cart_value);
|
||||
$number = is_bool($number) ? 0 : $number;
|
||||
public static function parse_cart_value(int|float|string|bool $cart_value): string
|
||||
{
|
||||
if (is_int($cart_value) || is_float($cart_value)) {
|
||||
return self::format_number($cart_value);
|
||||
}
|
||||
|
||||
return self::format_number($number);
|
||||
if (is_string($cart_value)) {
|
||||
$number = Number::parseInt($cart_value);
|
||||
$number = is_bool($number) ? 0 : $number;
|
||||
|
||||
return self::format_number($number);
|
||||
}
|
||||
|
||||
return '0.00';
|
||||
}
|
||||
|
||||
return '0.00';
|
||||
}
|
||||
|
||||
private static function format_number(int|float $number): string {
|
||||
$formatted_number = Number::format(
|
||||
number: $number,
|
||||
// precision et max_precision sont mutuellement exclusifs.
|
||||
precision: 2,
|
||||
locale: 'fr',
|
||||
);
|
||||
return is_bool($formatted_number) ? self::DEFAULT_VALUE : $formatted_number;
|
||||
}
|
||||
private static function format_number(int|float $number): string
|
||||
{
|
||||
$formatted_number = Number::format(
|
||||
number: $number,
|
||||
// precision et max_precision sont mutuellement exclusifs.
|
||||
precision: 2,
|
||||
locale: 'fr',
|
||||
);
|
||||
return is_bool($formatted_number) ? self::DEFAULT_VALUE : $formatted_number;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,32 +6,34 @@ namespace HaikuAtelier\Data;
|
|||
|
||||
use WC_Product;
|
||||
|
||||
final readonly class ProductVariation {
|
||||
/**
|
||||
* @param int $id L'ID de la Variation
|
||||
* @param string $price Le prix de la Variation
|
||||
* @param list<ProductVariationAttribute> $attributes Les attributs appliqués à la Variation
|
||||
*/
|
||||
private function __construct(
|
||||
public int $id,
|
||||
public string $price,
|
||||
public array $attributes,
|
||||
) {}
|
||||
final readonly class ProductVariation
|
||||
{
|
||||
/**
|
||||
* @param int $id L'ID de la Variation
|
||||
* @param string $price Le prix de la Variation
|
||||
* @param list<ProductVariationAttribute> $attributes Les attributs appliqués à la Variation
|
||||
*/
|
||||
private function __construct(
|
||||
public int $id,
|
||||
public string $price,
|
||||
public array $attributes,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Créé une nouvelle instance de `ProductVariation` à partir d'un `WC_Product`.
|
||||
*/
|
||||
public static function new(WC_Product $product): self {
|
||||
$id = $product->get_id();
|
||||
$price = $product->get_price();
|
||||
/** @var list<ProductVariationAttribute> */
|
||||
$attributes = array_map(
|
||||
/** @phpstan-ignore argument.type (Impossible à satisfaire) */
|
||||
static fn(string $key, string $value) => new ProductVariationAttribute($key, $value),
|
||||
array_keys($product->get_attributes()),
|
||||
array_values($product->get_attributes()),
|
||||
);
|
||||
/**
|
||||
* Créé une nouvelle instance de `ProductVariation` à partir d'un `WC_Product`.
|
||||
*/
|
||||
public static function new(WC_Product $product): self
|
||||
{
|
||||
$id = $product->get_id();
|
||||
$price = $product->get_price();
|
||||
/** @var list<ProductVariationAttribute> */
|
||||
$attributes = array_map(
|
||||
/** @phpstan-ignore argument.type (Impossible à satisfaire) */
|
||||
static fn(string $key, string $value) => new ProductVariationAttribute($key, $value),
|
||||
array_keys($product->get_attributes()),
|
||||
array_values($product->get_attributes()),
|
||||
);
|
||||
|
||||
return new self($id, $price, $attributes);
|
||||
}
|
||||
return new self($id, $price, $attributes);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,13 +4,14 @@ declare(strict_types=1);
|
|||
|
||||
namespace HaikuAtelier\Data;
|
||||
|
||||
final readonly class ProductVariationAttribute {
|
||||
/**
|
||||
* @param string $attribute Le slug de l'Attribut
|
||||
* @param string $value Le slug de la valeur de l'Attribut
|
||||
*/
|
||||
public function __construct(
|
||||
public string $attribute,
|
||||
public string $value,
|
||||
) {}
|
||||
final readonly class ProductVariationAttribute
|
||||
{
|
||||
/**
|
||||
* @param string $attribute Le slug de l'Attribut
|
||||
* @param string $value Le slug de la valeur de l'Attribut
|
||||
*/
|
||||
public function __construct(
|
||||
public string $attribute,
|
||||
public string $value,
|
||||
) {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,8 +9,9 @@ declare(strict_types=1);
|
|||
namespace HaikuAtelier;
|
||||
|
||||
// Désactive divers transformations du contenu par WordPress
|
||||
function desactive_wpautop(): void {
|
||||
remove_filter('the_content', 'wpautop');
|
||||
function desactive_wpautop(): void
|
||||
{
|
||||
remove_filter('the_content', 'wpautop');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -20,16 +21,17 @@ function desactive_wpautop(): void {
|
|||
*
|
||||
* @return array<string, bool> le même tableau avec des configurations en plus
|
||||
*/
|
||||
function desactive_transformation_contenu_tinymce(array $configuration): array {
|
||||
// Ne supprime pas les retours à la ligne
|
||||
$configuration['remove_linebreaks'] = false;
|
||||
// Convertis les caractères de retours à la ligne en <br>
|
||||
$configuration['convert_newlines_to_brs'] = true;
|
||||
// Supprime les <br> redondants
|
||||
$configuration['remove_redundant_brs'] = false;
|
||||
function desactive_transformation_contenu_tinymce(array $configuration): array
|
||||
{
|
||||
// Ne supprime pas les retours à la ligne
|
||||
$configuration['remove_linebreaks'] = false;
|
||||
// Convertis les caractères de retours à la ligne en <br>
|
||||
$configuration['convert_newlines_to_brs'] = true;
|
||||
// Supprime les <br> redondants
|
||||
$configuration['remove_redundant_brs'] = false;
|
||||
|
||||
// Retourne $configuration à WordPress
|
||||
return $configuration;
|
||||
// Retourne $configuration à WordPress
|
||||
return $configuration;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -39,19 +41,22 @@ function desactive_transformation_contenu_tinymce(array $configuration): array {
|
|||
*
|
||||
* @return array<string, string> le même tableau avec SVG en plus
|
||||
*/
|
||||
function autorise_import_svg_mediatheque(array $file_types): array {
|
||||
$new_filetypes = [];
|
||||
$new_filetypes['svg'] = 'image/svg+xml';
|
||||
function autorise_import_svg_mediatheque(array $file_types): array
|
||||
{
|
||||
$new_filetypes = [];
|
||||
$new_filetypes['svg'] = 'image/svg+xml';
|
||||
|
||||
return [...$file_types, ...$new_filetypes];
|
||||
return [...$file_types, ...$new_filetypes];
|
||||
}
|
||||
|
||||
function retire_motifs_blocs_gutenberg(): void {
|
||||
remove_theme_support('core-block-patterns');
|
||||
function retire_motifs_blocs_gutenberg(): void
|
||||
{
|
||||
remove_theme_support('core-block-patterns');
|
||||
}
|
||||
|
||||
function retire_styles_core_block(): void {
|
||||
wp_dequeue_style('core-block-supports');
|
||||
function retire_styles_core_block(): void
|
||||
{
|
||||
wp_dequeue_style('core-block-supports');
|
||||
}
|
||||
|
||||
// Désactive les appels à l'API de la mise à jour des traductions
|
||||
|
|
|
|||
|
|
@ -14,32 +14,33 @@ use function register_taxonomy;
|
|||
/**
|
||||
* Enregistre la Taxonomie « Collection ».
|
||||
*/
|
||||
function enregistre_taxonomie_collection(): void {
|
||||
$labels = [
|
||||
'add_new_item' => __('Add New Collection'),
|
||||
'all_items' => __('All Collections'),
|
||||
'edit_item' => __('Edit Collection'),
|
||||
'menu_name' => __('Collections'),
|
||||
'name' => __('Collections'),
|
||||
'new_item_name' => __('New Collection Name'),
|
||||
'search_items' => __('Search Collections'),
|
||||
'singular_name' => __('Collection'),
|
||||
'update_item' => __('Update Collection'),
|
||||
];
|
||||
$args = [
|
||||
'description' => __('An ensemble of pieces thematically or chronologically grouped together.'),
|
||||
'hierarchical' => false,
|
||||
'labels' => $labels,
|
||||
'publicly_queryable' => false,
|
||||
'query_var' => true,
|
||||
'rewrite' => ['slug' => 'collection'],
|
||||
'show_admin_column' => true,
|
||||
'show_in_menu' => true,
|
||||
'show_in_quick_edit' => true,
|
||||
'show_ui' => true,
|
||||
];
|
||||
function enregistre_taxonomie_collection(): void
|
||||
{
|
||||
$labels = [
|
||||
'add_new_item' => __('Add New Collection'),
|
||||
'all_items' => __('All Collections'),
|
||||
'edit_item' => __('Edit Collection'),
|
||||
'menu_name' => __('Collections'),
|
||||
'name' => __('Collections'),
|
||||
'new_item_name' => __('New Collection Name'),
|
||||
'search_items' => __('Search Collections'),
|
||||
'singular_name' => __('Collection'),
|
||||
'update_item' => __('Update Collection'),
|
||||
];
|
||||
$args = [
|
||||
'description' => __('An ensemble of pieces thematically or chronologically grouped together.'),
|
||||
'hierarchical' => false,
|
||||
'labels' => $labels,
|
||||
'publicly_queryable' => false,
|
||||
'query_var' => true,
|
||||
'rewrite' => ['slug' => 'collection'],
|
||||
'show_admin_column' => true,
|
||||
'show_in_menu' => true,
|
||||
'show_in_quick_edit' => true,
|
||||
'show_ui' => true,
|
||||
];
|
||||
|
||||
register_taxonomy('collection', ['product'], $args);
|
||||
register_taxonomy('collection', ['product'], $args);
|
||||
}
|
||||
|
||||
add_action('init', enregistre_taxonomie_collection(...));
|
||||
|
|
|
|||
|
|
@ -14,46 +14,50 @@ use function is_array;
|
|||
use function Psl\Option\none;
|
||||
use function Psl\Option\some;
|
||||
|
||||
final readonly class Post {
|
||||
/**
|
||||
* @return Option\Option<mixed>
|
||||
*/
|
||||
public static function get_post_meta(int $post_id, string $key): Option\Option {
|
||||
/** @var false|mixed|string */
|
||||
$value = get_post_meta($post_id, $key, true);
|
||||
final readonly class Post
|
||||
{
|
||||
/**
|
||||
* @return Option\Option<mixed>
|
||||
*/
|
||||
public static function get_post_meta(int $post_id, string $key): Option\Option
|
||||
{
|
||||
/** @var false|mixed|string */
|
||||
$value = get_post_meta($post_id, $key, true);
|
||||
|
||||
if ($value === false) {
|
||||
return none();
|
||||
if ($value === false) {
|
||||
return none();
|
||||
}
|
||||
|
||||
return some($value);
|
||||
}
|
||||
|
||||
return some($value);
|
||||
}
|
||||
/**
|
||||
* @return Option\Option<array<mixed>>
|
||||
*/
|
||||
public static function get_post_meta_array(int $post_id, string $key): Option\Option
|
||||
{
|
||||
/** @var array<mixed>|false */
|
||||
$value = get_post_meta($post_id, $key, false);
|
||||
|
||||
/**
|
||||
* @return Option\Option<array<mixed>>
|
||||
*/
|
||||
public static function get_post_meta_array(int $post_id, string $key): Option\Option {
|
||||
/** @var array<mixed>|false */
|
||||
$value = get_post_meta($post_id, $key, false);
|
||||
if (is_array($value)) {
|
||||
return some($value);
|
||||
}
|
||||
|
||||
if (is_array($value)) {
|
||||
return some($value);
|
||||
return none();
|
||||
}
|
||||
|
||||
return none();
|
||||
}
|
||||
/**
|
||||
* @return Option\Option<array<mixed>>
|
||||
*/
|
||||
public static function get_terms(int $post_id, string $taxonomy_name): Option\Option
|
||||
{
|
||||
/** @var false|list<WP_Term>|WP_Error */
|
||||
$terms = get_the_terms($post_id, $taxonomy_name);
|
||||
|
||||
/**
|
||||
* @return Option\Option<array<mixed>>
|
||||
*/
|
||||
public static function get_terms(int $post_id, string $taxonomy_name): Option\Option {
|
||||
/** @var false|list<WP_Term>|WP_Error */
|
||||
$terms = get_the_terms($post_id, $taxonomy_name);
|
||||
if (is_array($terms)) {
|
||||
return some($terms);
|
||||
}
|
||||
|
||||
if (is_array($terms)) {
|
||||
return some($terms);
|
||||
return none();
|
||||
}
|
||||
|
||||
return none();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,13 +7,11 @@ import { getOptionOrThrowWithError } from "./utils.ts";
|
|||
type ParentElement = Document | Element;
|
||||
|
||||
const getFirstSelectorFromParent =
|
||||
(parent: ParentElement) =>
|
||||
<E extends Element = Element>(selector: string): Option.Option<NonNullable<E>> =>
|
||||
(parent: ParentElement) => <E extends Element = Element>(selector: string): Option.Option<NonNullable<E>> =>
|
||||
Option.fromNullishOr(parent.querySelector<E>(selector));
|
||||
|
||||
const getFirstSelectorFromParentOrThrow =
|
||||
(parent: ParentElement) =>
|
||||
<E extends Element = Element>(selector: string): NonNullable<E> =>
|
||||
(parent: ParentElement) => <E extends Element = Element>(selector: string): NonNullable<E> =>
|
||||
pipe(
|
||||
getFirstSelectorFromParent(parent)<E>(selector),
|
||||
getOptionOrThrowWithError(`Il n'y a pas d'Élément dans le parent avec le sélecteur suivant : ${selector}.`),
|
||||
|
|
@ -29,8 +27,7 @@ const getFirstSelectorFromDocumentOrThrow = <E extends Element = Element>(select
|
|||
);
|
||||
|
||||
const getAllSelectorFromParent =
|
||||
(parent: ParentElement) =>
|
||||
<E extends Element = Element>(selector: string): Option.Option<NonEmptyReadonlyArray<E>> =>
|
||||
(parent: ParentElement) => <E extends Element = Element>(selector: string): Option.Option<NonEmptyReadonlyArray<E>> =>
|
||||
pipe(
|
||||
parent.querySelectorAll<E>(selector),
|
||||
// Convertis NodeListOf en Array.
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
import { Option, pipe } from "effect";
|
||||
|
||||
export const getOptionOrThrowWithError =
|
||||
(message: string) =>
|
||||
<T>(option: Option.Option<T>): T =>
|
||||
pipe(
|
||||
option,
|
||||
Option.getOrThrowWith(() => new Error(message)),
|
||||
);
|
||||
export const getOptionOrThrowWithError = (message: string) => <T>(option: Option.Option<T>): T =>
|
||||
pipe(
|
||||
option,
|
||||
Option.getOrThrowWith(() => new Error(message)),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,18 +1,12 @@
|
|||
export const forEach =
|
||||
<T>(fn: (_1: T) => void) =>
|
||||
(xs: Array<T>): void => {
|
||||
xs.forEach(fn);
|
||||
};
|
||||
export const forEach = <T>(fn: (_1: T) => void) => (xs: Array<T>): void => {
|
||||
xs.forEach(fn);
|
||||
};
|
||||
|
||||
export const forEachWithIndex =
|
||||
<T>(fn: (_1: T, _2: number) => void) =>
|
||||
(xs: Array<T>): void => {
|
||||
xs.forEach(fn);
|
||||
};
|
||||
export const forEachWithIndex = <T>(fn: (_1: T, _2: number) => void) => (xs: Array<T>): void => {
|
||||
xs.forEach(fn);
|
||||
};
|
||||
|
||||
export const map =
|
||||
<T>(fn: (_1: T) => void) =>
|
||||
(xs: Array<T>): Array<T> => {
|
||||
xs.map(fn);
|
||||
return xs;
|
||||
};
|
||||
export const map = <T>(fn: (_1: T) => void) => (xs: Array<T>): Array<T> => {
|
||||
xs.map(fn);
|
||||
return xs;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -18,26 +18,22 @@ import {
|
|||
} from "./erreurs";
|
||||
|
||||
export const recupereElementAvecSelecteur =
|
||||
(parent: ParentElement) =>
|
||||
<E extends Element = Element>(selecteur: string): Either<SyntaxError, E> =>
|
||||
(parent: ParentElement) => <E extends Element = Element>(selecteur: string): Either<SyntaxError, E> =>
|
||||
Either
|
||||
// Retourne une SyntaxError dans un Left si le sélecteur est invalide
|
||||
.encase(() => parent.querySelector<E>(selecteur))
|
||||
// Transforme le Left en une erreur plus sympathique
|
||||
.mapLeft((_) => creeSyntaxError(ERREUR_SYNTAXE_INVALIDE(selecteur)))
|
||||
.mapLeft(_ => creeSyntaxError(ERREUR_SYNTAXE_INVALIDE(selecteur)))
|
||||
// Retourne une SyntaxError si l'Élément est null
|
||||
.chain((e: E | null) =>
|
||||
G.isNotNullable(e) ? Right(e) : Left(creeSyntaxError(ERREUR_DOM_INEXISTANT(selecteur))),
|
||||
);
|
||||
.chain((e: E | null) => G.isNotNullable(e) ? Right(e) : Left(creeSyntaxError(ERREUR_DOM_INEXISTANT(selecteur))));
|
||||
|
||||
export const getDOMElementsWithSelector =
|
||||
(parent: ParentElement) =>
|
||||
<E extends Element = Element>(selecteur: string): Either<SyntaxError, Array<E>> =>
|
||||
(parent: ParentElement) => <E extends Element = Element>(selecteur: string): Either<SyntaxError, Array<E>> =>
|
||||
Either
|
||||
// Retourne une SyntaxError dans un Left si le sélecteur est invalide
|
||||
.encase(() => pipe(parent.querySelectorAll<E>(selecteur), Array.from<E>))
|
||||
// Transforme le Left en une erreur plus sympathique
|
||||
.mapLeft((_) => creeSyntaxError(ERREUR_SYNTAXE_INVALIDE(selecteur)))
|
||||
.mapLeft(_ => creeSyntaxError(ERREUR_SYNTAXE_INVALIDE(selecteur)))
|
||||
// Retourne une SyntaxError si le tableau est vide
|
||||
.chain((e: Array<E>) => (A.isEmpty(e) ? Left(creeSyntaxError(ERREUR_DOM_INEXISTANT(selecteur))) : Right(e)));
|
||||
|
||||
|
|
@ -55,20 +51,18 @@ export const recupereElementsOuLeve = <E extends Element = Element>(
|
|||
Right: identity,
|
||||
});
|
||||
|
||||
export const majElementInnerHtml =
|
||||
<T extends HTMLElement>(element: T) =>
|
||||
(innerHtml: string) => {
|
||||
element.innerHTML = innerHtml;
|
||||
return element;
|
||||
};
|
||||
export const majElementInnerHtml = <T extends HTMLElement>(element: T) => (innerHtml: string) => {
|
||||
element.innerHTML = innerHtml;
|
||||
return element;
|
||||
};
|
||||
|
||||
// Merci facon (https://github.com/terkelg/facon)
|
||||
export const html = (strings: TemplateStringsArray, ...args: Array<string>) =>
|
||||
pipe(
|
||||
document.createElement("template"),
|
||||
(template) =>
|
||||
template =>
|
||||
majElementInnerHtml(template)(args.reduce((prev, value, i) => prev + value + strings[i + 1], strings[0])),
|
||||
(template) => template.content,
|
||||
template => template.content,
|
||||
);
|
||||
|
||||
/**
|
||||
|
|
@ -86,10 +80,8 @@ export const safeJsonParse = (chaine: string): Either<SyntaxError, JSONValue> =>
|
|||
*
|
||||
* @returns Un booléen
|
||||
*/
|
||||
export const targetMatchesSelector = <E extends HTMLElement = HTMLElement>(
|
||||
cible: EventTarget | null,
|
||||
selecteur: string,
|
||||
): cible is E => cible !== null && (cible as HTMLElement).matches(selecteur);
|
||||
export const targetMatchesSelector = (cible: EventTarget | null, selecteur: string): cible is HTMLElement =>
|
||||
(cible as HTMLElement)?.matches(selecteur);
|
||||
|
||||
export const recupereElementsDocumentEither: <E extends Element = Element>(
|
||||
selecteur: string,
|
||||
|
|
@ -105,13 +97,11 @@ export const recupereElementDocumentEither: <E extends Element = Element>(select
|
|||
* @throws Une SyntaxError si l'Élément n'est pas trouvé.
|
||||
* @returns Un Élément.
|
||||
*/
|
||||
export const mustGetEleInDocument = <E extends Element = Element>(selecteur: string): E =>
|
||||
pipe(recupereElementDocumentEither<E>(selecteur), recupereElementOuLeve);
|
||||
export const mustGetEleInDocument = (selecteur: string): Element =>
|
||||
pipe(recupereElementDocumentEither<Element>(selecteur), recupereElementOuLeve);
|
||||
|
||||
export const mustGetEleInParent =
|
||||
(parent: ParentElement) =>
|
||||
<E extends HTMLElement>(selector: string) =>
|
||||
pipe(recupereElementAvecSelecteur(parent)<E>(selector), recupereElementOuLeve);
|
||||
export const mustGetEleInParent = (parent: ParentElement) => (selector: string) =>
|
||||
pipe(recupereElementAvecSelecteur(parent)<HTMLElement>(selector), recupereElementOuLeve);
|
||||
|
||||
/**
|
||||
* Fonction utilitaire pour récupérer des Éléments selon un sélecteur au sein du Document.
|
||||
|
|
@ -138,11 +128,11 @@ export const setButtonLoadingState = (button: HTMLButtonElement, isLoading: bool
|
|||
};
|
||||
|
||||
export const estErreurHttp = (erreur: unknown) =>
|
||||
erreur instanceof BadRequestError ||
|
||||
erreur instanceof ForbiddenError ||
|
||||
erreur instanceof NotFoundError ||
|
||||
erreur instanceof ServerError ||
|
||||
erreur instanceof UnauthorizedError;
|
||||
erreur instanceof BadRequestError
|
||||
|| erreur instanceof ForbiddenError
|
||||
|| erreur instanceof NotFoundError
|
||||
|| erreur instanceof ServerError
|
||||
|| erreur instanceof UnauthorizedError;
|
||||
|
||||
export const estErreurFetch = (erreur: unknown) =>
|
||||
erreur instanceof DOMException || erreur instanceof TypeError || erreur instanceof Error;
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ export const Erreur = (message: string): Error => new Error(message);
|
|||
export const ErreurInconnue = (erreur: unknown): UnknownError => new UnknownError(erreur);
|
||||
export const ErreurEntreeInexistante = (message: string): NonExistingKeyError => new NonExistingKeyError(message);
|
||||
|
||||
export const leveErreur = <E extends Error = Error>(erreur: E): never => {
|
||||
export const leveErreur = (erreur: Error): never => {
|
||||
throw erreur;
|
||||
};
|
||||
export const leveBadRequestError = (erreur: WCErrorBody): never => {
|
||||
|
|
@ -106,7 +106,7 @@ export const leveNonExistingKeyError = (message: string): never => {
|
|||
* @param erreur
|
||||
* @returns L'ID Sentry de l'évènement capturé.
|
||||
*/
|
||||
export const reporteErreur = <E extends Error>(erreur: E): string => captureException(erreur);
|
||||
export const reporteErreur = (erreur: Error): string => captureException(erreur);
|
||||
|
||||
/**
|
||||
* Reporte une Erreur, sous forme d'erreur console et au service GlitchTip, puis la lève sous forme
|
||||
|
|
@ -115,12 +115,12 @@ export const reporteErreur = <E extends Error>(erreur: E): string => captureExce
|
|||
* @param erreur
|
||||
* @returns never Lève une Erreur et ne retourne donc rien.
|
||||
*/
|
||||
export const reporteEtLeveErreur = <E extends Error>(erreur: E): never => {
|
||||
export const reporteEtLeveErreur = (erreur: Error): never => {
|
||||
reporteErreur(erreur);
|
||||
throw erreur;
|
||||
};
|
||||
|
||||
export const reporteEtJournaliseErreur = <E extends Error>(erreur: E): void => {
|
||||
export const reporteEtJournaliseErreur = (erreur: Error): void => {
|
||||
reporteErreur(erreur);
|
||||
console.error(erreur);
|
||||
if (erreur instanceof ValiError) {
|
||||
|
|
|
|||
|
|
@ -7,12 +7,12 @@ export const CODE_PROMO_MAJ_EVENT = new CustomEvent(CODE_PROMO_MAJ, {});
|
|||
|
||||
// Interfaces
|
||||
|
||||
export type UpdatedShippingRatesEvent = {
|
||||
export type UpdatedShippingRatesEvent = Event & {
|
||||
detail: { refresh_methods: boolean; shipping_rates: ReadonlyArray<WCStoreShippingRateShippingRate> };
|
||||
} & Event;
|
||||
export type UpdatedTotalsEvent = {
|
||||
};
|
||||
export type UpdatedTotalsEvent = Event & {
|
||||
detail: { totals: WCStoreCartTotals };
|
||||
} & Event;
|
||||
};
|
||||
|
||||
// Méthodes
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
import type { Constructor } from "./types/classes";
|
||||
|
||||
const estElement =
|
||||
<T extends HTMLElement>(typeElement: Constructor<T>) =>
|
||||
(element: unknown): element is T =>
|
||||
element instanceof typeElement;
|
||||
const estElement = <T extends HTMLElement>(typeElement: Constructor<T>) => (element: unknown): element is T =>
|
||||
element instanceof typeElement;
|
||||
|
||||
export const estHTMLSelectElement = estElement<HTMLSelectElement>(HTMLSelectElement);
|
||||
|
||||
|
|
|
|||
|
|
@ -55,30 +55,31 @@ export const emetMessageMajContenuPanier = (args: MessageMajContenuPanierDonnees
|
|||
* @param message Le message émis.
|
||||
* @return void
|
||||
*/
|
||||
export const emetUniqueMessageBroadcastChannel = <M>(nomCanal: string, message: M): void =>
|
||||
export const emetUniqueMessageBroadcastChannel = (nomCanal: string, message: unknown): void => {
|
||||
pipe(
|
||||
new BroadcastChannel(nomCanal),
|
||||
(canal) => canalPostMessage(canal, message),
|
||||
(canal) => canal.close(),
|
||||
canal => canalPostMessage(canal, message),
|
||||
canal => canal.close(),
|
||||
);
|
||||
};
|
||||
|
||||
// Validations
|
||||
export const valideMessageMajBoutonPanier = (
|
||||
evenementMessage: MessageEvent<unknown>,
|
||||
): Either<ValiError<typeof MessageMajBoutonPanierSchema>, MessageMajBoutonPanier> =>
|
||||
Either.of<ValiError<typeof MessageMajBoutonPanierSchema>, MessageMajBoutonPanier>(
|
||||
Either.of<ValiError<typeof MessageMajBoutonPanierSchema>>(
|
||||
parse(MessageMajBoutonPanierSchema, evenementMessage.data),
|
||||
).ifLeft((erreur) => reporteErreur(erreur));
|
||||
).ifLeft(erreur => reporteErreur(erreur));
|
||||
|
||||
export const valideMessageMajContenuPanier = (
|
||||
evenementMessage: MessageEvent<unknown>,
|
||||
): Either<ValiError<typeof MessageMajContenuPanierSchema>, MessageMajContenuPanier> =>
|
||||
Either.of<ValiError<typeof MessageMajContenuPanierSchema>, MessageMajContenuPanier>(
|
||||
Either.of<ValiError<typeof MessageMajContenuPanierSchema>>(
|
||||
parse(MessageMajContenuPanierSchema, evenementMessage.data),
|
||||
).ifLeft((erreur) => reporteErreur(erreur));
|
||||
).ifLeft(erreur => reporteErreur(erreur));
|
||||
|
||||
// Correspondances
|
||||
export const reponseEstCodeErreurWC = (reponse: SimplifiedResponse, codeErreurWC: string): boolean =>
|
||||
safeSchemaParse(reponse, WCErrorSchema)
|
||||
.map((v) => v.body.code === codeErreurWC)
|
||||
.map(v => v.body.code === codeErreurWC)
|
||||
.orDefault(false);
|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@ export const estEntreDeuxNombres = (nombre: number, min: number, max: number): b
|
|||
|
||||
export const diviseParCent = (nombre: number | string): number => Number(nombre) / 100;
|
||||
|
||||
export const arrondisADeuxDecimales = (nombre: number | string) => pipe(Number(nombre), (n) => n.toFixed(2));
|
||||
export const arrondisADeuxDecimales = (nombre: number | string) => pipe(Number(nombre), n => n.toFixed(2));
|
||||
|
||||
export const arrondisAZeroOuDeuxDecimales = (nombre: number | string): string =>
|
||||
pipe(Number(nombre), (n) => (n / Math.round(n) === 1 ? n.toFixed(0) : n.toFixed(2)));
|
||||
pipe(Number(nombre), n => (n / Math.round(n) === 1 ? n.toFixed(0) : n.toFixed(2)));
|
||||
|
||||
export const inverseNombre = (nombre: number | string): number => Number(nombre) * -1;
|
||||
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ type ArgumentsPostBackendWC = {
|
|||
|
||||
// Fetch
|
||||
|
||||
export const getBackend = (args: ArgumentsGetBackendWC): Promise<Response> =>
|
||||
export const getBackend = async (args: ArgumentsGetBackendWC): Promise<Response> =>
|
||||
fetch(args.route, {
|
||||
credentials: "same-origin",
|
||||
headers: {
|
||||
|
|
@ -53,7 +53,7 @@ export const getBackend = (args: ArgumentsGetBackendWC): Promise<Response> =>
|
|||
signal: AbortSignal.timeout(5000),
|
||||
});
|
||||
|
||||
export const getBackendAvecParametresUrl = (args: ArgumentsGetBackendWC): Promise<Response> =>
|
||||
export const getBackendAvecParametresUrl = async (args: ArgumentsGetBackendWC): Promise<Response> =>
|
||||
fetch(`${args.route}?${args.searchParams}`, {
|
||||
credentials: "same-origin",
|
||||
headers: {
|
||||
|
|
@ -68,7 +68,7 @@ export const getBackendAvecParametresUrl = (args: ArgumentsGetBackendWC): Promis
|
|||
signal: AbortSignal.timeout(5000),
|
||||
});
|
||||
|
||||
export const deleteBackend = (args: ArgumentsDeleteBackendWC): Promise<Response> =>
|
||||
export const deleteBackend = async (args: ArgumentsDeleteBackendWC): Promise<Response> =>
|
||||
fetch(args.route, {
|
||||
credentials: "same-origin",
|
||||
headers: {
|
||||
|
|
@ -83,7 +83,7 @@ export const deleteBackend = (args: ArgumentsDeleteBackendWC): Promise<Response>
|
|||
signal: AbortSignal.timeout(5000),
|
||||
});
|
||||
|
||||
export const postBackend = (args: ArgumentsPostBackendWC): Promise<Response> =>
|
||||
export const postBackend = async (args: ArgumentsPostBackendWC): Promise<Response> =>
|
||||
fetch(args.route, {
|
||||
body: args.corps,
|
||||
credentials: "same-origin",
|
||||
|
|
@ -101,7 +101,7 @@ export const postBackend = (args: ArgumentsPostBackendWC): Promise<Response> =>
|
|||
|
||||
export const prefilledPostBackend =
|
||||
(nonce: string, authString?: string) =>
|
||||
(route: string, body: BodyInit, needsAuthString: boolean): Promise<Response> =>
|
||||
async (route: string, body: BodyInit, needsAuthString: boolean): Promise<Response> =>
|
||||
fetch(route, {
|
||||
body: body,
|
||||
credentials: "same-origin",
|
||||
|
|
@ -127,9 +127,9 @@ export const newPartialResponse = async (reponse: Response): Promise<SimplifiedR
|
|||
|
||||
export const traiteErreursBackendWooCommerce = (rs: SimplifiedResponse): HttpCodeErrors =>
|
||||
match(rs)
|
||||
.with({ status: 400 }, () => new BadRequestError())
|
||||
.with({ status: 401 }, () => new UnauthorizedError())
|
||||
.with({ status: 403 }, () => new ForbiddenError())
|
||||
.with({ status: 404 }, () => new NotFoundError())
|
||||
.with({ status: 500 }, () => new ServerError())
|
||||
.otherwise((rs) => new Error(String(rs.status)));
|
||||
["with"]({ status: 400 }, () => new BadRequestError())
|
||||
["with"]({ status: 401 }, () => new UnauthorizedError())
|
||||
["with"]({ status: 403 }, () => new ForbiddenError())
|
||||
["with"]({ status: 404 }, () => new NotFoundError())
|
||||
["with"]({ status: 500 }, () => new ServerError())
|
||||
.otherwise(rs => new Error(String(rs.status)));
|
||||
|
|
|
|||
|
|
@ -7,7 +7,5 @@ import { Maybe } from "purify-ts";
|
|||
*/
|
||||
export const first = <T>(xs: Array<T>): Maybe<T> => Maybe.fromNullable(xs.at(0));
|
||||
|
||||
export const find =
|
||||
<T>(predicateFn: (_1: T) => boolean) =>
|
||||
(xs: Array<T>): Maybe<T> =>
|
||||
Maybe.fromNullable(xs.find(predicateFn));
|
||||
export const find = <T>(predicateFn: (_1: T) => boolean) => (xs: Array<T>): Maybe<T> =>
|
||||
Maybe.fromNullable(xs.find(predicateFn));
|
||||
|
|
|
|||
|
|
@ -20,24 +20,24 @@ export const WCStoreCartItemTotalsSchema = v.object({
|
|||
});
|
||||
|
||||
export const WCStoreCartItemSchema = v.object({
|
||||
backorders_allowed: v.boolean(),
|
||||
catalog_visibility: v.enum(CATALOG_VISIBILITIES),
|
||||
backorders_allowed: v["boolean"](),
|
||||
catalog_visibility: v["enum"](CATALOG_VISIBILITIES),
|
||||
description: v.string(),
|
||||
extensions: v.unknown(),
|
||||
id: v.number(),
|
||||
images: v.array(v.unknown()),
|
||||
item_data: v.array(v.unknown()),
|
||||
key: v.string(),
|
||||
low_stock_remaining: v.union([v.number(), v.null()]),
|
||||
low_stock_remaining: v.union([v.number(), v["null"]()]),
|
||||
name: v.string(),
|
||||
permalink: v.pipe(v.string(), v.url()),
|
||||
prices: v.unknown(),
|
||||
quantity: v.number(),
|
||||
quantity_limits: v.unknown(),
|
||||
short_description: v.string(),
|
||||
show_backorder_badge: v.boolean(),
|
||||
show_backorder_badge: v["boolean"](),
|
||||
sku: v.string(),
|
||||
sold_individually: v.boolean(),
|
||||
sold_individually: v["boolean"](),
|
||||
totals: WCStoreCartItemTotalsSchema,
|
||||
type: v.string(),
|
||||
variation: v.array(v.unknown()),
|
||||
|
|
@ -60,10 +60,10 @@ export const WCStoreCartTotalsSchema = v.object({
|
|||
total_items_tax: v.string(),
|
||||
total_price: v.pipe(v.union([v.string(), v.number()]), v.transform(Number)),
|
||||
total_shipping: v.pipe(
|
||||
v.union([v.string(), v.number(), v.null()]),
|
||||
v.transform((n) => (n ? Number(n) : 0)),
|
||||
v.union([v.string(), v.number(), v["null"]()]),
|
||||
v.transform(n => (n ? Number(n) : 0)),
|
||||
),
|
||||
total_shipping_tax: v.union([v.string(), v.null()]),
|
||||
total_shipping_tax: v.union([v.string(), v["null"]()]),
|
||||
total_tax: v.string(),
|
||||
});
|
||||
|
||||
|
|
@ -75,12 +75,12 @@ export const WCStoreCartSchema = v.object({
|
|||
errors: v.unknown(),
|
||||
extensions: v.unknown(),
|
||||
fees: v.unknown(),
|
||||
has_calculated_shipping: v.boolean(),
|
||||
has_calculated_shipping: v["boolean"](),
|
||||
items: v.array(WCStoreCartItemSchema),
|
||||
items_count: v.pipe(v.number(), v.integer()),
|
||||
items_weight: v.pipe(v.number(), v.integer()),
|
||||
needs_payment: v.boolean(),
|
||||
needs_shipping: v.boolean(),
|
||||
needs_payment: v["boolean"](),
|
||||
needs_shipping: v["boolean"](),
|
||||
payment_methods: v.unknown(),
|
||||
payment_requirements: v.unknown(),
|
||||
shipping_address: WCStoreShippingAddressSchema,
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ export const WCStoreShippingRateShippingRateSchema = v.object({
|
|||
name: v.string(),
|
||||
price: v.pipe(v.union([v.string(), v.number()]), v.transform(Number)),
|
||||
rate_id: v.string(),
|
||||
selected: v.boolean(),
|
||||
selected: v["boolean"](),
|
||||
taxes: v.string(),
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -24,14 +24,14 @@ export const WCAddressErrorSchema = v.object({
|
|||
billing: v.optional(
|
||||
v.object({
|
||||
code: v.string(),
|
||||
data: v.union([v.null(), v.string()]),
|
||||
data: v.union([v["null"](), v.string()]),
|
||||
message: v.string(),
|
||||
}),
|
||||
),
|
||||
shipping: v.optional(
|
||||
v.object({
|
||||
code: v.string(),
|
||||
data: v.union([v.null(), v.string()]),
|
||||
data: v.union([v["null"](), v.string()]),
|
||||
message: v.string(),
|
||||
}),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -17,36 +17,36 @@ import {
|
|||
export const WCProductsArgsSchema = v.object({
|
||||
// Date ISO8601
|
||||
after: v.optional(v.optional(v.string())),
|
||||
attribute_relation: v.optional(v.enum(ATTRIBUTES_RELATIONS)),
|
||||
attribute_relation: v.optional(v["enum"](ATTRIBUTES_RELATIONS)),
|
||||
attributes: v.optional(v.array(v.unknown())),
|
||||
// Date ISO8601
|
||||
before: v.optional(v.string()),
|
||||
catalog_visibility: v.optional(v.enum(CATALOG_VISIBILITIES)),
|
||||
catalog_visibility: v.optional(v["enum"](CATALOG_VISIBILITIES)),
|
||||
category: v.optional(v.string()),
|
||||
category_operator: v.optional(v.enum(CATEGORY_OPERATORS)),
|
||||
context: v.optional(v.enum(PRODUCTS_CONTEXTES)),
|
||||
date_column: v.optional(v.enum(DATE_COLUMN_VALUES)),
|
||||
category_operator: v.optional(v["enum"](CATEGORY_OPERATORS)),
|
||||
context: v.optional(v["enum"](PRODUCTS_CONTEXTES)),
|
||||
date_column: v.optional(v["enum"](DATE_COLUMN_VALUES)),
|
||||
exclude: v.optional(v.array(v.pipe(v.number(), v.integer()))),
|
||||
featured: v.optional(v.boolean()),
|
||||
featured: v.optional(v["boolean"]()),
|
||||
include: v.optional(v.array(v.pipe(v.number(), v.integer()))),
|
||||
max_price: v.optional(v.string()),
|
||||
min_price: v.optional(v.string()),
|
||||
offset: v.optional(v.number()),
|
||||
on_sale: v.optional(v.boolean()),
|
||||
order: v.optional(v.enum(ORDER_VALUES)),
|
||||
orderby: v.optional(v.enum(ORDERBY_VALUES)),
|
||||
on_sale: v.optional(v["boolean"]()),
|
||||
order: v.optional(v["enum"](ORDER_VALUES)),
|
||||
orderby: v.optional(v["enum"](ORDERBY_VALUES)),
|
||||
page: v.optional(v.pipe(v.number(), v.minValue(1))),
|
||||
parent: v.optional(v.array(v.pipe(v.number(), v.integer()))),
|
||||
parent_exclude: v.optional(v.array(v.pipe(v.number(), v.integer()))),
|
||||
per_page: v.optional(v.pipe(v.number(), v.minValue(0), v.maxValue(100))),
|
||||
rating: v.optional(v.array(v.enum(RATINGS))),
|
||||
rating: v.optional(v.array(v["enum"](RATINGS))),
|
||||
search: v.optional(v.string()),
|
||||
sku: v.optional(v.string()),
|
||||
slug: v.optional(v.string()),
|
||||
stock_status: v.optional(v.array(v.enum(STOCK_STATUSES))),
|
||||
stock_status: v.optional(v.array(v["enum"](STOCK_STATUSES))),
|
||||
tag: v.optional(v.string()),
|
||||
tag_operator: v.optional(v.enum(TAG_OPERATORS)),
|
||||
type: v.optional(v.enum(PRODUCT_TYPES)),
|
||||
tag_operator: v.optional(v["enum"](TAG_OPERATORS)),
|
||||
type: v.optional(v["enum"](PRODUCT_TYPES)),
|
||||
});
|
||||
|
||||
export const WCProductSchema = v.object({
|
||||
|
|
@ -70,7 +70,7 @@ export const WCProductSchema = v.object({
|
|||
),
|
||||
description: v.string(),
|
||||
extensions: v.unknown(),
|
||||
has_options: v.boolean(),
|
||||
has_options: v["boolean"](),
|
||||
id: v.number(),
|
||||
images: v.array(
|
||||
v.object({
|
||||
|
|
@ -83,12 +83,12 @@ export const WCProductSchema = v.object({
|
|||
thumbnail: v.string(),
|
||||
}),
|
||||
),
|
||||
is_in_stock: v.boolean(),
|
||||
is_on_backorder: v.boolean(),
|
||||
is_purchasable: v.boolean(),
|
||||
low_stock_remaining: v.union([v.number(), v.null()]),
|
||||
is_in_stock: v["boolean"](),
|
||||
is_on_backorder: v["boolean"](),
|
||||
is_purchasable: v["boolean"](),
|
||||
low_stock_remaining: v.union([v.number(), v["null"]()]),
|
||||
name: v.string(),
|
||||
on_sale: v.boolean(),
|
||||
on_sale: v["boolean"](),
|
||||
parent: v.number(),
|
||||
permalink: v.string(),
|
||||
price_html: v.string(),
|
||||
|
|
@ -109,7 +109,7 @@ export const WCProductSchema = v.object({
|
|||
short_description: v.string(),
|
||||
sku: v.string(),
|
||||
slug: v.string(),
|
||||
sold_individually: v.boolean(),
|
||||
sold_individually: v["boolean"](),
|
||||
tags: v.array(v.string()),
|
||||
type: v.string(),
|
||||
variation: v.unknown(),
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ export const WCV3OrdersCouponLineSchema = v.object({
|
|||
discount: v.string(),
|
||||
discount_tax: v.string(),
|
||||
discount_type: v.string(),
|
||||
free_shipping: v.boolean(),
|
||||
free_shipping: v["boolean"](),
|
||||
id: v.pipe(v.number(), v.integer()),
|
||||
meta_data: v.array(WCV3OrdersCouponLineMetaDataSchema),
|
||||
nominal_amount: v.number(),
|
||||
|
|
@ -37,7 +37,7 @@ export const WCV3OrdersFeeLineSchema = v.object({
|
|||
meta_data: v.array(WCV3OrdersFeeLineMetaDataSchema),
|
||||
name: v.string(),
|
||||
tax_class: v.string(),
|
||||
tax_status: v.enum(TAX_STATUSES),
|
||||
tax_status: v["enum"](TAX_STATUSES),
|
||||
taxes: v.array(WCV3OrdersFeeLineTaxSchema),
|
||||
total: v.string(),
|
||||
total_tax: v.string(),
|
||||
|
|
@ -88,7 +88,7 @@ export const WCV3OrdersLineItemSchema = v.object({
|
|||
image: v.optional(WCV3OrdersLineItemImageSchema),
|
||||
meta_data: v.optional(v.array(WCV3OrdersLineItemMetaDataSchema)),
|
||||
name: v.optional(v.string()),
|
||||
parent_name: v.optional(v.union([v.string(), v.null()])),
|
||||
parent_name: v.optional(v.union([v.string(), v["null"]()])),
|
||||
price: v.optional(v.number()),
|
||||
product_id: v.optional(v.pipe(v.number(), v.integer())),
|
||||
quantity: v.optional(v.pipe(v.number(), v.integer())),
|
||||
|
|
@ -116,14 +116,14 @@ export const WCV3OrdersArgsSchema = v.object({
|
|||
customer_note: v.optional(v.string()),
|
||||
fee_lines: v.optional(v.array(WCV3OrdersFeeLineSchema)),
|
||||
line_items: v.optional(v.array(WCV3OrdersLineItemSchema)),
|
||||
manual_update: v.optional(v.boolean()),
|
||||
manual_update: v.optional(v["boolean"]()),
|
||||
parent_id: v.optional(v.pipe(v.number(), v.integer())),
|
||||
payment_method: v.optional(v.string()),
|
||||
payment_method_title: v.optional(v.string()),
|
||||
set_paid: v.optional(v.boolean()),
|
||||
set_paid: v.optional(v["boolean"]()),
|
||||
shipping: v.optional(WCStoreShippingAddressSchema),
|
||||
shipping_lines: v.optional(v.array(WCV3OrdersShippingLineSchema)),
|
||||
status: v.optional(v.enum(ORDER_STATUSES)),
|
||||
status: v.optional(v["enum"](ORDER_STATUSES)),
|
||||
transaction_id: v.optional(v.string()),
|
||||
});
|
||||
|
||||
|
|
@ -139,37 +139,37 @@ export const WCV3OrderSchema = v.object({
|
|||
customer_ip_address: v.string(),
|
||||
customer_note: v.string(),
|
||||
customer_user_agent: v.string(),
|
||||
date_completed: v.union([v.string(), v.null()]),
|
||||
date_completed_gmt: v.union([v.string(), v.null()]),
|
||||
date_completed: v.union([v.string(), v["null"]()]),
|
||||
date_completed_gmt: v.union([v.string(), v["null"]()]),
|
||||
// Date
|
||||
date_created: v.string(),
|
||||
date_created_gmt: v.string(),
|
||||
date_modified: v.string(),
|
||||
date_modified_gmt: v.string(),
|
||||
date_paid: v.union([v.string(), v.null()]),
|
||||
date_paid_gmt: v.union([v.string(), v.null()]),
|
||||
date_paid: v.union([v.string(), v["null"]()]),
|
||||
date_paid_gmt: v.union([v.string(), v["null"]()]),
|
||||
discount_tax: v.string(),
|
||||
discount_total: v.string(),
|
||||
fee_lines: v.array(WCV3OrdersFeeLineSchema),
|
||||
id: v.pipe(v.number(), v.integer()),
|
||||
is_editable: v.boolean(),
|
||||
is_editable: v["boolean"](),
|
||||
line_items: v.array(WCV3OrdersLineItemSchema),
|
||||
meta_data: v.unknown(),
|
||||
needs_payment: v.boolean(),
|
||||
needs_processing: v.boolean(),
|
||||
needs_payment: v["boolean"](),
|
||||
needs_processing: v["boolean"](),
|
||||
number: v.string(),
|
||||
order_key: v.string(),
|
||||
parent_id: v.pipe(v.number(), v.integer()),
|
||||
payment_method: v.string(),
|
||||
payment_method_title: v.string(),
|
||||
payment_url: v.string(),
|
||||
prices_include_tax: v.boolean(),
|
||||
prices_include_tax: v["boolean"](),
|
||||
refunds: v.array(v.unknown()),
|
||||
shipping: WCStoreShippingAddressSchema,
|
||||
shipping_lines: v.array(WCV3OrdersShippingLineSchema),
|
||||
shipping_tax: v.string(),
|
||||
shipping_total: v.string(),
|
||||
status: v.enum(ORDER_STATUSES),
|
||||
status: v["enum"](ORDER_STATUSES),
|
||||
tax_lines: v.array(v.unknown()),
|
||||
total: v.string(),
|
||||
total_tax: v.string(),
|
||||
|
|
|
|||
|
|
@ -21,20 +21,20 @@ export const WCV3ProductsArgsSchema = v.object({
|
|||
// Date ISO8601
|
||||
after: v.optional(v.string()),
|
||||
attribute: v.optional(v.string()),
|
||||
attribute_relation: v.optional(v.enum(ATTRIBUTES_RELATIONS)),
|
||||
attribute_relation: v.optional(v["enum"](ATTRIBUTES_RELATIONS)),
|
||||
attribute_term: v.optional(v.string()),
|
||||
attributes: v.optional(v.array(v.unknown())),
|
||||
// Date ISO8601
|
||||
before: v.optional(v.string()),
|
||||
catalog_visibility: v.optional(v.enum(CATALOG_VISIBILITIES)),
|
||||
catalog_visibility: v.optional(v["enum"](CATALOG_VISIBILITIES)),
|
||||
category: v.optional(v.string()),
|
||||
category_operator: v.optional(v.enum(CATEGORY_OPERATORS)),
|
||||
context: v.optional(v.enum(PRODUCTS_CONTEXTES)),
|
||||
date_column: v.optional(v.enum(DATE_COLUMN_VALUES)),
|
||||
dates_are_gmt: v.optional(v.boolean()),
|
||||
category_operator: v.optional(v["enum"](CATEGORY_OPERATORS)),
|
||||
context: v.optional(v["enum"](PRODUCTS_CONTEXTES)),
|
||||
date_column: v.optional(v["enum"](DATE_COLUMN_VALUES)),
|
||||
dates_are_gmt: v.optional(v["boolean"]()),
|
||||
exclude: v.optional(v.array(v.pipe(v.number(), v.integer()))),
|
||||
exclude_meta: v.optional(v.array(v.string())),
|
||||
featured: v.optional(v.boolean()),
|
||||
featured: v.optional(v["boolean"]()),
|
||||
include: v.optional(v.array(v.pipe(v.number(), v.integer()))),
|
||||
include_meta: v.optional(v.array(v.string())),
|
||||
max_price: v.optional(v.string()),
|
||||
|
|
@ -44,24 +44,24 @@ export const WCV3ProductsArgsSchema = v.object({
|
|||
// Date ISO8601
|
||||
modified_before: v.optional(v.string()),
|
||||
offset: v.optional(v.pipe(v.number(), v.integer())),
|
||||
on_sale: v.optional(v.boolean()),
|
||||
order: v.optional(v.enum(ORDER_VALUES)),
|
||||
orderby: v.optional(v.enum(ORDERBY_VALUES)),
|
||||
on_sale: v.optional(v["boolean"]()),
|
||||
order: v.optional(v["enum"](ORDER_VALUES)),
|
||||
orderby: v.optional(v["enum"](ORDERBY_VALUES)),
|
||||
page: v.optional(v.pipe(v.number(), v.minValue(1))),
|
||||
parent: v.optional(v.array(v.pipe(v.number(), v.integer()))),
|
||||
parent_exclude: v.optional(v.array(v.pipe(v.number(), v.integer()))),
|
||||
per_page: v.optional(v.pipe(v.number(), v.minValue(0), v.maxValue(100))),
|
||||
rating: v.optional(v.array(v.enum(RATINGS))),
|
||||
rating: v.optional(v.array(v["enum"](RATINGS))),
|
||||
search: v.optional(v.string()),
|
||||
search_sku: v.optional(v.string()),
|
||||
shipping_class: v.optional(v.string()),
|
||||
sku: v.optional(v.string()),
|
||||
slug: v.optional(v.string()),
|
||||
status: v.optional(v.enum(PRODUCT_STATUTES)),
|
||||
stock_status: v.optional(v.array(v.enum(STOCK_STATUSES))),
|
||||
status: v.optional(v["enum"](PRODUCT_STATUTES)),
|
||||
stock_status: v.optional(v.array(v["enum"](STOCK_STATUSES))),
|
||||
tag: v.optional(v.string()),
|
||||
tag_operator: v.optional(v.enum(TAG_OPERATORS)),
|
||||
type: v.optional(v.enum(PRODUCT_TYPES)),
|
||||
tag_operator: v.optional(v["enum"](TAG_OPERATORS)),
|
||||
type: v.optional(v["enum"](PRODUCT_TYPES)),
|
||||
});
|
||||
|
||||
export const WCV3ProductDownloadsSchema = v.object({
|
||||
|
|
@ -99,8 +99,8 @@ export const WCV3ProductAttributeSchema = v.object({
|
|||
name: v.string(),
|
||||
options: v.array(v.string()),
|
||||
position: v.pipe(v.number(), v.integer()),
|
||||
variation: v.boolean(),
|
||||
visible: v.boolean(),
|
||||
variation: v["boolean"](),
|
||||
visible: v["boolean"](),
|
||||
});
|
||||
export const WCV3ProductDefaultAttributeSchema = v.object({
|
||||
id: v.pipe(v.number(), v.integer()),
|
||||
|
|
@ -116,46 +116,46 @@ export const WCV3ProductMetaDataSchema = v.object({
|
|||
export const WCV3ProductSchema = v.object({
|
||||
attributes: v.array(WCV3ProductAttributeSchema),
|
||||
average_rating: v.string(),
|
||||
backordered: v.boolean(),
|
||||
backorders: v.enum(BACKORDERS_SETTINGS),
|
||||
backorders_allowed: v.boolean(),
|
||||
backordered: v["boolean"](),
|
||||
backorders: v["enum"](BACKORDERS_SETTINGS),
|
||||
backorders_allowed: v["boolean"](),
|
||||
button_text: v.string(),
|
||||
catalog_visibility: v.enum(CATALOG_VISIBILITIES),
|
||||
catalog_visibility: v["enum"](CATALOG_VISIBILITIES),
|
||||
categories: v.array(WCV3ProductCategorySchema),
|
||||
cross_sell_ids: v.array(v.pipe(v.number(), v.integer())),
|
||||
date_created: v.string(),
|
||||
date_created_gmt: v.string(),
|
||||
date_modified: v.string(),
|
||||
date_modified_gmt: v.string(),
|
||||
date_on_sale_from: v.union([v.string(), v.null()]),
|
||||
date_on_sale_from_gmt: v.union([v.string(), v.null()]),
|
||||
date_on_sale_to: v.union([v.string(), v.null()]),
|
||||
date_on_sale_to_gmt: v.union([v.string(), v.null()]),
|
||||
date_on_sale_from: v.union([v.string(), v["null"]()]),
|
||||
date_on_sale_from_gmt: v.union([v.string(), v["null"]()]),
|
||||
date_on_sale_to: v.union([v.string(), v["null"]()]),
|
||||
date_on_sale_to_gmt: v.union([v.string(), v["null"]()]),
|
||||
default_attributes: v.array(WCV3ProductDefaultAttributeSchema),
|
||||
description: v.string(),
|
||||
dimensions: WCV3ProductDimensionsSchema,
|
||||
download_expiry: v.number(),
|
||||
download_limit: v.number(),
|
||||
downloadable: v.boolean(),
|
||||
downloadable: v["boolean"](),
|
||||
downloads: v.array(WCV3ProductDownloadsSchema),
|
||||
external_url: v.string(),
|
||||
featured: v.boolean(),
|
||||
featured: v["boolean"](),
|
||||
generated_slug: v.optional(v.string()),
|
||||
global_unique_id: v.string(),
|
||||
grouped_products: v.array(v.pipe(v.number(), v.integer())),
|
||||
has_options: v.boolean(),
|
||||
has_options: v["boolean"](),
|
||||
id: v.pipe(v.number(), v.integer()),
|
||||
// NOTE: Ajouté par mes soins
|
||||
image_repos: v.union([v.string(), v.null()]),
|
||||
image_repos: v.union([v.string(), v["null"]()]),
|
||||
// NOTE: Ajouté par mes soins
|
||||
image_survol: v.union([v.string(), v.null()]),
|
||||
image_survol: v.union([v.string(), v["null"]()]),
|
||||
images: v.array(WCV3ProductImageSchema),
|
||||
low_stock_amount: v.union([v.number(), v.null()]),
|
||||
manage_stock: v.boolean(),
|
||||
low_stock_amount: v.union([v.number(), v["null"]()]),
|
||||
manage_stock: v["boolean"](),
|
||||
menu_order: v.pipe(v.number(), v.integer()),
|
||||
meta_data: v.array(WCV3ProductMetaDataSchema),
|
||||
name: v.string(),
|
||||
on_sale: v.boolean(),
|
||||
on_sale: v["boolean"](),
|
||||
parent_id: v.pipe(v.number(), v.integer()),
|
||||
permalink: v.pipe(v.string(), v.url()),
|
||||
permalink_template: v.optional(v.string()),
|
||||
|
|
@ -163,32 +163,32 @@ export const WCV3ProductSchema = v.object({
|
|||
price: v.string(),
|
||||
price_html: v.string(),
|
||||
prix_maximal: v.string(),
|
||||
purchasable: v.boolean(),
|
||||
purchasable: v["boolean"](),
|
||||
purchase_note: v.string(),
|
||||
rating_count: v.pipe(v.number(), v.integer()),
|
||||
regular_price: v.string(),
|
||||
related_ids: v.array(v.pipe(v.number(), v.integer())),
|
||||
reviews_allowed: v.boolean(),
|
||||
reviews_allowed: v["boolean"](),
|
||||
sale_price: v.string(),
|
||||
shipping_class: v.string(),
|
||||
shipping_class_id: v.pipe(v.number(), v.integer()),
|
||||
shipping_required: v.boolean(),
|
||||
shipping_taxable: v.boolean(),
|
||||
shipping_required: v["boolean"](),
|
||||
shipping_taxable: v["boolean"](),
|
||||
short_description: v.string(),
|
||||
sku: v.string(),
|
||||
slug: v.string(),
|
||||
sold_individually: v.boolean(),
|
||||
status: v.enum(PRODUCT_STATUTES),
|
||||
stock_quantity: v.union([v.number(), v.null()]),
|
||||
stock_status: v.enum(STOCK_STATUSES),
|
||||
sold_individually: v["boolean"](),
|
||||
status: v["enum"](PRODUCT_STATUTES),
|
||||
stock_quantity: v.union([v.number(), v["null"]()]),
|
||||
stock_status: v["enum"](STOCK_STATUSES),
|
||||
tags: v.array(WCV3ProductTagSchema),
|
||||
tax_class: v.string(),
|
||||
tax_status: v.enum(TAX_STATUTES),
|
||||
tax_status: v["enum"](TAX_STATUTES),
|
||||
total_sales: v.pipe(v.number(), v.integer()),
|
||||
type: v.enum(PRODUCT_TYPES),
|
||||
type: v["enum"](PRODUCT_TYPES),
|
||||
upsell_ids: v.array(v.pipe(v.number(), v.integer())),
|
||||
variations: v.array(v.pipe(v.number(), v.integer())),
|
||||
virtual: v.boolean(),
|
||||
virtual: v["boolean"](),
|
||||
weight: v.string(),
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import * as v from "valibot";
|
|||
import { TYPES_MESSAGES } from "../../constantes/messages.ts";
|
||||
import { WCStoreCartItemSchema } from "./api/cart.ts";
|
||||
|
||||
export const TypesMessagesSchema = v.enum(TYPES_MESSAGES);
|
||||
export const TypesMessagesSchema = v["enum"](TYPES_MESSAGES);
|
||||
|
||||
export const MessageMajBoutonPanierDonneesSchema = v.object({
|
||||
quantiteProduits: v.number(),
|
||||
|
|
|
|||
|
|
@ -35,4 +35,4 @@ export const getSessionStorageByKey = <S extends GenericSchema>(key: string, sch
|
|||
export const setSessionStorageByKey =
|
||||
<S extends GenericSchema>(key: string, schema: S) =>
|
||||
(value: unknown): Either<DOMException | ValiError<S>, InferOutput<S>> =>
|
||||
safeSchemaParse(value, schema).chain((v) => eitherSetSessionStorage(key, v));
|
||||
safeSchemaParse(value, schema).chain(v => eitherSetSessionStorage(key, v));
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
export type Constructor<T> = new (...args: Array<unknown>) => T;
|
||||
export type Constructor<T> = new(...args: Array<unknown>) => T;
|
||||
|
|
|
|||
|
|
@ -7,9 +7,7 @@ import { CleNonTrouveError } from "./erreurs";
|
|||
/**
|
||||
* TODO
|
||||
*/
|
||||
export const propEither =
|
||||
<T, K extends keyof T>(cle: K) =>
|
||||
(donnees: T): Either<CleNonTrouveError, T[K]> =>
|
||||
Maybe.fromNullable(D.getUnsafe(donnees, cle)).toEither(
|
||||
new CleNonTrouveError(`La clé « ${String(cle)} » n'a pas été trouvé dans l'objet.`),
|
||||
);
|
||||
export const propEither = <T, K extends keyof T>(cle: K) => (donnees: T): Either<CleNonTrouveError, T[K]> =>
|
||||
Maybe.fromNullable(D.getUnsafe(donnees, cle)).toEither(
|
||||
new CleNonTrouveError(`La clé « ${String(cle)} » n'a pas été trouvé dans l'objet.`),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -12,6 +12,5 @@ export const safeSchemaParse = <Schema extends GenericSchema>(
|
|||
): Either<ValiError<Schema>, InferOutput<Schema>> => Either.encase(() => parse(schema, valeur));
|
||||
|
||||
export const safeSchemaParseCurried =
|
||||
<S extends GenericSchema>(schema: S) =>
|
||||
(valeur: unknown): Either<ValiError<S>, InferOutput<S>> =>
|
||||
<S extends GenericSchema>(schema: S) => (valeur: unknown): Either<ValiError<S>, InferOutput<S>> =>
|
||||
Either.encase(() => parse(schema, valeur));
|
||||
|
|
|
|||
|
|
@ -8,15 +8,16 @@ import { ValiError } from "valibot";
|
|||
import type { AnySchema } from "valibot";
|
||||
|
||||
import type { WCStoreBillingAddress, WCStoreShippingAddress } from "../lib/types/api/adresses.ts";
|
||||
import type { WCStoreCart, WCStoreShippingRate, WCStoreShippingRateShippingRate } from "../lib/types/api/cart.ts";
|
||||
import type { WCStoreCartUpdateCustomerArgs } from "../lib/types/api/cart-update-customer.ts";
|
||||
import type { WCStoreCart, WCStoreShippingRate, WCStoreShippingRateShippingRate } from "../lib/types/api/cart.ts";
|
||||
import type { WCV3Order, WCV3OrdersArgs } from "../lib/types/api/v3/orders.ts";
|
||||
import type { GenericPageState } from "../lib/types/pages.ts";
|
||||
import type { FetchErrors, HttpCodeErrors } from "../lib/types/reseau.ts";
|
||||
|
||||
import { Console, Effect, Stream } from "effect";
|
||||
import { ReadonlyRecord } from "effect/Record";
|
||||
import { ROUTE_API_MAJ_CLIENT, ROUTE_API_NOUVELLE_COMMANDES } from "../constantes/api.ts";
|
||||
import { ATTRIBUT_CHARGEMENT, ATTRIBUT_LIVRAISON_VALIDEE } from "../constantes/dom.ts";
|
||||
import { NOM_CANAL_REVALIDATION_LIVRAISON } from "../constantes/messages.ts";
|
||||
import {
|
||||
ERREUR_ADRESSE_GENERIQUE,
|
||||
ERREUR_ADRESSE_MAUVAIS_CODE_POSTAL,
|
||||
|
|
@ -24,6 +25,7 @@ import {
|
|||
ERREUR_GENERIQUE_RESEAU,
|
||||
ERREUR_GENERIQUE_SOUMISSION_ADRESSES,
|
||||
} from "../constantes/messages-utilisateur.ts";
|
||||
import { NOM_CANAL_REVALIDATION_LIVRAISON } from "../constantes/messages.ts";
|
||||
import { estErreurFetch, estErreurHttp, setButtonLoadingState } from "../lib/dom.ts";
|
||||
import { reporteEtJournaliseErreur } from "../lib/erreurs.ts";
|
||||
import { ErreurAdresseInvalide } from "../lib/erreurs/adresses.ts";
|
||||
|
|
@ -36,15 +38,13 @@ import { emetUniqueMessageBroadcastChannel } from "../lib/messages.ts";
|
|||
import { diviseParCent } from "../lib/nombres.ts";
|
||||
import { newPartialResponse, prefilledPostBackend, safeFetch, traiteErreursBackendWooCommerce } from "../lib/reseau.ts";
|
||||
import { find, first } from "../lib/safe-arrays.ts";
|
||||
import { WCStoreCartSchema } from "../lib/schemas/api/cart.ts";
|
||||
import { WCStoreCartUpdateCustomerArgsSchema } from "../lib/schemas/api/cart-update-customer.ts";
|
||||
import { WCStoreCartSchema } from "../lib/schemas/api/cart.ts";
|
||||
import { estWCAddressError } from "../lib/schemas/api/erreurs.ts";
|
||||
import { WCV3OrdersArgsSchema, WCV3OrderSchema } from "../lib/schemas/api/v3/orders.ts";
|
||||
import { safeSchemaParse } from "../lib/validation.ts";
|
||||
import { E } from "./scripts-page-panier-elements.ts";
|
||||
import { getShippingRatesLS } from "./scripts-page-panier-local-storage.ts";
|
||||
import { ReadonlyRecord } from "effect/Record";
|
||||
import { Console, Effect, Stream } from "effect";
|
||||
|
||||
type Addresses = {
|
||||
billing_address: WCStoreBillingAddress;
|
||||
|
|
@ -60,7 +60,7 @@ const postBackend = prefilledPostBackend(ETATS_PAGE.nonce, ETATS_PAGE.authString
|
|||
*
|
||||
* @returns Un `Effect` ne retournant rien et ne pouvant échouer.
|
||||
*/
|
||||
export const initCartFormEventEmitters = Effect.fn("initCartFormEventEmitters")(function* () {
|
||||
export const initCartFormEventEmitters = Effect.fn("initCartFormEventEmitters")(function*() {
|
||||
return yield* pipe(
|
||||
Stream.fromEventListener(E.FORMULAIRE_PANIER, "change"),
|
||||
Stream.tap((event: Event) => {
|
||||
|
|
@ -125,7 +125,7 @@ export const initShippingCalculationButton = (): void => {
|
|||
.fromFalsy(E.FORMULAIRE_PANIER.checkValidity())
|
||||
// Ne fais rien si la livraison a déjà été validée
|
||||
.chainNullable((): boolean | undefined =>
|
||||
E.BOUTON_ACTIONS_FORMULAIRE.hasAttribute(ATTRIBUT_LIVRAISON_VALIDEE) ? undefined : true,
|
||||
E.BOUTON_ACTIONS_FORMULAIRE.hasAttribute(ATTRIBUT_LIVRAISON_VALIDEE) ? undefined : true
|
||||
)
|
||||
.ifJust((): void => {
|
||||
event.preventDefault();
|
||||
|
|
@ -133,8 +133,8 @@ export const initShippingCalculationButton = (): void => {
|
|||
/** Les données du Formulaire transformées pour la requête vers le Backend. */
|
||||
const formArgs: WCStoreCartUpdateCustomerArgs = pipe(
|
||||
Object.fromEntries(new FormData(E.FORMULAIRE_PANIER)) as Record<string, string>,
|
||||
(fields) => dictMap(fields, stringTrim),
|
||||
(fields) => getAddressesFromForm(fields, E.BOUTON_SEPARATION_ADRESSES.checked),
|
||||
fields => dictMap(fields, stringTrim),
|
||||
fields => getAddressesFromForm(fields, E.BOUTON_SEPARATION_ADRESSES.checked),
|
||||
);
|
||||
|
||||
// Réalise la requête et traite sa réponse
|
||||
|
|
@ -142,7 +142,7 @@ export const initShippingCalculationButton = (): void => {
|
|||
// Désactive le Bouton pour empêcher des requêtes concurrentes
|
||||
.ifRight((): void => setButtonLoadingState(E.BOUTON_ACTIONS_FORMULAIRE, true))
|
||||
.chain((args: WCStoreCartUpdateCustomerArgs) =>
|
||||
safeFetch(postBackend(ROUTE_API_MAJ_CLIENT, JSON.stringify(args), false)),
|
||||
safeFetch(postBackend(ROUTE_API_MAJ_CLIENT, JSON.stringify(args), false))
|
||||
)
|
||||
.chain((rs: Response) =>
|
||||
EitherAsync<ErreurAdresseInvalide | HttpCodeErrors, unknown>(
|
||||
|
|
@ -151,18 +151,18 @@ export const initShippingCalculationButton = (): void => {
|
|||
.with({ status: 200 }, (rs): unknown => rs.body)
|
||||
.with(
|
||||
{
|
||||
body: P.when((body) => estWCAddressError(body)),
|
||||
body: P.when(body => estWCAddressError(body)),
|
||||
status: 400,
|
||||
},
|
||||
(rs): never => throwE(new ErreurAdresseInvalide(rs.body.data.params)),
|
||||
)
|
||||
.otherwise((rs): never => throwE(traiteErreursBackendWooCommerce(rs))),
|
||||
),
|
||||
)
|
||||
)
|
||||
.chain((b: unknown) => EitherAsync.liftEither(safeSchemaParse(b, WCStoreCartSchema)))
|
||||
.ifRight((cart: WCStoreCart): void => {
|
||||
/** La méthode de livraison sélectionnée dans le SessionStorage */
|
||||
const oldSelectedRateLS = getShippingRatesLS().chain(find((sr) => sr.selected));
|
||||
const oldSelectedRateLS = getShippingRatesLS().chain(find(sr => sr.selected));
|
||||
|
||||
/* Les méthodes de livraison mises à jour avec le nouveau choix de l'Utilisateur. */
|
||||
const updatedRates = first(cart.shipping_rates)
|
||||
|
|
@ -171,7 +171,7 @@ export const initShippingCalculationButton = (): void => {
|
|||
srs.map((sr: WCStoreShippingRateShippingRate, index: number) => {
|
||||
// Sélectionne la nouvelle méthode demandée OU la première si le SessionStorage n'a pas été défini
|
||||
oldSelectedRateLS.caseOf({
|
||||
Just: (sm) => {
|
||||
Just: sm => {
|
||||
sr.selected = sr.method_id === sm.method_id;
|
||||
},
|
||||
Nothing: () => {
|
||||
|
|
@ -183,7 +183,7 @@ export const initShippingCalculationButton = (): void => {
|
|||
sr.price = diviseParCent(sr.price);
|
||||
|
||||
return sr;
|
||||
}),
|
||||
})
|
||||
)
|
||||
.orDefault([]);
|
||||
|
||||
|
|
@ -191,13 +191,14 @@ export const initShippingCalculationButton = (): void => {
|
|||
window.dispatchEvent(createUpdatedShippingRatesEvent(updatedRates, true));
|
||||
|
||||
// Met à jour les Totaux
|
||||
const newShippingPrice = updatedRates.find((m) => m.selected)?.price ?? 0;
|
||||
const newShippingPrice = updatedRates.find(m => m.selected)?.price ?? 0;
|
||||
const newTotals = {
|
||||
...cart.totals,
|
||||
total_discount: diviseParCent(cart.totals.total_discount),
|
||||
total_items: diviseParCent(cart.totals.total_items),
|
||||
total_price:
|
||||
diviseParCent(cart.totals.total_items) - diviseParCent(cart.totals.total_discount) + newShippingPrice,
|
||||
total_price: diviseParCent(cart.totals.total_items)
|
||||
- diviseParCent(cart.totals.total_discount)
|
||||
+ newShippingPrice,
|
||||
total_shipping: newShippingPrice,
|
||||
};
|
||||
|
||||
|
|
@ -222,10 +223,10 @@ export const initShippingCalculationButton = (): void => {
|
|||
match(e.problemes)
|
||||
.when(
|
||||
// TODO: Créer une fonction utilitaire
|
||||
(p) =>
|
||||
p =>
|
||||
pipe(
|
||||
dictValues(p),
|
||||
arrayFind((c) => c === "The provided postcode is not valid"),
|
||||
arrayFind(c => c === "The provided postcode is not valid"),
|
||||
),
|
||||
// TODO: Créer une fonction utilitaire pour fixer le texte d'un message
|
||||
(): void => {
|
||||
|
|
@ -315,14 +316,14 @@ export const initOrderCreationButton = (): void => {
|
|||
};
|
||||
|
||||
// Retire toute méthode de livraison invalide.
|
||||
formArgs.shipping_lines = formArgs.shipping_lines.filter((line) => line.method_id !== undefined);
|
||||
formArgs.shipping_lines = formArgs.shipping_lines.filter(line => line.method_id !== undefined);
|
||||
|
||||
// Réalise la requête et traite sa réponse
|
||||
void EitherAsync.liftEither(safeSchemaParse(formArgs, WCV3OrdersArgsSchema))
|
||||
// Désactive le Bouton pour empêcher des requêtes concurrentes
|
||||
.ifRight((): void => setButtonLoadingState(E.BOUTON_ACTIONS_FORMULAIRE, true))
|
||||
.chain((args: WCV3OrdersArgs) =>
|
||||
safeFetch(postBackend(ROUTE_API_NOUVELLE_COMMANDES, JSON.stringify(args), true)),
|
||||
safeFetch(postBackend(ROUTE_API_NOUVELLE_COMMANDES, JSON.stringify(args), true))
|
||||
)
|
||||
.chain((rs: Response) =>
|
||||
EitherAsync<HttpCodeErrors, unknown>(
|
||||
|
|
@ -330,7 +331,7 @@ export const initOrderCreationButton = (): void => {
|
|||
match(await newPartialResponse(rs))
|
||||
.with({ status: 201 }, (rs): unknown => rs.body)
|
||||
.otherwise((rs): never => throwE(traiteErreursBackendWooCommerce(rs))),
|
||||
),
|
||||
)
|
||||
)
|
||||
.chain((b: unknown) => EitherAsync.liftEither(safeSchemaParse(b, WCV3OrderSchema)))
|
||||
.ifRight((order: WCV3Order): void => {
|
||||
|
|
@ -342,9 +343,9 @@ export const initOrderCreationButton = (): void => {
|
|||
|
||||
// Redirige vers Stripe
|
||||
Maybe.fromNullable(new URL(`https://${window.location.host}/checkout`))
|
||||
.ifJust((url) => url.searchParams.append("order_key", order.order_key))
|
||||
.ifJust((url) => url.searchParams.append("order_id", String(order.id)))
|
||||
.ifJust((url) => location.assign(url));
|
||||
.ifJust(url => url.searchParams.append("order_key", order.order_key))
|
||||
.ifJust(url => url.searchParams.append("order_id", String(order.id)))
|
||||
.ifJust(url => location.assign(url));
|
||||
})
|
||||
.ifLeft((err: FetchErrors | HttpCodeErrors | ValiError<AnySchema>): void => {
|
||||
match(err)
|
||||
|
|
|
|||
|
|
@ -54,193 +54,34 @@ export const initialiseElementsCodePromo = (): void => {
|
|||
codePromoPresent: recuperePresenceCodePromo(),
|
||||
valeurCodePromo: recupereValeurCodePromo(),
|
||||
})
|
||||
// Un code promo doit être ajouté
|
||||
// Aucun code promo n'est déjà présent et une valeur acceptable existe
|
||||
.with(
|
||||
[
|
||||
// Un code promo doit être ajouté
|
||||
// Aucun code promo n'est déjà présent et une valeur acceptable existe
|
||||
"with"
|
||||
](
|
||||
{
|
||||
cible: P.when((cible: EventTarget | null) =>
|
||||
targetMatchesSelector<HTMLButtonElement>(cible, DOM_BOUTON_CODE_PROMO),
|
||||
targetMatchesSelector<HTMLButtonElement>(cible, DOM_BOUTON_CODE_PROMO)
|
||||
),
|
||||
codePromoPresent: false,
|
||||
valeurCodePromo: P.string,
|
||||
},
|
||||
({ valeurCodePromo }) =>
|
||||
void EitherAsync
|
||||
// Vérifie le Schéma des arguments
|
||||
.liftEither(safeSchemaParse({ code: valeurCodePromo }, WCStoreCartApplyCouponArgsSchema))
|
||||
.ifRight(() => {
|
||||
// Désactive le Bouton pour empêcher des requêtes concurrentes
|
||||
E.BOUTON_CODE_PROMO.setAttribute(ATTRIBUT_DESACTIVE, "");
|
||||
E.BOUTON_CODE_PROMO.setAttribute(ATTRIBUT_CHARGEMENT, "");
|
||||
// Réinitialise le Message à l'Utilisateur
|
||||
E.MESSAGE_CODE_PROMO.textContent = "";
|
||||
|
||||
// Lance un cycle d'animation sur le texte de chargement
|
||||
lanceAnimationCycleLoading(E.BOUTON_CODE_PROMO, 500);
|
||||
})
|
||||
// Réalise la requête auprès du backend
|
||||
.map((args: WCStoreCartApplyCouponArgs) =>
|
||||
postBackend({
|
||||
corps: JSON.stringify(args),
|
||||
nonce: ETATS_PAGE.nonce,
|
||||
route: ROUTE_API_APPLIQUE_COUPON,
|
||||
}),
|
||||
)
|
||||
// Traite les cas d'Erreur
|
||||
.chain((reponse: Response) =>
|
||||
EitherAsync<ErreurCodePromoInvalide | ServerError, unknown>(async ({ throwE }) => {
|
||||
const reponseSimplifiee: SimplifiedResponse = {
|
||||
body: await reponse.json(),
|
||||
status: reponse.status,
|
||||
};
|
||||
|
||||
return match(reponseSimplifiee)
|
||||
.with({ status: 500 }, () => throwE(new ServerError("500 Server Error")))
|
||||
.with(
|
||||
{
|
||||
body: P.when(() => reponseEstCodeErreurWC(reponseSimplifiee, ERREUR_CODE_PROMO_INVALIDE)),
|
||||
status: 400,
|
||||
},
|
||||
() => throwE(new ErreurCodePromoInvalide(recupereValeurCodePromo() ?? "")),
|
||||
)
|
||||
.with({ status: 200 }, () => reponseSimplifiee.body)
|
||||
.run();
|
||||
}),
|
||||
)
|
||||
// Vérifie le Schéma de la Réponse du backend
|
||||
.chain((corpsReponse: unknown) => EitherAsync.liftEither(safeSchemaParse(corpsReponse, WCStoreCartSchema)))
|
||||
// Déclenche les mises à jour du DOM avec les données du nouveau Panier
|
||||
.ifRight((panier: WCStoreCart) => {
|
||||
E.ENSEMBLE_CODE_PROMO.toggleAttribute(ATTRIBUT_CODE_PROMO_PRESENT);
|
||||
E.CHAMP_CODE_PROMO.toggleAttribute(ATTRIBUT_DESACTIVE);
|
||||
E.CHAMP_CODE_PROMO.value = panier.coupons[0]?.code ?? "";
|
||||
E.BOUTON_CODE_PROMO.textContent = "Remove";
|
||||
|
||||
E.TOTAL_PANIER.textContent = pipe(
|
||||
diviseParCent(panier.totals.total_price),
|
||||
arrondisADeuxDecimales,
|
||||
formateEnEuros,
|
||||
);
|
||||
E.TOTAL_REDUCTION_LIGNE.toggleAttribute(ATTRIBUT_HIDDEN);
|
||||
E.TOTAL_REDUCTION_VALEUR.textContent = pipe(
|
||||
diviseParCent(panier.totals.total_discount),
|
||||
inverseNombre,
|
||||
arrondisADeuxDecimales,
|
||||
formateEnEuros,
|
||||
);
|
||||
|
||||
window.dispatchEvent(CODE_PROMO_MAJ_EVENT);
|
||||
// EmetUniqueMessageBroadcastChannel(NOM_CANAL_REVALIDATION_LIVRAISON, true);
|
||||
})
|
||||
.ifLeft((erreur) => {
|
||||
// Rétablis le texte d'origine
|
||||
E.BOUTON_CODE_PROMO.textContent = "Apply";
|
||||
|
||||
// Traite les Erreurs et affiche un Message à l'Utilisateur
|
||||
match(erreur)
|
||||
.with(P.instanceOf(ValiError), (e) => {
|
||||
reporteErreur(e);
|
||||
console.error("ValiError", e.issues);
|
||||
})
|
||||
.with(P.instanceOf(ErreurCodePromoInvalide), (e) => {
|
||||
E.MESSAGE_CODE_PROMO.textContent = "This promo code does not exist.";
|
||||
reporteErreur(e);
|
||||
console.error(e);
|
||||
})
|
||||
.with(P.instanceOf(ServerError), (e) => {
|
||||
E.MESSAGE_CODE_PROMO.textContent =
|
||||
"Sorry, something went wrong! Please refresh the page and try again.";
|
||||
reporteErreur(e);
|
||||
console.error(e);
|
||||
})
|
||||
.with(P.instanceOf(TypeError), (e) => {
|
||||
E.MESSAGE_CODE_PROMO.textContent =
|
||||
"Sorry, something went wrong! Please refresh the page and try again.";
|
||||
reporteErreur(e);
|
||||
console.error(e);
|
||||
})
|
||||
.exhaustive();
|
||||
})
|
||||
.finally(() => {
|
||||
// Désactive l'animation de chargement et rend le Bouton de nouveau cliquable
|
||||
// TODO: Créer un type d'Événement ?
|
||||
E.BOUTON_CODE_PROMO.removeAttribute(ATTRIBUT_CHARGEMENT);
|
||||
E.BOUTON_CODE_PROMO.removeAttribute(ATTRIBUT_DESACTIVE);
|
||||
})
|
||||
.run(),
|
||||
({ valeurCodePromo }) => undefined,
|
||||
)
|
||||
// Un code promo doit être retiré
|
||||
// Un code promo est présent sous forme de chaîne
|
||||
.with(
|
||||
[
|
||||
// Un code promo doit être retiré
|
||||
// Un code promo est présent sous forme de chaîne
|
||||
"with"
|
||||
](
|
||||
{
|
||||
cible: P.when((cible) => targetMatchesSelector<HTMLButtonElement>(cible, DOM_BOUTON_CODE_PROMO)),
|
||||
cible: P.when(cible => targetMatchesSelector<HTMLButtonElement>(cible, DOM_BOUTON_CODE_PROMO)),
|
||||
codePromoPresent: true,
|
||||
valeurCodePromo: P.string,
|
||||
},
|
||||
({ valeurCodePromo }) =>
|
||||
void EitherAsync.liftEither(safeSchemaParse({ code: valeurCodePromo }, WCStoreCartRemoveCouponArgsSchema))
|
||||
.ifRight(() => {
|
||||
E.BOUTON_CODE_PROMO.setAttribute(ATTRIBUT_DESACTIVE, "");
|
||||
E.BOUTON_CODE_PROMO.setAttribute(ATTRIBUT_CHARGEMENT, "");
|
||||
|
||||
lanceAnimationCycleLoading(E.BOUTON_CODE_PROMO, 500);
|
||||
})
|
||||
.map((args: WCStoreCartRemoveCouponArgs) =>
|
||||
postBackend({
|
||||
corps: JSON.stringify(args),
|
||||
nonce: ETATS_PAGE.nonce,
|
||||
route: ROUTE_API_RETIRE_COUPON,
|
||||
}),
|
||||
)
|
||||
.chain((reponse: Response) =>
|
||||
EitherAsync<ServerError, unknown>(async ({ throwE }) => {
|
||||
if (estReponse500(reponse)) {
|
||||
throwE(new ServerError("500 server Error"));
|
||||
}
|
||||
return await reponse.json();
|
||||
}),
|
||||
)
|
||||
.chain((corpsReponse: unknown) => EitherAsync.liftEither(safeSchemaParse(corpsReponse, WCStoreCartSchema)))
|
||||
.ifRight((panier: WCStoreCart) => {
|
||||
E.ENSEMBLE_CODE_PROMO.toggleAttribute(ATTRIBUT_CODE_PROMO_PRESENT);
|
||||
E.ENSEMBLE_CODE_PROMO.reset();
|
||||
E.CHAMP_CODE_PROMO.toggleAttribute(ATTRIBUT_DESACTIVE);
|
||||
E.CHAMP_CODE_PROMO.textContent = "";
|
||||
E.BOUTON_CODE_PROMO.textContent = "Apply";
|
||||
|
||||
E.TOTAL_PANIER.textContent = pipe(
|
||||
diviseParCent(panier.totals.total_price),
|
||||
arrondisADeuxDecimales,
|
||||
formateEnEuros,
|
||||
);
|
||||
E.TOTAL_REDUCTION_LIGNE.toggleAttribute(ATTRIBUT_HIDDEN);
|
||||
E.TOTAL_REDUCTION_VALEUR.textContent = "-0€";
|
||||
|
||||
emetUniqueMessageBroadcastChannel(NOM_CANAL_REVALIDATION_LIVRAISON, true);
|
||||
})
|
||||
.ifLeft((erreur) =>
|
||||
match(erreur)
|
||||
.with(P.instanceOf(ValiError), (e) => {
|
||||
reporteErreur(e);
|
||||
console.error("retour ajout code promo", e.issues);
|
||||
})
|
||||
.with(P.instanceOf(ServerError), (e) => {
|
||||
reporteErreur(e);
|
||||
console.error("retour ajout code promo", e);
|
||||
})
|
||||
.with(P.instanceOf(TypeError), (e) => {
|
||||
reporteErreur(e);
|
||||
console.error("retour ajout code promo", e);
|
||||
})
|
||||
.exhaustive(),
|
||||
)
|
||||
.finally(() => {
|
||||
E.BOUTON_CODE_PROMO.removeAttribute(ATTRIBUT_CHARGEMENT);
|
||||
E.BOUTON_CODE_PROMO.removeAttribute(ATTRIBUT_DESACTIVE);
|
||||
})
|
||||
.run(),
|
||||
({ valeurCodePromo }) => undefined,
|
||||
)
|
||||
// Ne rien faire en dehors de ces deux situations
|
||||
.with(P._, identity),
|
||||
);
|
||||
[
|
||||
// Ne rien faire en dehors de ces deux situations
|
||||
"with"
|
||||
](P._, identity));
|
||||
};
|
||||
|
|
|
|||
|
|
@ -26,20 +26,20 @@ export const reinitialiseValidationLivraison = (): void => {
|
|||
* @returns void
|
||||
*/
|
||||
export const souscrisEvenementsPanier = (): void => {
|
||||
window.addEventListener(ADRESSES_MAJ, (): void => {
|
||||
globalThis.addEventListener(ADRESSES_MAJ, (): void => {
|
||||
reinitialiseValidationLivraison();
|
||||
});
|
||||
|
||||
window.addEventListener(CODE_PROMO_MAJ, (): void => {
|
||||
globalThis.addEventListener(CODE_PROMO_MAJ, (): void => {
|
||||
reinitialiseValidationLivraison();
|
||||
});
|
||||
|
||||
window.addEventListener(SHIPPING_RATES_UPDATED, (event: Event): void => {
|
||||
globalThis.addEventListener(SHIPPING_RATES_UPDATED, (event: Event): void => {
|
||||
Either
|
||||
// La vérification du schéma se fait à l'émission
|
||||
.encase(() => (event as UpdatedShippingRatesEvent).detail)
|
||||
// Met à jour le DOM
|
||||
.ifRight((event) => {
|
||||
.ifRight(event => {
|
||||
// Met à jour les Méthodes à l'Utilisateur si demandé
|
||||
// Il peut y en avoir aucune
|
||||
if (event.refresh_methods) {
|
||||
|
|
@ -47,18 +47,18 @@ export const souscrisEvenementsPanier = (): void => {
|
|||
}
|
||||
})
|
||||
// Met à jour le SessionStorage
|
||||
.chain((event) => eitherSetSessionStorage("shipping_rates", event.shipping_rates))
|
||||
.chain(event => eitherSetSessionStorage("shipping_rates", event.shipping_rates))
|
||||
.ifLeft(reporteEtJournaliseErreur);
|
||||
});
|
||||
|
||||
window.addEventListener(TOTALS_UPDATED, (event: Event): void => {
|
||||
globalThis.addEventListener(TOTALS_UPDATED, (event: Event): void => {
|
||||
Either
|
||||
// La vérification du Schéma se fait à l'émission
|
||||
.encase(() => (event as UpdatedTotalsEvent).detail.totals)
|
||||
.chain((ts) => eitherSetSessionStorage("totals", ts))
|
||||
.chain(ts => eitherSetSessionStorage("totals", ts))
|
||||
.ifLeft(reporteEtJournaliseErreur)
|
||||
// Met à jour le DOM
|
||||
.ifRight((ts) => {
|
||||
.ifRight(ts => {
|
||||
E.SOUS_TOTAL_LIVRAISON_VALEUR.textContent = formateEnEuros(ts.total_shipping);
|
||||
E.SOUS_TOTAL_PRODUITS_VALEUR.textContent = formateEnEuros(ts.total_items);
|
||||
E.SOUS_TOTAL_REDUCTION_VALEUR.textContent = formateEnEuros(ts.total_discount * -1);
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import { getShippingRatesLS } from "./scripts-page-panier-local-storage";
|
|||
|
||||
export const initShippingRatesChoicesActions = (): void => {
|
||||
getDOMElementsWithSelector(E.CONTENEUR_METHODES_LIVRAISON)<HTMLInputElement>("input").ifRight(
|
||||
forEach((el: HTMLInputElement): void =>
|
||||
forEach((el: HTMLInputElement): void => {
|
||||
el.addEventListener("click", (event: MouseEvent): void => {
|
||||
// Récupère les méthodes du SessionStorage et les met à jour avec le nouveau choix
|
||||
getShippingRatesLS()
|
||||
|
|
@ -32,10 +32,10 @@ export const initShippingRatesChoicesActions = (): void => {
|
|||
)
|
||||
// Met à jour les Méthodes de livraison dans le SessionStorage et le DOM
|
||||
.ifJust((srs: WCStoreShippingRateShippingRates): void => {
|
||||
window.dispatchEvent(createUpdatedShippingRatesEvent(srs, false));
|
||||
globalThis.dispatchEvent(createUpdatedShippingRatesEvent(srs, false));
|
||||
})
|
||||
// Met à jour les totaux dans le SessionStorage et le DOM
|
||||
.chain(find((sr) => sr.selected))
|
||||
.chain(find(sr => sr.selected))
|
||||
.ifJust((sr: WCStoreShippingRateShippingRate): void => {
|
||||
getSessionStorageByKey("totals", WCStoreCartTotalsSchema)
|
||||
.ifLeft(reporteEtJournaliseErreur)
|
||||
|
|
@ -45,11 +45,11 @@ export const initShippingRatesChoicesActions = (): void => {
|
|||
return ts;
|
||||
})
|
||||
.ifRight((ts: WCStoreCartTotals): void => {
|
||||
window.dispatchEvent(createUpdatedTotalsEvent(ts));
|
||||
globalThis.dispatchEvent(createUpdatedTotalsEvent(ts));
|
||||
});
|
||||
});
|
||||
}),
|
||||
),
|
||||
});
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
|
|
@ -64,12 +64,17 @@ export const generateShippingRatesHTML = (
|
|||
}
|
||||
|
||||
// Retire les méthodes de livraison initiales
|
||||
getDOMElementsWithSelector(container)("div[data-methode-initiale]").ifRight(arrayForEach((div) => div.remove()));
|
||||
getDOMElementsWithSelector(container)("div[data-methode-initiale]").ifRight(
|
||||
arrayForEach(div => {
|
||||
div.remove();
|
||||
}),
|
||||
);
|
||||
|
||||
const selectedShippingRate: string = shippingRates.find((sr) => sr.selected)?.method_id ?? "";
|
||||
const selectedShippingRate: string = shippingRates.find(sr => sr.selected)?.method_id ?? "";
|
||||
const shippingRatesHTML: ReadonlyArray<TemplateResult> = arrayMap(
|
||||
shippingRates,
|
||||
(methode) => html` <div>
|
||||
methode =>
|
||||
html` <div>
|
||||
<input
|
||||
id="methode-livraison-${methode.method_id}"
|
||||
name="choix-methode-livraison"
|
||||
|
|
|
|||
|
|
@ -7,12 +7,13 @@ import { match, P } from "ts-pattern";
|
|||
import { ValiError } from "valibot";
|
||||
import type { AnySchema } from "valibot";
|
||||
|
||||
import type { WCStoreCart } from "../lib/types/api/cart.ts";
|
||||
import type { WCStoreCartRemoveItemArgs } from "../lib/types/api/cart-remove-item.ts";
|
||||
import type { WCStoreCartUpdateItemArgs } from "../lib/types/api/cart-update-item.ts";
|
||||
import type { WCStoreCart } from "../lib/types/api/cart.ts";
|
||||
import type { GenericPageState } from "../lib/types/pages.ts";
|
||||
import type { FetchErrors, HttpCodeErrors } from "../lib/types/reseau.ts";
|
||||
|
||||
import { getFirstSelectorFromParentOrThrow } from "../../scripts-effect/lib/dom.ts";
|
||||
import { ROUTE_API_MAJ_ARTICLE_PANIER, ROUTE_API_RETIRE_ARTICLE_PANIER } from "../constantes/api.ts";
|
||||
import {
|
||||
ATTRIBUT_CLE_PANIER,
|
||||
|
|
@ -31,12 +32,11 @@ import {
|
|||
} from "../lib/messages.ts";
|
||||
import { diviseParCent } from "../lib/nombres.ts";
|
||||
import { newPartialResponse, postBackend, safeFetch, traiteErreursBackendWooCommerce } from "../lib/reseau.ts";
|
||||
import { WCStoreCartSchema } from "../lib/schemas/api/cart.ts";
|
||||
import { WCStoreCartRemoveItemArgsSchema } from "../lib/schemas/api/cart-remove-item.ts";
|
||||
import { WCStoreCartUpdateItemArgsSchema } from "../lib/schemas/api/cart-update-item.ts";
|
||||
import { WCStoreCartSchema } from "../lib/schemas/api/cart.ts";
|
||||
import { safeSchemaParse } from "../lib/validation.ts";
|
||||
import { E } from "./scripts-page-panier-elements.ts";
|
||||
import { getFirstSelectorFromParentOrThrow } from "../../scripts-effect/lib/dom.ts";
|
||||
|
||||
// @ts-expect-error -- États injectés par le modèle PHP
|
||||
const PAGE_STATE: GenericPageState = _etats;
|
||||
|
|
@ -65,8 +65,7 @@ const getCartEntryInteractiveEles = (entry: HTMLElement): CartEntryInteractiveEl
|
|||
* @returns Rien.
|
||||
*/
|
||||
const toggleCartEntryButtons =
|
||||
(activated: boolean) =>
|
||||
(cartEntries: ReadonlyArray<CartEntryInteractiveElements>): void => {
|
||||
(activated: boolean) => (cartEntries: ReadonlyArray<CartEntryInteractiveElements>): void => {
|
||||
arrayForEach(cartEntries, (e: CartEntryInteractiveElements): void => {
|
||||
if (activated) {
|
||||
// Active les Boutons
|
||||
|
|
@ -121,10 +120,10 @@ const initActionsOnCartEntries = (): void => {
|
|||
.fromNullable(entryButtons.quantityInput.valueAsNumber)
|
||||
.toEither(new Error("Quantité manquante pour cette ligne du Panier !")),
|
||||
)
|
||||
.chain((q) =>
|
||||
.chain(q =>
|
||||
EitherAsync.liftEither(
|
||||
safeSchemaParse({ key: entryKey, quantity: q + 1 }, WCStoreCartUpdateItemArgsSchema),
|
||||
),
|
||||
)
|
||||
)
|
||||
.ifRight(() => {
|
||||
pipe(cartEntries, arrayMap(getCartEntryInteractiveEles), toggleCartEntryButtons(false));
|
||||
|
|
@ -136,14 +135,14 @@ const initActionsOnCartEntries = (): void => {
|
|||
nonce: PAGE_STATE.nonce,
|
||||
route: ROUTE_API_MAJ_ARTICLE_PANIER,
|
||||
}),
|
||||
),
|
||||
)
|
||||
)
|
||||
.chain((r: Response) =>
|
||||
EitherAsync<ServerError, unknown>(async ({ throwE }) =>
|
||||
match(await newPartialResponse(r))
|
||||
["with"]({ status: 200 }, (r) => r.body)
|
||||
.otherwise((rs): never => throwE(traiteErreursBackendWooCommerce(rs))),
|
||||
),
|
||||
["with"]({ status: 200 }, r => r.body)
|
||||
.otherwise((rs): never => throwE(traiteErreursBackendWooCommerce(rs)))
|
||||
)
|
||||
)
|
||||
.chain((b: unknown) => EitherAsync.liftEither(safeSchemaParse(b, WCStoreCartSchema)))
|
||||
.ifRight((c: WCStoreCart): void => {
|
||||
|
|
@ -161,17 +160,17 @@ const initActionsOnCartEntries = (): void => {
|
|||
})
|
||||
.ifLeft((err: FetchErrors | HttpCodeErrors | ValiError<AnySchema>): void => {
|
||||
match(err)
|
||||
["with"](P.instanceOf(ValiError), (e) => {
|
||||
["with"](P.instanceOf(ValiError), e => {
|
||||
reporteErreur(e);
|
||||
console.error(e.issues);
|
||||
// E.MESSAGE_ADRESSES.textContent = ERREUR_GENERIQUE_SOUMISSION_ADRESSES;
|
||||
})
|
||||
["with"](P.instanceOf(ServerError), P.instanceOf(BadRequestError), (e) => {
|
||||
["with"](P.instanceOf(ServerError), P.instanceOf(BadRequestError), e => {
|
||||
reporteErreur(e);
|
||||
console.error(e);
|
||||
// E.MESSAGE_ADRESSES.textContent = ERREUR_GENERIQUE_SOUMISSION_ADRESSES;
|
||||
})
|
||||
["with"](P.instanceOf(DOMException), P.instanceOf(TypeError), P.instanceOf(Error), (e) => {
|
||||
["with"](P.instanceOf(DOMException), P.instanceOf(TypeError), P.instanceOf(Error), e => {
|
||||
reporteErreur(e);
|
||||
console.error(e);
|
||||
// E.MESSAGE_ADRESSES.textContent = ERREUR_GENERIQUE_RESEAU;
|
||||
|
|
@ -191,7 +190,7 @@ const initActionsOnCartEntries = (): void => {
|
|||
Maybe
|
||||
// Nécessaire pour que l'on ait une valeur à incrémenter
|
||||
.fromNullable(entryButtons.quantityInput.valueAsNumber)
|
||||
.filter((valeur) => valeur > 1)
|
||||
.filter(valeur => valeur > 1)
|
||||
.ifJust((valeur: number) => {
|
||||
// Réalise la requête et traite sa réponse
|
||||
void EitherAsync
|
||||
|
|
@ -211,7 +210,7 @@ const initActionsOnCartEntries = (): void => {
|
|||
nonce: PAGE_STATE.nonce,
|
||||
route: ROUTE_API_MAJ_ARTICLE_PANIER,
|
||||
}),
|
||||
),
|
||||
)
|
||||
)
|
||||
// 4. Traite les cas d'Erreurs et récupère le Corps de la Réponse
|
||||
.chain((reponse: Response) =>
|
||||
|
|
@ -220,9 +219,9 @@ const initActionsOnCartEntries = (): void => {
|
|||
match(await newPartialResponse(reponse))
|
||||
["with"]({ status: 500 }, () => throwE(new ServerError("500 Server Error")))
|
||||
["with"]({ status: 400 }, () => throwE(new BadRequestError("400 Bad Request Error")))
|
||||
["with"]({ status: 200 }, (r) => r.body)
|
||||
.otherwise((erreur) => throwE(new Error(`Erreur inconnue ${String(erreur.status)}`))),
|
||||
),
|
||||
["with"]({ status: 200 }, r => r.body)
|
||||
.otherwise(erreur => throwE(new Error(`Erreur inconnue ${String(erreur.status)}`)))
|
||||
)
|
||||
)
|
||||
// 5. Vérifie le Schéma de la Réponse
|
||||
.chain((corps: unknown) => EitherAsync.liftEither(safeSchemaParse(corps, WCStoreCartSchema)))
|
||||
|
|
@ -243,17 +242,17 @@ const initActionsOnCartEntries = (): void => {
|
|||
// 7. Traite les Erreurs et affiche un message à l'Utilisateur
|
||||
.ifLeft((erreur: BadRequestError | FetchErrors | ServerError | ValiError<AnySchema>): void => {
|
||||
match(erreur)
|
||||
["with"](P.instanceOf(ValiError), (e) => {
|
||||
["with"](P.instanceOf(ValiError), e => {
|
||||
reporteErreur(e);
|
||||
console.error(e.issues);
|
||||
// E.MESSAGE_ADRESSES.textContent = ERREUR_GENERIQUE_SOUMISSION_ADRESSES;
|
||||
})
|
||||
["with"](P.instanceOf(ServerError), P.instanceOf(BadRequestError), (e) => {
|
||||
["with"](P.instanceOf(ServerError), P.instanceOf(BadRequestError), e => {
|
||||
reporteErreur(e);
|
||||
console.error(e);
|
||||
// E.MESSAGE_ADRESSES.textContent = ERREUR_GENERIQUE_SOUMISSION_ADRESSES;
|
||||
})
|
||||
["with"](P.instanceOf(DOMException), P.instanceOf(TypeError), P.instanceOf(Error), (e) => {
|
||||
["with"](P.instanceOf(DOMException), P.instanceOf(TypeError), P.instanceOf(Error), e => {
|
||||
reporteErreur(e);
|
||||
console.error(e);
|
||||
// E.MESSAGE_ADRESSES.textContent = ERREUR_GENERIQUE_RESEAU;
|
||||
|
|
@ -292,7 +291,7 @@ const initActionsOnCartEntries = (): void => {
|
|||
nonce: PAGE_STATE.nonce,
|
||||
route: ROUTE_API_RETIRE_ARTICLE_PANIER,
|
||||
}),
|
||||
),
|
||||
)
|
||||
)
|
||||
// 4. Traite les cas d'Erreurs et récupère le Corps de la Réponse
|
||||
.chain((reponse: Response) =>
|
||||
|
|
@ -301,9 +300,9 @@ const initActionsOnCartEntries = (): void => {
|
|||
match(await newPartialResponse(reponse))
|
||||
["with"]({ status: 500 }, () => throwE(new ServerError("500 Server Error")))
|
||||
["with"]({ status: 400 }, () => throwE(new BadRequestError("400 Bad Request Error")))
|
||||
["with"]({ status: 200 }, (r) => r.body)
|
||||
.otherwise((erreur) => throwE(new Error(`Erreur inconnue ${String(erreur.status)}`))),
|
||||
),
|
||||
["with"]({ status: 200 }, r => r.body)
|
||||
.otherwise(erreur => throwE(new Error(`Erreur inconnue ${String(erreur.status)}`)))
|
||||
)
|
||||
)
|
||||
// 5. Vérifie le Schéma de la Réponse
|
||||
.chain((corps: unknown) => EitherAsync.liftEither(safeSchemaParse(corps, WCStoreCartSchema)))
|
||||
|
|
@ -327,17 +326,17 @@ const initActionsOnCartEntries = (): void => {
|
|||
// 7. Traite les Erreurs et affiche un message à l'Utilisateur
|
||||
.ifLeft((erreur: BadRequestError | FetchErrors | ServerError | ValiError<AnySchema>): void => {
|
||||
match(erreur)
|
||||
["with"](P.instanceOf(ValiError), (e) => {
|
||||
["with"](P.instanceOf(ValiError), e => {
|
||||
reporteErreur(e);
|
||||
console.error(e.issues);
|
||||
// E.MESSAGE_ADRESSES.textContent = ERREUR_GENERIQUE_SOUMISSION_ADRESSES;
|
||||
})
|
||||
["with"](P.instanceOf(ServerError), P.instanceOf(BadRequestError), (e) => {
|
||||
["with"](P.instanceOf(ServerError), P.instanceOf(BadRequestError), e => {
|
||||
reporteErreur(e);
|
||||
console.error(e);
|
||||
// E.MESSAGE_ADRESSES.textContent = ERREUR_GENERIQUE_SOUMISSION_ADRESSES;
|
||||
})
|
||||
["with"](P.instanceOf(DOMException), P.instanceOf(TypeError), P.instanceOf(Error), (e) => {
|
||||
["with"](P.instanceOf(DOMException), P.instanceOf(TypeError), P.instanceOf(Error), e => {
|
||||
reporteErreur(e);
|
||||
console.error(e);
|
||||
// E.MESSAGE_ADRESSES.textContent = ERREUR_GENERIQUE_RESEAU;
|
||||
|
|
@ -352,10 +351,10 @@ const initActionsOnCartEntries = (): void => {
|
|||
});
|
||||
},
|
||||
)
|
||||
.otherwise((_) => {});
|
||||
.otherwise(_ => {});
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export { toggleCartEntryButtons, initActionsOnCartEntries as initialiseActionsEntreesPanier };
|
||||
export { initActionsOnCartEntries as initialiseActionsEntreesPanier, toggleCartEntryButtons };
|
||||
|
|
|
|||
|
|
@ -68,9 +68,9 @@ const initialiseObservationFenetre = (): void => {
|
|||
}
|
||||
|
||||
etapePlanifiee = true;
|
||||
requestAnimationFrame((): void =>
|
||||
majVisibiliteBouton(defilementY > window.innerHeight * RATIO_MINIMUM_PAGE_PAR_FENETRE),
|
||||
);
|
||||
requestAnimationFrame((): void => {
|
||||
majVisibiliteBouton(defilementY > window.innerHeight * RATIO_MINIMUM_PAGE_PAR_FENETRE);
|
||||
});
|
||||
});
|
||||
|
||||
new ResizeObserver((entrees: Array<ResizeObserverEntry>): void => {
|
||||
|
|
|
|||
|
|
@ -2,13 +2,14 @@
|
|||
|
||||
import { Array as EffectArray, Match, Predicate } from "effect";
|
||||
|
||||
import { DOM_ENTREES_MENU_CATEGORIES_PRODUITS, DOM_MENU_CATEGORIES_PRODUITS } from "./constantes/dom.ts";
|
||||
import { getAllSelectorFromDocumentOrThrow, getFirstSelectorFromDocumentOrThrow } from "../scripts-effect/lib/dom.ts";
|
||||
import { DOM_ENTREES_MENU_CATEGORIES_PRODUITS, DOM_MENU_CATEGORIES_PRODUITS } from "./constantes/dom.ts";
|
||||
|
||||
// Initialise les attributs HTML pour l'affichage initiale des flèches de défilement du menu de catégories de Produits.
|
||||
document.addEventListener("DOMContentLoaded", (): void => {
|
||||
const productsCategoriesMenu: HTMLElement =
|
||||
getFirstSelectorFromDocumentOrThrow<HTMLElement>(DOM_MENU_CATEGORIES_PRODUITS);
|
||||
const productsCategoriesMenu: HTMLElement = getFirstSelectorFromDocumentOrThrow<HTMLElement>(
|
||||
DOM_MENU_CATEGORIES_PRODUITS,
|
||||
);
|
||||
const menuEntries: ReadonlyArray<HTMLAnchorElement> = getAllSelectorFromDocumentOrThrow(
|
||||
DOM_ENTREES_MENU_CATEGORIES_PRODUITS,
|
||||
);
|
||||
|
|
@ -22,17 +23,25 @@ document.addEventListener("DOMContentLoaded", (): void => {
|
|||
}
|
||||
|
||||
new IntersectionObserver(
|
||||
EffectArray.forEach((intersectionEntry) => {
|
||||
EffectArray.forEach(intersectionEntry => {
|
||||
// Ne déclenche rien si le scroll n'est pas horizontal
|
||||
if (intersectionEntry.boundingClientRect.top <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
Match.value([intersectionEntry.isIntersecting]).pipe(
|
||||
Match.when([true, 0], () => productsCategoriesMenu.removeAttribute("data-entrees-presentes-debut")),
|
||||
Match.when([true, 1], () => productsCategoriesMenu.removeAttribute("data-entrees-presentes-fin")),
|
||||
Match.when([false, 0], () => productsCategoriesMenu.setAttribute("data-entrees-presentes-debut", "")),
|
||||
Match.when([false, 1], () => productsCategoriesMenu.setAttribute("data-entrees-presentes-fin", "")),
|
||||
Match.when([true, 0], () => {
|
||||
productsCategoriesMenu.removeAttribute("data-entrees-presentes-debut");
|
||||
}),
|
||||
Match.when([true, 1], () => {
|
||||
productsCategoriesMenu.removeAttribute("data-entrees-presentes-fin");
|
||||
}),
|
||||
Match.when([false, 0], () => {
|
||||
productsCategoriesMenu.setAttribute("data-entrees-presentes-debut", "");
|
||||
}),
|
||||
Match.when([false, 1], () => {
|
||||
productsCategoriesMenu.setAttribute("data-entrees-presentes-fin", "");
|
||||
}),
|
||||
Match.orElse(() => {}),
|
||||
);
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -18,19 +18,19 @@ const E = {
|
|||
const initialiseBoutonMenuMobile = (): void => {
|
||||
const menuMobile = new A11yDialog(E.MENU_MOBILE);
|
||||
|
||||
new ResizeObserver((entrees) =>
|
||||
new ResizeObserver(entrees =>
|
||||
// Cache le Menu mobile pour les grandes tailles d'écrans
|
||||
pipe(
|
||||
A.head(entrees),
|
||||
O.filter((entree: ResizeObserverEntry) => entree.borderBoxSize[0]!.inlineSize > 1000),
|
||||
O.tap((_) => menuMobile.hide()),
|
||||
),
|
||||
O.tap(_ => menuMobile.hide()),
|
||||
)
|
||||
).observe(E.CORPS_HTML);
|
||||
|
||||
E.BOUTON_MENU_MOBILE.addEventListener("click", (): void => {
|
||||
// Renvoie à la Page d'accueil pour les grandes tailles d'écrans
|
||||
if (window.innerWidth > 1000) {
|
||||
window.location.href = "/";
|
||||
globalThis.location.href = "/";
|
||||
return;
|
||||
}
|
||||
// Cache le Menu mobile s'il est actif
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* Scripts pour les fonctionnalités de la Page À Propos (« About »).
|
||||
*/
|
||||
|
||||
import { A, pipe as beltPipe, O } from "@mobily/ts-belt";
|
||||
import { A, O, pipe as beltPipe } from "@mobily/ts-belt";
|
||||
|
||||
import {
|
||||
ATTRIBUT_ENSEMBLE_EPINGLE_BOITE_ACTIF,
|
||||
|
|
@ -48,7 +48,11 @@ document.addEventListener("DOMContentLoaded", (): void => {
|
|||
O.tap((id: string) => {
|
||||
beltPipe(
|
||||
O.fromNullable(ENSEMBLES_EPINGLES_BOITES_TEXTE.get(id)),
|
||||
O.tap(A.forEach((element) => element.removeAttribute(ATTRIBUT_ENSEMBLE_EPINGLE_BOITE_ACTIF))),
|
||||
O.tap(
|
||||
A.forEach(element => {
|
||||
element.removeAttribute(ATTRIBUT_ENSEMBLE_EPINGLE_BOITE_ACTIF);
|
||||
}),
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
|
@ -64,7 +68,11 @@ document.addEventListener("DOMContentLoaded", (): void => {
|
|||
if (cible.hasAttribute(ATTRIBUT_ENSEMBLE_EPINGLE_BOITE_ACTIF)) {
|
||||
beltPipe(
|
||||
O.fromNullable(ENSEMBLES_EPINGLES_BOITES_TEXTE.get(id)),
|
||||
O.tap(A.forEach((element) => element.removeAttribute(ATTRIBUT_ENSEMBLE_EPINGLE_BOITE_ACTIF))),
|
||||
O.tap(
|
||||
A.forEach(element => {
|
||||
element.removeAttribute(ATTRIBUT_ENSEMBLE_EPINGLE_BOITE_ACTIF);
|
||||
}),
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
|
@ -73,12 +81,14 @@ document.addEventListener("DOMContentLoaded", (): void => {
|
|||
beltPipe(
|
||||
Array.from(ENSEMBLES_EPINGLES_BOITES_TEXTE.values()),
|
||||
A.flat,
|
||||
A.forEach((element) => element.removeAttribute(ATTRIBUT_ENSEMBLE_EPINGLE_BOITE_ACTIF)),
|
||||
A.forEach(element => {
|
||||
element.removeAttribute(ATTRIBUT_ENSEMBLE_EPINGLE_BOITE_ACTIF);
|
||||
}),
|
||||
);
|
||||
// Active l'Attribut sur l'Ensemble
|
||||
beltPipe(
|
||||
O.fromNullable(ENSEMBLES_EPINGLES_BOITES_TEXTE.get(id)),
|
||||
O.tap(A.forEach((element) => element.toggleAttribute(ATTRIBUT_ENSEMBLE_EPINGLE_BOITE_ACTIF))),
|
||||
O.tap(A.forEach(element => element.toggleAttribute(ATTRIBUT_ENSEMBLE_EPINGLE_BOITE_ACTIF))),
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -94,23 +94,25 @@ const initDefilementStorytelling = (): void => {
|
|||
}).observe(E.STORYTELLING);
|
||||
|
||||
// Initialise la mise à jour des images au défilement sur le Conteneur.
|
||||
E.STORYTELLING.addEventListener("scroll", (): void => majVisibilitéImagesStorytelling());
|
||||
E.STORYTELLING.addEventListener("scroll", (): void => {
|
||||
majVisibilitéImagesStorytelling();
|
||||
});
|
||||
};
|
||||
|
||||
const initGestionAnimation = (): void => {
|
||||
pipe(
|
||||
A.at(E.IMAGES_STORYTELLING, 0),
|
||||
O.tap((img) => {
|
||||
O.tap(img => {
|
||||
const options: IntersectionObserverInit = {
|
||||
root: undefined,
|
||||
rootMargin: "0px",
|
||||
threshold: 0,
|
||||
};
|
||||
const callback = (entries: Array<IntersectionObserverEntry>) => {
|
||||
A.forEach(entries, (e) => {
|
||||
e.intersectionRatio >= 0.9
|
||||
? E.CONTENEUR_ANIMATION.removeAttribute(ATTRIBUT_HIDDEN)
|
||||
: E.CONTENEUR_ANIMATION.setAttribute(ATTRIBUT_HIDDEN, "");
|
||||
A.forEach(entries, e => {
|
||||
e.intersectionRatio >= 0.9 ?
|
||||
E.CONTENEUR_ANIMATION.removeAttribute(ATTRIBUT_HIDDEN) :
|
||||
E.CONTENEUR_ANIMATION.setAttribute(ATTRIBUT_HIDDEN, "");
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import type { WCV3Products, WCV3ProductsArgs } from "./lib/types/api/v3/products
|
|||
import type { GenericPageState } from "./lib/types/pages";
|
||||
|
||||
import { ROUTE_API_NOUVELLE_PRODUCTS } from "./constantes/api.ts";
|
||||
import { PRODUCT_STATUTES } from "./constantes/api/products.ts";
|
||||
import {
|
||||
ATTRIBUT_CHARGEMENT,
|
||||
ATTRIBUT_DESACTIVE,
|
||||
|
|
@ -28,7 +29,6 @@ import { BadRequestError, reporteErreur, ServerError } from "./lib/erreurs.ts";
|
|||
import { getBackendAvecParametresUrl, newPartialResponse } from "./lib/reseau.ts";
|
||||
import { WCV3ProductsArgsSchema, WCV3ProductsSchema } from "./lib/schemas/api/v3/products.ts";
|
||||
import { safeSchemaParse } from "./lib/validation.ts";
|
||||
import { PRODUCT_STATUTES } from "./constantes/api/products.ts";
|
||||
|
||||
type APIProductsErrors =
|
||||
| APIFetchErrors
|
||||
|
|
@ -67,109 +67,7 @@ const initialisePageBoutique = (): void => {
|
|||
...(idCategorieProduits && { category: idCategorieProduits }),
|
||||
};
|
||||
|
||||
void EitherAsync
|
||||
// 1. Valide les Arguments de la Requête
|
||||
.liftEither(safeSchemaParse(args, WCV3ProductsArgsSchema))
|
||||
// 2. Exécute un Effet pour empêcher les requêtes concurrentes et lancer une animation de chargement
|
||||
.ifRight((): void => {
|
||||
// Désactive le Bouton pour empêcher des requêtes concurrentes
|
||||
E.BOUTON_PLUS_DE_PRODUITS.setAttribute(ATTRIBUT_DESACTIVE, "");
|
||||
E.BOUTON_PLUS_DE_PRODUITS.setAttribute(ATTRIBUT_CHARGEMENT, "");
|
||||
|
||||
// Lance un cycle d'animation sur le texte de chargement
|
||||
lanceAnimationCycleLoading(E.BOUTON_PLUS_DE_PRODUITS, 500);
|
||||
})
|
||||
// 3. Exécute la requête via fetch sous forme d'EitherAsync
|
||||
.chain((args: WCV3ProductsArgs) =>
|
||||
EitherAsync<DOMException | Error, Response>(() =>
|
||||
getBackendAvecParametresUrl({
|
||||
authString: ETATS_PAGE.authString,
|
||||
nonce: ETATS_PAGE.nonce,
|
||||
route: ROUTE_API_NOUVELLE_PRODUCTS,
|
||||
searchParams: new URLSearchParams(args).toString(),
|
||||
}),
|
||||
),
|
||||
)
|
||||
// 4. Traite les cas d'Erreurs et récupère le Corps de la Réponse
|
||||
.chain((reponse: Response) =>
|
||||
EitherAsync<APIFetchErrors, unknown>(async ({ throwE }) =>
|
||||
match(await newPartialResponse(reponse))
|
||||
.with({ status: 500 }, () => throwE(new ServerError("500 Server Error")))
|
||||
.with({ status: 400 }, () => throwE(new BadRequestError("400 Server Error")))
|
||||
.with({ status: 200 }, (r) => r.body)
|
||||
.run(),
|
||||
),
|
||||
)
|
||||
// 5. Vérifie le Schéma de la Réponse
|
||||
.chain((corpsReponse: unknown) => EitherAsync.liftEither(safeSchemaParse(corpsReponse, WCV3ProductsSchema)))
|
||||
// 6. Exécute un Effet pour la mise à jour du DOM avec les Résultats
|
||||
.ifRight((donnees: WCV3Products) => {
|
||||
// Cache le bouton s'il y a moins de PRODUCTS_PER_PAGE Produits disponibles (que l'on est à la dernière page)
|
||||
if (donnees.length < PRODUCTS_PER_PAGE) {
|
||||
E.BOUTON_PLUS_DE_PRODUITS.toggleAttribute(ATTRIBUT_HIDDEN);
|
||||
}
|
||||
|
||||
// Créé un DocumentFragment qui recevra tous les nouveaux Produits
|
||||
const fragment: DocumentFragment = document.createDocumentFragment();
|
||||
|
||||
// Créé les Éléments <article> à insérer
|
||||
for (const produit of donnees.slice(0, PRODUCTS_PER_PAGE)) {
|
||||
pipe(
|
||||
html`
|
||||
<article class="produit">
|
||||
<figure>
|
||||
<a href="/product/${produit.slug}">
|
||||
<picture class="produit__illustration produit__illustration__principale">
|
||||
${produit.image_repos ?? ""}
|
||||
</picture>
|
||||
|
||||
<picture class="produit__illustration produit__illustration__survol">
|
||||
${produit.image_survol ?? ""}
|
||||
</picture>
|
||||
</a>
|
||||
|
||||
<figcaption class="produit__textuel">
|
||||
<h3 class="produit__textuel__titre">
|
||||
<a href="${produit.permalink}">${produit.name}</a>
|
||||
</h3>
|
||||
<p class="produit__textuel__prix">
|
||||
${produit.prix_maximal}€
|
||||
</p>
|
||||
</figcaption>
|
||||
</figure>
|
||||
</article>
|
||||
`,
|
||||
tap((article) => fragment.append(article)),
|
||||
);
|
||||
}
|
||||
|
||||
// Ajoute les nouveaux Produits dans le DOM
|
||||
E.GRILLE_PRODUITS.append(fragment);
|
||||
E.GRILLE_PRODUITS.setAttribute(ATTRIBUT_PAGE, String(nouveauNumeroPage));
|
||||
|
||||
E.BOUTON_PLUS_DE_PRODUITS.textContent = "Show more";
|
||||
})
|
||||
// 7. Traite les Erreurs et affiche un Message à l'Utilisateur
|
||||
.ifLeft((erreur: APIProductsErrors) => {
|
||||
match(erreur)
|
||||
.with(P.instanceOf(ValiError), (e) => {
|
||||
reporteErreur(e);
|
||||
console.error("ValiError", e.issues);
|
||||
})
|
||||
.otherwise((e) => {
|
||||
reporteErreur(e);
|
||||
console.error("Erreur", e);
|
||||
});
|
||||
|
||||
E.BOUTON_PLUS_DE_PRODUITS.textContent = "Error, try again?";
|
||||
})
|
||||
// 8. Quel que soit le résultat, réactiver le Bouton et arrêter l'animation
|
||||
.finally(() => {
|
||||
// Désactive l'animation de chargement et rend le Bouton de nouveau cliquable
|
||||
E.BOUTON_PLUS_DE_PRODUITS.removeAttribute(ATTRIBUT_CHARGEMENT);
|
||||
E.BOUTON_PLUS_DE_PRODUITS.removeAttribute(ATTRIBUT_DESACTIVE);
|
||||
})
|
||||
.run();
|
||||
undefined;
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ import type { MessageMajContenuPanierSchema } from "./lib/schemas/messages.ts";
|
|||
import type { WCStoreCartItem } from "./lib/types/api/cart";
|
||||
import type { MessageMajBoutonPanierDonnees, MessageMajContenuPanierDonnees } from "./lib/types/messages";
|
||||
|
||||
import { Effect } from "effect";
|
||||
import { Effect } from "effect";
|
||||
import {
|
||||
ATTRIBUT_CLE_PANIER,
|
||||
ATTRIBUT_CONTIENT_ARTICLES,
|
||||
|
|
@ -36,7 +38,6 @@ import { E } from "./page-panier/scripts-page-panier-elements.ts";
|
|||
import { souscrisEvenementsPanier } from "./page-panier/scripts-page-panier-evenement.ts";
|
||||
import { initShippingRatesChoicesActions } from "./page-panier/scripts-page-panier-methodes-livraison.ts";
|
||||
import { initialiseActionsEntreesPanier } from "./page-panier/scripts-page-panier-panneau-produits.ts";
|
||||
import { Effect } from "effect";
|
||||
|
||||
type ElementsEntreePanier = {
|
||||
boutonAddition: HTMLButtonElement;
|
||||
|
|
@ -54,7 +55,6 @@ type EtatsPage = {
|
|||
|
||||
// @ts-expect-error -- États injectés par le modèle PHP
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- États injectés par le modèle PHP
|
||||
const ETATS_PAGE: EtatsPage = _etats;
|
||||
|
||||
/**
|
||||
* Fonction utilitaire pour récupérer un Élément dans une ligne (entrée) du Panier, en levant une
|
||||
|
|
@ -64,13 +64,11 @@ const ETATS_PAGE: EtatsPage = _etats;
|
|||
* @returns L'Élément demandé.
|
||||
* @throws Une SyntaxError si l'Élément n'est pas trouvé.
|
||||
*/
|
||||
const recupereElementDansEntreePanierOuLeve =
|
||||
(entree: HTMLElement) =>
|
||||
<E extends Element = Element>(selecteur: string) =>
|
||||
pipe(recupereElementAvecSelecteur(entree)<E>(selecteur), recupereElementOuLeve);
|
||||
const recupereElementDansEntreePanierOuLeve = (entree: HTMLElement) => (selecteur: string) =>
|
||||
pipe(recupereElementAvecSelecteur(entree)<Element>(selecteur), recupereElementOuLeve);
|
||||
|
||||
// NOTE: Nécessaire pour éviter une condition de course entre la réussite de la requête et l'émission effective du Message
|
||||
const majEtatsActivationBoutons = (entrees: Array<HTMLElement>): void =>
|
||||
const majEtatsActivationBoutons = (entrees: Array<HTMLElement>): void => {
|
||||
entrees.forEach((entree: HTMLElement) => {
|
||||
// Fonction utilitaire
|
||||
const recupereElementDansEntree = recupereElementDansEntreePanierOuLeve(entree);
|
||||
|
|
@ -82,13 +80,14 @@ const majEtatsActivationBoutons = (entrees: Array<HTMLElement>): void =>
|
|||
champQuantite: recupereElementDansEntree<HTMLInputElement>(DOM_CHAMP_QUANTITE_LIGNE_PANIER),
|
||||
};
|
||||
|
||||
Number(elements.champQuantite?.value) === 1
|
||||
? elements.boutonSoustraction.setAttribute(ATTRIBUT_DESACTIVE, "")
|
||||
: elements.boutonSoustraction.removeAttribute(ATTRIBUT_DESACTIVE);
|
||||
Number(elements.champQuantite?.value) === 1 ?
|
||||
elements.boutonSoustraction.setAttribute(ATTRIBUT_DESACTIVE, "") :
|
||||
elements.boutonSoustraction.removeAttribute(ATTRIBUT_DESACTIVE);
|
||||
elements.boutonAddition.removeAttribute(ATTRIBUT_DESACTIVE);
|
||||
elements.boutonSuppression.removeAttribute(ATTRIBUT_DESACTIVE);
|
||||
elements.boutonSuppression.textContent = "Remove";
|
||||
});
|
||||
};
|
||||
|
||||
const initialiseMajConteneurPanier = (): void => {
|
||||
new BroadcastChannel(NOM_CANAL_BOUTON_PANIER).onmessage = (evenementMessage: MessageEvent<unknown>): void => {
|
||||
|
|
@ -111,7 +110,7 @@ const initialiseMajContenuPanier = (): void => {
|
|||
donnees.produits.forEach((ligne: WCStoreCartItem) => {
|
||||
// Met à jour les entrées du Panier
|
||||
E.ENTREES_PANIER.ifRight((entrees: Array<HTMLElement>) => {
|
||||
Maybe.fromNullable(entrees.find((entree) => entree.getAttribute(ATTRIBUT_CLE_PANIER) === ligne.key)).ifJust(
|
||||
Maybe.fromNullable(entrees.find(entree => entree.getAttribute(ATTRIBUT_CLE_PANIER) === ligne.key)).ifJust(
|
||||
(entree: HTMLElement) => {
|
||||
// Fonction utilitaire
|
||||
const recupereElementDansEntree = recupereElementDansEntreePanierOuLeve(entree);
|
||||
|
|
@ -144,7 +143,9 @@ const initialiseMajContenuPanier = (): void => {
|
|||
// Reporte tout Erreur et réactive les Boutons
|
||||
.ifLeft((erreur: CleNonTrouveError | ValiError<typeof MessageMajContenuPanierSchema>) => {
|
||||
reporteErreur(erreur);
|
||||
E.ENTREES_PANIER.ifRight((entrees) => majEtatsActivationBoutons(entrees));
|
||||
E.ENTREES_PANIER.ifRight(entrees => {
|
||||
majEtatsActivationBoutons(entrees);
|
||||
});
|
||||
});
|
||||
};
|
||||
};
|
||||
|
|
@ -157,7 +158,9 @@ const initialiseMajFormulairesPanier = (): void => {
|
|||
// Rend visible le formulaire de facturation.
|
||||
E.FORMULAIRE_FACTURATION.removeAttribute(ATTRIBUT_HIDDEN);
|
||||
getDOMElementsWithSelector(E.FORMULAIRE_FACTURATION)("input, select").ifRight(
|
||||
arrayForEach((champ) => champ.removeAttribute(ATTRIBUT_DESACTIVE)),
|
||||
arrayForEach(champ => {
|
||||
champ.removeAttribute(ATTRIBUT_DESACTIVE);
|
||||
}),
|
||||
);
|
||||
})
|
||||
// Les Adresses sont combinées.
|
||||
|
|
@ -167,7 +170,7 @@ const initialiseMajFormulairesPanier = (): void => {
|
|||
getDOMElementsWithSelector(E.FORMULAIRE_FACTURATION)<HTMLInputElement | HTMLSelectElement>(
|
||||
"input, select",
|
||||
).ifRight(
|
||||
arrayForEach((champ) => {
|
||||
arrayForEach(champ => {
|
||||
champ.setAttribute(ATTRIBUT_DESACTIVE, "");
|
||||
champ.value = "";
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,132 @@
|
|||
import { Array as FxArray, Console, Context, Effect, HashMap, Layer, ManagedRuntime, Option, pipe } from "effect";
|
||||
import type { NonEmptyReadonlyArray } from "effect/Array";
|
||||
import type { NoSuchElementError } from "effect/Cause";
|
||||
import { getAllSelectorFromDocument, getFirstSelectorFromDocument } from "../scripts-effect/lib/dom.ts";
|
||||
import {
|
||||
ATTRIBUT_ARIA_CONTROLS,
|
||||
ATTRIBUT_ARIA_EXPANDED,
|
||||
ATTRIBUT_HIDDEN,
|
||||
DOM_BOUTON_AJOUT_PANIER,
|
||||
DOM_BOUTONS_ACCORDEON,
|
||||
DOM_CONTENUS_ACCORDEON,
|
||||
DOM_PRIX_PRODUIT,
|
||||
} from "./constantes/dom.ts";
|
||||
import type { WCStoreCartAddItemArgsItems } from "./lib/types/api/cart-add-item.d.ts";
|
||||
|
||||
/** Représente un ensemble bouton-contenu d'une Section dans la description du Produit. */
|
||||
type DetailEnsemble = {
|
||||
button: HTMLButtonElement;
|
||||
content: HTMLDivElement;
|
||||
};
|
||||
|
||||
class ProductPageElements extends Context.Service<
|
||||
ProductPageElements,
|
||||
{
|
||||
AddProductButton: HTMLButtonElement;
|
||||
Details: HashMap.HashMap<string, DetailEnsemble>;
|
||||
DetailsButtons: NonEmptyReadonlyArray<HTMLButtonElement>;
|
||||
DetailsContents: NonEmptyReadonlyArray<HTMLDivElement>;
|
||||
ProductPrice: HTMLParagraphElement;
|
||||
ProductRawJson: HTMLScriptElement;
|
||||
VariationChoiceForm: HTMLFormElement;
|
||||
VariationSelectors: ReadonlyArray<HTMLSelectElement>;
|
||||
}
|
||||
>()("haikuatelier.fr/Produit/ProductPageElements") {
|
||||
static readonly layer = Layer.effect(
|
||||
ProductPageElements,
|
||||
Effect.gen(function*() {
|
||||
const AddProductButton = yield* getFirstSelectorFromDocument<HTMLButtonElement>(DOM_BOUTON_AJOUT_PANIER);
|
||||
const DetailsButtons = yield* getAllSelectorFromDocument<HTMLButtonElement>(DOM_BOUTONS_ACCORDEON);
|
||||
const DetailsContents = yield* getAllSelectorFromDocument<HTMLDivElement>(DOM_CONTENUS_ACCORDEON);
|
||||
const ProductPrice = yield* getFirstSelectorFromDocument<HTMLParagraphElement>(DOM_PRIX_PRODUIT);
|
||||
const ProductRawJson = yield* getFirstSelectorFromDocument<HTMLScriptElement>("#product-json");
|
||||
const VariationChoiceForm = yield* getFirstSelectorFromDocument<HTMLFormElement>("#variation-choice");
|
||||
const VariationSelectors = yield* pipe(
|
||||
getAllSelectorFromDocument<HTMLSelectElement>(".selecteur-produit select"),
|
||||
Option.orElseSome(() => FxArray.empty<HTMLSelectElement>()),
|
||||
);
|
||||
|
||||
const Details = yield* pipe(
|
||||
DetailsButtons,
|
||||
FxArray.map(
|
||||
(button: HTMLButtonElement, index: number): Effect.Effect<[string, DetailEnsemble], NoSuchElementError> =>
|
||||
Effect.gen(function*() {
|
||||
const contentId = yield* Option.fromNullishOr(button.getAttribute(ATTRIBUT_ARIA_CONTROLS));
|
||||
const content = yield* FxArray.get(DetailsContents, index);
|
||||
|
||||
return [contentId, { button, content } satisfies DetailEnsemble];
|
||||
}),
|
||||
),
|
||||
Effect.all,
|
||||
Effect.map(HashMap.fromIterable<string, DetailEnsemble>),
|
||||
);
|
||||
|
||||
return ProductPageElements.of({
|
||||
AddProductButton,
|
||||
Details,
|
||||
DetailsButtons,
|
||||
DetailsContents,
|
||||
ProductPrice,
|
||||
ProductRawJson,
|
||||
VariationChoiceForm,
|
||||
VariationSelectors,
|
||||
});
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
class ProductPageDOM extends Context.Service<
|
||||
ProductPageDOM,
|
||||
{
|
||||
/**
|
||||
* Récupère les Attributs du Produit depuis les Elements au sein du DOM.
|
||||
*/
|
||||
getProductAttributesFromDOM: () => Effect.Effect<ReadonlyArray<WCStoreCartAddItemArgsItems>>;
|
||||
/**
|
||||
* Replie toutes les sections de la description du Produit.
|
||||
*/
|
||||
toggleAllDetails: () => Effect.Effect<void>;
|
||||
}
|
||||
>()("haikuatelier.fr/Produit/ProductPageDOM") {
|
||||
static readonly layer = Layer.effect(
|
||||
ProductPageDOM,
|
||||
Effect.gen(function*() {
|
||||
const { Details, VariationSelectors } = yield* ProductPageElements;
|
||||
|
||||
const toggleAllDetails: () => Effect.Effect<void> = () =>
|
||||
Effect.sync((): void => {
|
||||
pipe(
|
||||
// Récupère les Sections sous forme d'Ensembles.
|
||||
[...HashMap.values(Details)],
|
||||
FxArray.forEach((detail: DetailEnsemble) => {
|
||||
detail.button.toggleAttribute(ATTRIBUT_ARIA_EXPANDED, false);
|
||||
detail.content.toggleAttribute(ATTRIBUT_HIDDEN, true);
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
const getProductAttributesFromDOM: () => Effect.Effect<ReadonlyArray<WCStoreCartAddItemArgsItems>> = () =>
|
||||
Effect.sync(() =>
|
||||
FxArray.map(VariationSelectors, (select: HTMLSelectElement) => ({
|
||||
attribute: select.id,
|
||||
value: select.value,
|
||||
}))
|
||||
);
|
||||
|
||||
return ProductPageDOM.of({
|
||||
getProductAttributesFromDOM,
|
||||
toggleAllDetails,
|
||||
});
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
const ProductPageRuntime = ManagedRuntime.make(
|
||||
pipe(
|
||||
ProductPageDOM.layer,
|
||||
Layer.provide(ProductPageElements.layer),
|
||||
Layer.tapError(error => Console.error("ManagedRuntime", "Impossible de créer le Layer :", error.name)),
|
||||
),
|
||||
);
|
||||
|
||||
export { type DetailEnsemble, ProductPageDOM, ProductPageElements, ProductPageRuntime };
|
||||
|
|
@ -3,25 +3,14 @@
|
|||
import { pipe } from "@mobily/ts-belt";
|
||||
import { get as dictGet } from "@mobily/ts-belt/Dict";
|
||||
import { tap as optionTap } from "@mobily/ts-belt/Option";
|
||||
import {
|
||||
Array as FxArray,
|
||||
Effect,
|
||||
pipe as epipe,
|
||||
Option,
|
||||
Stream,
|
||||
ServiceMap,
|
||||
Layer,
|
||||
ManagedRuntime,
|
||||
Console,
|
||||
HashMap,
|
||||
} from "effect";
|
||||
import { Array as FxArray, Console, Effect, HashMap, Option, pipe as epipe, Stream } from "effect";
|
||||
import { EitherAsync } from "purify-ts";
|
||||
import { match, P } from "ts-pattern";
|
||||
import { ValiError } from "valibot";
|
||||
import type { AnySchema } from "valibot";
|
||||
|
||||
import type { WCStoreCart } from "./lib/types/api/cart.ts";
|
||||
import type { WCStoreCartAddItemArgs, WCStoreCartAddItemArgsItems } from "./lib/types/api/cart-add-item.ts";
|
||||
import type { WCStoreCart } from "./lib/types/api/cart.ts";
|
||||
import type { FetchErrors } from "./lib/types/reseau.ts";
|
||||
|
||||
import { ROUTE_API_AJOUTE_ARTICLE_PANIER } from "./constantes/api.ts";
|
||||
|
|
@ -44,15 +33,8 @@ import { emetMessageMajBoutonPanier } from "./lib/messages.ts";
|
|||
import { newPartialResponse, postBackend, safeFetch } from "./lib/reseau.ts";
|
||||
import { WCStoreCartAddItemArgsSchema } from "./lib/schemas/api/cart-add-item.ts";
|
||||
import { WCStoreCartSchema } from "./lib/schemas/api/cart.ts";
|
||||
import { safeSchemaParse } from "./lib/validation";
|
||||
import { getAllSelectorFromDocument, getFirstSelectorFromDocument } from "../scripts-effect/lib/dom.ts";
|
||||
import { NonEmptyReadonlyArray } from "effect/Array";
|
||||
import { NoSuchElementError } from "effect/Cause";
|
||||
|
||||
type DetailEnsemble = {
|
||||
button: HTMLButtonElement;
|
||||
content: HTMLDivElement;
|
||||
};
|
||||
import { safeSchemaParse } from "./lib/validation.ts";
|
||||
import { ProductPageElements, ProductPageRuntime } from "./scripts-page-produit-service.ts";
|
||||
|
||||
/** États utiles pour les scripts de la page. */
|
||||
type EtatsPage = {
|
||||
|
|
@ -65,109 +47,6 @@ type EtatsPage = {
|
|||
// @ts-expect-error -- États injectés par le modèle PHP
|
||||
const ETATS_PAGE: EtatsPage = _etats;
|
||||
|
||||
class ProductPageElements extends ServiceMap.Service<
|
||||
ProductPageElements,
|
||||
{
|
||||
AddProductButton: HTMLButtonElement;
|
||||
DetailsButtons: NonEmptyReadonlyArray<HTMLButtonElement>;
|
||||
DetailsContents: NonEmptyReadonlyArray<HTMLDivElement>;
|
||||
Details: HashMap.HashMap<string, DetailEnsemble>;
|
||||
ProductPrice: HTMLParagraphElement;
|
||||
ProductRawJson: HTMLScriptElement;
|
||||
VariationChoiceForm: HTMLFormElement;
|
||||
VariationSelectors: ReadonlyArray<HTMLSelectElement>;
|
||||
}
|
||||
>()("haikuatelier.fr/Produit/ProductPageElements") {
|
||||
static readonly layer = Layer.effect(
|
||||
ProductPageElements,
|
||||
Effect.gen(function* () {
|
||||
const AddProductButton = yield* getFirstSelectorFromDocument<HTMLButtonElement>(DOM_BOUTON_AJOUT_PANIER);
|
||||
const DetailsButtons = yield* getAllSelectorFromDocument<HTMLButtonElement>(DOM_BOUTONS_ACCORDEON);
|
||||
const DetailsContents = yield* getAllSelectorFromDocument<HTMLDivElement>(DOM_CONTENUS_ACCORDEON);
|
||||
const ProductPrice = yield* getFirstSelectorFromDocument<HTMLParagraphElement>(DOM_PRIX_PRODUIT);
|
||||
const ProductRawJson = yield* getFirstSelectorFromDocument<HTMLScriptElement>("#product-json");
|
||||
const VariationChoiceForm = yield* getFirstSelectorFromDocument<HTMLFormElement>("#variation-choice");
|
||||
const VariationSelectors = yield* pipe(
|
||||
getAllSelectorFromDocument<HTMLSelectElement>(".selecteur-produit select"),
|
||||
Option.orElseSome(() => FxArray.empty<HTMLSelectElement>()),
|
||||
);
|
||||
|
||||
const Details = yield* pipe(
|
||||
DetailsButtons,
|
||||
FxArray.map(
|
||||
(button: HTMLButtonElement, index: number): Effect.Effect<[string, DetailEnsemble], NoSuchElementError> =>
|
||||
Effect.gen(function* () {
|
||||
const contentId = yield* Option.fromNullishOr(button.getAttribute(ATTRIBUT_ARIA_CONTROLS));
|
||||
const content = yield* FxArray.get(DetailsContents, index);
|
||||
|
||||
return [contentId, { button, content } satisfies DetailEnsemble];
|
||||
}),
|
||||
),
|
||||
Effect.all,
|
||||
Effect.map(HashMap.fromIterable<string, DetailEnsemble>),
|
||||
);
|
||||
|
||||
return {
|
||||
AddProductButton,
|
||||
DetailsButtons,
|
||||
DetailsContents,
|
||||
Details,
|
||||
ProductPrice,
|
||||
ProductRawJson,
|
||||
VariationChoiceForm,
|
||||
VariationSelectors,
|
||||
};
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
const ProductPageRuntime = ManagedRuntime.make(
|
||||
pipe(
|
||||
ProductPageElements.layer,
|
||||
Layer.tapError((error) => Console.error("ManagedRuntime", "Impossible de créer le Layer :", error.name)),
|
||||
),
|
||||
);
|
||||
|
||||
// Éléments d'intérêt
|
||||
const E = {
|
||||
BOUTON_AJOUT_PANIER: mustGetEleInDocument<HTMLButtonElement>(DOM_BOUTON_AJOUT_PANIER),
|
||||
BOUTONS_ACCORDEON: mustGetElesInDocument<HTMLAnchorElement>(DOM_BOUTONS_ACCORDEON),
|
||||
CONTENUS_ACCORDEON: mustGetElesInDocument<HTMLDivElement>(DOM_CONTENUS_ACCORDEON),
|
||||
DOM_VARIATION: recupereElementDocumentEither<HTMLSelectElement>(DOM_DOM_QUANTITE),
|
||||
PRIX_PRODUIT: mustGetEleInDocument<HTMLParagraphElement>(DOM_PRIX_PRODUIT),
|
||||
PRODUCT_JSON: mustGetEleInDocument<HTMLScriptElement>("#product-json"),
|
||||
VARIATION_CHOICE_FORM: mustGetEleInDocument<HTMLFormElement>("#variation-choice"),
|
||||
};
|
||||
|
||||
const toggleAllDetails = Effect.fn("toggleAllDetails")(function* () {
|
||||
const PageElements = yield* ProductPageElements;
|
||||
// Récupère les Ensembles sous forme de tableau.
|
||||
const details = [...HashMap.values(PageElements.Details)];
|
||||
|
||||
FxArray.forEach(details, (detail: DetailEnsemble) => {
|
||||
detail.button.toggleAttribute(ATTRIBUT_ARIA_EXPANDED, false);
|
||||
detail.content.toggleAttribute(ATTRIBUT_HIDDEN, true);
|
||||
});
|
||||
});
|
||||
|
||||
// TODO: Utiliser Effect.
|
||||
const getAttributesFromDom = (): ReadonlyArray<WCStoreCartAddItemArgsItems> => {
|
||||
const selectElements = epipe(
|
||||
document.querySelectorAll<HTMLSelectElement>(".selecteur-produit select"),
|
||||
Array.from<HTMLSelectElement>,
|
||||
);
|
||||
if (selectElements.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const attributes = selectElements.map((select: HTMLSelectElement) => ({
|
||||
attribute: select.id,
|
||||
value: select.value,
|
||||
}));
|
||||
|
||||
return attributes;
|
||||
};
|
||||
|
||||
const areArraysEqual = <T>(array1: Array<T>, array2: Array<T>): boolean => {
|
||||
if (array1 !== array2) {
|
||||
const a1 = JSON.stringify(array1.toSorted());
|
||||
|
|
@ -186,7 +65,7 @@ const updatePriceOnAttributeChange = (): void => {
|
|||
const productVariations: Array<unknown> = epipe(E.PRODUCT_JSON.textContent, JSON.parse)?.variations;
|
||||
const chosenAttributes = getAttributesFromDom();
|
||||
|
||||
const chosenVariation = productVariations.find((v) => areArraysEqual(v.attributes, chosenAttributes));
|
||||
const chosenVariation = productVariations.find(v => areArraysEqual(v.attributes, chosenAttributes));
|
||||
const newPrice: string = chosenVariation.price;
|
||||
|
||||
E.PRIX_PRODUIT.textContent = `${newPrice}€`;
|
||||
|
|
@ -227,7 +106,7 @@ const ajouteProduitAuPanier = (event: MouseEvent): void => {
|
|||
nonce: ETATS_PAGE.nonce,
|
||||
route: ROUTE_API_AJOUTE_ARTICLE_PANIER,
|
||||
}),
|
||||
),
|
||||
)
|
||||
)
|
||||
// 4. Traite les cas d'Erreurs et récupère le Corps de la Réponse
|
||||
.chain((reponse: Response) =>
|
||||
|
|
@ -236,9 +115,9 @@ const ajouteProduitAuPanier = (event: MouseEvent): void => {
|
|||
match(await newPartialResponse(reponse))
|
||||
.with({ status: 500 }, () => throwE(new ServerError("500 Server Error")))
|
||||
.with({ status: 400 }, () => throwE(new BadRequestError("400 Bad Request Error")))
|
||||
.with({ status: 201 }, (r) => r.body)
|
||||
.otherwise((erreur) => throwE(new Error(`Erreur inconnue ${String(erreur.status)}`))),
|
||||
),
|
||||
.with({ status: 201 }, r => r.body)
|
||||
.otherwise(erreur => throwE(new Error(`Erreur inconnue ${String(erreur.status)}`)))
|
||||
)
|
||||
)
|
||||
// 5. Vérifie le Schéma de la Réponse
|
||||
.chain((corpsReponse: unknown) => EitherAsync.liftEither(safeSchemaParse(corpsReponse, WCStoreCartSchema)))
|
||||
|
|
@ -250,21 +129,21 @@ const ajouteProduitAuPanier = (event: MouseEvent): void => {
|
|||
E.BOUTON_AJOUT_PANIER.textContent = "Added to cart!";
|
||||
emetMessageMajBoutonPanier({ quantiteProduits: totalArticles });
|
||||
}),
|
||||
),
|
||||
)
|
||||
)
|
||||
.ifLeft((erreur: BadRequestError | FetchErrors | ServerError | ValiError<AnySchema>) => {
|
||||
match(erreur)
|
||||
.with(P.instanceOf(ValiError), (e) => {
|
||||
.with(P.instanceOf(ValiError), e => {
|
||||
reporteErreur(e);
|
||||
console.error(e.issues);
|
||||
// E.MESSAGE_ADRESSES.textContent = ERREUR_GENERIQUE_SOUMISSION_ADRESSES;
|
||||
})
|
||||
.with(P.instanceOf(ServerError), P.instanceOf(BadRequestError), (e) => {
|
||||
.with(P.instanceOf(ServerError), P.instanceOf(BadRequestError), e => {
|
||||
reporteErreur(e);
|
||||
console.error(e);
|
||||
// E.MESSAGE_ADRESSES.textContent = ERREUR_GENERIQUE_SOUMISSION_ADRESSES;
|
||||
})
|
||||
.with(P.instanceOf(DOMException), P.instanceOf(TypeError), P.instanceOf(Error), (e) => {
|
||||
.with(P.instanceOf(DOMException), P.instanceOf(TypeError), P.instanceOf(Error), e => {
|
||||
reporteErreur(e);
|
||||
console.error(e);
|
||||
// E.MESSAGE_ADRESSES.textContent = ERREUR_GENERIQUE_RESEAU;
|
||||
|
|
@ -284,7 +163,7 @@ const ajouteProduitAuPanier = (event: MouseEvent): void => {
|
|||
/**
|
||||
* Initialise l'état initial d'interactivité du Bouton d'ajout de Produit au Panier.
|
||||
*/
|
||||
const initAddToCartButton = Effect.fn("initAddToCartButton")(function* () {
|
||||
const initAddToCartButton = Effect.fn("initAddToCartButton")(function*() {
|
||||
const { AddProductButton, VariationSelectors } = yield* ProductPageElements;
|
||||
/** Est-ce que le Produit affiché est en stock ? */
|
||||
const isProductInStock = AddProductButton.hasAttribute("data-in-stock") === true;
|
||||
|
|
@ -304,10 +183,10 @@ const initAddToCartButton = Effect.fn("initAddToCartButton")(function* () {
|
|||
return yield* Effect.void;
|
||||
});
|
||||
|
||||
const onFormChange = Effect.fnUntraced(function* (event: Event) {
|
||||
const onFormChange = Effect.fnUntraced(function*(evt: Event) {
|
||||
const { AddProductButton } = yield* ProductPageElements;
|
||||
// La cible ne peut qu'être un Formulaire.
|
||||
const target = event.target as HTMLFormElement;
|
||||
const target: HTMLFormElement = evt.target as HTMLFormElement;
|
||||
const isClickAllowed = target.checkValidity() === false;
|
||||
|
||||
// Active/désactive le Bouton en fonction de la validité du Formulaire du Produit.
|
||||
|
|
@ -319,7 +198,7 @@ const onFormChange = Effect.fnUntraced(function* (event: Event) {
|
|||
/**
|
||||
* Initialise la mise à jour de l'état d'interactivité du Bouton d'ajout de Produit au Panier en fonction des actions de l'Utilisateur.
|
||||
*/
|
||||
const initAddToCartInteractionUpdates = Effect.fn("initAddToCartInteractionUpdates")(function* () {
|
||||
const initAddToCartInteractionUpdates = Effect.fn("initAddToCartInteractionUpdates")(function*() {
|
||||
return yield* pipe(
|
||||
Stream.fromEventListener(E.VARIATION_CHOICE_FORM, "change"),
|
||||
Stream.tap(onFormChange),
|
||||
|
|
@ -327,13 +206,13 @@ const initAddToCartInteractionUpdates = Effect.fn("initAddToCartInteractionUpdat
|
|||
);
|
||||
});
|
||||
|
||||
const onDetailButtonClick = Effect.fnUntraced(function* (event: Event) {
|
||||
const onDetailButtonClick = Effect.fnUntraced(function*(evt: Event) {
|
||||
const { Details } = yield* ProductPageElements;
|
||||
// Empêche la pollution de l'historique de navigation
|
||||
event.preventDefault();
|
||||
evt.preventDefault();
|
||||
|
||||
// La cible est connue.
|
||||
const target = event.target as HTMLButtonElement;
|
||||
const target = evt.target as HTMLButtonElement;
|
||||
// Récupère le contenu correspondant.
|
||||
const linkedSection = yield* pipe(
|
||||
Option.fromNullishOr(target.getAttribute(ATTRIBUT_ARIA_CONTROLS)),
|
||||
|
|
@ -357,18 +236,36 @@ const onDetailButtonClick = Effect.fnUntraced(function* (event: Event) {
|
|||
return yield* Effect.void;
|
||||
});
|
||||
|
||||
const initDetailInteractions = Effect.fn("initDetailInteractions")(function* () {
|
||||
const initDetailInteractions = Effect.fn("initDetailInteractions")(function*() {
|
||||
const PageElements = yield* ProductPageElements;
|
||||
|
||||
return yield* pipe(
|
||||
FxArray.map(PageElements.DetailsButtons, (button: HTMLButtonElement) =>
|
||||
pipe(Stream.fromEventListener(button, "click"), Stream.tap(onDetailButtonClick)),
|
||||
FxArray.map(
|
||||
PageElements.DetailsButtons,
|
||||
(button: HTMLButtonElement) => pipe(Stream.fromEventListener(button, "click"), Stream.tap(onDetailButtonClick)),
|
||||
),
|
||||
Stream.mergeAll({ concurrency: "unbounded" }),
|
||||
Stream.runDrain,
|
||||
);
|
||||
});
|
||||
|
||||
const getAttributesFromDom = (): ReadonlyArray<WCStoreCartAddItemArgsItems> => {
|
||||
const selectElements = epipe(
|
||||
document.querySelectorAll<HTMLSelectElement>(".selecteur-produit select"),
|
||||
Array.from<HTMLSelectElement>,
|
||||
);
|
||||
if (selectElements.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const attributes = selectElements.map((select: HTMLSelectElement) => ({
|
||||
attribute: select.id,
|
||||
value: select.value,
|
||||
}));
|
||||
|
||||
return attributes;
|
||||
};
|
||||
|
||||
document.addEventListener("DOMContentLoaded", (): void => {
|
||||
ProductPageRuntime.runFork(pipe(initAddToCartButton(), Effect.tapCause(Console.error)));
|
||||
ProductPageRuntime.runFork(pipe(initAddToCartInteractionUpdates(), Effect.tapCause(Console.error)));
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
/// <reference types="vite/client" />
|
||||
|
||||
type ImportMeta = {
|
||||
readonly env: ImportMetaEnv;
|
||||
};
|
||||
type ImportMeta = Readonly<{
|
||||
env: ImportMetaEnv;
|
||||
}>;
|
||||
|
||||
type ImportMetaEnv = {};
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ $products = wc_get_products([
|
|||
])
|
||||
|> function (/** @var list<WC_Product>|stdClass */ mixed $products): array {
|
||||
assert(is_array($products), 'Les Produits de la Catégorie doivent être un tableau.');
|
||||
|
||||
return $products;
|
||||
}
|
||||
|> (static fn(/** @var list<WC_Product> */ array $products): array => Arr::map(
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ use Illuminate\Support\Str;
|
|||
use Timber\Timber;
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit();
|
||||
exit();
|
||||
}
|
||||
|
||||
// Initialise Timber
|
||||
|
|
@ -30,13 +30,13 @@ $commande = $order;
|
|||
$date = new Carbon($commande->get_date_created());
|
||||
|
||||
$email = [
|
||||
'commande' => ['date' => $date->toDateString(), 'id' => $commande->get_id()],
|
||||
'livraison' => [
|
||||
'transporteur' => Str::of($commande->get_shipping_method())->replace(' (Free)', ''),
|
||||
'numero_suivi' => blank($commande->get_meta('tracking_number'))
|
||||
? 'UNKNOWN_TRACKING_NUMBER'
|
||||
: $commande->get_meta('tracking_number'),
|
||||
],
|
||||
'commande' => ['date' => $date->toDateString(), 'id' => $commande->get_id()],
|
||||
'livraison' => [
|
||||
'transporteur' => Str::of($commande->get_shipping_method())->replace(' (Free)', ''),
|
||||
'numero_suivi' => blank($commande->get_meta('tracking_number'))
|
||||
? 'UNKNOWN_TRACKING_NUMBER'
|
||||
: $commande->get_meta('tracking_number'),
|
||||
],
|
||||
];
|
||||
|
||||
$context['commande'] = $email;
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ use Illuminate\Support\Str;
|
|||
use Timber\Timber;
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit();
|
||||
exit();
|
||||
}
|
||||
|
||||
// Initialise Timber
|
||||
|
|
@ -31,44 +31,49 @@ $commande = $order;
|
|||
$date = new Carbon($commande->get_date_created());
|
||||
|
||||
$email = [
|
||||
'adresses' => ['facturation' => $commande->get_address('billing'), 'livraison' => $commande->get_address('shipping')],
|
||||
'commande' => ['date' => $date->toDateString(), 'id' => $commande->get_id()],
|
||||
'livraison' => [
|
||||
'methode' => $commande->get_shipping_method(),
|
||||
'numero_suivi' => $commande->get_meta('tracking_number'),
|
||||
],
|
||||
'paiement' => ['methode' => ''],
|
||||
'produits' => collect($commande->get_items())->map(static function (WC_Order_Item_Product $article) {
|
||||
$produit = $article->get_product();
|
||||
'adresses' => [
|
||||
'facturation' => $commande->get_address('billing'),
|
||||
'livraison' => $commande->get_address('shipping'),
|
||||
],
|
||||
'commande' => ['date' => $date->toDateString(), 'id' => $commande->get_id()],
|
||||
'livraison' => [
|
||||
'methode' => $commande->get_shipping_method(),
|
||||
'numero_suivi' => $commande->get_meta('tracking_number'),
|
||||
],
|
||||
'paiement' => ['methode' => ''],
|
||||
'produits' => collect($commande->get_items())->map(static function (WC_Order_Item_Product $article) {
|
||||
$produit = $article->get_product();
|
||||
|
||||
if (is_bool($produit) || $produit === null) {
|
||||
return [];
|
||||
}
|
||||
if (is_bool($produit) || $produit === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
// Récupère l'Attribut d'un Produit variable ou renvoie un tableau vide
|
||||
'attribut' => $produit->is_type('variable')
|
||||
? collect($produit->get_attributes())
|
||||
->mapWithKeys(static fn($_atr, $cle): array => [
|
||||
'nom' => Str::lower(wc_attribute_label($cle, $produit)),
|
||||
'valeur' => $produit->get_attribute($cle),
|
||||
])
|
||||
->toArray()
|
||||
: [],
|
||||
'lien' => $produit->get_permalink(),
|
||||
'nom' => $produit->get_title(),
|
||||
'prix_total' => $article->get_total(),
|
||||
'quantite' => $article->get_quantity(),
|
||||
];
|
||||
}),
|
||||
'totaux' => [
|
||||
'sous_total_livraison' => '0' === $commande->get_shipping_total() ? 'Free' : $commande->get_shipping_total() . '€',
|
||||
'sous_total_produits' => $commande->get_subtotal() . '€',
|
||||
'sous_total_reduction' => '0.00' === $commande->get_discount_total()
|
||||
? '0'
|
||||
: Number::format((float) $commande->get_discount_total(), maxPrecision: 2) . '€',
|
||||
'total' => Number::format((float) $commande->get_total(), maxPrecision: 2) . '€',
|
||||
],
|
||||
return [
|
||||
// Récupère l'Attribut d'un Produit variable ou renvoie un tableau vide
|
||||
'attribut' => $produit->is_type('variable')
|
||||
? collect($produit->get_attributes())
|
||||
->mapWithKeys(static fn($_atr, $cle): array => [
|
||||
'nom' => Str::lower(wc_attribute_label($cle, $produit)),
|
||||
'valeur' => $produit->get_attribute($cle),
|
||||
])
|
||||
->toArray()
|
||||
: [],
|
||||
'lien' => $produit->get_permalink(),
|
||||
'nom' => $produit->get_title(),
|
||||
'prix_total' => $article->get_total(),
|
||||
'quantite' => $article->get_quantity(),
|
||||
];
|
||||
}),
|
||||
'totaux' => [
|
||||
'sous_total_livraison' => '0' === $commande->get_shipping_total()
|
||||
? 'Free'
|
||||
: $commande->get_shipping_total() . '€',
|
||||
'sous_total_produits' => $commande->get_subtotal() . '€',
|
||||
'sous_total_reduction' => '0.00' === $commande->get_discount_total()
|
||||
? '0'
|
||||
: Number::format((float) $commande->get_discount_total(), maxPrecision: 2) . '€',
|
||||
'total' => Number::format((float) $commande->get_total(), maxPrecision: 2) . '€',
|
||||
],
|
||||
];
|
||||
// Transforme les codes de pays en noms de pays
|
||||
$email['adresses']['livraison']['country'] = WC()->countries->countries[$commande->get_shipping_country()];
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ use Illuminate\Support\Str;
|
|||
use Timber\Timber;
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit();
|
||||
exit();
|
||||
}
|
||||
|
||||
// Initialise Timber
|
||||
|
|
@ -31,40 +31,45 @@ $commande = $order;
|
|||
$date = new Carbon($commande->get_date_created());
|
||||
|
||||
$email = [
|
||||
'adresses' => ['facturation' => $commande->get_address('billing'), 'livraison' => $commande->get_address('shipping')],
|
||||
'commande' => ['date' => $date->toDateString(), 'id' => $commande->get_id()],
|
||||
'paiement' => ['methode' => ''],
|
||||
'produits' => collect($commande->get_items())->map(static function (WC_Order_Item_Product $article) {
|
||||
$produit = $article->get_product();
|
||||
'adresses' => [
|
||||
'facturation' => $commande->get_address('billing'),
|
||||
'livraison' => $commande->get_address('shipping'),
|
||||
],
|
||||
'commande' => ['date' => $date->toDateString(), 'id' => $commande->get_id()],
|
||||
'paiement' => ['methode' => ''],
|
||||
'produits' => collect($commande->get_items())->map(static function (WC_Order_Item_Product $article) {
|
||||
$produit = $article->get_product();
|
||||
|
||||
if (is_bool($produit) || $produit === null) {
|
||||
return [];
|
||||
}
|
||||
if (is_bool($produit) || $produit === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
// Récupère l'Attribut d'un Produit variable ou renvoie un tableau vide
|
||||
'attribut' => $article->is_type('variable')
|
||||
? collect($produit->get_attributes())
|
||||
->mapWithKeys(static fn($_atr, $cle): array => [
|
||||
'nom' => Str::lower(wc_attribute_label($cle, $produit)),
|
||||
'valeur' => $produit->get_attribute($cle),
|
||||
])
|
||||
->toArray()
|
||||
: [],
|
||||
'lien' => $produit->get_permalink(),
|
||||
'nom' => $produit->get_title(),
|
||||
'prix_total' => $article->get_total(),
|
||||
'quantite' => $article->get_quantity(),
|
||||
];
|
||||
}),
|
||||
'totaux' => [
|
||||
'sous_total_livraison' => '0' === $commande->get_shipping_total() ? 'Free' : $commande->get_shipping_total() . '€',
|
||||
'sous_total_produits' => $commande->get_subtotal() . '€',
|
||||
'sous_total_reduction' => '0.00' === $commande->get_discount_total()
|
||||
? '0'
|
||||
: Number::format((float) $commande->get_discount_total(), maxPrecision: 2) . '€',
|
||||
'total' => Number::format((float) $commande->get_total(), maxPrecision: 2) . '€',
|
||||
],
|
||||
return [
|
||||
// Récupère l'Attribut d'un Produit variable ou renvoie un tableau vide
|
||||
'attribut' => $article->is_type('variable')
|
||||
? collect($produit->get_attributes())
|
||||
->mapWithKeys(static fn($_atr, $cle): array => [
|
||||
'nom' => Str::lower(wc_attribute_label($cle, $produit)),
|
||||
'valeur' => $produit->get_attribute($cle),
|
||||
])
|
||||
->toArray()
|
||||
: [],
|
||||
'lien' => $produit->get_permalink(),
|
||||
'nom' => $produit->get_title(),
|
||||
'prix_total' => $article->get_total(),
|
||||
'quantite' => $article->get_quantity(),
|
||||
];
|
||||
}),
|
||||
'totaux' => [
|
||||
'sous_total_livraison' => '0' === $commande->get_shipping_total()
|
||||
? 'Free'
|
||||
: $commande->get_shipping_total() . '€',
|
||||
'sous_total_produits' => $commande->get_subtotal() . '€',
|
||||
'sous_total_reduction' => '0.00' === $commande->get_discount_total()
|
||||
? '0'
|
||||
: Number::format((float) $commande->get_discount_total(), maxPrecision: 2) . '€',
|
||||
'total' => Number::format((float) $commande->get_total(), maxPrecision: 2) . '€',
|
||||
],
|
||||
];
|
||||
// Transforme les codes de pays en noms de pays
|
||||
$email['adresses']['livraison']['country'] = WC()->countries->countries[$commande->get_shipping_country()];
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue