2026-04-13
This commit is contained in:
parent
d50de6d534
commit
08ad871e0c
9 changed files with 320 additions and 293 deletions
|
|
@ -8,7 +8,6 @@ import type { MessageMajContenuPanierSchema } from "./lib/schemas/messages.ts";
|
|||
import type { WCStoreCartItem } from "./lib/types/api/cart";
|
||||
import type { MessageMajBoutonPanierDonnees, MessageMajContenuPanierDonnees } from "./lib/types/messages";
|
||||
|
||||
import { Effect } from "effect";
|
||||
import { Effect } from "effect";
|
||||
import {
|
||||
ATTRIBUT_CLE_PANIER,
|
||||
|
|
|
|||
|
|
@ -1,10 +1,23 @@
|
|||
import { Array as FxArray, Console, Context, Effect, HashMap, Layer, ManagedRuntime, Option, pipe } from "effect";
|
||||
// oxlint-disable typescript/dot-notation
|
||||
import {
|
||||
Array as FxArray,
|
||||
Console,
|
||||
Context,
|
||||
Effect,
|
||||
HashMap,
|
||||
Layer,
|
||||
ManagedRuntime,
|
||||
Option,
|
||||
pipe,
|
||||
Stream,
|
||||
} from "effect";
|
||||
import type { NonEmptyReadonlyArray } from "effect/Array";
|
||||
import type { NoSuchElementError } from "effect/Cause";
|
||||
import { getAllSelectorFromDocument, getFirstSelectorFromDocument } from "../scripts-effect/lib/dom.ts";
|
||||
import {
|
||||
ATTRIBUT_ARIA_CONTROLS,
|
||||
ATTRIBUT_ARIA_EXPANDED,
|
||||
ATTRIBUT_DESACTIVE,
|
||||
ATTRIBUT_HIDDEN,
|
||||
DOM_BOUTON_AJOUT_PANIER,
|
||||
DOM_BOUTONS_ACCORDEON,
|
||||
|
|
@ -22,7 +35,7 @@ type DetailEnsemble = {
|
|||
class ProductPageElements extends Context.Service<
|
||||
ProductPageElements,
|
||||
{
|
||||
AddProductButton: HTMLButtonElement;
|
||||
AddToCartButton: HTMLButtonElement;
|
||||
Details: HashMap.HashMap<string, DetailEnsemble>;
|
||||
DetailsButtons: NonEmptyReadonlyArray<HTMLButtonElement>;
|
||||
DetailsContents: NonEmptyReadonlyArray<HTMLDivElement>;
|
||||
|
|
@ -35,7 +48,7 @@ class ProductPageElements extends Context.Service<
|
|||
static readonly layer = Layer.effect(
|
||||
ProductPageElements,
|
||||
Effect.gen(function*() {
|
||||
const AddProductButton = yield* getFirstSelectorFromDocument<HTMLButtonElement>(DOM_BOUTON_AJOUT_PANIER);
|
||||
const AddToCartButton = yield* getFirstSelectorFromDocument<HTMLButtonElement>(DOM_BOUTON_AJOUT_PANIER);
|
||||
const DetailsButtons = yield* getAllSelectorFromDocument<HTMLButtonElement>(DOM_BOUTONS_ACCORDEON);
|
||||
const DetailsContents = yield* getAllSelectorFromDocument<HTMLDivElement>(DOM_CONTENUS_ACCORDEON);
|
||||
const ProductPrice = yield* getFirstSelectorFromDocument<HTMLParagraphElement>(DOM_PRIX_PRODUIT);
|
||||
|
|
@ -62,7 +75,7 @@ class ProductPageElements extends Context.Service<
|
|||
);
|
||||
|
||||
return ProductPageElements.of({
|
||||
AddProductButton,
|
||||
AddToCartButton,
|
||||
Details,
|
||||
DetailsButtons,
|
||||
DetailsContents,
|
||||
|
|
@ -78,10 +91,32 @@ class ProductPageElements extends Context.Service<
|
|||
class ProductPageDOM extends Context.Service<
|
||||
ProductPageDOM,
|
||||
{
|
||||
initPriceUpdatesOnVariationChange: () => Effect.Effect<void>;
|
||||
onVariationChangeHandler: () => Effect.Effect<void>;
|
||||
/**
|
||||
* Récupère les Attributs du Produit depuis les Elements au sein du DOM.
|
||||
*/
|
||||
getProductAttributesFromDOM: () => Effect.Effect<ReadonlyArray<WCStoreCartAddItemArgsItems>>;
|
||||
/**
|
||||
* Initialise l'état initial du Bouton d'ajout au Panier.
|
||||
*/
|
||||
initAddToCartButtonInitialState: () => Effect.Effect<void>;
|
||||
/**
|
||||
* Initialise les mises à jour du Bouton d'ajout au Panier en fonction des interactions de l'Utilisateur.
|
||||
*/
|
||||
initAddToCartButtonUpdates: () => Effect.Effect<void>;
|
||||
/**
|
||||
* Initialise les interactions des Sections de la Description du Produit.
|
||||
*/
|
||||
initDetailInteractions: () => Effect.Effect<void, NoSuchElementError>;
|
||||
/**
|
||||
* Met à jour l'état des Sections de la Description du Produit.
|
||||
*/
|
||||
onDetailButtonClickHandler: (evt: Event) => Effect.Effect<void, NoSuchElementError>;
|
||||
/**
|
||||
* Met à jour l'état du Bouton d'ajout au Panier.
|
||||
*/
|
||||
onFormChangeHandler: (evt: Event) => Effect.Effect<void>;
|
||||
/**
|
||||
* Replie toutes les sections de la description du Produit.
|
||||
*/
|
||||
|
|
@ -91,7 +126,19 @@ class ProductPageDOM extends Context.Service<
|
|||
static readonly layer = Layer.effect(
|
||||
ProductPageDOM,
|
||||
Effect.gen(function*() {
|
||||
const { Details, VariationSelectors } = yield* ProductPageElements;
|
||||
const { AddToCartButton, Details, ProductPrice, DetailsButtons, ProductRawJson, VariationChoiceForm, VariationSelectors } =
|
||||
yield* ProductPageElements;
|
||||
|
||||
const onFormChangeHandler = Effect.fnUntraced(function*(evt: Event) {
|
||||
// La cible ne peut qu'être un Formulaire.
|
||||
const target: HTMLFormElement = evt.target as HTMLFormElement;
|
||||
const isClickAllowed = target.checkValidity() === false;
|
||||
|
||||
// Active/désactive le Bouton en fonction de la validité du Formulaire du Produit.
|
||||
AddToCartButton.toggleAttribute(ATTRIBUT_DESACTIVE, isClickAllowed);
|
||||
|
||||
return yield* Effect.void;
|
||||
});
|
||||
|
||||
const toggleAllDetails: () => Effect.Effect<void> = () =>
|
||||
Effect.sync((): void => {
|
||||
|
|
@ -105,6 +152,37 @@ class ProductPageDOM extends Context.Service<
|
|||
);
|
||||
});
|
||||
|
||||
const onDetailButtonClickHandler = Effect.fnUntraced(function*(evt: Event) {
|
||||
// Empêche la pollution de l'historique de navigation
|
||||
evt.preventDefault();
|
||||
|
||||
// La cible est connue.
|
||||
const target = evt.target as HTMLButtonElement;
|
||||
|
||||
// Récupère le contenu correspondant au Bouton.
|
||||
const linkedSection = yield* pipe(
|
||||
Option.fromNullishOr(target.getAttribute(ATTRIBUT_ARIA_CONTROLS)),
|
||||
Option.flatMap((contentId: string) => HashMap.get(Details, contentId)),
|
||||
);
|
||||
|
||||
// Sauvegarde l'état d'ouverture de la Section avant de toutes les fermer.
|
||||
const wasCurrentSection: boolean = target.getAttribute(ATTRIBUT_ARIA_EXPANDED) === "true";
|
||||
|
||||
// Replie toutes les Sections.
|
||||
yield* toggleAllDetails();
|
||||
|
||||
// Ne fais rien de plus si l'onglet sélectionné était le courant
|
||||
if (wasCurrentSection === true) {
|
||||
return yield* Effect.void;
|
||||
}
|
||||
|
||||
// Ouvre le nouvel onglet sélectionné
|
||||
target.toggleAttribute(ATTRIBUT_ARIA_EXPANDED, true);
|
||||
linkedSection.content.toggleAttribute(ATTRIBUT_HIDDEN, false);
|
||||
|
||||
return yield* Effect.void;
|
||||
});
|
||||
|
||||
const getProductAttributesFromDOM: () => Effect.Effect<ReadonlyArray<WCStoreCartAddItemArgsItems>> = () =>
|
||||
Effect.sync(() =>
|
||||
FxArray.map(VariationSelectors, (select: HTMLSelectElement) => ({
|
||||
|
|
@ -113,8 +191,79 @@ class ProductPageDOM extends Context.Service<
|
|||
}))
|
||||
);
|
||||
|
||||
const initAddToCartButtonInitialState = Effect.fn("initAddToCartButtonInitialState")(function*() {
|
||||
/** Est-ce que le Produit affiché est en stock ? */
|
||||
const isProductInStock = AddToCartButton.hasAttribute("data-in-stock") === true;
|
||||
|
||||
// S'il n'y a pas de stock, ne rien faire.
|
||||
if (isProductInStock === false) {
|
||||
return yield* Effect.void;
|
||||
}
|
||||
|
||||
// S'il n'y a pas de Sélecteurs de variations, activer le Bouton d'ajout au Panier.
|
||||
if (FxArray.isReadonlyArrayEmpty(VariationSelectors)) {
|
||||
AddToCartButton.removeAttribute(ATTRIBUT_DESACTIVE);
|
||||
}
|
||||
|
||||
return yield* Effect.void;
|
||||
});
|
||||
|
||||
const initAddToCartButtonUpdates = Effect.fn("initAddToCartInteractionUpdates")(function*() {
|
||||
return yield* pipe(
|
||||
Stream.fromEventListener(VariationChoiceForm, "change"),
|
||||
Stream.tap(onFormChangeHandler),
|
||||
Stream.runDrain,
|
||||
);
|
||||
});
|
||||
|
||||
const initPriceUpdatesOnVariationChange = Effect.fn("initPriceUpdatesOnVariationChange")(function*(){
|
||||
return yield* pipe(
|
||||
Stream.fromEventListener(VariationChoiceForm, "change"),
|
||||
Stream.tap(onVariationChangeHandler),
|
||||
Stream.runDrain,
|
||||
)
|
||||
});
|
||||
|
||||
const onVariationChangeHandler = Effect.fn("onVariationChangeHandler")(function*(){
|
||||
if (VariationChoiceForm.checkValidity() === false) {
|
||||
return yield* Effect.void;
|
||||
}
|
||||
|
||||
const variations = JSON.parse(ProductRawJson.textContent)?.variations as ReadonlyArray<unknown>;
|
||||
const chosenAttributes = yield* getProductAttributesFromDOM();
|
||||
|
||||
const equivalence = FxArray.makeEquivalence<{attribute: string,value: string}>((a,b) => {
|
||||
return a.attribute === b.attribute && a.value === b.value;
|
||||
});
|
||||
const chosenVariation = yield* FxArray.findFirst(variations, variation => equivalence(variation.attributes, chosenAttributes));
|
||||
const newPrice = chosenVariation.price;
|
||||
|
||||
ProductPrice.textContent = `${newPrice}€`;
|
||||
return yield* Effect.void;
|
||||
});
|
||||
|
||||
const initDetailInteractions = Effect.fn("initDetailInteractions")(function*() {
|
||||
return yield* pipe(
|
||||
// Créé un Stream par Bouton de Section.
|
||||
FxArray.map(
|
||||
DetailsButtons,
|
||||
(button: HTMLButtonElement) =>
|
||||
pipe(Stream.fromEventListener(button, "click"), Stream.tap(onDetailButtonClickHandler)),
|
||||
),
|
||||
Stream.mergeAll({ concurrency: "unbounded" }),
|
||||
Stream.runDrain,
|
||||
);
|
||||
});
|
||||
|
||||
return ProductPageDOM.of({
|
||||
getProductAttributesFromDOM,
|
||||
initAddToCartButtonInitialState,
|
||||
initAddToCartButtonUpdates,
|
||||
initDetailInteractions,
|
||||
initPriceUpdatesOnVariationChange,
|
||||
onDetailButtonClickHandler,
|
||||
onFormChangeHandler,
|
||||
onVariationChangeHandler,
|
||||
toggleAllDetails,
|
||||
});
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -1,40 +1,10 @@
|
|||
// Scripts pour la Page Produit
|
||||
|
||||
import { pipe } from "@mobily/ts-belt";
|
||||
import { get as dictGet } from "@mobily/ts-belt/Dict";
|
||||
import { tap as optionTap } from "@mobily/ts-belt/Option";
|
||||
import { Array as FxArray, Console, Effect, HashMap, Option, pipe as epipe, Stream } from "effect";
|
||||
import { EitherAsync } from "purify-ts";
|
||||
import { match, P } from "ts-pattern";
|
||||
import { ValiError } from "valibot";
|
||||
import type { AnySchema } from "valibot";
|
||||
import { Console, Effect, pipe as epipe } from "effect";
|
||||
|
||||
import type { WCStoreCartAddItemArgs, WCStoreCartAddItemArgsItems } from "./lib/types/api/cart-add-item.ts";
|
||||
import type { WCStoreCart } from "./lib/types/api/cart.ts";
|
||||
import type { FetchErrors } from "./lib/types/reseau.ts";
|
||||
|
||||
import { ROUTE_API_AJOUTE_ARTICLE_PANIER } from "./constantes/api.ts";
|
||||
import {
|
||||
ATTRIBUT_ARIA_CONTROLS,
|
||||
ATTRIBUT_ARIA_EXPANDED,
|
||||
ATTRIBUT_CHARGEMENT,
|
||||
ATTRIBUT_DESACTIVE,
|
||||
ATTRIBUT_HIDDEN,
|
||||
DOM_BOUTON_AJOUT_PANIER,
|
||||
DOM_BOUTONS_ACCORDEON,
|
||||
DOM_CONTENUS_ACCORDEON,
|
||||
DOM_DOM_QUANTITE,
|
||||
DOM_PRIX_PRODUIT,
|
||||
} from "./constantes/dom.ts";
|
||||
import { lanceAnimationCycleLoading } from "./lib/animations.ts";
|
||||
import { mustGetEleInDocument, mustGetElesInDocument, recupereElementDocumentEither } from "./lib/dom.ts";
|
||||
import { BadRequestError, reporteErreur, ServerError } from "./lib/erreurs.ts";
|
||||
import { emetMessageMajBoutonPanier } from "./lib/messages.ts";
|
||||
import { newPartialResponse, postBackend, safeFetch } from "./lib/reseau.ts";
|
||||
import { WCStoreCartAddItemArgsSchema } from "./lib/schemas/api/cart-add-item.ts";
|
||||
import { WCStoreCartSchema } from "./lib/schemas/api/cart.ts";
|
||||
import { safeSchemaParse } from "./lib/validation.ts";
|
||||
import { ProductPageElements, ProductPageRuntime } from "./scripts-page-produit-service.ts";
|
||||
import { ProductPageRuntime } from "./scripts-page-produit-service.ts";
|
||||
|
||||
/** États utiles pour les scripts de la page. */
|
||||
type EtatsPage = {
|
||||
|
|
@ -72,199 +42,93 @@ const updatePriceOnAttributeChange = (): void => {
|
|||
});
|
||||
};
|
||||
|
||||
const ajouteProduitAuPanier = (event: MouseEvent): void => {
|
||||
event.preventDefault();
|
||||
console.debug("getAttributeValuesFromDom", getAttributesFromDom());
|
||||
// const ajouteProduitAuPanier = (event: MouseEvent): void => {
|
||||
// event.preventDefault();
|
||||
// console.debug("getAttributeValuesFromDom", getAttributesFromDom());
|
||||
|
||||
// Construis les arguments de la requête au backend
|
||||
const argsRequete: WCStoreCartAddItemArgs = {
|
||||
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),
|
||||
quantity: 1,
|
||||
variation: getAttributesFromDom(),
|
||||
};
|
||||
// // Construis les arguments de la requête au backend
|
||||
// const argsRequete: WCStoreCartAddItemArgs = {
|
||||
// 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),
|
||||
// quantity: 1,
|
||||
// variation: getAttributesFromDom(),
|
||||
// };
|
||||
|
||||
// Réalise la Requête et traite sa Réponse
|
||||
void EitherAsync
|
||||
// 1. Valide les arguments de la Requête
|
||||
.liftEither(safeSchemaParse(argsRequete, WCStoreCartAddItemArgsSchema))
|
||||
// 2. Exécute un Effet pour empêcher les requêtes concurrentes et lancer une animation de chargement
|
||||
.ifRight(() => {
|
||||
// Désactive le Bouton pour empêcher des requêtes concurrentes
|
||||
E.BOUTON_AJOUT_PANIER.setAttribute(ATTRIBUT_DESACTIVE, "");
|
||||
E.BOUTON_AJOUT_PANIER.setAttribute(ATTRIBUT_CHARGEMENT, "");
|
||||
// // Réalise la Requête et traite sa Réponse
|
||||
// void EitherAsync
|
||||
// // 1. Valide les arguments de la Requête
|
||||
// .liftEither(safeSchemaParse(argsRequete, WCStoreCartAddItemArgsSchema))
|
||||
// // 2. Exécute un Effet pour empêcher les requêtes concurrentes et lancer une animation de chargement
|
||||
// .ifRight(() => {
|
||||
// // Désactive le Bouton pour empêcher des requêtes concurrentes
|
||||
// E.BOUTON_AJOUT_PANIER.setAttribute(ATTRIBUT_DESACTIVE, "");
|
||||
// E.BOUTON_AJOUT_PANIER.setAttribute(ATTRIBUT_CHARGEMENT, "");
|
||||
|
||||
// Lance un cycle d'animation sur le texte de chargement
|
||||
lanceAnimationCycleLoading(E.BOUTON_AJOUT_PANIER, 500);
|
||||
})
|
||||
// 3. Exécute la requête via fetch sous forme d'EitherAsync
|
||||
.chain((args: WCStoreCartAddItemArgs) =>
|
||||
safeFetch(
|
||||
postBackend({
|
||||
corps: JSON.stringify(args),
|
||||
nonce: ETATS_PAGE.nonce,
|
||||
route: ROUTE_API_AJOUTE_ARTICLE_PANIER,
|
||||
}),
|
||||
)
|
||||
)
|
||||
// 4. Traite les cas d'Erreurs et récupère le Corps de la Réponse
|
||||
.chain((reponse: Response) =>
|
||||
EitherAsync<BadRequestError | ServerError, unknown>(async ({ throwE }) =>
|
||||
// Simplifie les données à matcher
|
||||
match(await newPartialResponse(reponse))
|
||||
.with({ status: 500 }, () => throwE(new ServerError("500 Server Error")))
|
||||
.with({ status: 400 }, () => throwE(new BadRequestError("400 Bad Request Error")))
|
||||
.with({ status: 201 }, r => r.body)
|
||||
.otherwise(erreur => throwE(new Error(`Erreur inconnue ${String(erreur.status)}`)))
|
||||
)
|
||||
)
|
||||
// 5. Vérifie le Schéma de la Réponse
|
||||
.chain((corpsReponse: unknown) => EitherAsync.liftEither(safeSchemaParse(corpsReponse, WCStoreCartSchema)))
|
||||
// 6. Exécute un Effet pour la mise à jour du DOM avec les Résultats
|
||||
.ifRight((panier: WCStoreCart) =>
|
||||
pipe(
|
||||
dictGet(panier, "items_count"),
|
||||
optionTap((totalArticles: number) => {
|
||||
E.BOUTON_AJOUT_PANIER.textContent = "Added to cart!";
|
||||
emetMessageMajBoutonPanier({ quantiteProduits: totalArticles });
|
||||
}),
|
||||
)
|
||||
)
|
||||
.ifLeft((erreur: BadRequestError | FetchErrors | ServerError | ValiError<AnySchema>) => {
|
||||
match(erreur)
|
||||
.with(P.instanceOf(ValiError), e => {
|
||||
reporteErreur(e);
|
||||
console.error(e.issues);
|
||||
// E.MESSAGE_ADRESSES.textContent = ERREUR_GENERIQUE_SOUMISSION_ADRESSES;
|
||||
})
|
||||
.with(P.instanceOf(ServerError), P.instanceOf(BadRequestError), e => {
|
||||
reporteErreur(e);
|
||||
console.error(e);
|
||||
// E.MESSAGE_ADRESSES.textContent = ERREUR_GENERIQUE_SOUMISSION_ADRESSES;
|
||||
})
|
||||
.with(P.instanceOf(DOMException), P.instanceOf(TypeError), P.instanceOf(Error), e => {
|
||||
reporteErreur(e);
|
||||
console.error(e);
|
||||
// E.MESSAGE_ADRESSES.textContent = ERREUR_GENERIQUE_RESEAU;
|
||||
})
|
||||
.exhaustive();
|
||||
// // Lance un cycle d'animation sur le texte de chargement
|
||||
// lanceAnimationCycleLoading(E.BOUTON_AJOUT_PANIER, 500);
|
||||
// })
|
||||
// // 3. Exécute la requête via fetch sous forme d'EitherAsync
|
||||
// .chain((args: WCStoreCartAddItemArgs) =>
|
||||
// safeFetch(
|
||||
// postBackend({
|
||||
// corps: JSON.stringify(args),
|
||||
// nonce: ETATS_PAGE.nonce,
|
||||
// route: ROUTE_API_AJOUTE_ARTICLE_PANIER,
|
||||
// }),
|
||||
// )
|
||||
// )
|
||||
// // 4. Traite les cas d'Erreurs et récupère le Corps de la Réponse
|
||||
// .chain((reponse: Response) =>
|
||||
// EitherAsync<BadRequestError | ServerError, unknown>(async ({ throwE }) =>
|
||||
// // Simplifie les données à matcher
|
||||
// match(await newPartialResponse(reponse))
|
||||
// .with({ status: 500 }, () => throwE(new ServerError("500 Server Error")))
|
||||
// .with({ status: 400 }, () => throwE(new BadRequestError("400 Bad Request Error")))
|
||||
// .with({ status: 201 }, r => r.body)
|
||||
// .otherwise(erreur => throwE(new Error(`Erreur inconnue ${String(erreur.status)}`)))
|
||||
// )
|
||||
// )
|
||||
// // 5. Vérifie le Schéma de la Réponse
|
||||
// .chain((corpsReponse: unknown) => EitherAsync.liftEither(safeSchemaParse(corpsReponse, WCStoreCartSchema)))
|
||||
// // 6. Exécute un Effet pour la mise à jour du DOM avec les Résultats
|
||||
// .ifRight((panier: WCStoreCart) =>
|
||||
// pipe(
|
||||
// dictGet(panier, "items_count"),
|
||||
// optionTap((totalArticles: number) => {
|
||||
// E.BOUTON_AJOUT_PANIER.textContent = "Added to cart!";
|
||||
// emetMessageMajBoutonPanier({ quantiteProduits: totalArticles });
|
||||
// }),
|
||||
// )
|
||||
// )
|
||||
// .ifLeft((erreur: BadRequestError | FetchErrors | ServerError | ValiError<AnySchema>) => {
|
||||
// match(erreur)
|
||||
// .with(P.instanceOf(ValiError), e => {
|
||||
// reporteErreur(e);
|
||||
// console.error(e.issues);
|
||||
// // E.MESSAGE_ADRESSES.textContent = ERREUR_GENERIQUE_SOUMISSION_ADRESSES;
|
||||
// })
|
||||
// .with(P.instanceOf(ServerError), P.instanceOf(BadRequestError), e => {
|
||||
// reporteErreur(e);
|
||||
// console.error(e);
|
||||
// // E.MESSAGE_ADRESSES.textContent = ERREUR_GENERIQUE_SOUMISSION_ADRESSES;
|
||||
// })
|
||||
// .with(P.instanceOf(DOMException), P.instanceOf(TypeError), P.instanceOf(Error), e => {
|
||||
// reporteErreur(e);
|
||||
// console.error(e);
|
||||
// // E.MESSAGE_ADRESSES.textContent = ERREUR_GENERIQUE_RESEAU;
|
||||
// })
|
||||
// .exhaustive();
|
||||
|
||||
E.BOUTON_AJOUT_PANIER.textContent = "Add to cart";
|
||||
})
|
||||
.finally((): void => {
|
||||
// Désactive l'animation de chargement et rend le Bouton de nouveau cliquable
|
||||
E.BOUTON_AJOUT_PANIER.removeAttribute(ATTRIBUT_CHARGEMENT);
|
||||
E.BOUTON_AJOUT_PANIER.removeAttribute(ATTRIBUT_DESACTIVE);
|
||||
})
|
||||
.run();
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialise l'état initial d'interactivité du Bouton d'ajout de Produit au Panier.
|
||||
*/
|
||||
const initAddToCartButton = Effect.fn("initAddToCartButton")(function*() {
|
||||
const { AddProductButton, VariationSelectors } = yield* ProductPageElements;
|
||||
/** Est-ce que le Produit affiché est en stock ? */
|
||||
const isProductInStock = AddProductButton.hasAttribute("data-in-stock") === true;
|
||||
|
||||
// S'il n'y a pas de stock, ne rien faire.
|
||||
if (isProductInStock === false) {
|
||||
console.debug("initAddToCartButton", "Pas de stock.");
|
||||
return yield* Effect.void;
|
||||
}
|
||||
|
||||
// S'il n'y a pas de Sélecteurs de variations, activer le Bouton d'ajout au Panier.
|
||||
if (FxArray.isReadonlyArrayEmpty(VariationSelectors)) {
|
||||
console.debug("initAddToCartButton", "Produt simple.");
|
||||
E.BOUTON_AJOUT_PANIER.removeAttribute(ATTRIBUT_DESACTIVE);
|
||||
}
|
||||
|
||||
return yield* Effect.void;
|
||||
});
|
||||
|
||||
const onFormChange = Effect.fnUntraced(function*(evt: Event) {
|
||||
const { AddProductButton } = yield* ProductPageElements;
|
||||
// La cible ne peut qu'être un Formulaire.
|
||||
const target: HTMLFormElement = evt.target as HTMLFormElement;
|
||||
const isClickAllowed = target.checkValidity() === false;
|
||||
|
||||
// Active/désactive le Bouton en fonction de la validité du Formulaire du Produit.
|
||||
AddProductButton.toggleAttribute(ATTRIBUT_DESACTIVE, isClickAllowed);
|
||||
|
||||
return yield* Effect.void;
|
||||
});
|
||||
|
||||
/**
|
||||
* Initialise la mise à jour de l'état d'interactivité du Bouton d'ajout de Produit au Panier en fonction des actions de l'Utilisateur.
|
||||
*/
|
||||
const initAddToCartInteractionUpdates = Effect.fn("initAddToCartInteractionUpdates")(function*() {
|
||||
return yield* pipe(
|
||||
Stream.fromEventListener(E.VARIATION_CHOICE_FORM, "change"),
|
||||
Stream.tap(onFormChange),
|
||||
Stream.runDrain,
|
||||
);
|
||||
});
|
||||
|
||||
const onDetailButtonClick = Effect.fnUntraced(function*(evt: Event) {
|
||||
const { Details } = yield* ProductPageElements;
|
||||
// Empêche la pollution de l'historique de navigation
|
||||
evt.preventDefault();
|
||||
|
||||
// La cible est connue.
|
||||
const target = evt.target as HTMLButtonElement;
|
||||
// Récupère le contenu correspondant.
|
||||
const linkedSection = yield* pipe(
|
||||
Option.fromNullishOr(target.getAttribute(ATTRIBUT_ARIA_CONTROLS)),
|
||||
Option.flatMap((contentId: string) => HashMap.get(Details, contentId)),
|
||||
);
|
||||
// Sauvegarde l'état d'ouverture de la Section avant de toutes les fermer.
|
||||
const wasCurrentSection: boolean = target.getAttribute(ATTRIBUT_ARIA_EXPANDED) === "true";
|
||||
|
||||
// Replie toutes les Sections.
|
||||
yield* toggleAllDetails();
|
||||
|
||||
// Ne fais rien de plus si l'onglet sélectionné était le courant
|
||||
if (wasCurrentSection === true) {
|
||||
return yield* Effect.void;
|
||||
}
|
||||
|
||||
// Ouvre le nouvel onglet sélectionné
|
||||
target.toggleAttribute(ATTRIBUT_ARIA_EXPANDED, true);
|
||||
linkedSection.content.toggleAttribute(ATTRIBUT_HIDDEN, false);
|
||||
|
||||
return yield* Effect.void;
|
||||
});
|
||||
|
||||
const initDetailInteractions = Effect.fn("initDetailInteractions")(function*() {
|
||||
const PageElements = yield* ProductPageElements;
|
||||
|
||||
return yield* pipe(
|
||||
FxArray.map(
|
||||
PageElements.DetailsButtons,
|
||||
(button: HTMLButtonElement) => pipe(Stream.fromEventListener(button, "click"), Stream.tap(onDetailButtonClick)),
|
||||
),
|
||||
Stream.mergeAll({ concurrency: "unbounded" }),
|
||||
Stream.runDrain,
|
||||
);
|
||||
});
|
||||
|
||||
const getAttributesFromDom = (): ReadonlyArray<WCStoreCartAddItemArgsItems> => {
|
||||
const selectElements = epipe(
|
||||
document.querySelectorAll<HTMLSelectElement>(".selecteur-produit select"),
|
||||
Array.from<HTMLSelectElement>,
|
||||
);
|
||||
if (selectElements.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const attributes = selectElements.map((select: HTMLSelectElement) => ({
|
||||
attribute: select.id,
|
||||
value: select.value,
|
||||
}));
|
||||
|
||||
return attributes;
|
||||
};
|
||||
// E.BOUTON_AJOUT_PANIER.textContent = "Add to cart";
|
||||
// })
|
||||
// .finally((): void => {
|
||||
// // Désactive l'animation de chargement et rend le Bouton de nouveau cliquable
|
||||
// E.BOUTON_AJOUT_PANIER.removeAttribute(ATTRIBUT_CHARGEMENT);
|
||||
// E.BOUTON_AJOUT_PANIER.removeAttribute(ATTRIBUT_DESACTIVE);
|
||||
// })
|
||||
// .run();
|
||||
// };
|
||||
|
||||
document.addEventListener("DOMContentLoaded", (): void => {
|
||||
ProductPageRuntime.runFork(pipe(initAddToCartButton(), Effect.tapCause(Console.error)));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue