2026-04-06
This commit is contained in:
parent
2971f5516d
commit
7baeb28fc1
36 changed files with 390 additions and 415 deletions
|
|
@ -31,7 +31,4 @@ function load_page_resources(): void {
|
|||
|
||||
add_action('wp_enqueue_scripts', load_page_resources(...));
|
||||
|
||||
Timber::render(
|
||||
data: $context,
|
||||
filenames: $templates,
|
||||
);
|
||||
Timber::render(data: $context, filenames: $templates);
|
||||
|
|
|
|||
|
|
@ -23,10 +23,7 @@ $templates = ['boutique.twig'];
|
|||
/** @var list<WC_Product> $wc_products Les informations brutes des Produits. */
|
||||
$wc_products = wc_get_products(['limit' => 12, 'order' => 'DESC', 'orderby' => 'date', 'status' => 'publish']);
|
||||
|
||||
$products = array_map(
|
||||
callback: Product::new(...),
|
||||
array: $wc_products,
|
||||
);
|
||||
$products = array_map(callback: Product::from_wc_product(...), array: $wc_products);
|
||||
$context['products'] = $products;
|
||||
|
||||
add_action('wp_enqueue_scripts', function (): void {
|
||||
|
|
@ -44,7 +41,4 @@ add_action('wp_enqueue_scripts', function (): void {
|
|||
);
|
||||
});
|
||||
|
||||
Timber::render(
|
||||
data: $context,
|
||||
filenames: $templates,
|
||||
);
|
||||
Timber::render(data: $context, filenames: $templates);
|
||||
|
|
|
|||
|
|
@ -27,7 +27,4 @@ add_action('wp_enqueue_scripts', function (): void {
|
|||
);
|
||||
});
|
||||
|
||||
Timber::render(
|
||||
data: $context,
|
||||
filenames: $templates,
|
||||
);
|
||||
Timber::render(data: $context, filenames: $templates);
|
||||
|
|
|
|||
|
|
@ -23,12 +23,6 @@ Timber::$dirname = ['views'];
|
|||
|
||||
// Charge les Scripts du thème (report d'erreurs)
|
||||
function load_scripts(): void {
|
||||
// wp_enqueue_script_module(
|
||||
// id: 'haiku-atelier-2024-gaffe',
|
||||
// deps: [],
|
||||
// src: get_template_directory_uri() . '/assets/js/gaffe.js',
|
||||
// version: filemtime(get_template_directory() . '/assets/js/gaffe.js'),
|
||||
// );
|
||||
wp_enqueue_script_module(
|
||||
id: 'haiku-atelier-2024-bouton-panier',
|
||||
deps: [],
|
||||
|
|
|
|||
|
|
@ -7,7 +7,4 @@ use Timber\Timber;
|
|||
$context = Timber::context();
|
||||
$templates = ['base.twig'];
|
||||
|
||||
Timber::render(
|
||||
data: $context,
|
||||
filenames: $templates,
|
||||
);
|
||||
Timber::render(data: $context, filenames: $templates);
|
||||
|
|
|
|||
|
|
@ -40,7 +40,4 @@ add_action('wp_enqueue_scripts', function (): void {
|
|||
);
|
||||
});
|
||||
|
||||
Timber::render(
|
||||
data: $context,
|
||||
filenames: $templates,
|
||||
);
|
||||
Timber::render(data: $context, filenames: $templates);
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ declare(strict_types=1);
|
|||
namespace HaikuAtelier;
|
||||
|
||||
use HaikuAtelier\Data\Cart;
|
||||
use HaikuAtelier\Data\Product;
|
||||
use HaikuAtelier\WP\Resource;
|
||||
use Illuminate\Support\Number;
|
||||
use Timber\Timber;
|
||||
|
|
@ -17,13 +18,8 @@ use WC_Shipping_Rate;
|
|||
use function add_action;
|
||||
use function collect;
|
||||
use function Crell\fp\pipe;
|
||||
use function genere_balise_img_multiformats;
|
||||
use function recupere_et_formate_attributs_produit;
|
||||
use function WC;
|
||||
|
||||
// Importe la fonction pour récupérer les informations affichées des Produits dans le Panier
|
||||
require_once __DIR__ . '/src/inc/TraitementInformations.php';
|
||||
|
||||
$context = Timber::context();
|
||||
$templates = ['panier.twig'];
|
||||
|
||||
|
|
@ -52,12 +48,12 @@ $shipping_subtotal = Cart::parse_cart_value($cart_totals['shipping_total'] ?? 0)
|
|||
foreach (WC()->cart->get_cart() as $cle_panier => $article_panier) {
|
||||
$cart[$cle_panier] = [
|
||||
'attributs' => $article_panier['data']?->get_type() === 'variation'
|
||||
? recupere_et_formate_attributs_produit($article_panier['data']?->get_attributes())
|
||||
? Product::recupere_et_formate_attributs_produit($article_panier['data']?->get_attributes())
|
||||
: [],
|
||||
'cle' => $cle_panier,
|
||||
'id_produit' => $article_panier['product_id'],
|
||||
'id_variation' => $article_panier['variation_id'],
|
||||
'image' => pipe($article_panier['data']?->get_image_id(), static fn($id): string => genere_balise_img_multiformats(
|
||||
'image' => pipe($article_panier['data']?->get_image_id(), static fn($id): string => Resource::output_multi_formats_img_tag(
|
||||
id: (string) $id,
|
||||
lazy: true,
|
||||
)),
|
||||
|
|
@ -110,7 +106,4 @@ add_action('wp_enqueue_scripts', function (): void {
|
|||
});
|
||||
|
||||
// Rendu
|
||||
Timber::render(
|
||||
filenames: $templates,
|
||||
data: $context,
|
||||
);
|
||||
Timber::render(filenames: $templates, data: $context);
|
||||
|
|
|
|||
|
|
@ -24,7 +24,4 @@ add_action('wp_enqueue_scripts', function (): void {
|
|||
});
|
||||
|
||||
// Rendu
|
||||
Timber::render(
|
||||
filenames: $templates,
|
||||
data: $context,
|
||||
);
|
||||
Timber::render(filenames: $templates, data: $context);
|
||||
|
|
|
|||
|
|
@ -24,7 +24,4 @@ add_action('wp_enqueue_scripts', function (): void {
|
|||
});
|
||||
|
||||
// Rendu
|
||||
Timber::render(
|
||||
filenames: $templates,
|
||||
data: $context,
|
||||
);
|
||||
Timber::render(filenames: $templates, data: $context);
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace HaikuAtelier;
|
||||
|
||||
use HaikuAtelier\WP\Resource;
|
||||
use Roots\WPConfig\Config;
|
||||
use Stripe\Checkout\Session;
|
||||
use Stripe\StripeClient;
|
||||
|
|
@ -18,8 +19,6 @@ use WC_Order_Refund;
|
|||
|
||||
use function Crell\fp\pipe;
|
||||
|
||||
require_once __DIR__ . '/src/inc/TraitementInformations.php';
|
||||
|
||||
/** @var string $url_accueil L'URL de la page d'Accueil. */
|
||||
$url_accueil = get_page_link(get_page_by_path('home')?->ID);
|
||||
|
||||
|
|
@ -87,7 +86,7 @@ try {
|
|||
return [
|
||||
'attribut' => $attribut,
|
||||
'id_produit' => $id_produit,
|
||||
'image' => pipe($produit->get_image_id(), static fn($id): string => genere_balise_img_multiformats(
|
||||
'image' => pipe($produit->get_image_id(), static fn($id): string => Resource::output_multi_formats_img_tag(
|
||||
id: $id,
|
||||
lazy: true,
|
||||
)),
|
||||
|
|
@ -114,10 +113,7 @@ try {
|
|||
add_action('wp_enqueue_scripts', 'charge_scripts_styles_page_succes_commande');
|
||||
|
||||
// Rendu
|
||||
Timber::render(
|
||||
filenames: $templates,
|
||||
data: $context,
|
||||
);
|
||||
Timber::render(filenames: $templates, data: $context);
|
||||
} catch (Error $error) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => esc_html($error->getMessage())]);
|
||||
|
|
|
|||
|
|
@ -24,7 +24,4 @@ add_action('wp_enqueue_scripts', function (): void {
|
|||
});
|
||||
|
||||
// Rendu
|
||||
Timber::render(
|
||||
filenames: $templates,
|
||||
data: $context,
|
||||
);
|
||||
Timber::render(filenames: $templates, data: $context);
|
||||
|
|
|
|||
|
|
@ -11,7 +11,9 @@ namespace HaikuAtelier;
|
|||
use Exception;
|
||||
use HaikuAtelier\Data\Product;
|
||||
use HaikuAtelier\WP\Resource;
|
||||
use HaikuAtelier\WP\Term;
|
||||
use Illuminate\Support\Arr;
|
||||
use Psl\Option\Option;
|
||||
use stdClass;
|
||||
use Timber\Timber;
|
||||
use WC_Product;
|
||||
|
|
@ -20,12 +22,9 @@ use function add_action;
|
|||
use function assert;
|
||||
use function collect;
|
||||
use function is_array;
|
||||
use function recupere_produits_meme_collection;
|
||||
use function wc_get_product;
|
||||
use function wp_json_encode;
|
||||
|
||||
require_once __DIR__ . '/src/inc/TraitementInformations.php';
|
||||
|
||||
$context = Timber::context();
|
||||
$templates = ['produit.twig'];
|
||||
|
||||
|
|
@ -37,23 +36,41 @@ if ($raw_product === null || $raw_product === false) {
|
|||
}
|
||||
|
||||
// Assemble les données d'intérêt pour la page au sein d'une Classe.
|
||||
$product = Product::new($raw_product);
|
||||
$product = Product::from_wc_product($raw_product);
|
||||
|
||||
/** @var int $maximum_price Le prix de la Variation la plus chère */
|
||||
$maximum_price = collect($product->variations)->max('price');
|
||||
|
||||
/** @var list<Product> Les Produits de la même collection que celui affiché dans la Page. */
|
||||
$same_collection_products = recupere_produits_meme_collection($product->collection)($product->id)
|
||||
$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($products, Product::new(...)));
|
||||
|> (static fn(/** @var list<WC_Product> */ array $products): array => Arr::map(
|
||||
$products,
|
||||
Product::from_wc_product(...),
|
||||
));
|
||||
|
||||
$context['product'] = $product;
|
||||
$context['product_json'] = wp_json_encode($product);
|
||||
$context['maximum_price'] = $maximum_price;
|
||||
$context['same_collection_products'] = $same_collection_products;
|
||||
$product_tags = $raw_product->get_tag_ids()
|
||||
|> (static fn($tags_ids) => Arr::map($tags_ids, static fn($id) => Term::get_term_by_id(
|
||||
id: $id,
|
||||
taxonomy: 'product_tag',
|
||||
)))
|
||||
|> (static fn(/** @var list<Option<WC_Term>> */ $tags) => Arr::reject($tags, static fn($tag) => $tag->isNone()))
|
||||
|> (static fn(/** @var list<Option<WC_Term>> */ $tags) => Arr::map($tags, static fn($tag) => $tag->unwrap()));
|
||||
$tags = get_terms(['taxonomy' => 'product_tag', 'hide_empty' => true]);
|
||||
|
||||
echo '<pre>';
|
||||
print_r($product_tags);
|
||||
print_r($tags);
|
||||
echo '</pre>';
|
||||
|
||||
exit();
|
||||
|
||||
add_action('wp_enqueue_scripts', function (): void {
|
||||
Resource::enqueue_script_module_file(
|
||||
|
|
@ -67,7 +84,4 @@ add_action('wp_enqueue_scripts', function (): void {
|
|||
});
|
||||
|
||||
// Rendu
|
||||
Timber::render(
|
||||
filenames: $templates,
|
||||
data: $context,
|
||||
);
|
||||
Timber::render(filenames: $templates, data: $context);
|
||||
|
|
|
|||
|
|
@ -94,22 +94,11 @@ final class StarterSite extends Site {
|
|||
// Récupère la Page courante
|
||||
$url_courante = URLHelper::get_current_url();
|
||||
$context['page_courante'] = $url_courante;
|
||||
$context['est_page_tous_produits'] = preg_match(
|
||||
pattern: '/(\bshop\b)/',
|
||||
subject: $url_courante,
|
||||
);
|
||||
$context['est_page_boutique'] = preg_match(
|
||||
pattern: '/(\bshop\b)/',
|
||||
subject: $url_courante,
|
||||
)
|
||||
|| preg_match(
|
||||
pattern: '/(\bproduct\b)/',
|
||||
subject: $url_courante,
|
||||
)
|
||||
|| preg_match(
|
||||
pattern: '/(\bproduct-category\b)/',
|
||||
subject: $url_courante,
|
||||
);
|
||||
$context['est_page_tous_produits'] = preg_match(pattern: '/(\bshop\b)/', subject: $url_courante);
|
||||
$context['est_page_boutique'] =
|
||||
preg_match(pattern: '/(\bshop\b)/', subject: $url_courante)
|
||||
|| preg_match(pattern: '/(\bproduct\b)/', subject: $url_courante)
|
||||
|| preg_match(pattern: '/(\bproduct-category\b)/', subject: $url_courante);
|
||||
|
||||
// Politique de confidentialité
|
||||
$politique_confidentialite_lien = pipe(get_privacy_policy_url(), esc_url(...));
|
||||
|
|
@ -130,10 +119,7 @@ final class StarterSite extends Site {
|
|||
];
|
||||
$entrees_menu_categories = pipe(
|
||||
get_categories(['hide_empty' => false, 'orderby' => 'menu_order', 'taxonomy' => 'product_cat']),
|
||||
static fn($categories): array => array_map(
|
||||
callback: $cree_entree_menu,
|
||||
array: $categories,
|
||||
),
|
||||
static fn($categories): array => array_map(callback: $cree_entree_menu, array: $categories),
|
||||
);
|
||||
|
||||
$context['categories_produits'] = $entrees_menu_categories;
|
||||
|
|
|
|||
|
|
@ -28,10 +28,6 @@ final readonly class Attribute {
|
|||
/** @var list<AttributeOption> */
|
||||
$options = Arr::map($terms, AttributeOption::new(...));
|
||||
|
||||
return new self(
|
||||
name: $name,
|
||||
slug: $slug,
|
||||
options: $options,
|
||||
);
|
||||
return new self(name: $name, slug: $slug, options: $options);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,10 +18,6 @@ final readonly class AttributeOption {
|
|||
$name = $term->name;
|
||||
$slug = $term->slug;
|
||||
|
||||
return new self(
|
||||
id: $id,
|
||||
name: $name,
|
||||
slug: $slug,
|
||||
);
|
||||
return new self(id: $id, name: $name, slug: $slug);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,15 +5,17 @@ declare(strict_types=1);
|
|||
namespace HaikuAtelier\Data;
|
||||
|
||||
use HaikuAtelier\WP\HaikuProduct;
|
||||
use HaikuAtelier\WP\Resource;
|
||||
use HaikuAtelier\WP\Term;
|
||||
use Illuminate\Support\Arr;
|
||||
use Psl\Option;
|
||||
use stdClass;
|
||||
use WC_Product;
|
||||
use WP_Term;
|
||||
|
||||
use function HaikuAtelier\genere_balise_img_multiformats;
|
||||
use function head;
|
||||
use function Psl\Option\from_nullable;
|
||||
use function wc_get_products;
|
||||
use function wpautop;
|
||||
|
||||
final readonly class Product {
|
||||
|
|
@ -41,16 +43,7 @@ final readonly class Product {
|
|||
public string $url,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @return list<Attribute>
|
||||
*/
|
||||
public static function get_attributes_for_product(WC_Product $product): array {
|
||||
/** @var list<Attribute> */
|
||||
return $product->get_attributes()
|
||||
|> (static fn(array $attributes): array => Arr::map($attributes, Attribute::new(...)));
|
||||
}
|
||||
|
||||
public static function new(WC_Product $product): self {
|
||||
public static function from_wc_product(WC_Product $product): self {
|
||||
$attributes = self::get_attributes_for_product($product);
|
||||
/** @var lowercase-string */
|
||||
$category = $product->get_id() |> wc_get_product_category_list(...) |> strtolower(...);
|
||||
|
|
@ -73,8 +66,8 @@ final readonly class Product {
|
|||
$left_column_photos = HaikuProduct::get_left_column_photos($id);
|
||||
/** @var list<string> */
|
||||
$right_column_photos = HaikuProduct::get_right_column_photos($id);
|
||||
$default_photo = $left_column_photos[0] ?? genere_balise_img_multiformats('-1');
|
||||
$hover_photo = $right_column_photos[0] ?? genere_balise_img_multiformats('-1', true);
|
||||
$default_photo = $left_column_photos[0] ?? Resource::output_multi_formats_img_tag('-1');
|
||||
$hover_photo = $right_column_photos[0] ?? Resource::output_multi_formats_img_tag('-1', true);
|
||||
$slug = $product->get_slug();
|
||||
$stock = $product->get_stock_quantity() ?? 1;
|
||||
/** @var array<ProductVariation> */
|
||||
|
|
@ -104,4 +97,38 @@ final readonly class Product {
|
|||
url: $url,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<Attribute>
|
||||
*/
|
||||
public static function get_attributes_for_product(WC_Product $product): array {
|
||||
/** @var list<Attribute> */
|
||||
return $product->get_attributes()
|
||||
|> (static fn(array $attributes): array => Arr::map($attributes, Attribute::new(...)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère les informations utilisées pour la grille des Produits similaires (de la même
|
||||
* collection) et les retourne sous forme de tableau associatif.
|
||||
*
|
||||
* Pour faciliter l'usage avec `array_map`, utilise une fonction avec curryfication.
|
||||
*/
|
||||
public static function get_same_collection_products(string $slug_collection): callable {
|
||||
return static fn(int $id_produit): array|stdClass => wc_get_products([
|
||||
'exclude' => [$id_produit],
|
||||
'limit' => 4,
|
||||
'order' => 'DESC',
|
||||
'orderby' => 'date',
|
||||
'status' => 'publish',
|
||||
'tax_query' => [['taxonomy' => 'collection', 'field' => 'slug', 'terms' => $slug_collection]],
|
||||
]);
|
||||
}
|
||||
|
||||
public static function recupere_et_formate_attributs_produit(mixed $attributs_produit): mixed {
|
||||
return [
|
||||
'taille' => ['nom' => 'Size', 'valeur' => $attributs_produit['pa_size'] ?? false],
|
||||
'pierre' => ['nom' => 'Stone', 'valeur' => $attributs_produit['pa_stone'] ?? false],
|
||||
'cote' => ['nom' => 'Side', 'valeur' => $attributs_produit['pa_side'] ?? false],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,9 +6,9 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
use function Crell\fp\pipe;
|
||||
use HaikuAtelier\WP\Resource;
|
||||
|
||||
require_once 'TraitementInformations.php';
|
||||
use function Crell\fp\pipe;
|
||||
|
||||
// Images du Produit
|
||||
|
||||
|
|
@ -145,13 +145,10 @@ function genere_balises_img_dans_produit_dans_reponse_rest(
|
|||
array: $metadata,
|
||||
callback: static fn($entree): bool => '_photos_colonne_gauche|||0|value' === $entree->key,
|
||||
),
|
||||
static fn($metadata): array => array_map(
|
||||
array: $metadata,
|
||||
callback: static fn($entree): string => genere_balise_img_multiformats(
|
||||
id: $entree?->value,
|
||||
lazy: true,
|
||||
),
|
||||
),
|
||||
static fn($metadata): array => array_map(array: $metadata, callback: static fn($entree): string => Resource::output_multi_formats_img_tag(
|
||||
id: $entree?->value,
|
||||
lazy: true,
|
||||
)),
|
||||
static fn($image) => array_values(array: $image)[0],
|
||||
);
|
||||
|
||||
|
|
@ -162,13 +159,10 @@ function genere_balises_img_dans_produit_dans_reponse_rest(
|
|||
array: $metadata,
|
||||
callback: static fn($entree): bool => '_photos_colonne_droite|||0|value' === $entree->key,
|
||||
),
|
||||
static fn($metadata): array => array_map(
|
||||
array: $metadata,
|
||||
callback: static fn($entree): string => genere_balise_img_multiformats(
|
||||
id: $entree?->value,
|
||||
lazy: true,
|
||||
),
|
||||
),
|
||||
static fn($metadata): array => array_map(array: $metadata, callback: static fn($entree): string => Resource::output_multi_formats_img_tag(
|
||||
id: $entree?->value,
|
||||
lazy: true,
|
||||
)),
|
||||
static fn($image) => array_values(array: $image)[0],
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,116 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Fonctions pour le traitement d'informations.
|
||||
*/
|
||||
|
||||
namespace HaikuAtelier;
|
||||
|
||||
use function Crell\fp\pipe;
|
||||
|
||||
// Page Shop
|
||||
|
||||
/**
|
||||
* TODO.
|
||||
*
|
||||
* @param string $id TODO
|
||||
* @param bool $lazy TODO
|
||||
*
|
||||
* @return string TODO
|
||||
*/
|
||||
function genere_balise_img_multiformats(string $id, bool $lazy = false): string {
|
||||
$int_id = (int) $id;
|
||||
|
||||
if (-1 === $int_id) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$url = wp_get_attachment_image_url($int_id, 'full');
|
||||
$chemin = realpath(get_attached_file($int_id)) ?: realpath(get_attached_file($int_id));
|
||||
$alt = get_post_meta($int_id, '_wp_attachment_image_alt', true);
|
||||
$dimensions = $chemin ? getimagesize($chemin) : ['', ''];
|
||||
|
||||
$avif = $chemin ? realpath(pathinfo($chemin)['dirname'] . '/' . pathinfo($chemin)['filename'] . '.avif') : false;
|
||||
$jxl = $chemin ? realpath(pathinfo($chemin)['dirname'] . '/' . pathinfo($chemin)['filename'] . '.jxl') : false;
|
||||
|
||||
// Génère un tableau avec les différents formats valides
|
||||
$formats = pipe(
|
||||
[$avif, $jxl],
|
||||
static fn($tableau): array => array_filter(
|
||||
array: $tableau,
|
||||
callback: static fn($chemin_format): bool => false !== $chemin_format,
|
||||
),
|
||||
static fn($tableau): array => array_map(
|
||||
array: $tableau,
|
||||
callback: static fn($chemin_format): array => [
|
||||
'format' => pathinfo((string) $chemin_format)['extension'],
|
||||
'taille' => filesize($chemin_format),
|
||||
'url' =>
|
||||
pathinfo($url)['dirname']
|
||||
. '/'
|
||||
. pathinfo($url)['filename']
|
||||
. '.'
|
||||
. pathinfo((string) $chemin_format)['extension'],
|
||||
],
|
||||
),
|
||||
);
|
||||
usort(
|
||||
array: $formats,
|
||||
callback: static fn($a, $b): int => $a['taille'] <=> $b['taille'],
|
||||
);
|
||||
|
||||
// Construis les balises <source> avec les formats valides
|
||||
$sources = '';
|
||||
foreach ($formats as $format) {
|
||||
$height = $dimensions[0];
|
||||
$width = $dimensions[1];
|
||||
$sources .= "<source height='{$height}' srcset='{$format['url']}' type='image/{$format['format']}' width='{$width}' />\n";
|
||||
}
|
||||
|
||||
$loading = $lazy ? 'lazy' : 'eager';
|
||||
|
||||
return <<<EOD
|
||||
{$sources}
|
||||
|
||||
<img
|
||||
alt="{$alt}"
|
||||
decoding="async"
|
||||
height="{$dimensions[0]}"
|
||||
loading="{$loading}"
|
||||
onload="this.style.opacity=1"
|
||||
src="{$url}"
|
||||
width="{$dimensions[1]}"
|
||||
/>
|
||||
EOD;
|
||||
}
|
||||
|
||||
// Page Produit
|
||||
|
||||
/**
|
||||
* Récupère les informations utilisées pour la grille des Produits similaires (de la même
|
||||
* collection) et les retourne sous forme de tableau associatif.
|
||||
*
|
||||
* Pour faciliter l'usage avec `array_map`, utilise une fonction avec curryfication.
|
||||
*/
|
||||
function recupere_produits_meme_collection(string $slug_collection): callable {
|
||||
return static fn(int $id_produit): array|stdClass => wc_get_products([
|
||||
'exclude' => [$id_produit],
|
||||
'limit' => 4,
|
||||
'order' => 'DESC',
|
||||
'orderby' => 'date',
|
||||
'status' => 'publish',
|
||||
'tax_query' => [['taxonomy' => 'collection', 'field' => 'slug', 'terms' => $slug_collection]],
|
||||
]);
|
||||
}
|
||||
|
||||
// Page Panier
|
||||
|
||||
function recupere_et_formate_attributs_produit(mixed $attributs_produit): mixed {
|
||||
return [
|
||||
'taille' => ['nom' => 'Size', 'valeur' => $attributs_produit['pa_size'] ?? false],
|
||||
'pierre' => ['nom' => 'Stone', 'valeur' => $attributs_produit['pa_stone'] ?? false],
|
||||
'cote' => ['nom' => 'Side', 'valeur' => $attributs_produit['pa_side'] ?? false],
|
||||
];
|
||||
}
|
||||
|
|
@ -7,7 +7,6 @@ namespace HaikuAtelier\WP;
|
|||
use Illuminate\Support\Arr;
|
||||
|
||||
use function carbon_get_post_meta;
|
||||
use function HaikuAtelier\genere_balise_img_multiformats;
|
||||
use function is_array;
|
||||
use function is_string;
|
||||
|
||||
|
|
@ -19,7 +18,7 @@ final readonly class HaikuProduct {
|
|||
/** @var list<string> */
|
||||
return Post::get_post_meta_array($post_id, '_photos_colonne_gauche|||0|value')->unwrapOr([])
|
||||
|> (static fn(array $meta) => Arr::where($meta, static fn($meta): bool => is_string($meta)))
|
||||
|> (static fn(array $array) => Arr::map($array, genere_balise_img_multiformats(...)));
|
||||
|> (static fn(array $array) => Arr::map($array, Resource::output_multi_formats_img_tag(...)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -31,7 +30,7 @@ final readonly class HaikuProduct {
|
|||
if (is_array($meta)) {
|
||||
/** @var list<string> */
|
||||
return Arr::where($meta, static fn($meta): bool => is_string($meta))
|
||||
|> (static fn(array $array) => Arr::map($array, genere_balise_img_multiformats(...)));
|
||||
|> (static fn(array $array) => Arr::map($array, Resource::output_multi_formats_img_tag(...)));
|
||||
}
|
||||
|
||||
return [];
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ namespace HaikuAtelier\WP;
|
|||
|
||||
use Exception;
|
||||
|
||||
use function Crell\fp\pipe;
|
||||
use function filemtime;
|
||||
use function get_template_directory;
|
||||
use function get_template_directory_uri;
|
||||
|
|
@ -29,12 +30,7 @@ final readonly class Resource {
|
|||
|
||||
$version = (string) $file_mtime;
|
||||
|
||||
wp_enqueue_script_module(
|
||||
id: $id,
|
||||
src: $file_uri,
|
||||
deps: [],
|
||||
version: $version,
|
||||
);
|
||||
wp_enqueue_script_module(id: $id, src: $file_uri, deps: [], version: $version);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -52,12 +48,74 @@ final readonly class Resource {
|
|||
|
||||
$ver = (string) $file_mtime;
|
||||
|
||||
wp_enqueue_style(
|
||||
handle: $handle,
|
||||
src: $file_uri,
|
||||
deps: [],
|
||||
ver: $ver,
|
||||
media: 'all',
|
||||
wp_enqueue_style(handle: $handle, src: $file_uri, deps: [], ver: $ver, media: 'all');
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO.
|
||||
*
|
||||
* @param string $id TODO
|
||||
* @param bool $lazy TODO
|
||||
*
|
||||
* @return string TODO
|
||||
*/
|
||||
public static function output_multi_formats_img_tag(string $id, bool $lazy = false): string {
|
||||
$int_id = (int) $id;
|
||||
|
||||
if (-1 === $int_id) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$url = wp_get_attachment_image_url($int_id, 'full');
|
||||
$chemin = realpath(get_attached_file($int_id)) ?: realpath(get_attached_file($int_id));
|
||||
$alt = get_post_meta($int_id, '_wp_attachment_image_alt', true);
|
||||
$dimensions = $chemin ? getimagesize($chemin) : ['', ''];
|
||||
|
||||
$avif = $chemin ? realpath(pathinfo($chemin)['dirname'] . '/' . pathinfo($chemin)['filename'] . '.avif') : false;
|
||||
$jxl = $chemin ? realpath(pathinfo($chemin)['dirname'] . '/' . pathinfo($chemin)['filename'] . '.jxl') : false;
|
||||
|
||||
// Génère un tableau avec les différents formats valides
|
||||
$formats = pipe(
|
||||
[$avif, $jxl],
|
||||
static fn($tableau): array => array_filter(
|
||||
array: $tableau,
|
||||
callback: static fn($chemin_format): bool => false !== $chemin_format,
|
||||
),
|
||||
static fn($tableau): array => array_map(array: $tableau, callback: static fn($chemin_format): array => [
|
||||
'format' => pathinfo((string) $chemin_format)['extension'],
|
||||
'taille' => filesize($chemin_format),
|
||||
'url' =>
|
||||
pathinfo($url)['dirname']
|
||||
. '/'
|
||||
. pathinfo($url)['filename']
|
||||
. '.'
|
||||
. pathinfo((string) $chemin_format)['extension'],
|
||||
]),
|
||||
);
|
||||
usort(array: $formats, callback: static fn($a, $b): int => $a['taille'] <=> $b['taille']);
|
||||
|
||||
// Construis les balises <source> avec les formats valides
|
||||
$sources = '';
|
||||
foreach ($formats as $format) {
|
||||
$height = $dimensions[0];
|
||||
$width = $dimensions[1];
|
||||
$sources .= "<source height='{$height}' srcset='{$format['url']}' type='image/{$format['format']}' width='{$width}' />\n";
|
||||
}
|
||||
|
||||
$loading = $lazy ? 'lazy' : 'eager';
|
||||
|
||||
return <<<EOD
|
||||
{$sources}
|
||||
|
||||
<img
|
||||
alt="{$alt}"
|
||||
decoding="async"
|
||||
height="{$dimensions[0]}"
|
||||
loading="{$loading}"
|
||||
onload="this.style.opacity=1"
|
||||
src="{$url}"
|
||||
width="{$dimensions[1]}"
|
||||
/>
|
||||
EOD;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,19 @@ use function Psl\Option\none;
|
|||
use function Psl\Option\some;
|
||||
|
||||
final readonly class Term {
|
||||
/**
|
||||
* @return Option\Option<WP_Term>
|
||||
*/
|
||||
public static function get_term_by_id(int $id, string $taxonomy): Option\Option {
|
||||
$term_data = get_term(term: $id, taxonomy: $taxonomy);
|
||||
|
||||
if ($term_data instanceof WP_Term) {
|
||||
return some($term_data);
|
||||
} else {
|
||||
return none();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Option\Option<list<WP_Term>>
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,27 +1,34 @@
|
|||
import { Option, pipe } from "effect";
|
||||
import { Array as FxArray } from "effect";
|
||||
import { NonEmptyReadonlyArray } from "effect/Array";
|
||||
import { getOptionOrThrowWithError } from "./utils";
|
||||
import { Array as FxArray, Option, pipe } from "effect";
|
||||
import type { NonEmptyReadonlyArray } from "effect/Array";
|
||||
|
||||
import { getOptionOrThrowWithError } from "./utils.ts";
|
||||
|
||||
/** Type union des parents possibles pour un `querySelector`. */
|
||||
export type ParentElement = Document | Element;
|
||||
type ParentElement = Document | Element;
|
||||
|
||||
export const getFirstSelectorFromParent =
|
||||
const getFirstSelectorFromParent =
|
||||
(parent: ParentElement) =>
|
||||
<E extends Element = Element>(selector: string): Option.Option<NonNullable<E>> =>
|
||||
Option.fromNullishOr(parent.querySelector<E>(selector));
|
||||
|
||||
export const getFirstSelectorFromDocument = <E extends Element = Element>(
|
||||
selector: string,
|
||||
): Option.Option<NonNullable<E>> => getFirstSelectorFromParent(document)<E>(selector);
|
||||
const getFirstSelectorFromParentOrThrow =
|
||||
(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}.`),
|
||||
);
|
||||
|
||||
export const getFirstSelectorFromDocumentOrThrow = <E extends Element = Element>(selector: string): NonNullable<E> =>
|
||||
const getFirstSelectorFromDocument = <E extends Element = Element>(selector: string): Option.Option<NonNullable<E>> =>
|
||||
getFirstSelectorFromParent(document)<E>(selector);
|
||||
|
||||
const getFirstSelectorFromDocumentOrThrow = <E extends Element = Element>(selector: string): NonNullable<E> =>
|
||||
pipe(
|
||||
getFirstSelectorFromDocument<E>(selector),
|
||||
getOptionOrThrowWithError(`Il n'y a pas d'Élément dans le Document avec le sélecteur suivant : ${selector}.`),
|
||||
);
|
||||
|
||||
export const getAllSelectorFromParent =
|
||||
const getAllSelectorFromParent =
|
||||
(parent: ParentElement) =>
|
||||
<E extends Element = Element>(selector: string): Option.Option<NonEmptyReadonlyArray<E>> =>
|
||||
pipe(
|
||||
|
|
@ -31,14 +38,23 @@ export const getAllSelectorFromParent =
|
|||
(xs: Array<E>) => Option.liftPredicate(FxArray.isReadonlyArrayNonEmpty)(xs),
|
||||
);
|
||||
|
||||
export const getAllSelectorFromDocument = <E extends Element = Element>(
|
||||
const getAllSelectorFromDocument = <E extends Element = Element>(
|
||||
selector: string,
|
||||
): Option.Option<NonEmptyReadonlyArray<E>> => getAllSelectorFromParent(document)<E>(selector);
|
||||
|
||||
export const getAllSelectorFromDocumentOrThrow = <E extends Element = Element>(
|
||||
selector: string,
|
||||
): NonEmptyReadonlyArray<E> =>
|
||||
const getAllSelectorFromDocumentOrThrow = <E extends Element = Element>(selector: string): NonEmptyReadonlyArray<E> =>
|
||||
pipe(
|
||||
getAllSelectorFromDocument<E>(selector),
|
||||
getOptionOrThrowWithError(`Il n'y a pas d'Éléments dans le Document avec le sélecteur suivant : ${selector}.`),
|
||||
);
|
||||
|
||||
export {
|
||||
getAllSelectorFromDocument,
|
||||
getAllSelectorFromDocumentOrThrow,
|
||||
getAllSelectorFromParent,
|
||||
getFirstSelectorFromDocument,
|
||||
getFirstSelectorFromDocumentOrThrow,
|
||||
getFirstSelectorFromParent,
|
||||
getFirstSelectorFromParentOrThrow,
|
||||
type ParentElement,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -13,5 +13,5 @@ export const WCStoreCartAddItemArgsSchema = v.object({
|
|||
/** Quantity of this item to add to the basket. */
|
||||
quantity: v.optional(v.number()),
|
||||
/** Chosen attributes (for variations). */
|
||||
variation: v.optional(v.array(WCStoreCartAddItemArgsItemsSchema)),
|
||||
variation: v.pipe(v.optional(v.array(WCStoreCartAddItemArgsItemsSchema)), v.readonly()),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,42 +7,44 @@ import { match, P } from "ts-pattern";
|
|||
import { ValiError } from "valibot";
|
||||
import type { AnySchema } from "valibot";
|
||||
|
||||
import type { WCStoreBillingAddress, WCStoreShippingAddress } from "../lib/types/api/adresses";
|
||||
import type { WCStoreCart, WCStoreShippingRate, WCStoreShippingRateShippingRate } from "../lib/types/api/cart";
|
||||
import type { WCStoreCartUpdateCustomerArgs } from "../lib/types/api/cart-update-customer";
|
||||
import type { WCV3Order, WCV3OrdersArgs } from "../lib/types/api/v3/orders";
|
||||
import type { GenericPageState } from "../lib/types/pages";
|
||||
import type { FetchErrors, HttpCodeErrors } from "../lib/types/reseau";
|
||||
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 { 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 { ROUTE_API_MAJ_CLIENT, ROUTE_API_NOUVELLE_COMMANDES } from "../constantes/api";
|
||||
import { ATTRIBUT_CHARGEMENT, ATTRIBUT_LIVRAISON_VALIDEE } from "../constantes/dom";
|
||||
import { NOM_CANAL_REVALIDATION_LIVRAISON } from "../constantes/messages";
|
||||
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,
|
||||
ERREUR_GENERIQUE_CREATION_COMMANDE,
|
||||
ERREUR_GENERIQUE_RESEAU,
|
||||
ERREUR_GENERIQUE_SOUMISSION_ADRESSES,
|
||||
} from "../constantes/messages-utilisateur";
|
||||
import { estErreurFetch, estErreurHttp, setButtonLoadingState } from "../lib/dom";
|
||||
import { reporteEtJournaliseErreur } from "../lib/erreurs";
|
||||
import { ErreurAdresseInvalide } from "../lib/erreurs/adresses";
|
||||
} from "../constantes/messages-utilisateur.ts";
|
||||
import { estErreurFetch, estErreurHttp, setButtonLoadingState } from "../lib/dom.ts";
|
||||
import { reporteEtJournaliseErreur } from "../lib/erreurs.ts";
|
||||
import { ErreurAdresseInvalide } from "../lib/erreurs/adresses.ts";
|
||||
import {
|
||||
ADRESSES_MAJ_EVENT,
|
||||
createUpdatedShippingRatesEvent,
|
||||
createUpdatedTotalsEvent,
|
||||
} from "../lib/evenements/panier";
|
||||
import { emetUniqueMessageBroadcastChannel } from "../lib/messages";
|
||||
import { diviseParCent } from "../lib/nombres";
|
||||
import { newPartialResponse, prefilledPostBackend, safeFetch, traiteErreursBackendWooCommerce } from "../lib/reseau";
|
||||
import { find, first } from "../lib/safe-arrays";
|
||||
import { WCStoreCartSchema } from "../lib/schemas/api/cart";
|
||||
import { WCStoreCartUpdateCustomerArgsSchema } from "../lib/schemas/api/cart-update-customer";
|
||||
import { estWCAddressError } from "../lib/schemas/api/erreurs";
|
||||
import { WCV3OrdersArgsSchema, WCV3OrderSchema } from "../lib/schemas/api/v3/orders";
|
||||
import { safeSchemaParse } from "../lib/validation";
|
||||
import { E } from "./scripts-page-panier-elements";
|
||||
import { getShippingRatesLS } from "./scripts-page-panier-local-storage";
|
||||
} from "../lib/evenements/panier.ts";
|
||||
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 { 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;
|
||||
|
|
@ -50,23 +52,44 @@ type Addresses = {
|
|||
};
|
||||
|
||||
// @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: GenericPageState = _etats;
|
||||
const postBackend = prefilledPostBackend(ETATS_PAGE.nonce, ETATS_PAGE.authString);
|
||||
|
||||
/**
|
||||
* Initialise les Émetteurs d'Événements sur divers parties du Panier.
|
||||
*
|
||||
* @returns void
|
||||
* @returns Un `Effect` ne retournant rien et ne pouvant échouer.
|
||||
*/
|
||||
export const initCartFormEventEmitters = (): void => {
|
||||
E.FORMULAIRE_PANIER.addEventListener("change", (): void => {
|
||||
Maybe.fromFalsy(E.FORMULAIRE_PANIER.checkValidity()).ifJust((): boolean =>
|
||||
window.dispatchEvent(ADRESSES_MAJ_EVENT),
|
||||
);
|
||||
});
|
||||
};
|
||||
export const initCartFormEventEmitters = Effect.fn("initCartFormEventEmitters")(function* () {
|
||||
return yield* pipe(
|
||||
Stream.fromEventListener(E.FORMULAIRE_PANIER, "change"),
|
||||
Stream.tap((event: Event) => {
|
||||
// La cible ne peut qu'être un Formulaire.
|
||||
const target = event.target as HTMLFormElement;
|
||||
|
||||
export const getAddressesFromForm = (formFields: Record<string, string>, areAddressesMerged: boolean): Addresses => ({
|
||||
if (target.checkValidity()) {
|
||||
window.dispatchEvent(ADRESSES_MAJ_EVENT);
|
||||
}
|
||||
|
||||
return Effect.void;
|
||||
}),
|
||||
Stream.runDrain,
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* Récupère les valeurs des adresses renseignées dans les Formulaires correspondants.
|
||||
*
|
||||
* @param formFields Les champs du Formulaire sous forme d'Objet.
|
||||
* @param areAddressesMerged Est-ce que l'utilisateur utilise la même adresse pour livraison et facturation.
|
||||
* @returns Les Adresses proprement renseignées.
|
||||
*
|
||||
* TODO: Utiliser un Schéma pour parser.
|
||||
*/
|
||||
export const getAddressesFromForm = (
|
||||
formFields: ReadonlyRecord<string, string>,
|
||||
areAddressesMerged: boolean,
|
||||
): Addresses => ({
|
||||
billing_address: {
|
||||
address_1: formFields["facturation-adresse"] ?? formFields["livraison-adresse"] ?? "",
|
||||
address_2: "",
|
||||
|
|
|
|||
|
|
@ -7,13 +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";
|
||||
import type { WCStoreCartRemoveItemArgs } from "../lib/types/api/cart-remove-item";
|
||||
import type { WCStoreCartUpdateItemArgs } from "../lib/types/api/cart-update-item";
|
||||
import type { GenericPageState } from "../lib/types/pages";
|
||||
import type { FetchErrors, HttpCodeErrors } from "../lib/types/reseau";
|
||||
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 { GenericPageState } from "../lib/types/pages.ts";
|
||||
import type { FetchErrors, HttpCodeErrors } from "../lib/types/reseau.ts";
|
||||
|
||||
import { ROUTE_API_MAJ_ARTICLE_PANIER, ROUTE_API_RETIRE_ARTICLE_PANIER } from "../constantes/api";
|
||||
import { ROUTE_API_MAJ_ARTICLE_PANIER, ROUTE_API_RETIRE_ARTICLE_PANIER } from "../constantes/api.ts";
|
||||
import {
|
||||
ATTRIBUT_CLE_PANIER,
|
||||
ATTRIBUT_DESACTIVE,
|
||||
|
|
@ -21,25 +21,24 @@ import {
|
|||
DOM_BOUTON_SOUSTRACTION_QUANTITE,
|
||||
DOM_BOUTON_SUPPRESSION_PANIER,
|
||||
DOM_CHAMP_QUANTITE_LIGNE_PANIER,
|
||||
} from "../constantes/dom";
|
||||
import { NOM_CANAL_REVALIDATION_LIVRAISON } from "../constantes/messages";
|
||||
import { mustGetEleInParent } from "../lib/dom";
|
||||
import { BadRequestError, reporteErreur, ServerError } from "../lib/erreurs";
|
||||
} from "../constantes/dom.ts";
|
||||
import { NOM_CANAL_REVALIDATION_LIVRAISON } from "../constantes/messages.ts";
|
||||
import { BadRequestError, reporteErreur, ServerError } from "../lib/erreurs.ts";
|
||||
import {
|
||||
emetMessageMajBoutonPanier,
|
||||
emetMessageMajContenuPanier,
|
||||
emetUniqueMessageBroadcastChannel,
|
||||
} from "../lib/messages";
|
||||
import { diviseParCent } from "../lib/nombres";
|
||||
import { newPartialResponse, postBackend, safeFetch, traiteErreursBackendWooCommerce } from "../lib/reseau";
|
||||
import { WCStoreCartSchema } from "../lib/schemas/api/cart";
|
||||
import { WCStoreCartRemoveItemArgsSchema } from "../lib/schemas/api/cart-remove-item";
|
||||
import { WCStoreCartUpdateItemArgsSchema } from "../lib/schemas/api/cart-update-item";
|
||||
import { safeSchemaParse } from "../lib/validation";
|
||||
import { E } from "./scripts-page-panier-elements";
|
||||
} 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 { 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
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- États injectés par le modèle PHP
|
||||
const PAGE_STATE: GenericPageState = _etats;
|
||||
|
||||
type CartEntryInteractiveElements = {
|
||||
|
|
@ -49,31 +48,35 @@ type CartEntryInteractiveElements = {
|
|||
substractionButton: HTMLButtonElement;
|
||||
};
|
||||
|
||||
const getCartEntryInteractiveEles = (entree: HTMLElement): CartEntryInteractiveElements => {
|
||||
const mustGetEle = mustGetEleInParent(entree);
|
||||
const getCartEntryInteractiveEles = (entry: HTMLElement): CartEntryInteractiveElements => {
|
||||
const entrySelector = getFirstSelectorFromParentOrThrow(entry);
|
||||
return {
|
||||
additionButton: mustGetEle<HTMLButtonElement>(DOM_BOUTON_ADDITION_QUANTITE),
|
||||
deletionButton: mustGetEle<HTMLButtonElement>(DOM_BOUTON_SUPPRESSION_PANIER),
|
||||
quantityInput: mustGetEle<HTMLInputElement>(DOM_CHAMP_QUANTITE_LIGNE_PANIER),
|
||||
substractionButton: mustGetEle<HTMLButtonElement>(DOM_BOUTON_SOUSTRACTION_QUANTITE),
|
||||
additionButton: entrySelector<HTMLButtonElement>(DOM_BOUTON_ADDITION_QUANTITE),
|
||||
deletionButton: entrySelector<HTMLButtonElement>(DOM_BOUTON_SUPPRESSION_PANIER),
|
||||
quantityInput: entrySelector<HTMLInputElement>(DOM_CHAMP_QUANTITE_LIGNE_PANIER),
|
||||
substractionButton: entrySelector<HTMLButtonElement>(DOM_BOUTON_SOUSTRACTION_QUANTITE),
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Met à jour l'état d'activation des Boutons d'action sur chaque Entrée du Panier.
|
||||
* Met à jour l'état d'activation des Boutons d'action pour toutes les Entrées du Panier.
|
||||
|
||||
* @param activated Le nouvel état d'activation (activé/désactivé).
|
||||
* @returns Rien.
|
||||
*/
|
||||
export const toggleCartEntryButtons =
|
||||
const toggleCartEntryButtons =
|
||||
(activated: boolean) =>
|
||||
(cartEntries: ReadonlyArray<CartEntryInteractiveElements>): void =>
|
||||
(cartEntries: ReadonlyArray<CartEntryInteractiveElements>): void => {
|
||||
arrayForEach(cartEntries, (e: CartEntryInteractiveElements): void => {
|
||||
if (activated) {
|
||||
// Active les Boutons
|
||||
Number(e.quantityInput.value) === 1
|
||||
? e.substractionButton.setAttribute(ATTRIBUT_DESACTIVE, "")
|
||||
: e.substractionButton.removeAttribute(ATTRIBUT_DESACTIVE);
|
||||
const entryQuantity = Number(e.quantityInput.value);
|
||||
// Empêche la réducation de la quantité si on se trouve au minimum.
|
||||
if (entryQuantity === 1) {
|
||||
e.substractionButton.setAttribute(ATTRIBUT_DESACTIVE, "");
|
||||
} else {
|
||||
e.substractionButton.removeAttribute(ATTRIBUT_DESACTIVE);
|
||||
}
|
||||
e.additionButton.removeAttribute(ATTRIBUT_DESACTIVE);
|
||||
e.deletionButton.removeAttribute(ATTRIBUT_DESACTIVE);
|
||||
e.deletionButton.textContent = "Remove";
|
||||
|
|
@ -85,22 +88,29 @@ export const toggleCartEntryButtons =
|
|||
e.deletionButton.textContent = "Loading";
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const initialiseActionsEntreesPanier = (): void => {
|
||||
const initActionsOnCartEntries = (): void => {
|
||||
// Initialise des actions uniquement si des Entrées dans le Panier existent
|
||||
E.ENTREES_PANIER.ifRight((cartEntries: Array<HTMLElement>) =>
|
||||
E.ENTREES_PANIER.ifRight((cartEntries: Array<HTMLElement>) => {
|
||||
arrayForEach(cartEntries, (entry: HTMLElement): void => {
|
||||
// Retire l'entrée du DOM si la clé Panier n'existe pas puis arrête précocement
|
||||
const entryKey: string = Maybe.fromNullable(entry.getAttribute(ATTRIBUT_CLE_PANIER))
|
||||
.ifNothing(() => entry.remove())
|
||||
.ifNothing(() => {
|
||||
entry.remove();
|
||||
})
|
||||
.orDefault("CLE_PANIER_INEXISTANTE");
|
||||
const entryButtons: CartEntryInteractiveElements = getCartEntryInteractiveEles(entry);
|
||||
|
||||
entry.addEventListener("click", (event: Event): void => {
|
||||
// Discrimine en fonction de l'Élément cliqué (délégation d'Événements)
|
||||
match(event.target)
|
||||
// Cas impossible
|
||||
.with(P.nullish, () => console.error(event.target))
|
||||
[
|
||||
// Cas impossible
|
||||
"with"
|
||||
](P.nullish, () => {
|
||||
console.error(event.target);
|
||||
})
|
||||
// Clic sur le Bouton d'addition
|
||||
.when(
|
||||
(target: EventTarget) => (target as HTMLElement).matches(DOM_BOUTON_ADDITION_QUANTITE),
|
||||
|
|
@ -116,7 +126,9 @@ export const initialiseActionsEntreesPanier = (): void => {
|
|||
safeSchemaParse({ key: entryKey, quantity: q + 1 }, WCStoreCartUpdateItemArgsSchema),
|
||||
),
|
||||
)
|
||||
.ifRight(() => pipe(cartEntries, arrayMap(getCartEntryInteractiveEles), toggleCartEntryButtons(false)))
|
||||
.ifRight(() => {
|
||||
pipe(cartEntries, arrayMap(getCartEntryInteractiveEles), toggleCartEntryButtons(false));
|
||||
})
|
||||
.chain((args: WCStoreCartUpdateItemArgs) =>
|
||||
safeFetch(
|
||||
postBackend({
|
||||
|
|
@ -129,7 +141,7 @@ export const initialiseActionsEntreesPanier = (): void => {
|
|||
.chain((r: Response) =>
|
||||
EitherAsync<ServerError, unknown>(async ({ throwE }) =>
|
||||
match(await newPartialResponse(r))
|
||||
.with({ status: 200 }, (r) => r.body)
|
||||
["with"]({ status: 200 }, (r) => r.body)
|
||||
.otherwise((rs): never => throwE(traiteErreursBackendWooCommerce(rs))),
|
||||
),
|
||||
)
|
||||
|
|
@ -149,24 +161,24 @@ export const initialiseActionsEntreesPanier = (): 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;
|
||||
})
|
||||
.exhaustive();
|
||||
})
|
||||
.finally(() => {
|
||||
["finally"](() => {
|
||||
pipe(cartEntries, arrayMap(getCartEntryInteractiveEles), toggleCartEntryButtons(true));
|
||||
})
|
||||
.run();
|
||||
|
|
@ -188,9 +200,9 @@ export const initialiseActionsEntreesPanier = (): void => {
|
|||
safeSchemaParse({ key: entryKey, quantity: valeur - 1 }, WCStoreCartUpdateItemArgsSchema),
|
||||
)
|
||||
// 2. Exécute un Effet pour empêcher les requêtes concurrentes
|
||||
.ifRight(() =>
|
||||
pipe(cartEntries, arrayMap(getCartEntryInteractiveEles), toggleCartEntryButtons(false)),
|
||||
)
|
||||
.ifRight(() => {
|
||||
pipe(cartEntries, arrayMap(getCartEntryInteractiveEles), toggleCartEntryButtons(false));
|
||||
})
|
||||
// 3. Exécute la requête via fetch sous forme d'EitherAsync
|
||||
.chain((args: WCStoreCartUpdateItemArgs) =>
|
||||
safeFetch(
|
||||
|
|
@ -206,9 +218,9 @@ export const initialiseActionsEntreesPanier = (): void => {
|
|||
EitherAsync<BadRequestError | Error | ServerError, unknown>(async ({ throwE }) =>
|
||||
// Simplifie les données à matcher
|
||||
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)
|
||||
["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)}`))),
|
||||
),
|
||||
)
|
||||
|
|
@ -231,24 +243,24 @@ export const initialiseActionsEntreesPanier = (): 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;
|
||||
})
|
||||
.exhaustive();
|
||||
})
|
||||
.finally(() => {
|
||||
["finally"](() => {
|
||||
// Réactive les Boutons
|
||||
pipe(cartEntries, arrayMap(getCartEntryInteractiveEles), toggleCartEntryButtons(true));
|
||||
})
|
||||
|
|
@ -269,9 +281,9 @@ export const initialiseActionsEntreesPanier = (): void => {
|
|||
// 1. Valide les Arguments de la Requête
|
||||
.liftEither(safeSchemaParse({ key: entryKey }, WCStoreCartRemoveItemArgsSchema))
|
||||
// 2. Exécute un Effet pour empêcher les requêtes concurrentes
|
||||
.ifRight(() =>
|
||||
pipe(cartEntries, arrayMap(getCartEntryInteractiveEles), toggleCartEntryButtons(false)),
|
||||
)
|
||||
.ifRight(() => {
|
||||
pipe(cartEntries, arrayMap(getCartEntryInteractiveEles), toggleCartEntryButtons(false));
|
||||
})
|
||||
// 3. Exécute la requête via fetch sous forme d'EitherAsync
|
||||
.chain((args: WCStoreCartRemoveItemArgs) =>
|
||||
safeFetch(
|
||||
|
|
@ -287,9 +299,9 @@ export const initialiseActionsEntreesPanier = (): void => {
|
|||
EitherAsync<BadRequestError | Error | ServerError, unknown>(async ({ throwE }) =>
|
||||
// Simplifie les données à matcher
|
||||
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)
|
||||
["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)}`))),
|
||||
),
|
||||
)
|
||||
|
|
@ -315,24 +327,24 @@ export const initialiseActionsEntreesPanier = (): 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;
|
||||
})
|
||||
.exhaustive();
|
||||
})
|
||||
.finally(() => {
|
||||
["finally"](() => {
|
||||
// Réactive les Boutons
|
||||
pipe(cartEntries, arrayMap(getCartEntryInteractiveEles), toggleCartEntryButtons(true));
|
||||
})
|
||||
|
|
@ -342,6 +354,8 @@ export const initialiseActionsEntreesPanier = (): void => {
|
|||
)
|
||||
.otherwise((_) => {});
|
||||
});
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export { toggleCartEntryButtons, initActionsOnCartEntries as initialiseActionsEntreesPanier };
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ 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;
|
||||
|
|
@ -176,7 +177,7 @@ const initialiseMajFormulairesPanier = (): void => {
|
|||
};
|
||||
|
||||
document.addEventListener("DOMContentLoaded", (): void => {
|
||||
initCartFormEventEmitters();
|
||||
Effect.runFork(initCartFormEventEmitters());
|
||||
souscrisEvenementsPanier();
|
||||
initialiseActionsEntreesPanier();
|
||||
initShippingRatesChoicesActions();
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import { match, P } from "ts-pattern";
|
|||
import { ValiError } from "valibot";
|
||||
import type { AnySchema } from "valibot";
|
||||
|
||||
import type { WCStoreCart } from "./lib/types/api/cart";
|
||||
import type { WCStoreCart } from "./lib/types/api/cart.ts";
|
||||
import type { WCStoreCartAddItemArgs, WCStoreCartAddItemArgsItems } from "./lib/types/api/cart-add-item.ts";
|
||||
import type { FetchErrors } from "./lib/types/reseau.ts";
|
||||
|
||||
|
|
@ -39,6 +39,7 @@ import { WCStoreCartSchema } from "./lib/schemas/api/cart.ts";
|
|||
import { safeSchemaParse } from "./lib/validation";
|
||||
|
||||
type EnsembleLienContenu = [HTMLAnchorElement, HTMLElement];
|
||||
|
||||
/** États utiles pour les scripts de la page. */
|
||||
type EtatsPage = {
|
||||
/** L'ID en base de données du Produit. */
|
||||
|
|
@ -48,7 +49,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;
|
||||
|
||||
/**
|
||||
|
|
@ -149,18 +149,18 @@ const getAttributesFromDom = (): ReadonlyArray<WCStoreCartAddItemArgsItems> => {
|
|||
return attributes;
|
||||
};
|
||||
|
||||
function areArraysEqual<T>(array1: Array<T>, array2: Array<T>): boolean {
|
||||
const areArraysEqual = <T>(array1: Array<T>, array2: Array<T>): boolean => {
|
||||
if (array1 !== array2) {
|
||||
const a1 = JSON.stringify(array1.toSorted());
|
||||
const a2 = JSON.stringify(array2.toSorted());
|
||||
return a1 === a2;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
const updatePriceOnAttributeChange = (): void => {
|
||||
E.VARIATION_CHOICE_FORM.addEventListener("change", (): void => {
|
||||
if (!E.VARIATION_CHOICE_FORM.checkValidity()) {
|
||||
if (E.VARIATION_CHOICE_FORM.checkValidity() === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -183,9 +183,8 @@ const ajouteProduitAuPanier = (event: MouseEvent): void => {
|
|||
id: E.DOM_VARIATION.map((selecteur: HTMLSelectElement): number => Number(selecteur.value))
|
||||
// Récupère l'ID du Produit de la Page pour les Produits simples
|
||||
.orDefault(ETATS_PAGE.idProduit),
|
||||
// Id: ETATS_PAGE.idProduit,
|
||||
quantity: 1,
|
||||
// Variation: getAttributeValuesFromDom(),
|
||||
variation: getAttributesFromDom(),
|
||||
};
|
||||
|
||||
// Réalise la Requête et traite sa Réponse
|
||||
|
|
|
|||
|
|
@ -39,7 +39,10 @@ $products = wc_get_products([
|
|||
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($products, Product::new(...)));
|
||||
|> (static fn(/** @var list<WC_Product> */ array $products): array => Arr::map(
|
||||
$products,
|
||||
Product::from_wc_product(...),
|
||||
));
|
||||
|
||||
$context['products'] = $products;
|
||||
$context['category_id'] = $current_term->term_id;
|
||||
|
|
@ -60,7 +63,4 @@ add_action('wp_enqueue_scripts', function (): void {
|
|||
});
|
||||
|
||||
// Rendu
|
||||
Timber::render(
|
||||
filenames: $templates,
|
||||
data: $context,
|
||||
);
|
||||
Timber::render(filenames: $templates, data: $context);
|
||||
|
|
|
|||
|
|
@ -41,7 +41,4 @@ $email = [
|
|||
|
||||
$context['commande'] = $email;
|
||||
// Rendu
|
||||
Timber::render(
|
||||
filenames: $templates,
|
||||
data: $context,
|
||||
);
|
||||
Timber::render(filenames: $templates, data: $context);
|
||||
|
|
|
|||
|
|
@ -76,7 +76,4 @@ $email['adresses']['facturation']['country'] = WC()->countries->countries[$comma
|
|||
|
||||
$context['commande'] = $email;
|
||||
// Rendu
|
||||
Timber::render(
|
||||
filenames: $templates,
|
||||
data: $context,
|
||||
);
|
||||
Timber::render(filenames: $templates, data: $context);
|
||||
|
|
|
|||
|
|
@ -73,7 +73,4 @@ $email['adresses']['facturation']['country'] = WC()->countries->countries[$comma
|
|||
$context['commande'] = $email;
|
||||
|
||||
// Rendu
|
||||
Timber::render(
|
||||
filenames: $templates,
|
||||
data: $context,
|
||||
);
|
||||
Timber::render(filenames: $templates, data: $context);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue