2026-04-05
This commit is contained in:
parent
5f835ca4e6
commit
2971f5516d
62 changed files with 439 additions and 497 deletions
|
|
@ -8,7 +8,6 @@ declare(strict_types=1);
|
|||
|
||||
namespace HaikuAtelier;
|
||||
|
||||
use Exception;
|
||||
use HaikuAtelier\Data\Product;
|
||||
use HaikuAtelier\WP\Resource;
|
||||
use Timber\Timber;
|
||||
|
|
@ -18,8 +17,6 @@ use function add_action;
|
|||
use function array_map;
|
||||
use function wc_get_products;
|
||||
|
||||
require_once __DIR__ . '/src/inc/TraitementInformations.php';
|
||||
|
||||
$context = Timber::context();
|
||||
$templates = ['boutique.twig'];
|
||||
|
||||
|
|
@ -32,12 +29,7 @@ $products = array_map(
|
|||
);
|
||||
$context['products'] = $products;
|
||||
|
||||
/**
|
||||
* Charge les scripts et styles de la page.
|
||||
*
|
||||
* @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 {
|
||||
add_action('wp_enqueue_scripts', function (): void {
|
||||
Resource::enqueue_style_file(
|
||||
handle: 'haiku-atelier-2024-styles-page-boutique',
|
||||
path: '/assets/css/pages/page-boutique.css',
|
||||
|
|
@ -50,9 +42,7 @@ function load_page_resources(): void {
|
|||
id: 'haiku-atelier-2024-scripts-menu-categories',
|
||||
path: '/assets/js/scripts-menu-categories.js',
|
||||
);
|
||||
}
|
||||
|
||||
add_action('wp_enqueue_scripts', load_page_resources(...));
|
||||
});
|
||||
|
||||
Timber::render(
|
||||
data: $context,
|
||||
|
|
|
|||
|
|
@ -310,6 +310,7 @@ button.bouton-blanc-sur-noir {
|
|||
}
|
||||
button.bouton-retour-haut {
|
||||
position: fixed;
|
||||
z-index: 500;
|
||||
right: var(--espace-xl);
|
||||
bottom: calc(var(--espace-l) + var(--pied-de-page-hauteur));
|
||||
transform: rotate(180deg);
|
||||
|
|
@ -321,7 +322,6 @@ button.bouton-retour-haut {
|
|||
background: var(--couleur-fond);
|
||||
box-shadow: initial;
|
||||
transition: 0.2s background, 0.2s opacity, 0.2s visibility;
|
||||
z-index: 500;
|
||||
}
|
||||
button.bouton-retour-haut img {
|
||||
width: 1rem;
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -11,18 +11,18 @@ jQuery(document).ready(function ($) {
|
|||
|
||||
$(".customize-control-tinymce-editor").each(function () {
|
||||
// Get the toolbar strings that were passed from the PHP Class
|
||||
var tinyMCEToolbar1String = _wpCustomizeSettings.controls[$(this).attr("id")].skyrockettinymcetoolbar1;
|
||||
var tinyMCEToolbar2String = _wpCustomizeSettings.controls[$(this).attr("id")].skyrockettinymcetoolbar2;
|
||||
var tinyMCEMediaButtons = _wpCustomizeSettings.controls[$(this).attr("id")].skyrocketmediabuttons;
|
||||
const tinyMCEToolbar1String = _wpCustomizeSettings.controls[$(this).attr("id")].skyrockettinymcetoolbar1;
|
||||
const tinyMCEToolbar2String = _wpCustomizeSettings.controls[$(this).attr("id")].skyrockettinymcetoolbar2;
|
||||
const tinyMCEMediaButtons = _wpCustomizeSettings.controls[$(this).attr("id")].skyrocketmediabuttons;
|
||||
|
||||
wp.editor.initialize($(this).attr("id"), {
|
||||
mediaButtons: tinyMCEMediaButtons,
|
||||
quicktags: true,
|
||||
tinymce: {
|
||||
wpautop: true,
|
||||
toolbar1: tinyMCEToolbar1String,
|
||||
toolbar2: tinyMCEToolbar2String,
|
||||
},
|
||||
quicktags: true,
|
||||
mediaButtons: tinyMCEMediaButtons,
|
||||
});
|
||||
});
|
||||
$(document).on("tinymce-editor-init", function (event, editor) {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ declare(strict_types=1);
|
|||
|
||||
namespace HaikuAtelier;
|
||||
|
||||
use Exception;
|
||||
use HaikuAtelier\WP\Resource;
|
||||
use Timber\Timber;
|
||||
|
||||
|
|
@ -17,12 +16,7 @@ use function add_action;
|
|||
$context = Timber::context();
|
||||
$templates = ['accueil.twig'];
|
||||
|
||||
/**
|
||||
* Charge les scripts et styles de la page.
|
||||
*
|
||||
* @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 {
|
||||
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',
|
||||
|
|
@ -31,9 +25,7 @@ function load_page_resources(): void {
|
|||
id: 'haiku-atelier-2024-scripts-page-accueil',
|
||||
path: '/assets/js/scripts-page-accueil.js',
|
||||
);
|
||||
}
|
||||
|
||||
add_action('wp_enqueue_scripts', load_page_resources(...));
|
||||
});
|
||||
|
||||
Timber::render(
|
||||
data: $context,
|
||||
|
|
|
|||
|
|
@ -29,12 +29,7 @@ if (is_bool($image_dimensions)) {
|
|||
|
||||
$context['image_dimensions'] = $image_dimensions;
|
||||
|
||||
/**
|
||||
* Charge les scripts et styles de la page.
|
||||
*
|
||||
* @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 {
|
||||
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',
|
||||
|
|
@ -43,9 +38,7 @@ function load_page_resources(): void {
|
|||
id: 'haiku-atelier-2024-scripts-page-a-propos',
|
||||
path: '/assets/js/scripts-page-a-propos.js',
|
||||
);
|
||||
}
|
||||
|
||||
add_action('wp_enqueue_scripts', load_page_resources(...));
|
||||
});
|
||||
|
||||
Timber::render(
|
||||
data: $context,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ declare(strict_types=1);
|
|||
|
||||
namespace HaikuAtelier;
|
||||
|
||||
use Exception;
|
||||
use HaikuAtelier\Data\Cart;
|
||||
use HaikuAtelier\WP\Resource;
|
||||
use Illuminate\Support\Number;
|
||||
|
|
@ -99,12 +98,7 @@ $context['pays_livraison'] = $allowed_countries;
|
|||
$context['sous_total_livraison'] = $shipping_subtotal;
|
||||
$context['methodes_livraison'] = $methodes_livraison;
|
||||
|
||||
/**
|
||||
* Charge les scripts et styles de la page.
|
||||
*
|
||||
* @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 {
|
||||
add_action('wp_enqueue_scripts', function (): void {
|
||||
Resource::enqueue_style_file(
|
||||
handle: 'haiku-atelier-2024-styles-page-panier',
|
||||
path: '/assets/css/pages/page-panier.css',
|
||||
|
|
@ -113,9 +107,7 @@ function load_page_resources(): void {
|
|||
id: 'haiku-atelier-2024-scripts-page-panier',
|
||||
path: '/assets/js/scripts-page-panier.js',
|
||||
);
|
||||
}
|
||||
|
||||
add_action('wp_enqueue_scripts', load_page_resources(...));
|
||||
});
|
||||
|
||||
// Rendu
|
||||
Timber::render(
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ declare(strict_types=1);
|
|||
|
||||
namespace HaikuAtelier;
|
||||
|
||||
use Exception;
|
||||
use HaikuAtelier\WP\Resource;
|
||||
use Timber\Timber;
|
||||
|
||||
|
|
@ -17,19 +16,12 @@ use function add_action;
|
|||
$context = Timber::context();
|
||||
$templates = ['contact.twig'];
|
||||
|
||||
/**
|
||||
* Charge les scripts et styles de la page.
|
||||
*
|
||||
* @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 {
|
||||
add_action('wp_enqueue_scripts', function (): void {
|
||||
Resource::enqueue_style_file(
|
||||
handle: 'haiku-atelier-2024-styles-page-contact',
|
||||
path: '/assets/css/pages/page-contact.css',
|
||||
);
|
||||
}
|
||||
|
||||
add_action('wp_enqueue_scripts', load_page_resources(...));
|
||||
});
|
||||
|
||||
// Rendu
|
||||
Timber::render(
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ declare(strict_types=1);
|
|||
|
||||
namespace HaikuAtelier;
|
||||
|
||||
use Exception;
|
||||
use HaikuAtelier\WP\Resource;
|
||||
use Timber\Timber;
|
||||
|
||||
|
|
@ -17,19 +16,12 @@ use function add_action;
|
|||
$context = Timber::context();
|
||||
$templates = ['echec-commande.twig'];
|
||||
|
||||
/**
|
||||
* Charge les scripts et styles de la page.
|
||||
*
|
||||
* @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 {
|
||||
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',
|
||||
);
|
||||
}
|
||||
|
||||
add_action('wp_enqueue_scripts', load_page_resources(...));
|
||||
});
|
||||
|
||||
// Rendu
|
||||
Timber::render(
|
||||
|
|
|
|||
|
|
@ -8,26 +8,20 @@ declare(strict_types=1);
|
|||
|
||||
namespace HaikuAtelier;
|
||||
|
||||
use Exception;
|
||||
use HaikuAtelier\WP\Resource;
|
||||
use Timber\Timber;
|
||||
|
||||
use function add_action;
|
||||
|
||||
$context = Timber::context();
|
||||
$templates = ['cgv.twig'];
|
||||
|
||||
/**
|
||||
* Charge les scripts et styles de la page.
|
||||
*
|
||||
* @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 {
|
||||
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',
|
||||
);
|
||||
}
|
||||
|
||||
add_action('wp_enqueue_scripts', load_page_resources(...));
|
||||
});
|
||||
|
||||
// Rendu
|
||||
Timber::render(
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ use function add_action;
|
|||
use function assert;
|
||||
use function collect;
|
||||
use function is_array;
|
||||
use function is_bool;
|
||||
use function recupere_produits_meme_collection;
|
||||
use function wc_get_product;
|
||||
use function wp_json_encode;
|
||||
|
|
@ -56,12 +55,7 @@ $context['product_json'] = wp_json_encode($product);
|
|||
$context['maximum_price'] = $maximum_price;
|
||||
$context['same_collection_products'] = $same_collection_products;
|
||||
|
||||
/**
|
||||
* Charge les scripts et styles de la page.
|
||||
*
|
||||
* @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 {
|
||||
add_action('wp_enqueue_scripts', function (): void {
|
||||
Resource::enqueue_script_module_file(
|
||||
id: 'haiku-atelier-2024-scripts-page-produit',
|
||||
path: '/assets/js/scripts-page-produit.js',
|
||||
|
|
@ -70,9 +64,7 @@ function load_page_resources(): void {
|
|||
id: 'haiku-atelier-2024-scripts-menu-categories',
|
||||
path: '/assets/js/scripts-menu-categories.js',
|
||||
);
|
||||
}
|
||||
|
||||
add_action('wp_enqueue_scripts', load_page_resources(...));
|
||||
});
|
||||
|
||||
// Rendu
|
||||
Timber::render(
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
|
||||
|
|
@ -8,6 +8,8 @@ use Illuminate\Support\Arr;
|
|||
use WC_Product_Attribute;
|
||||
use WP_Term;
|
||||
|
||||
use function wc_attribute_label;
|
||||
|
||||
final readonly class Attribute {
|
||||
/**
|
||||
* @param list<AttributeOption> $options
|
||||
|
|
|
|||
|
|
@ -11,14 +11,17 @@ use Psl\Option;
|
|||
use WC_Product;
|
||||
use WP_Term;
|
||||
|
||||
use function HaikuAtelier\genere_balise_img_multiformats;
|
||||
use function head;
|
||||
use function Psl\Option\from_nullable;
|
||||
use function wpautop;
|
||||
|
||||
final readonly class Product {
|
||||
/**
|
||||
* @param list<Attribute> $attributes
|
||||
* @param list<string> $left_column_photos
|
||||
* @param list<string> $right_column_photos
|
||||
* @param list<ProductVariation> $variations
|
||||
* @param array<ProductVariation> $variations
|
||||
*/
|
||||
private function __construct(
|
||||
public array $attributes,
|
||||
|
|
@ -43,11 +46,11 @@ final readonly class Product {
|
|||
*/
|
||||
public static function get_attributes_for_product(WC_Product $product): array {
|
||||
/** @var list<Attribute> */
|
||||
return $product->get_attributes() |> (static fn($attributes) => Arr::map($attributes, Attribute::new(...)));
|
||||
return $product->get_attributes()
|
||||
|> (static fn(array $attributes): array => Arr::map($attributes, Attribute::new(...)));
|
||||
}
|
||||
|
||||
public static function new(WC_Product $product): self {
|
||||
/** @var list<Attribute> */
|
||||
$attributes = self::get_attributes_for_product($product);
|
||||
/** @var lowercase-string */
|
||||
$category = $product->get_id() |> wc_get_product_category_list(...) |> strtolower(...);
|
||||
|
|
@ -74,9 +77,13 @@ final readonly class Product {
|
|||
$hover_photo = $right_column_photos[0] ?? genere_balise_img_multiformats('-1', true);
|
||||
$slug = $product->get_slug();
|
||||
$stock = $product->get_stock_quantity() ?? 1;
|
||||
/** @var array<ProductVariation> */
|
||||
$variations = $product->get_children()
|
||||
|> (static fn($ids) => Arr::map($ids, wc_get_product(...)))
|
||||
|> (static fn($products) => Arr::map($products, ProductVariation::new(...)));
|
||||
|> (static fn(/** @var list<int> */ array $ids): array => Arr::map($ids, wc_get_product(...)))
|
||||
|> (static fn(/** @var list<WC_Product> */ array $products): array => Arr::map(
|
||||
$products,
|
||||
ProductVariation::new(...),
|
||||
));
|
||||
$url = $product->get_permalink();
|
||||
|
||||
return new self(
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Définis les fonctionnalités du thème et personnalise certaines existantes.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
namespace HaikuAtelier;
|
||||
|
||||
// Désactive divers transformations du contenu par WordPress
|
||||
function desactive_wpautop(): void {
|
||||
|
|
@ -41,7 +43,7 @@ function autorise_import_svg_mediatheque(array $file_types): array {
|
|||
$new_filetypes = [];
|
||||
$new_filetypes['svg'] = 'image/svg+xml';
|
||||
|
||||
return array_merge($file_types, $new_filetypes);
|
||||
return [...$file_types, ...$new_filetypes];
|
||||
}
|
||||
|
||||
function retire_motifs_blocs_gutenberg(): void {
|
||||
|
|
@ -56,8 +58,8 @@ function retire_styles_core_block(): void {
|
|||
add_filter('async_update_translation', '__return_false');
|
||||
add_filter('auto_update_translation', '__return_false');
|
||||
|
||||
add_action('init', 'desactive_wpautop');
|
||||
add_filter('tiny_mce_before_init', 'desactive_transformation_contenu_tinymce');
|
||||
add_filter('upload_mimes', 'autorise_import_svg_mediatheque');
|
||||
add_action('after_setup_theme', 'retire_motifs_blocs_gutenberg');
|
||||
add_action('wp_footer', 'retire_styles_core_block', 5);
|
||||
add_action('init', desactive_wpautop(...));
|
||||
add_filter('tiny_mce_before_init', desactive_transformation_contenu_tinymce(...));
|
||||
add_filter('upload_mimes', autorise_import_svg_mediatheque(...));
|
||||
add_action('after_setup_theme', retire_motifs_blocs_gutenberg(...));
|
||||
add_action('wp_footer', retire_styles_core_block(...), 5);
|
||||
|
|
|
|||
|
|
@ -1,10 +1,15 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Créé les Taxonomies associées aux Produits.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
namespace HaikuAtelier;
|
||||
|
||||
use function add_action;
|
||||
use function register_taxonomy;
|
||||
|
||||
/**
|
||||
* Enregistre la Taxonomie « Collection ».
|
||||
|
|
@ -37,4 +42,4 @@ function enregistre_taxonomie_collection(): void {
|
|||
register_taxonomy('collection', ['product'], $args);
|
||||
}
|
||||
|
||||
add_action('init', 'enregistre_taxonomie_collection');
|
||||
add_action('init', enregistre_taxonomie_collection(...));
|
||||
|
|
|
|||
|
|
@ -6,8 +6,7 @@ declare(strict_types=1);
|
|||
* Fonctions pour le traitement d'informations.
|
||||
*/
|
||||
|
||||
use HaikuAtelier\Data\Attribute;
|
||||
use HaikuAtelier\Data\Product;
|
||||
namespace HaikuAtelier;
|
||||
|
||||
use function Crell\fp\pipe;
|
||||
|
||||
|
|
@ -87,118 +86,8 @@ function genere_balise_img_multiformats(string $id, bool $lazy = false): string
|
|||
EOD;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO.
|
||||
*/
|
||||
function tri_variations_par_prix_descendant(WC_Product $a, WC_Product $b): int {
|
||||
return $b->get_price() <=> $a->get_price();
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère les informations utilisées pour la grille des Produits et les retourne sous forme
|
||||
* de tableau associatif.
|
||||
*
|
||||
* @return mixed un tableau avec uniquement les informations pour la Grille de Produits
|
||||
*/
|
||||
function recupere_informations_produit_shop(WC_Product $produit): mixed {
|
||||
/** @var int $prix_maximal Le prix maximal du Produit. */
|
||||
$prix_maximal = pipe(
|
||||
// Récupère les Variations
|
||||
$produit->get_children(),
|
||||
// Récupère les informations de chaque Variation
|
||||
static fn($enfants): array => array_map(
|
||||
callback: wc_get_product(...),
|
||||
array: $enfants,
|
||||
),
|
||||
// Trie les Variations par prix descendant
|
||||
static fn($variations): array => array_map(
|
||||
callback: static fn($variation) => $variation->get_price(),
|
||||
array: $variations,
|
||||
),
|
||||
// Récupère le Prix de la Variation la plus chère
|
||||
static fn($prix) => collect($prix)->max(),
|
||||
// Récupère le Prix pour la Variation la plus chère OU le prix du Produit simple
|
||||
static fn($prix_variation_maximale) => $prix_variation_maximale ?? $produit->get_price(),
|
||||
);
|
||||
|
||||
// TEMP: Cas de la Carte Cadeau où aucun prix ne doit être affiché. Idéalement utiliser un système d'étiquettes pour ces cas là.
|
||||
if ($produit->get_sku() === 'GIFTcard') {
|
||||
$prix_maximal = '';
|
||||
}
|
||||
|
||||
return [
|
||||
// Identifiant du Produit
|
||||
'id' => $produit->get_id(),
|
||||
// Nom affiché du Produit
|
||||
'nom' => $produit->get_name(),
|
||||
// Prix affiché du Produit
|
||||
'prix' => "{$prix_maximal}",
|
||||
// Photo du Produit affichée par défaut
|
||||
'photo_repos' => genere_balise_img_multiformats(
|
||||
get_post_meta($post_id = $produit->get_id(), $key = '_photos_colonne_gauche|||0|value')[0] ?? -1,
|
||||
false,
|
||||
),
|
||||
// Photo du Produit affichée au survol de l'image
|
||||
'photo_survol' => genere_balise_img_multiformats(
|
||||
get_post_meta($post_id = $produit->get_id(), $key = '_photos_colonne_droite|||0|value')[0] ?? -1,
|
||||
true,
|
||||
),
|
||||
// URL du Produit pour les liens vers celui-ci
|
||||
'url' => $produit->get_permalink(),
|
||||
];
|
||||
}
|
||||
|
||||
// Page Produit
|
||||
|
||||
/**
|
||||
* Retourne un tableau associatif des informations affichées sur la page Produit depuis les données brutes d'un Produit.
|
||||
*/
|
||||
function recupere_informations_produit_page_produit(WC_Product $product): mixed {
|
||||
/** @var list<Attribute> */
|
||||
$attributs = Product::get_attributes_for_product($product);
|
||||
|
||||
return [
|
||||
// Attributs du Produit
|
||||
'attributs' => $attributs,
|
||||
// Catégorie du Produit
|
||||
'categorie' => pipe($product->get_id(), wc_get_product_category_list(...), strtolower(...)),
|
||||
// Slug de la Collection - Peut ne pas avoir été défini
|
||||
'collection' => get_the_terms($product->get_id(), 'collection')[0]->slug ?? '',
|
||||
// Détails (Description) du Produit
|
||||
'details' => wpautop($product->get_description()),
|
||||
// Identifiant du Produit
|
||||
'id' => $product->get_id(),
|
||||
// Nom affiché du Produit
|
||||
'nom' => $product->get_name(),
|
||||
// Prix affiché du Produit
|
||||
'prix' => $product->get_price(),
|
||||
'photos_colonne_gauche' => array_map(
|
||||
callback: genere_balise_img_multiformats(...),
|
||||
array: get_post_meta($post_id = $product->get_id(), $key = '_photos_colonne_gauche|||0|value'),
|
||||
),
|
||||
'photos_colonne_droite' => array_map(
|
||||
callback: genere_balise_img_multiformats(...),
|
||||
array: carbon_get_the_post_meta('photos_colonne_droite'),
|
||||
),
|
||||
'photo_repos' => genere_balise_img_multiformats(
|
||||
get_post_meta($post_id = $product->get_id(), $key = '_photos_colonne_gauche|||0|value')[0] ?? -1,
|
||||
false,
|
||||
),
|
||||
'photo_survol' => genere_balise_img_multiformats(
|
||||
get_post_meta($post_id = $product->get_id(), $key = '_photos_colonne_droite|||0|value')[0] ?? -1,
|
||||
true,
|
||||
),
|
||||
// Slug du Produit
|
||||
'slug' => $product->get_slug(),
|
||||
// Quantité de Produit en stock
|
||||
'stock' => $product->get_stock_quantity() ?? 1,
|
||||
// Variations du Produit
|
||||
'variations_ids' => $product->get_children(),
|
||||
// URL du Produit
|
||||
'url' => $product->get_permalink(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ 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;
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ use Psl\Option;
|
|||
use WP_Error;
|
||||
use WP_Term;
|
||||
|
||||
use function get_post_meta;
|
||||
use function get_the_terms;
|
||||
use function is_array;
|
||||
use function Psl\Option\none;
|
||||
use function Psl\Option\some;
|
||||
|
|
|
|||
|
|
@ -6,9 +6,17 @@ namespace HaikuAtelier\WP;
|
|||
|
||||
use Exception;
|
||||
|
||||
use function filemtime;
|
||||
use function get_template_directory;
|
||||
use function get_template_directory_uri;
|
||||
use function is_bool;
|
||||
use function wp_enqueue_script_module;
|
||||
use function wp_enqueue_style;
|
||||
|
||||
final readonly class Resource {
|
||||
/**
|
||||
* @throws Exception Lève une `Exception` s'il est impossible d'obtenir les attributs du fichier au chemin passé en paramètre.
|
||||
*/
|
||||
public static function enqueue_script_module_file(string $path, string $id): void {
|
||||
$file_uri = get_template_directory_uri() . $path;
|
||||
|
||||
|
|
@ -16,7 +24,7 @@ final readonly class Resource {
|
|||
$file_mtime = filemtime($file_path);
|
||||
|
||||
if (is_bool($file_mtime)) {
|
||||
throw new Exception("Could not get modification time of file: {$file_uri} ");
|
||||
throw new Exception("Impossible de récupérer la date de modification du fichier : {$file_uri}.");
|
||||
}
|
||||
|
||||
$version = (string) $file_mtime;
|
||||
|
|
@ -29,6 +37,9 @@ final readonly class Resource {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception Lève une `Exception` s'il est impossible d'obtenir les attributs du fichier au chemin passé en paramètre.
|
||||
*/
|
||||
public static function enqueue_style_file(string $path, string $handle): void {
|
||||
$file_uri = get_template_directory_uri() . $path;
|
||||
|
||||
|
|
@ -36,7 +47,7 @@ final readonly class Resource {
|
|||
$file_mtime = filemtime($file_path);
|
||||
|
||||
if (is_bool($file_mtime)) {
|
||||
throw new Exception("Could not get modification time of file: {$file_uri} ");
|
||||
throw new Exception("Impossible de récupérer la date de modification du fichier : {$file_uri}.");
|
||||
}
|
||||
|
||||
$ver = (string) $file_mtime;
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ button {
|
|||
|
||||
&.bouton-retour-haut {
|
||||
position: fixed;
|
||||
z-index: 500;
|
||||
right: var(--espace-xl);
|
||||
bottom: calc(var(--espace-l) + var(--pied-de-page-hauteur));
|
||||
transform: rotate(180deg);
|
||||
|
|
@ -80,7 +81,6 @@ button {
|
|||
background: var(--couleur-fond);
|
||||
box-shadow: initial;
|
||||
transition: 0.2s background, 0.2s opacity, 0.2s visibility;
|
||||
z-index: 500;
|
||||
|
||||
img {
|
||||
width: 1rem;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { Option, pipe } from "effect";
|
||||
import { Array as EffectArray } from "effect";
|
||||
import { Array as FxArray } from "effect";
|
||||
import { NonEmptyReadonlyArray } from "effect/Array";
|
||||
import { getOptionOrThrowWithError } from "./utils";
|
||||
|
||||
|
|
@ -27,8 +27,8 @@ export const getAllSelectorFromParent =
|
|||
pipe(
|
||||
parent.querySelectorAll<E>(selector),
|
||||
// Convertis NodeListOf en Array.
|
||||
Array.from<E>,
|
||||
(xs: Array<E>) => Option.liftPredicate(EffectArray.isNonEmptyReadonlyArray)(xs),
|
||||
(xs: NodeListOf<E>) => Array.from<E>(xs),
|
||||
(xs: Array<E>) => Option.liftPredicate(FxArray.isReadonlyArrayNonEmpty)(xs),
|
||||
);
|
||||
|
||||
export const getAllSelectorFromDocument = <E extends Element = Element>(
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { pipe, Option } from "effect";
|
||||
import { Option, pipe } from "effect";
|
||||
|
||||
export const getOptionOrThrowWithError =
|
||||
(message: string) =>
|
||||
|
|
|
|||
|
|
@ -3,18 +3,18 @@
|
|||
import { ATTRIBUT_CHARGEMENT } from "../constantes/dom";
|
||||
|
||||
// Types
|
||||
interface AnimationCycleTexte {
|
||||
type AnimationCycleTexte = {
|
||||
callback: () => void;
|
||||
etapes: Array<string>;
|
||||
index: number;
|
||||
interval: NodeJS.Timeout;
|
||||
}
|
||||
};
|
||||
|
||||
interface ParametresAnimationCycleTexte {
|
||||
type ParametresAnimationCycleTexte = {
|
||||
attribut: string;
|
||||
element: HTMLElement;
|
||||
etapes: Array<string>;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Créer le nécessaire pour une animation s'exécutant jusqu'à ce que un interval soit manuellement arrêté. L'animation
|
||||
|
|
@ -39,7 +39,7 @@ const lanceAnimationCycleTexte = (args: ParametresAnimationCycleTexte): Animatio
|
|||
},
|
||||
etapes: args.etapes,
|
||||
index: 0,
|
||||
interval: setInterval(() => {}, 2147483647),
|
||||
interval: setInterval(() => {}, 2_147_483_647),
|
||||
};
|
||||
|
||||
return animation;
|
||||
|
|
|
|||
|
|
@ -123,8 +123,12 @@ export const reporteEtLeveErreur = <E extends Error>(erreur: E): never => {
|
|||
export const reporteEtJournaliseErreur = <E extends Error>(erreur: E): void => {
|
||||
reporteErreur(erreur);
|
||||
console.error(erreur);
|
||||
if (erreur instanceof ValiError) console.error(erreur.issues);
|
||||
if (erreur instanceof ErreurAdresseInvalide) console.error(erreur.problemes);
|
||||
if (erreur instanceof ValiError) {
|
||||
console.error(erreur.issues);
|
||||
}
|
||||
if (erreur instanceof ErreurAdresseInvalide) {
|
||||
console.error(erreur.problemes);
|
||||
}
|
||||
};
|
||||
|
||||
export const reporteEtRetourneErreur = <E extends Error>(erreur: E): E => {
|
||||
|
|
|
|||
|
|
@ -7,12 +7,12 @@ export const CODE_PROMO_MAJ_EVENT = new CustomEvent(CODE_PROMO_MAJ, {});
|
|||
|
||||
// Interfaces
|
||||
|
||||
export interface UpdatedShippingRatesEvent extends Event {
|
||||
export type UpdatedShippingRatesEvent = {
|
||||
detail: { refresh_methods: boolean; shipping_rates: ReadonlyArray<WCStoreShippingRateShippingRate> };
|
||||
}
|
||||
export interface UpdatedTotalsEvent extends Event {
|
||||
} & Event;
|
||||
export type UpdatedTotalsEvent = {
|
||||
detail: { totals: WCStoreCartTotals };
|
||||
}
|
||||
} & Event;
|
||||
|
||||
// Méthodes
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { pipe } from "@mobily/ts-belt";
|
||||
import { Either } from "purify-ts";
|
||||
import { parse, type ValiError } from "valibot";
|
||||
import { parse } from "valibot";
|
||||
import type { ValiError } from "valibot";
|
||||
|
||||
import type {
|
||||
MessageMajBoutonPanier,
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ type ArgumentsPostBackendWC = {
|
|||
route: string;
|
||||
};
|
||||
|
||||
// fetch
|
||||
// Fetch
|
||||
|
||||
export const getBackend = (args: ArgumentsGetBackendWC): Promise<Response> =>
|
||||
fetch(args.route, {
|
||||
|
|
@ -120,19 +120,16 @@ export const safeFetch = (f: Promise<Response>): EitherAsync<DOMException | Type
|
|||
EitherAsync<DOMException | TypeError, Response>(async () => await f);
|
||||
|
||||
// Réponses Simplifiées
|
||||
export const newPartialResponse = async (reponse: Response): Promise<SimplifiedResponse> => {
|
||||
return {
|
||||
body: await reponse.json(),
|
||||
status: reponse.status,
|
||||
};
|
||||
};
|
||||
export const newPartialResponse = async (reponse: Response): Promise<SimplifiedResponse> => ({
|
||||
body: await reponse.json(),
|
||||
status: reponse.status,
|
||||
});
|
||||
|
||||
export const traiteErreursBackendWooCommerce = (rs: SimplifiedResponse): HttpCodeErrors => {
|
||||
return match(rs)
|
||||
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)));
|
||||
};
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ export const WCStoreBillingAddressSchema = v.object({
|
|||
city: v.string(),
|
||||
company: v.string(),
|
||||
country: v.string(),
|
||||
// email: v.optional(v.pipe(v.string(), v.email())),
|
||||
// Email: v.optional(v.pipe(v.string(), v.email())),
|
||||
email: v.string(),
|
||||
first_name: v.string(),
|
||||
last_name: v.string(),
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@ import type { GenericSchema, InferOutput, ValiError } from "valibot";
|
|||
import { Either, Maybe } from "purify-ts";
|
||||
|
||||
import { safeJsonParse } from "./dom.ts";
|
||||
import { ErreurEntreeInexistante, type NonExistingKeyError } from "./erreurs.ts";
|
||||
import { ErreurEntreeInexistante } from "./erreurs.ts";
|
||||
import type { NonExistingKeyError } from "./erreurs.ts";
|
||||
import { safeSchemaParse, safeSchemaParseCurried } from "./validation.ts";
|
||||
|
||||
export type GetSessionStorage<S extends GenericSchema> = Either<ErreursGetSessionStorage<S>, InferOutput<S>>;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ export type FetchErrors = DOMException | Error | TypeError;
|
|||
|
||||
export type HttpCodeErrors = BadRequestError | Error | ForbiddenError | NotFoundError | ServerError | UnauthorizedError;
|
||||
|
||||
export interface SimplifiedResponse {
|
||||
export type SimplifiedResponse = {
|
||||
body: unknown;
|
||||
status: number;
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { D } from "@mobily/ts-belt";
|
||||
import { type Either, Maybe } from "purify-ts";
|
||||
import { Maybe } from "purify-ts";
|
||||
import type { Either } from "purify-ts";
|
||||
|
||||
import { CleNonTrouveError } from "./erreurs";
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@
|
|||
*/
|
||||
|
||||
import { Either } from "purify-ts";
|
||||
import { type GenericSchema, type InferOutput, parse, type ValiError } from "valibot";
|
||||
import { parse } from "valibot";
|
||||
import type { GenericSchema, InferOutput, ValiError } from "valibot";
|
||||
|
||||
export const safeSchemaParse = <Schema extends GenericSchema>(
|
||||
valeur: unknown,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@ import { map as dictMap, values as dictValues } from "@mobily/ts-belt/Dict";
|
|||
import { trim as stringTrim } from "@mobily/ts-belt/String";
|
||||
import { EitherAsync, Maybe } from "purify-ts";
|
||||
import { match, P } from "ts-pattern";
|
||||
import { type AnySchema, ValiError } from "valibot";
|
||||
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";
|
||||
|
|
@ -43,10 +44,10 @@ import { safeSchemaParse } from "../lib/validation";
|
|||
import { E } from "./scripts-page-panier-elements";
|
||||
import { getShippingRatesLS } from "./scripts-page-panier-local-storage";
|
||||
|
||||
interface Addresses {
|
||||
type Addresses = {
|
||||
billing_address: WCStoreBillingAddress;
|
||||
shipping_address: WCStoreShippingAddress;
|
||||
}
|
||||
};
|
||||
|
||||
// @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
|
||||
|
|
@ -65,35 +66,33 @@ export const initCartFormEventEmitters = (): void => {
|
|||
});
|
||||
};
|
||||
|
||||
export const getAddressesFromForm = (formFields: Record<string, string>, areAddressesMerged: boolean): Addresses => {
|
||||
return {
|
||||
billing_address: {
|
||||
address_1: formFields["facturation-adresse"] ?? formFields["livraison-adresse"] ?? "",
|
||||
address_2: "",
|
||||
city: formFields["facturation-ville"] ?? formFields["livraison-ville"] ?? "",
|
||||
company: "",
|
||||
country: areAddressesMerged ? (formFields["facturation-pays"] ?? "") : (formFields["livraison-pays"] ?? ""),
|
||||
email: formFields["facturation-email"] ?? formFields["livraison-email"] ?? "",
|
||||
first_name: formFields["facturation-prenom"] ?? formFields["livraison-prenom"] ?? "",
|
||||
last_name: formFields["facturation-nom"] ?? formFields["livraison-nom"] ?? "",
|
||||
phone: formFields["facturation-telephone"] ?? formFields["livraison-telephone"] ?? "",
|
||||
postcode: formFields["facturation-code-postal"] ?? formFields["livraison-code-postal"] ?? "",
|
||||
state: formFields["facturation-region-etat"] ?? formFields["livraison-region-etat"] ?? "",
|
||||
},
|
||||
shipping_address: {
|
||||
address_1: formFields["livraison-adresse"] ?? "",
|
||||
address_2: "",
|
||||
city: formFields["livraison-ville"] ?? "",
|
||||
company: "",
|
||||
country: formFields["livraison-pays"] ?? "",
|
||||
first_name: formFields["livraison-prenom"] ?? "",
|
||||
last_name: formFields["livraison-nom"] ?? "",
|
||||
phone: formFields["livraison-telephone"] ?? "",
|
||||
postcode: formFields["livraison-code-postal"] ?? "",
|
||||
state: formFields["livraison-region-etat"] ?? "",
|
||||
},
|
||||
};
|
||||
};
|
||||
export const getAddressesFromForm = (formFields: Record<string, string>, areAddressesMerged: boolean): Addresses => ({
|
||||
billing_address: {
|
||||
address_1: formFields["facturation-adresse"] ?? formFields["livraison-adresse"] ?? "",
|
||||
address_2: "",
|
||||
city: formFields["facturation-ville"] ?? formFields["livraison-ville"] ?? "",
|
||||
company: "",
|
||||
country: areAddressesMerged ? (formFields["facturation-pays"] ?? "") : (formFields["livraison-pays"] ?? ""),
|
||||
email: formFields["facturation-email"] ?? formFields["livraison-email"] ?? "",
|
||||
first_name: formFields["facturation-prenom"] ?? formFields["livraison-prenom"] ?? "",
|
||||
last_name: formFields["facturation-nom"] ?? formFields["livraison-nom"] ?? "",
|
||||
phone: formFields["facturation-telephone"] ?? formFields["livraison-telephone"] ?? "",
|
||||
postcode: formFields["facturation-code-postal"] ?? formFields["livraison-code-postal"] ?? "",
|
||||
state: formFields["facturation-region-etat"] ?? formFields["livraison-region-etat"] ?? "",
|
||||
},
|
||||
shipping_address: {
|
||||
address_1: formFields["livraison-adresse"] ?? "",
|
||||
address_2: "",
|
||||
city: formFields["livraison-ville"] ?? "",
|
||||
company: "",
|
||||
country: formFields["livraison-pays"] ?? "",
|
||||
first_name: formFields["livraison-prenom"] ?? "",
|
||||
last_name: formFields["livraison-nom"] ?? "",
|
||||
phone: formFields["livraison-telephone"] ?? "",
|
||||
postcode: formFields["livraison-code-postal"] ?? "",
|
||||
state: formFields["livraison-region-etat"] ?? "",
|
||||
},
|
||||
});
|
||||
|
||||
export const initShippingCalculationButton = (): void => {
|
||||
// Déclenche au clic sur le Bouton de soumission du Formulaire la requête pour le calcul des frais de livraison
|
||||
|
|
@ -119,11 +118,11 @@ export const initShippingCalculationButton = (): void => {
|
|||
void EitherAsync.liftEither(safeSchemaParse(formArgs, WCStoreCartUpdateCustomerArgsSchema))
|
||||
// Désactive le Bouton pour empêcher des requêtes concurrentes
|
||||
.ifRight((): void => setButtonLoadingState(E.BOUTON_ACTIONS_FORMULAIRE, true))
|
||||
.chain((args: WCStoreCartUpdateCustomerArgs) => {
|
||||
return safeFetch(postBackend(ROUTE_API_MAJ_CLIENT, JSON.stringify(args), false));
|
||||
})
|
||||
.chain((rs: Response) => {
|
||||
return EitherAsync<ErreurAdresseInvalide | HttpCodeErrors, unknown>(
|
||||
.chain((args: WCStoreCartUpdateCustomerArgs) =>
|
||||
safeFetch(postBackend(ROUTE_API_MAJ_CLIENT, JSON.stringify(args), false)),
|
||||
)
|
||||
.chain((rs: Response) =>
|
||||
EitherAsync<ErreurAdresseInvalide | HttpCodeErrors, unknown>(
|
||||
async ({ throwE }): Promise<unknown> =>
|
||||
match(await newPartialResponse(rs))
|
||||
.with({ status: 200 }, (rs): unknown => rs.body)
|
||||
|
|
@ -135,8 +134,8 @@ export const initShippingCalculationButton = (): void => {
|
|||
(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 */
|
||||
|
|
@ -299,9 +298,9 @@ export const initOrderCreationButton = (): void => {
|
|||
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) => {
|
||||
return safeFetch(postBackend(ROUTE_API_NOUVELLE_COMMANDES, JSON.stringify(args), true));
|
||||
})
|
||||
.chain((args: WCV3OrdersArgs) =>
|
||||
safeFetch(postBackend(ROUTE_API_NOUVELLE_COMMANDES, JSON.stringify(args), true)),
|
||||
)
|
||||
.chain((rs: Response) =>
|
||||
EitherAsync<HttpCodeErrors, unknown>(
|
||||
async ({ throwE }): Promise<unknown> =>
|
||||
|
|
|
|||
|
|
@ -130,7 +130,7 @@ export const initialiseElementsCodePromo = (): void => {
|
|||
);
|
||||
|
||||
window.dispatchEvent(CODE_PROMO_MAJ_EVENT);
|
||||
// emetUniqueMessageBroadcastChannel(NOM_CANAL_REVALIDATION_LIVRAISON, true);
|
||||
// EmetUniqueMessageBroadcastChannel(NOM_CANAL_REVALIDATION_LIVRAISON, true);
|
||||
})
|
||||
.ifLeft((erreur) => {
|
||||
// Rétablis le texte d'origine
|
||||
|
|
@ -194,7 +194,9 @@ export const initialiseElementsCodePromo = (): void => {
|
|||
)
|
||||
.chain((reponse: Response) =>
|
||||
EitherAsync<ServerError, unknown>(async ({ throwE }) => {
|
||||
if (estReponse500(reponse)) throwE(new ServerError("500 server Error"));
|
||||
if (estReponse500(reponse)) {
|
||||
throwE(new ServerError("500 server Error"));
|
||||
}
|
||||
return await reponse.json();
|
||||
}),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { forEach as arrayForEach, map as arrayMap } from "@mobily/ts-belt/Array";
|
||||
import { html, render, type TemplateResult } from "lit-html";
|
||||
import { html, render } from "lit-html";
|
||||
import type { TemplateResult } from "lit-html";
|
||||
|
||||
import type { WCStoreCartTotals, WCStoreShippingRateShippingRate } from "../lib/types/api/cart";
|
||||
import type { WCStoreShippingRateShippingRates } from "../lib/types/api/couts-livraison";
|
||||
|
|
@ -66,8 +67,9 @@ export const generateShippingRatesHTML = (
|
|||
getDOMElementsWithSelector(container)("div[data-methode-initiale]").ifRight(arrayForEach((div) => div.remove()));
|
||||
|
||||
const selectedShippingRate: string = shippingRates.find((sr) => sr.selected)?.method_id ?? "";
|
||||
const shippingRatesHTML: ReadonlyArray<TemplateResult> = arrayMap(shippingRates, (methode) => {
|
||||
return html` <div>
|
||||
const shippingRatesHTML: ReadonlyArray<TemplateResult> = arrayMap(
|
||||
shippingRates,
|
||||
(methode) => html` <div>
|
||||
<input
|
||||
id="methode-livraison-${methode.method_id}"
|
||||
name="choix-methode-livraison"
|
||||
|
|
@ -78,8 +80,8 @@ export const generateShippingRatesHTML = (
|
|||
<label for="methode-livraison-${methode.method_id}"
|
||||
>${methode.name} (${formateEnEuros(methode.price)})</label
|
||||
>
|
||||
</div>`;
|
||||
});
|
||||
</div>`,
|
||||
);
|
||||
|
||||
// Ajoute les nouveaux Produits dans le DOM
|
||||
container.removeAttribute(ATTRIBUT_HIDDEN);
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@ import { pipe } from "@mobily/ts-belt";
|
|||
import { forEach as arrayForEach, map as arrayMap } from "@mobily/ts-belt/Array";
|
||||
import { EitherAsync, Maybe } from "purify-ts";
|
||||
import { match, P } from "ts-pattern";
|
||||
import { type AnySchema, ValiError } from "valibot";
|
||||
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";
|
||||
|
|
|
|||
|
|
@ -59,9 +59,13 @@ const initialiseObservationFenetre = (): void => {
|
|||
// Met à jour la valeur du défilement vertical dans la page
|
||||
defilementY = majDefilementY();
|
||||
// Vérifie que le Ratio soit le bon
|
||||
if (ratioActuel < RATIO_MINIMUM_PAGE_PAR_FENETRE) return;
|
||||
if (ratioActuel < RATIO_MINIMUM_PAGE_PAR_FENETRE) {
|
||||
return;
|
||||
}
|
||||
// Attend la prochaine étape
|
||||
if (etapePlanifiee) return;
|
||||
if (etapePlanifiee) {
|
||||
return;
|
||||
}
|
||||
|
||||
etapePlanifiee = true;
|
||||
requestAnimationFrame((): void =>
|
||||
|
|
|
|||
|
|
@ -17,12 +17,16 @@ document.addEventListener("DOMContentLoaded", (): void => {
|
|||
|
||||
// Créé un nouvel Observer pour la première et dernière entrée.
|
||||
EffectArray.forEach(firstAndLastEntries, (menuEntry, _index) => {
|
||||
if (Predicate.isUndefined(menuEntry)) return;
|
||||
if (Predicate.isUndefined(menuEntry)) {
|
||||
return;
|
||||
}
|
||||
|
||||
new IntersectionObserver(
|
||||
EffectArray.forEach((intersectionEntry) => {
|
||||
// Ne déclenche rien si le scroll n'est pas horizontal
|
||||
if (intersectionEntry.boundingClientRect.top <= 0) return;
|
||||
if (intersectionEntry.boundingClientRect.top <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
Match.value([intersectionEntry.isIntersecting]).pipe(
|
||||
Match.when([true, 0], () => productsCategoriesMenu.removeAttribute("data-entrees-presentes-debut")),
|
||||
|
|
@ -32,7 +36,7 @@ document.addEventListener("DOMContentLoaded", (): void => {
|
|||
Match.orElse(() => {}),
|
||||
);
|
||||
}),
|
||||
{ root: null, threshold: 0.9 },
|
||||
{ root: undefined, threshold: 0.9 },
|
||||
).observe(menuEntry);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ const initGestionAnimation = (): void => {
|
|||
A.at(E.IMAGES_STORYTELLING, 0),
|
||||
O.tap((img) => {
|
||||
const options: IntersectionObserverInit = {
|
||||
root: null,
|
||||
root: undefined,
|
||||
rootMargin: "0px",
|
||||
threshold: 0,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -92,13 +92,13 @@ const initialisePageBoutique = (): void => {
|
|||
)
|
||||
// 4. Traite les cas d'Erreurs et récupère le Corps de la Réponse
|
||||
.chain((reponse: Response) =>
|
||||
EitherAsync<APIFetchErrors, unknown>(async ({ throwE }) => {
|
||||
return match(await newPartialResponse(reponse))
|
||||
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();
|
||||
}),
|
||||
.run(),
|
||||
),
|
||||
)
|
||||
// 5. Vérifie le Schéma de la Réponse
|
||||
.chain((corpsReponse: unknown) => EitherAsync.liftEither(safeSchemaParse(corpsReponse, WCV3ProductsSchema)))
|
||||
|
|
@ -139,12 +139,12 @@ const initialisePageBoutique = (): void => {
|
|||
</figure>
|
||||
</article>
|
||||
`,
|
||||
tap((article) => fragment.appendChild(article)),
|
||||
tap((article) => fragment.append(article)),
|
||||
);
|
||||
}
|
||||
|
||||
// Ajoute les nouveaux Produits dans le DOM
|
||||
E.GRILLE_PRODUITS.appendChild(fragment);
|
||||
E.GRILLE_PRODUITS.append(fragment);
|
||||
E.GRILLE_PRODUITS.setAttribute(ATTRIBUT_PAGE, String(nouveauNumeroPage));
|
||||
|
||||
E.BOUTON_PLUS_DE_PRODUITS.textContent = "Show more";
|
||||
|
|
|
|||
|
|
@ -21,7 +21,8 @@ import {
|
|||
} from "./constantes/dom.ts";
|
||||
import { NOM_CANAL_BOUTON_PANIER, NOM_CANAL_CONTENU_PANIER } from "./constantes/messages.ts";
|
||||
import { getDOMElementsWithSelector, recupereElementAvecSelecteur, recupereElementOuLeve } from "./lib/dom.ts";
|
||||
import { type CleNonTrouveError, reporteErreur } from "./lib/erreurs.ts";
|
||||
import { reporteErreur } from "./lib/erreurs.ts";
|
||||
import type { CleNonTrouveError } from "./lib/erreurs.ts";
|
||||
import { valideMessageMajBoutonPanier, valideMessageMajContenuPanier } from "./lib/messages.ts";
|
||||
import { arrondisADeuxDecimales, diviseParCent, formateEnEuros, inverseNombre } from "./lib/nombres.ts";
|
||||
import { propEither } from "./lib/utils.ts";
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@ import { tap as optionTap } from "@mobily/ts-belt/Option";
|
|||
import { pipe as epipe } from "effect";
|
||||
import { EitherAsync, Maybe } from "purify-ts";
|
||||
import { match, P } from "ts-pattern";
|
||||
import { type AnySchema, ValiError } from "valibot";
|
||||
import { ValiError } from "valibot";
|
||||
import type { AnySchema } from "valibot";
|
||||
|
||||
import type { WCStoreCart } from "./lib/types/api/cart";
|
||||
import type { WCStoreCartAddItemArgs, WCStoreCartAddItemArgsItems } from "./lib/types/api/cart-add-item.ts";
|
||||
|
|
@ -79,8 +80,12 @@ const gereAccordeonDetailsProduit = (): void => {
|
|||
const idContenu: null | string = bouton.getAttribute(ATTRIBUT_ARIA_CONTROLS);
|
||||
const sectionCorrespondante: HTMLDivElement | undefined = E.CONTENUS_ACCORDEON[index];
|
||||
|
||||
if (!idContenu) throw new Error("Le lien ne dispose pas d'ID !");
|
||||
if (!sectionCorrespondante) throw new Error("Le lien ne dispose pas de section correspondante !");
|
||||
if (!idContenu) {
|
||||
throw new Error("Le lien ne dispose pas d'ID !");
|
||||
}
|
||||
if (!sectionCorrespondante) {
|
||||
throw new Error("Le lien ne dispose pas de section correspondante !");
|
||||
}
|
||||
|
||||
contenus.set(idContenu, [bouton, sectionCorrespondante]);
|
||||
|
||||
|
|
@ -93,7 +98,9 @@ const gereAccordeonDetailsProduit = (): void => {
|
|||
pipe(contenus.values(), Array.from<EnsembleLienContenu>, deplieToutesSections);
|
||||
|
||||
// Ne fais rien de plus si l'onglet sélectionné était le courant
|
||||
if (estAncienContenuDeplie) return;
|
||||
if (estAncienContenuDeplie) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ouvre le nouvel onglet sélectionné
|
||||
bouton.setAttribute(ATTRIBUT_ARIA_EXPANDED, "true");
|
||||
|
|
@ -130,14 +137,14 @@ const getAttributesFromDom = (): ReadonlyArray<WCStoreCartAddItemArgsItems> => {
|
|||
document.querySelectorAll<HTMLSelectElement>(".selecteur-produit select"),
|
||||
Array.from<HTMLSelectElement>,
|
||||
);
|
||||
if (selectElements.length === 0) return [];
|
||||
if (selectElements.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const attributes = selectElements.map((select: HTMLSelectElement) => {
|
||||
return {
|
||||
attribute: select.id,
|
||||
value: select.value,
|
||||
} satisfies WCStoreCartAddItemArgsItems;
|
||||
});
|
||||
const attributes = selectElements.map((select: HTMLSelectElement) => ({
|
||||
attribute: select.id,
|
||||
value: select.value,
|
||||
}));
|
||||
|
||||
return attributes;
|
||||
};
|
||||
|
|
@ -176,9 +183,9 @@ 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,
|
||||
// Id: ETATS_PAGE.idProduit,
|
||||
quantity: 1,
|
||||
// variation: getAttributeValuesFromDom(),
|
||||
// Variation: getAttributeValuesFromDom(),
|
||||
};
|
||||
|
||||
// Réalise la Requête et traite sa Réponse
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
/// <reference types="vite/client" />
|
||||
|
||||
interface ImportMeta {
|
||||
type ImportMeta = {
|
||||
readonly env: ImportMetaEnv;
|
||||
}
|
||||
};
|
||||
|
||||
interface ImportMetaEnv {}
|
||||
type ImportMetaEnv = {};
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ declare(strict_types=1);
|
|||
|
||||
namespace HaikuAtelier;
|
||||
|
||||
use Exception;
|
||||
use HaikuAtelier\Data\Product;
|
||||
use HaikuAtelier\WP\Resource;
|
||||
use Illuminate\Support\Arr;
|
||||
|
|
@ -16,38 +15,36 @@ use Timber\Timber;
|
|||
use WC_Product;
|
||||
use WP_Term;
|
||||
|
||||
require_once __DIR__ . '/src/inc/TraitementInformations.php';
|
||||
use function add_action;
|
||||
use function assert;
|
||||
use function get_queried_object;
|
||||
use function is_array;
|
||||
use function wc_get_products;
|
||||
|
||||
$context = Timber::context();
|
||||
$templates = ['boutique.twig'];
|
||||
|
||||
/** @var WP_Term */
|
||||
/** @var WP_Term La Catégorie affichée. */
|
||||
$current_term = get_queried_object();
|
||||
$category_slug = $current_term->slug;
|
||||
|
||||
/** @var list<WC_Product> $raw_products Les informations brutes des Produits. */
|
||||
$raw_products = wc_get_products([
|
||||
'category' => [$category_slug],
|
||||
/** @var list<Product> Les Produits de la Catégorie affichée. */
|
||||
$products = wc_get_products([
|
||||
'category' => [$current_term->slug],
|
||||
'limit' => 12,
|
||||
'order' => 'DESC',
|
||||
'orderby' => 'date',
|
||||
'status' => 'publish',
|
||||
]);
|
||||
])
|
||||
|> 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($products, Product::new(...)));
|
||||
|
||||
/** @var list<Product> */
|
||||
$products = Arr::map($raw_products, Product::new(...));
|
||||
$context['products'] = $products;
|
||||
$context['category_id'] = $current_term->term_id;
|
||||
|
||||
/** @var string */
|
||||
$products_category_id = array_shift($raw_products)?->get_category_ids()[0] ?? '';
|
||||
$context['products_category_id'] = $products_category_id;
|
||||
|
||||
/**
|
||||
* Charge les scripts et styles de la page.
|
||||
*
|
||||
* @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 {
|
||||
add_action('wp_enqueue_scripts', function (): void {
|
||||
Resource::enqueue_style_file(
|
||||
handle: 'haiku-atelier-2024-styles-page-boutique',
|
||||
path: '/assets/css/pages/page-boutique.css',
|
||||
|
|
@ -60,9 +57,7 @@ function load_page_resources(): void {
|
|||
id: 'haiku-atelier-2024-scripts-menu-categories',
|
||||
path: '/assets/js/scripts-menu-categories.js',
|
||||
);
|
||||
}
|
||||
|
||||
add_action('wp_enqueue_scripts', load_page_resources(...));
|
||||
});
|
||||
|
||||
// Rendu
|
||||
Timber::render(
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<div
|
||||
class="grille-produits"
|
||||
data-page="1"
|
||||
{% if products_category_id %}data-id-categorie-produits="{{ products_category_id }}"{% endif %}
|
||||
{% if category_id %}data-id-categorie-produits="{{ category_id }}"{% endif %}
|
||||
>
|
||||
{% if products|length > 0 %}
|
||||
{% for product in products %}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue