2026-04-01
This commit is contained in:
parent
ef19ba2b72
commit
5f332f4068
34 changed files with 9392 additions and 391 deletions
|
|
@ -321,6 +321,7 @@ 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
|
|
@ -16,28 +16,28 @@ require_once __DIR__ . '/src/inc/TraitementInformations.php';
|
|||
$context = Timber::context();
|
||||
$templates = ['produit.twig'];
|
||||
|
||||
$product = wc_get_product();
|
||||
$raw_product = wc_get_product();
|
||||
|
||||
// Le Produit DOIT exister.
|
||||
if ($product === null || is_bool($product)) {
|
||||
if ($raw_product === null || is_bool($raw_product)) {
|
||||
throw new Exception("Le Produit n'existe pas.");
|
||||
}
|
||||
|
||||
// Assemble les données d'intérêt pour la page au sein d'une Classe.
|
||||
$donnees_produit = Product::new($product);
|
||||
$product = Product::new($raw_product);
|
||||
|
||||
/** @var int $prix_maximal Le prix de la Variation la plus chère */
|
||||
$prix_maximal = collect($donnees_produit->variations)->max('price');
|
||||
$maximum_price = collect($product->variations)->max('price');
|
||||
|
||||
$produits_meme_collection = array_map(
|
||||
array: recupere_produits_meme_collection($donnees_produit->collection)($donnees_produit->id),
|
||||
$same_collection_products = array_map(
|
||||
array: recupere_produits_meme_collection($product->collection)($product->id),
|
||||
callback: Product::new(...)
|
||||
);
|
||||
|
||||
$context['produit'] = $donnees_produit;
|
||||
$context['product_json'] = wp_json_encode($donnees_produit);
|
||||
$context['prix_maximal'] = $prix_maximal;
|
||||
$context['produits_meme_collection'] = $produits_meme_collection;
|
||||
$context['product'] = $product;
|
||||
$context['product_json'] = wp_json_encode($product);
|
||||
$context['maximum_price'] = $maximum_price;
|
||||
$context['same_collection_products'] = $same_collection_products;
|
||||
|
||||
/**
|
||||
* Charge les Scripts nécessaires pour la page Produit.
|
||||
|
|
@ -59,9 +59,6 @@ function charge_scripts_page_produit(): void {
|
|||
|
||||
add_action('wp_enqueue_scripts', 'charge_scripts_page_produit');
|
||||
|
||||
$lal = wp_json_encode($context);
|
||||
echo "<script>console.debug({$lal});</script>";
|
||||
|
||||
// Rendu
|
||||
Timber::render(
|
||||
filenames: $templates,
|
||||
|
|
|
|||
|
|
@ -21,74 +21,78 @@ use function Crell\fp\pipe;
|
|||
*
|
||||
* @return string TODO
|
||||
*/
|
||||
function genere_balise_img_multiformats($id, bool $lazy = false): string
|
||||
{
|
||||
$int_id = (int) $id;
|
||||
function genere_balise_img_multiformats(string $id, bool $lazy = false): string {
|
||||
$int_id = (int) $id;
|
||||
|
||||
if (-1 === $id) {
|
||||
return '';
|
||||
}
|
||||
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) : ['', ''];
|
||||
$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;
|
||||
$webp = $chemin ? realpath(pathinfo($chemin)['dirname'] . '/' . pathinfo($chemin)['filename'] . '.webp') : false;
|
||||
$avif = $chemin ? realpath(pathinfo($chemin)['dirname'] . '/' . pathinfo($chemin)['filename'] . '.avif') : false;
|
||||
$jxl = $chemin ? realpath(pathinfo($chemin)['dirname'] . '/' . pathinfo($chemin)['filename'] . '.jxl') : false;
|
||||
$webp = $chemin ? realpath(pathinfo($chemin)['dirname'] . '/' . pathinfo($chemin)['filename'] . '.webp') : false;
|
||||
|
||||
// Génère un tableau avec les différents formats valides
|
||||
$formats = pipe(
|
||||
[$avif, $jxl, $webp],
|
||||
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']);
|
||||
// Génère un tableau avec les différents formats valides
|
||||
$formats = pipe(
|
||||
[$avif, $jxl, $webp],
|
||||
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";
|
||||
}
|
||||
// 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';
|
||||
$loading = $lazy ? 'lazy' : 'eager';
|
||||
|
||||
return <<<EOD
|
||||
{$sources}
|
||||
return <<<EOD
|
||||
{$sources}
|
||||
|
||||
<img
|
||||
alt="{$alt}"
|
||||
decoding="async"
|
||||
height="{$dimensions[0]}"
|
||||
loading="{$loading}"
|
||||
onload="this.style.opacity=1"
|
||||
src="{$url}"
|
||||
width="{$dimensions[1]}"
|
||||
/>
|
||||
EOD;
|
||||
<img
|
||||
alt="{$alt}"
|
||||
decoding="async"
|
||||
height="{$dimensions[0]}"
|
||||
loading="{$loading}"
|
||||
onload="this.style.opacity=1"
|
||||
src="{$url}"
|
||||
width="{$dimensions[1]}"
|
||||
/>
|
||||
EOD;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO.
|
||||
*/
|
||||
function tri_variations_par_prix_descendant(WC_Product $a, WC_Product $b): int
|
||||
{
|
||||
return $b->get_price() <=> $a->get_price();
|
||||
function tri_variations_par_prix_descendant(WC_Product $a, WC_Product $b): int {
|
||||
return $b->get_price() <=> $a->get_price();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -97,50 +101,52 @@ function tri_variations_par_prix_descendant(WC_Product $a, WC_Product $b): int
|
|||
*
|
||||
* @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(),
|
||||
);
|
||||
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 = '';
|
||||
}
|
||||
// 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(),
|
||||
];
|
||||
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
|
||||
|
|
@ -148,51 +154,50 @@ function recupere_informations_produit_shop(WC_Product $produit): mixed
|
|||
/**
|
||||
* 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);
|
||||
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(),
|
||||
];
|
||||
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()
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -201,26 +206,24 @@ function recupere_informations_produit_page_produit(WC_Product $product): mixed
|
|||
*
|
||||
* Pour faciliter l'usage avec `array_map`, utilise une fonction avec curryfication.
|
||||
*/
|
||||
function recupere_produits_meme_collection(string $slug_collection): mixed
|
||||
{
|
||||
// @param int $id_produit
|
||||
return static fn($id_produit) => wc_get_products([
|
||||
'exclude' => [$id_produit],
|
||||
'limit' => 4,
|
||||
'order' => 'DESC',
|
||||
'orderby' => 'date',
|
||||
'status' => 'publish',
|
||||
'tax_query' => [['taxonomy' => 'collection', 'field' => 'slug', 'terms' => $slug_collection]],
|
||||
]);
|
||||
function recupere_produits_meme_collection(string $slug_collection): mixed {
|
||||
// @param int $id_produit
|
||||
return static fn($id_produit) => 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],
|
||||
];
|
||||
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]
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,6 +80,7 @@ button {
|
|||
background: var(--couleur-fond);
|
||||
box-shadow: initial;
|
||||
transition: 0.2s background, 0.2s opacity, 0.2s visibility;
|
||||
z-index: 500;
|
||||
|
||||
img {
|
||||
width: 1rem;
|
||||
|
|
|
|||
|
|
@ -7,8 +7,9 @@ import { getOptionOrThrowWithError } from "./utils";
|
|||
export type ParentElement = Document | Element;
|
||||
|
||||
export const getFirstSelectorFromParent =
|
||||
(parent: ParentElement) => <E extends Element = Element>(selector: string): Option.Option<NonNullable<E>> =>
|
||||
Option.fromNullishOr(parent.querySelector<E>(selector));
|
||||
(parent: ParentElement) =>
|
||||
<E extends Element = Element>(selector: string): Option.Option<NonNullable<E>> =>
|
||||
Option.fromNullable(parent.querySelector<E>(selector));
|
||||
|
||||
export const getFirstSelectorFromDocument = <E extends Element = Element>(
|
||||
selector: string,
|
||||
|
|
@ -21,12 +22,13 @@ export const getFirstSelectorFromDocumentOrThrow = <E extends Element = Element>
|
|||
);
|
||||
|
||||
export const getAllSelectorFromParent =
|
||||
(parent: ParentElement) => <E extends Element = Element>(selector: string): Option.Option<NonEmptyReadonlyArray<E>> =>
|
||||
(parent: ParentElement) =>
|
||||
<E extends Element = Element>(selector: string): Option.Option<NonEmptyReadonlyArray<E>> =>
|
||||
pipe(
|
||||
parent.querySelectorAll<E>(selector),
|
||||
// Convertis NodeListOf en Array.
|
||||
Array.from<E>,
|
||||
(xs: Array<E>) => Option.liftPredicate(EffectArray.isReadonlyArrayNonEmpty)(xs),
|
||||
(xs: Array<E>) => Option.liftPredicate(EffectArray.isNonEmptyReadonlyArray)(xs),
|
||||
);
|
||||
|
||||
export const getAllSelectorFromDocument = <E extends Element = Element>(
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import { pipe, Option } from "effect";
|
||||
|
||||
export const getOptionOrThrowWithError = (message: string) => <T>(option: Option.Option<T>): T =>
|
||||
pipe(
|
||||
option,
|
||||
Option.getOrThrowWith(() => new Error(message)),
|
||||
);
|
||||
export const getOptionOrThrowWithError =
|
||||
(message: string) =>
|
||||
<T>(option: Option.Option<T>): T =>
|
||||
pipe(
|
||||
option,
|
||||
Option.getOrThrowWith(() => new Error(message)),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -4,13 +4,7 @@ import { match } from "ts-pattern";
|
|||
import type { HttpCodeErrors, SimplifiedResponse } from "./types/reseau";
|
||||
|
||||
import { ENTETE_WC_NONCE } from "../constantes/api.ts";
|
||||
import {
|
||||
BadRequestError,
|
||||
ForbiddenError,
|
||||
NotFoundError,
|
||||
ServerError,
|
||||
UnauthorizedError,
|
||||
} from "./erreurs.ts";
|
||||
import { BadRequestError, ForbiddenError, NotFoundError, ServerError, UnauthorizedError } from "./erreurs.ts";
|
||||
|
||||
// Types
|
||||
|
||||
|
|
@ -59,9 +53,7 @@ export const getBackend = (args: ArgumentsGetBackendWC): Promise<Response> =>
|
|||
signal: AbortSignal.timeout(5000),
|
||||
});
|
||||
|
||||
export const getBackendAvecParametresUrl = (
|
||||
args: ArgumentsGetBackendWC,
|
||||
): Promise<Response> =>
|
||||
export const getBackendAvecParametresUrl = (args: ArgumentsGetBackendWC): Promise<Response> =>
|
||||
fetch(`${args.route}?${args.searchParams}`, {
|
||||
credentials: "same-origin",
|
||||
headers: {
|
||||
|
|
@ -76,9 +68,7 @@ export const getBackendAvecParametresUrl = (
|
|||
signal: AbortSignal.timeout(5000),
|
||||
});
|
||||
|
||||
export const deleteBackend = (
|
||||
args: ArgumentsDeleteBackendWC,
|
||||
): Promise<Response> =>
|
||||
export const deleteBackend = (args: ArgumentsDeleteBackendWC): Promise<Response> =>
|
||||
fetch(args.route, {
|
||||
credentials: "same-origin",
|
||||
headers: {
|
||||
|
|
@ -111,11 +101,7 @@ export const postBackend = (args: ArgumentsPostBackendWC): Promise<Response> =>
|
|||
|
||||
export const prefilledPostBackend =
|
||||
(nonce: string, authString?: string) =>
|
||||
(
|
||||
route: string,
|
||||
body: BodyInit,
|
||||
needsAuthString: boolean,
|
||||
): Promise<Response> =>
|
||||
(route: string, body: BodyInit, needsAuthString: boolean): Promise<Response> =>
|
||||
fetch(route, {
|
||||
body: body,
|
||||
credentials: "same-origin",
|
||||
|
|
@ -123,32 +109,25 @@ export const prefilledPostBackend =
|
|||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
[ENTETE_WC_NONCE]: nonce,
|
||||
...(authString &&
|
||||
needsAuthString && { Authorization: `Basic ${authString}` }),
|
||||
...(authString && needsAuthString && { Authorization: `Basic ${authString}` }),
|
||||
},
|
||||
method: "POST",
|
||||
mode: "same-origin",
|
||||
signal: AbortSignal.timeout(5000),
|
||||
});
|
||||
|
||||
export const safeFetch = (
|
||||
f: Promise<Response>,
|
||||
): EitherAsync<DOMException | TypeError, Response> =>
|
||||
export const safeFetch = (f: Promise<Response>): EitherAsync<DOMException | TypeError, Response> =>
|
||||
EitherAsync<DOMException | TypeError, Response>(async () => await f);
|
||||
|
||||
// Réponses Simplifiées
|
||||
export const newPartialResponse = async (
|
||||
reponse: Response,
|
||||
): Promise<SimplifiedResponse> => {
|
||||
export const newPartialResponse = async (reponse: Response): Promise<SimplifiedResponse> => {
|
||||
return {
|
||||
body: await reponse.json(),
|
||||
status: reponse.status,
|
||||
};
|
||||
};
|
||||
|
||||
export const traiteErreursBackendWooCommerce = (
|
||||
rs: SimplifiedResponse,
|
||||
): HttpCodeErrors => {
|
||||
export const traiteErreursBackendWooCommerce = (rs: SimplifiedResponse): HttpCodeErrors => {
|
||||
return match(rs)
|
||||
.with({ status: 400 }, () => new BadRequestError())
|
||||
.with({ status: 401 }, () => new UnauthorizedError())
|
||||
|
|
|
|||
|
|
@ -2,28 +2,18 @@
|
|||
|
||||
import { Array as EffectArray, Match, Predicate } from "effect";
|
||||
|
||||
import {
|
||||
DOM_ENTREES_MENU_CATEGORIES_PRODUITS,
|
||||
DOM_MENU_CATEGORIES_PRODUITS,
|
||||
} from "./constantes/dom.ts";
|
||||
import {
|
||||
getAllSelectorFromDocumentOrThrow,
|
||||
getFirstSelectorFromDocumentOrThrow,
|
||||
} from "../scripts-effect/lib/dom.ts";
|
||||
import { DOM_ENTREES_MENU_CATEGORIES_PRODUITS, DOM_MENU_CATEGORIES_PRODUITS } from "./constantes/dom.ts";
|
||||
import { getAllSelectorFromDocumentOrThrow, getFirstSelectorFromDocumentOrThrow } from "../scripts-effect/lib/dom.ts";
|
||||
|
||||
// Initialise les attributs HTML pour l'affichage initiale des flèches de défilement du menu de catégories de Produits.
|
||||
document.addEventListener("DOMContentLoaded", (): void => {
|
||||
const productsCategoriesMenu: HTMLElement =
|
||||
getFirstSelectorFromDocumentOrThrow<HTMLElement>(
|
||||
DOM_MENU_CATEGORIES_PRODUITS,
|
||||
);
|
||||
const menuEntries: ReadonlyArray<HTMLAnchorElement> =
|
||||
getAllSelectorFromDocumentOrThrow(DOM_ENTREES_MENU_CATEGORIES_PRODUITS);
|
||||
getFirstSelectorFromDocumentOrThrow<HTMLElement>(DOM_MENU_CATEGORIES_PRODUITS);
|
||||
const menuEntries: ReadonlyArray<HTMLAnchorElement> = getAllSelectorFromDocumentOrThrow(
|
||||
DOM_ENTREES_MENU_CATEGORIES_PRODUITS,
|
||||
);
|
||||
|
||||
const firstAndLastEntries: Array<HTMLAnchorElement | undefined> = [
|
||||
menuEntries.at(0),
|
||||
menuEntries.at(-1),
|
||||
];
|
||||
const firstAndLastEntries: Array<HTMLAnchorElement | undefined> = [menuEntries.at(0), menuEntries.at(-1)];
|
||||
|
||||
// Créé un nouvel Observer pour la première et dernière entrée.
|
||||
EffectArray.forEach(firstAndLastEntries, (menuEntry, _index) => {
|
||||
|
|
@ -35,28 +25,10 @@ document.addEventListener("DOMContentLoaded", (): void => {
|
|||
if (intersectionEntry.boundingClientRect.top <= 0) return;
|
||||
|
||||
Match.value([intersectionEntry.isIntersecting]).pipe(
|
||||
Match.when([true, 0], () =>
|
||||
productsCategoriesMenu.removeAttribute(
|
||||
"data-entrees-presentes-debut",
|
||||
),
|
||||
),
|
||||
Match.when([true, 1], () =>
|
||||
productsCategoriesMenu.removeAttribute(
|
||||
"data-entrees-presentes-fin",
|
||||
),
|
||||
),
|
||||
Match.when([false, 0], () =>
|
||||
productsCategoriesMenu.setAttribute(
|
||||
"data-entrees-presentes-debut",
|
||||
"",
|
||||
),
|
||||
),
|
||||
Match.when([false, 1], () =>
|
||||
productsCategoriesMenu.setAttribute(
|
||||
"data-entrees-presentes-fin",
|
||||
"",
|
||||
),
|
||||
),
|
||||
Match.when([true, 0], () => productsCategoriesMenu.removeAttribute("data-entrees-presentes-debut")),
|
||||
Match.when([true, 1], () => productsCategoriesMenu.removeAttribute("data-entrees-presentes-fin")),
|
||||
Match.when([false, 0], () => productsCategoriesMenu.setAttribute("data-entrees-presentes-debut", "")),
|
||||
Match.when([false, 1], () => productsCategoriesMenu.setAttribute("data-entrees-presentes-fin", "")),
|
||||
Match.orElse(() => {}),
|
||||
);
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -173,8 +173,7 @@ const ajouteProduitAuPanier = (event: MouseEvent): void => {
|
|||
|
||||
// Construis les arguments de la requête au backend
|
||||
const argsRequete: WCStoreCartAddItemArgs = {
|
||||
id: E.DOM_VARIATION
|
||||
.map((selecteur: HTMLSelectElement): number => Number(selecteur.value))
|
||||
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,
|
||||
|
|
|
|||
|
|
@ -6,61 +6,62 @@ declare(strict_types=1);
|
|||
* Le modèle de la Page d'Archive d'une Catégorie de Produits.
|
||||
*/
|
||||
|
||||
use HaikuAtelier\Data\Product;
|
||||
use HaikuAtelier\WP\Resource;
|
||||
use Timber\Timber;
|
||||
|
||||
require_once __DIR__ . '/src/inc/TraitementInformations.php';
|
||||
|
||||
// Contexte et modèles
|
||||
$contexte = Timber::context();
|
||||
$modeles = ['boutique.twig'];
|
||||
$context = Timber::context();
|
||||
$templates = ['boutique.twig'];
|
||||
|
||||
/** @var list<WC_Product> $informations_produits Les informations brutes des Produits. */
|
||||
$informations_produits = wc_get_products([
|
||||
'category' => [get_queried_object()?->slug],
|
||||
/** @var WP_Term */
|
||||
$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],
|
||||
'limit' => 12,
|
||||
'order' => 'DESC',
|
||||
'orderby' => 'date',
|
||||
'status' => 'publish'
|
||||
]);
|
||||
|
||||
/** @var InformationsProduitShop $produits Les informations strictement nécessaires pour la grille des Produits. */
|
||||
$produits = array_map(
|
||||
callback: recupere_informations_produit_shop(...),
|
||||
array: $informations_produits
|
||||
$products = array_map(
|
||||
callback: Product::new(...),
|
||||
array: $raw_products
|
||||
);
|
||||
$contexte['produits'] = $produits;
|
||||
$id_categorie_produits = array_shift($informations_produits)?->get_category_ids()[0] ?? '';
|
||||
$contexte['id_categorie_produits'] = $id_categorie_produits;
|
||||
$context['products'] = $products;
|
||||
$products_category_id = array_shift($raw_products)?->get_category_ids()[0] ?? '';
|
||||
$context['products_category_id'] = $products_category_id;
|
||||
|
||||
/**
|
||||
* Charge les Scripts nécessaires pour la page d'Archive.
|
||||
* Charge les ressources nécessaires pour la page d'Archive.
|
||||
*/
|
||||
function charge_scripts_page_archive_produits(): void {
|
||||
wp_enqueue_style(
|
||||
function load_page_resources(): void {
|
||||
Resource::enqueue_style_file(
|
||||
handle: 'haiku-atelier-2024-styles-page-boutique',
|
||||
src: get_template_directory_uri() . '/assets/css/pages/page-boutique.css',
|
||||
deps: [],
|
||||
ver: filemtime(get_template_directory() . '/assets/css/pages/page-boutique.css'),
|
||||
media: 'all'
|
||||
path: '/assets/css/pages/page-boutique.css'
|
||||
);
|
||||
wp_enqueue_script_module(
|
||||
Resource::enqueue_script_module_file(
|
||||
id: 'haiku-atelier-2024-scripts-page-boutique',
|
||||
src: get_template_directory_uri() . '/assets/js/scripts-page-boutique.js',
|
||||
deps: [],
|
||||
version: filemtime(get_template_directory() . '/assets/js/scripts-page-boutique.js')
|
||||
path: '/assets/js/scripts-page-boutique.js'
|
||||
);
|
||||
wp_enqueue_script_module(
|
||||
Resource::enqueue_script_module_file(
|
||||
id: 'haiku-atelier-2024-scripts-menu-categories',
|
||||
src: get_template_directory_uri() . '/assets/js/scripts-menu-categories.js',
|
||||
deps: [],
|
||||
version: filemtime(get_template_directory() . '/assets/js/scripts-menu-categories.js')
|
||||
path: '/assets/js/scripts-menu-categories.js'
|
||||
);
|
||||
}
|
||||
|
||||
add_action('wp_enqueue_scripts', 'charge_scripts_page_archive_produits');
|
||||
add_action('wp_enqueue_scripts', 'load_page_resources');
|
||||
|
||||
$lal = wp_json_encode($context);
|
||||
echo "<script>console.debug({$lal});</script>";
|
||||
|
||||
// Rendu
|
||||
Timber::render(
|
||||
filenames: $modeles,
|
||||
data: $contexte
|
||||
filenames: $templates,
|
||||
data: $context
|
||||
);
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ const _etats = {
|
|||
|
||||
<div class="actions">
|
||||
<button
|
||||
{{ produits|length == 12 ? '' : 'hidden' }}
|
||||
{{ products|length == 12 ? '' : 'hidden' }}
|
||||
class="bouton-case-pleine bouton-blanc-sur-noir"
|
||||
id="bouton-plus-de-produits"
|
||||
type="button"
|
||||
|
|
|
|||
|
|
@ -1,24 +1,24 @@
|
|||
<div class="grille-produits-similaires">
|
||||
{% for produit in produits_meme_collection %}
|
||||
{% for product in same_collection_products %}
|
||||
{# TODO: Trouver une meilleure arborescence et des noms de classe #}
|
||||
<article class="produit">
|
||||
<figure role="figure">
|
||||
<a href="{{ produit.url }}">
|
||||
<a href="{{ product.url }}">
|
||||
<picture class="produit__illustration produit__illustration__principale">
|
||||
{{ produit.default_photo }}
|
||||
{{ product.default_photo }}
|
||||
</picture>
|
||||
|
||||
<picture class="produit__illustration produit__illustration__survol">
|
||||
{{ produit.hover_photo }}
|
||||
{{ product.hover_photo }}
|
||||
</picture>
|
||||
</a>
|
||||
|
||||
<figcaption class="produit__textuel">
|
||||
<h3 class="produit__textuel__titre">
|
||||
<a href="{{ produit.url }}">{{ produit.name }}</a>
|
||||
<a href="{{ product.url }}">{{ product.name }}</a>
|
||||
</h3>
|
||||
<p class="produit__textuel__prix">
|
||||
{{ produit.price }}€
|
||||
{{ product.price }}€
|
||||
</p>
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
|
|
|||
|
|
@ -9,11 +9,11 @@
|
|||
id="variation-choice"
|
||||
name="variation-choice"
|
||||
>
|
||||
<h3 class="selecteur-produit__nom">{{ produit.name }}</h3>
|
||||
<h3 class="selecteur-produit__nom">{{ product.name }}</h3>
|
||||
|
||||
<div class="selecteur-produit__attribut-variation">
|
||||
{% if produit.attributes %}
|
||||
{% for attribut in produit.attributes %}
|
||||
{% if product.attributes %}
|
||||
{% for attribut in product.attributes %}
|
||||
<div class="test">
|
||||
{{ include('parts/pages/produit/selecteur-attributs-produit.twig') }}
|
||||
</div>
|
||||
|
|
@ -56,7 +56,7 @@
|
|||
#}
|
||||
</div>
|
||||
|
||||
<p class="selecteur-produit__prix">{{ prix_maximal ?? produit.price }}€</p>
|
||||
<p class="selecteur-produit__prix">{{ maximum_price ?? product.price }}€</p>
|
||||
</form>
|
||||
</aside>
|
||||
|
||||
|
|
@ -79,7 +79,7 @@
|
|||
class="section-textuelle__contenu"
|
||||
id="section-details-produit"
|
||||
>
|
||||
{{ produit.details }}
|
||||
{{ product.details }}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
|
@ -122,7 +122,7 @@
|
|||
|
||||
<div class="details-produit__actions">
|
||||
{# Désactive le bouton d'ajout au panier en cas d'absence de stock. #}
|
||||
{% if produit.stock > 0 %}
|
||||
{% if product.stock > 0 %}
|
||||
<button
|
||||
class="bouton-case-pleine"
|
||||
disabled
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
aria-label="Photo of the Product alone"
|
||||
class="colonne colonne-gauche"
|
||||
>
|
||||
{% for photo in produit.left_column_photos %}
|
||||
{% for photo in product.left_column_photos %}
|
||||
<figure
|
||||
data-index="0"
|
||||
role="figure"
|
||||
|
|
@ -19,7 +19,7 @@
|
|||
aria-label="Photos of the Product worn"
|
||||
class="colonne colonne-droite"
|
||||
>
|
||||
{% for photo in produit.right_column_photos %}
|
||||
{% for photo in product.right_column_photos %}
|
||||
<figure
|
||||
data-index="{{ loop.index }}"
|
||||
role="figure"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<div
|
||||
class="grille-produits"
|
||||
data-page="1"
|
||||
{% if id_categorie_produits %}data-id-categorie-produits="{{ id_categorie_produits }}"{% endif %}
|
||||
{% if products_category_id %}data-id-categorie-produits="{{ products_category_id }}"{% endif %}
|
||||
>
|
||||
{% if products|length > 0 %}
|
||||
{% for product in products %}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
/** @type {Etats} */
|
||||
const _etats = {
|
||||
idProduit: {{ produit.id }},
|
||||
idProduit: {{ product.id }},
|
||||
nonce: "{{ nonce_wc }}",
|
||||
};
|
||||
</script>
|
||||
|
|
@ -38,7 +38,7 @@
|
|||
{{ include('parts/pages/produit/informations-produit.twig') }}
|
||||
|
||||
{# Produits de la même Collection (Produits similaires) #}
|
||||
{% if produit.collection != '' %}
|
||||
{% if product.collection != '' and same_collection_products|length > 0 %}
|
||||
{{ include('parts/pages/produit/produits-similaires.twig') }}
|
||||
{% endif %}
|
||||
{% endblock contenu %}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue