- une mise à jour WooCommerce a changé les clés attendues des adresses dans le corps de la requête pour la création d'une comande.
176 lines
7.1 KiB
TypeScript
Executable file
176 lines
7.1 KiB
TypeScript
Executable file
/**
|
|
* Scripts pour les fonctionnalités de la page Boutique.
|
|
*/
|
|
|
|
import { pipe } from "@mobily/ts-belt";
|
|
import { tap } from "@mobily/ts-belt/Function";
|
|
import { EitherAsync } from "purify-ts";
|
|
import { match, P } from "ts-pattern";
|
|
import { ValiError } from "valibot";
|
|
|
|
import type { APIFetchErrors } from "./lib/types/api/erreurs";
|
|
import type { WCV3Products, WCV3ProductsArgs } from "./lib/types/api/v3/products.ts";
|
|
import type { GenericPageState } from "./lib/types/pages";
|
|
|
|
import { ROUTE_API_NOUVELLE_PRODUCTS } from "./constantes/api.ts";
|
|
import {
|
|
ATTRIBUT_CHARGEMENT,
|
|
ATTRIBUT_DESACTIVE,
|
|
ATTRIBUT_HIDDEN,
|
|
ATTRIBUT_ID_CATEGORIE_PRODUITS,
|
|
ATTRIBUT_PAGE,
|
|
SELECTEUR_BOUTON_PLUS_PRODUITS,
|
|
SELECTEUR_GRILLE_PRODUITS,
|
|
} from "./constantes/dom.ts";
|
|
import { lanceAnimationCycleLoading } from "./lib/animations.ts";
|
|
import { html, mustGetEleInDocument } from "./lib/dom.ts";
|
|
import { BadRequestError, reporteErreur, ServerError } from "./lib/erreurs.ts";
|
|
import { getBackendAvecParametresUrl, newPartialResponse } from "./lib/reseau.ts";
|
|
import { WCV3ProductsArgsSchema, WCV3ProductsSchema } from "./lib/schemas/api/v3/products.ts";
|
|
import { safeSchemaParse } from "./lib/validation.ts";
|
|
|
|
type APIProductsErrors =
|
|
| APIFetchErrors
|
|
| ValiError<typeof WCV3ProductsArgsSchema>
|
|
| ValiError<typeof WCV3ProductsSchema>;
|
|
|
|
// @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;
|
|
|
|
// Numéros magiques
|
|
const PRODUCTS_PER_PAGE = 12;
|
|
|
|
// Éléments d'intérêt
|
|
const E = {
|
|
BOUTON_PLUS_DE_PRODUITS: mustGetEleInDocument<HTMLButtonElement>(SELECTEUR_BOUTON_PLUS_PRODUITS),
|
|
GRILLE_PRODUITS: mustGetEleInDocument<HTMLDivElement>(SELECTEUR_GRILLE_PRODUITS),
|
|
};
|
|
|
|
/**
|
|
* TODO
|
|
*/
|
|
const initialisePageBoutique = (): void => {
|
|
/** ID de la Catégorie de Produits si la Page courante est l'Archive d'une Catégorie. */
|
|
const idCategorieProduits: null | string = E.GRILLE_PRODUITS.getAttribute(ATTRIBUT_ID_CATEGORIE_PRODUITS);
|
|
|
|
E.BOUTON_PLUS_DE_PRODUITS.addEventListener("click", (): void => {
|
|
/** Le numéro de page demandée par l'Utilisateur. */
|
|
const nouveauNumeroPage = Number(E.GRILLE_PRODUITS.getAttribute(ATTRIBUT_PAGE)) + 1;
|
|
/** Les arguments passés à la requête auprès Backend pour la nouvelle page de Produits. */
|
|
const args: WCV3ProductsArgs = {
|
|
page: nouveauNumeroPage,
|
|
per_page: PRODUCTS_PER_PAGE,
|
|
// Ajoute conditionnellement la Catégorie de Produits
|
|
...(idCategorieProduits && { category: idCategorieProduits }),
|
|
};
|
|
|
|
void EitherAsync
|
|
// 1. Valide les Arguments de la Requête
|
|
.liftEither(safeSchemaParse(args, WCV3ProductsArgsSchema))
|
|
// 2. Exécute un Effet pour empêcher les requêtes concurrentes et lancer une animation de chargement
|
|
.ifRight((): void => {
|
|
// Désactive le Bouton pour empêcher des requêtes concurrentes
|
|
E.BOUTON_PLUS_DE_PRODUITS.setAttribute(ATTRIBUT_DESACTIVE, "");
|
|
E.BOUTON_PLUS_DE_PRODUITS.setAttribute(ATTRIBUT_CHARGEMENT, "");
|
|
|
|
// Lance un cycle d'animation sur le texte de chargement
|
|
lanceAnimationCycleLoading(E.BOUTON_PLUS_DE_PRODUITS, 500);
|
|
})
|
|
// 3. Exécute la requête via fetch sous forme d'EitherAsync
|
|
.chain((args: WCV3ProductsArgs) =>
|
|
EitherAsync<DOMException | Error, Response>(() =>
|
|
getBackendAvecParametresUrl({
|
|
authString: ETATS_PAGE.authString,
|
|
nonce: ETATS_PAGE.nonce,
|
|
route: ROUTE_API_NOUVELLE_PRODUCTS,
|
|
searchParams: new URLSearchParams(args).toString(),
|
|
})
|
|
)
|
|
)
|
|
// 4. Traite les cas d'Erreurs et récupère le Corps de la Réponse
|
|
.chain((reponse: Response) =>
|
|
EitherAsync<APIFetchErrors, unknown>(async ({ throwE }) => {
|
|
return match(await newPartialResponse(reponse))
|
|
.with({ status: 500 }, () => throwE(new ServerError("500 Server Error")))
|
|
.with({ status: 400 }, () => throwE(new BadRequestError("400 Server Error")))
|
|
.with({ status: 200 }, r => r.body)
|
|
.run();
|
|
})
|
|
)
|
|
// 5. Vérifie le Schéma de la Réponse
|
|
.chain((corpsReponse: unknown) => EitherAsync.liftEither(safeSchemaParse(corpsReponse, WCV3ProductsSchema)))
|
|
// 6. Exécute un Effet pour la mise à jour du DOM avec les Résultats
|
|
.ifRight((donnees: WCV3Products) => {
|
|
// Cache le bouton s'il y a moins de PRODUCTS_PER_PAGE Produits disponibles (que l'on est à la dernière page)
|
|
if (donnees.length < PRODUCTS_PER_PAGE) {
|
|
E.BOUTON_PLUS_DE_PRODUITS.toggleAttribute(ATTRIBUT_HIDDEN);
|
|
}
|
|
|
|
// Créé un DocumentFragment qui recevra tous les nouveaux Produits
|
|
const fragment: DocumentFragment = document.createDocumentFragment();
|
|
|
|
// Créé les Éléments <article> à insérer
|
|
for (const produit of donnees.slice(0, PRODUCTS_PER_PAGE)) {
|
|
pipe(
|
|
html`
|
|
<article class="produit">
|
|
<figure>
|
|
<a href="/product/${produit.slug}">
|
|
<picture class="produit__illustration produit__illustration__principale">
|
|
${produit.image_repos ?? ""}
|
|
</picture>
|
|
|
|
<picture class="produit__illustration produit__illustration__survol">
|
|
${produit.image_survol ?? ""}
|
|
</picture>
|
|
</a>
|
|
|
|
<figcaption class="produit__textuel">
|
|
<h3 class="produit__textuel__titre">
|
|
<a href="${produit.permalink}">${produit.name}</a>
|
|
</h3>
|
|
<p class="produit__textuel__prix">
|
|
${produit.prix_maximal}€
|
|
</p>
|
|
</figcaption>
|
|
</figure>
|
|
</article>
|
|
`,
|
|
tap(article => fragment.appendChild(article)),
|
|
);
|
|
}
|
|
|
|
// Ajoute les nouveaux Produits dans le DOM
|
|
E.GRILLE_PRODUITS.appendChild(fragment);
|
|
E.GRILLE_PRODUITS.setAttribute(ATTRIBUT_PAGE, String(nouveauNumeroPage));
|
|
|
|
E.BOUTON_PLUS_DE_PRODUITS.textContent = "Show more";
|
|
})
|
|
// 7. Traite les Erreurs et affiche un Message à l'Utilisateur
|
|
.ifLeft((erreur: APIProductsErrors) => {
|
|
match(erreur)
|
|
.with(P.instanceOf(ValiError), e => {
|
|
reporteErreur(e);
|
|
console.error("ValiError", e.issues);
|
|
})
|
|
.otherwise(e => {
|
|
reporteErreur(e);
|
|
console.error("Erreur", e);
|
|
});
|
|
|
|
E.BOUTON_PLUS_DE_PRODUITS.textContent = "Error, try again?";
|
|
})
|
|
// 8. Quel que soit le résultat, réactiver le Bouton et arrêter l'animation
|
|
.finally(() => {
|
|
// Désactive l'animation de chargement et rend le Bouton de nouveau cliquable
|
|
E.BOUTON_PLUS_DE_PRODUITS.removeAttribute(ATTRIBUT_CHARGEMENT);
|
|
E.BOUTON_PLUS_DE_PRODUITS.removeAttribute(ATTRIBUT_DESACTIVE);
|
|
})
|
|
.run();
|
|
});
|
|
};
|
|
|
|
document.addEventListener("DOMContentLoaded", (): void => {
|
|
initialisePageBoutique();
|
|
});
|