ref: refactorise les scripts de la page Produit
This commit is contained in:
parent
2013b4e1cc
commit
89d0c80736
9 changed files with 301 additions and 117 deletions
|
|
@ -0,0 +1,50 @@
|
|||
import { Context, Effect, Layer, Schema } from "effect";
|
||||
import { ENTETE_WC_NONCE, ROUTE_API_AJOUTE_ARTICLE_PANIER } from "../scripts/constantes/api.ts";
|
||||
import { CartProduct } from "./schemas/api.ts";
|
||||
|
||||
const REQUEST_TIMEOUT = 5_000;
|
||||
|
||||
class APIError extends Schema.TaggedErrorClass<APIError>()("APIError", {
|
||||
cause: Schema.Defect,
|
||||
}) {}
|
||||
|
||||
class WooCommerceAPI extends Context.Service<
|
||||
WooCommerceAPI,
|
||||
{
|
||||
AddProductToCart: (nonce: string, requestBody: CartProduct) => Effect.Effect<Response, APIError>;
|
||||
}
|
||||
>()("haikuatelier.fr/WooCommerceAPI") {
|
||||
static readonly layer = Layer.effect(
|
||||
WooCommerceAPI,
|
||||
// oxlint-disable-next-line require-yield
|
||||
Effect.gen(function*() {
|
||||
const AddProductToCart = Effect.fn("AddProductToCart")(function*(nonce: string, product: CartProduct) {
|
||||
const response = yield* Effect.tryPromise({
|
||||
catch: error => new APIError({ cause: error }),
|
||||
try: async () =>
|
||||
fetch(ROUTE_API_AJOUTE_ARTICLE_PANIER, {
|
||||
// Convertis en chaîne de caractères (équivalent à JSON.stringify).
|
||||
body: Schema.encodeSync(Schema.fromJsonString(CartProduct))(product),
|
||||
credentials: "same-origin",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
[ENTETE_WC_NONCE]: nonce,
|
||||
},
|
||||
method: "POST",
|
||||
mode: "same-origin",
|
||||
signal: AbortSignal.timeout(REQUEST_TIMEOUT),
|
||||
}),
|
||||
});
|
||||
|
||||
return response;
|
||||
});
|
||||
|
||||
return WooCommerceAPI.of({
|
||||
AddProductToCart,
|
||||
});
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export { WooCommerceAPI };
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
import { Schema } from "effect";
|
||||
import { ProductAttribute } from "./product.ts";
|
||||
import { ProductId, ProductQuantity, ProductVariationAttribute } from "./product.ts";
|
||||
|
||||
class AddProductToCart extends Schema.Class<AddProductToCart>("AddProductToCart")({
|
||||
id: Schema.Int,
|
||||
quantity: Schema.Int.check(Schema.isGreaterThan(0)),
|
||||
variation: Schema.Array(ProductAttribute),
|
||||
class CartProduct extends Schema.Class<CartProduct>("CartProduct")({
|
||||
id: ProductId,
|
||||
quantity: ProductQuantity,
|
||||
variation: Schema.Array(ProductVariationAttribute),
|
||||
}) {}
|
||||
|
||||
export { AddProductToCart };
|
||||
export { CartProduct };
|
||||
|
|
|
|||
|
|
@ -1,7 +1,49 @@
|
|||
// oxlint-disable no-magic-numbers -- Pas besoin ici.
|
||||
import { Schema } from "effect";
|
||||
import { Effect, Option, pipe, Schema, SchemaIssue, SchemaTransformation } from "effect";
|
||||
import type { SchemaError } from "effect/Schema";
|
||||
|
||||
/** Représente l'identifiant numérique unique d'un Produit. */
|
||||
const ProductId = Schema.Int.pipe(Schema.brand("ProductId")).check(Schema.isGreaterThan(0));
|
||||
|
||||
/** Représente l'identifiant numérique unique d'un Attribut. */
|
||||
const AttributeId = Schema.Int.pipe(Schema.brand("AttributeId")).check(Schema.isGreaterThan(0));
|
||||
|
||||
/** Réprésente une quantité (nombre d'unités) de Produit. */
|
||||
const ProductQuantity = Schema.Int.pipe(Schema.brand("ProductQuantity")).check(Schema.isGreaterThanOrEqualTo(0));
|
||||
/** Schéma transformant une chaîne de caractères en `ProductId` avec validation. */
|
||||
const ProductQuantityFromString = Schema.String.pipe(
|
||||
Schema.decodeTo(
|
||||
ProductQuantity,
|
||||
SchemaTransformation.transformOrFail({
|
||||
decode: (value: string) =>
|
||||
pipe(
|
||||
globalThis.Number(value),
|
||||
(number: number) => ProductQuantity.makeEffect(number),
|
||||
Effect.mapError((error: SchemaError) => new SchemaIssue.InvalidValue(Option.some(value), { cause: error })),
|
||||
),
|
||||
encode: (value: number) => Effect.succeed(String(value)),
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
class AttributeValue extends Schema.Class<AttributeValue>("AttributeValue")({
|
||||
/** L'identifiant numérique de la Valeur de l'Attribut. */
|
||||
id: AttributeId,
|
||||
/** Le nom (texte affiché) de la Valeur de l'Attribut. */
|
||||
name: Schema.String,
|
||||
/** L'identifiant alphanumérique _(slug)_ de la Valeur de l'Attribut. */
|
||||
slug: Schema.String,
|
||||
}) {}
|
||||
|
||||
class ProductAttribute extends Schema.Class<ProductAttribute>("ProductAttribute")({
|
||||
/** Le nom de l'Attribut. */
|
||||
name: Schema.String,
|
||||
options: Schema.Array(AttributeValue),
|
||||
/** L'identifiant _(slug)_ de l'Attribut. */
|
||||
slug: Schema.String,
|
||||
}) {}
|
||||
|
||||
class ProductVariationAttribute extends Schema.Class<ProductVariationAttribute>("ProductVariationAttribute")({
|
||||
/** L'identifiant _(slug)_ de l'Attribut. */
|
||||
attribute: Schema.String,
|
||||
/** La valeur de l'attribut. */
|
||||
|
|
@ -10,11 +52,32 @@ class ProductAttribute extends Schema.Class<ProductAttribute>("ProductAttribute"
|
|||
|
||||
class ProductVariation extends Schema.Class<ProductVariation>("ProductVariation")({
|
||||
/** Les Attributs présents pour cette Variation. */
|
||||
attributes: Schema.Array(ProductAttribute),
|
||||
attributes: Schema.Array(ProductVariationAttribute),
|
||||
/** L'identifiant numérique unique de la Variation. */
|
||||
id: Schema.Int.check(Schema.isGreaterThan(0)),
|
||||
id: ProductId,
|
||||
/** Le prix de la Variation. */
|
||||
price: Schema.NonEmptyString,
|
||||
price: ProductQuantityFromString,
|
||||
}) {}
|
||||
|
||||
export { ProductAttribute, ProductVariation };
|
||||
class Product extends Schema.Class<Product>("Product")({
|
||||
/** Les Attributs applicables au Produit (en cas de Produit simple). */
|
||||
attributes: Schema.Union([Schema.Record(Schema.String, ProductAttribute), Schema.Array(ProductAttribute)]),
|
||||
/** L'identifiant numérique unique du Produit. */
|
||||
id: ProductId,
|
||||
/** Le prix du Produit. */
|
||||
price: ProductQuantityFromString,
|
||||
/** Les Variations existantes du Produit (en cas de Produit variable). */
|
||||
variations: Schema.Array(ProductVariation),
|
||||
}) {}
|
||||
|
||||
export {
|
||||
AttributeId,
|
||||
AttributeValue,
|
||||
Product,
|
||||
ProductAttribute,
|
||||
ProductId,
|
||||
ProductQuantity,
|
||||
ProductQuantityFromString,
|
||||
ProductVariation,
|
||||
ProductVariationAttribute,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
import { Schema, SchemaIssue } from "effect";
|
||||
import { NoSuchElementError } from "effect/Cause";
|
||||
import { SchemaError } from "effect/Schema";
|
||||
|
||||
class IncoherentDOMError extends Schema.TaggedErrorClass<IncoherentDOMError>()("IncoherentDOMError", {
|
||||
cause: Schema.String,
|
||||
}) {
|
||||
static readonly fromSchemaError = (error: SchemaError): IncoherentDOMError =>
|
||||
new IncoherentDOMError({
|
||||
cause: SchemaIssue.makeFormatterDefault()(error.issue),
|
||||
});
|
||||
static readonly fromNoSuchElementError = (error: NoSuchElementError): IncoherentDOMError =>
|
||||
new IncoherentDOMError({
|
||||
cause: `Impossible de trouver l'Element suivant dans le DOM : ${error.message}.`,
|
||||
});
|
||||
}
|
||||
|
||||
export { IncoherentDOMError };
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import { Console, Layer, ManagedRuntime, pipe } from "effect";
|
||||
import { WooCommerceAPI } from "../../scripts-effect/api-service.ts";
|
||||
import ProductPageDOM from "./service-dom.ts";
|
||||
import ProductPageElements from "./service-elements.ts";
|
||||
|
||||
|
|
@ -6,6 +7,7 @@ const ProductPageRuntime = ManagedRuntime.make(
|
|||
pipe(
|
||||
ProductPageDOM.layer,
|
||||
Layer.provide(ProductPageElements.layer),
|
||||
Layer.provide(WooCommerceAPI.layer),
|
||||
Layer.tapError(error => Console.error("ProductPageRuntime", "Impossible de créer le Layer :", error)),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -14,17 +14,36 @@ import {
|
|||
Stream,
|
||||
} from "effect";
|
||||
import type { NoSuchElementError } from "effect/Cause";
|
||||
import { AddProductToCart } from "../../scripts-effect/schemas/api.ts";
|
||||
import { ProductAttribute, ProductVariation } from "../../scripts-effect/schemas/product.ts";
|
||||
import type { SchemaError } from "effect/Schema";
|
||||
import { WooCommerceAPI } from "../../scripts-effect/api-service.ts";
|
||||
import { AddProductToCart, CartProduct } from "../../scripts-effect/schemas/api.ts";
|
||||
import { Product, ProductVariation, ProductVariationAttribute } from "../../scripts-effect/schemas/product.ts";
|
||||
import {
|
||||
ATTRIBUT_ARIA_CONTROLS,
|
||||
ATTRIBUT_ARIA_EXPANDED,
|
||||
ATTRIBUT_CHARGEMENT,
|
||||
ATTRIBUT_DESACTIVE,
|
||||
ATTRIBUT_HIDDEN,
|
||||
} from "../constantes/dom.ts";
|
||||
import { lanceAnimationCycleLoading } from "../lib/animations.ts";
|
||||
import { IncoherentDOMError } from "./errors.ts";
|
||||
import ProductPageElements from "./service-elements.ts";
|
||||
import type { DetailEnsemble } from "./types.d.ts";
|
||||
|
||||
const PageStatesSchema = Schema.Struct({
|
||||
nonce: Schema.NonEmptyString,
|
||||
product: Product,
|
||||
});
|
||||
|
||||
class InvalidPageStateError extends Schema.TaggedErrorClass<InvalidPageStateError>()("InvalidPageStateError", {
|
||||
cause: Schema.String,
|
||||
}) {
|
||||
static readonly fromSchemaError = (schemaError: SchemaError): InvalidPageStateError =>
|
||||
new InvalidPageStateError({
|
||||
cause: SchemaIssue.makeFormatterDefault()(schemaError.issue),
|
||||
});
|
||||
}
|
||||
|
||||
class ProductPageDOM extends Context.Service<
|
||||
ProductPageDOM,
|
||||
{
|
||||
|
|
@ -48,7 +67,7 @@ class ProductPageDOM extends Context.Service<
|
|||
* Replie toutes les sections de la description du Produit.
|
||||
*/
|
||||
initAddToCartButtonClicks: () => unknown;
|
||||
ProductVariations: ReadonlyArray<ProductVariation>;
|
||||
PageStates: typeof PageStatesSchema.Type;
|
||||
CurrentVariation: Ref.Ref<Option.Option<ProductVariation>>;
|
||||
}
|
||||
>()("haikuatelier.fr/Produit/ProductPageDOM") {
|
||||
|
|
@ -60,11 +79,23 @@ class ProductPageDOM extends Context.Service<
|
|||
Details,
|
||||
DetailsButtons,
|
||||
ProductPrice,
|
||||
ProductRawJson,
|
||||
VariationChoiceForm,
|
||||
VariationSelectors,
|
||||
PageStatesRawJson,
|
||||
} = yield* ProductPageElements;
|
||||
const onFormChangeHandler = Effect.fn("onFormChangeHandler")(function*(evt: Event): Effect.fn.Return<void> {
|
||||
const API = yield* WooCommerceAPI;
|
||||
|
||||
const PageStates = yield* pipe(
|
||||
PageStatesRawJson.textContent,
|
||||
(textContent: string) =>
|
||||
Schema.decodeUnknownEffect(Schema.fromJsonString(PageStatesSchema))(textContent, { errors: "all" }),
|
||||
Effect.mapError(InvalidPageStateError.fromSchemaError),
|
||||
Effect.tapError(Console.error),
|
||||
);
|
||||
|
||||
const CurrentProduct = yield* Ref.make(Option.none<ProductVariation>());
|
||||
|
||||
const onFormChangeHandler = Effect.fn("onFormChangeHandler")(function*(evt: Event) {
|
||||
// La cible ne peut qu'être un Formulaire.
|
||||
const target: HTMLFormElement = evt.target as HTMLFormElement;
|
||||
const isClickAllowed = target.checkValidity() === false;
|
||||
|
|
@ -75,56 +106,57 @@ class ProductPageDOM extends Context.Service<
|
|||
return yield* Effect.void;
|
||||
});
|
||||
|
||||
const toggleAllDetails: () => Effect.Effect<void> = () =>
|
||||
const toggleAllDetails: (shouldOpen: boolean) => Effect.Effect<void> = (shouldOpen: boolean) =>
|
||||
Effect.sync((): void => {
|
||||
console.debug("toggleAllDetails");
|
||||
pipe(
|
||||
// Récupère les Sections sous forme d'Ensembles.
|
||||
[...HashMap.values(Details)],
|
||||
FxArray.forEach((detail: DetailEnsemble) => {
|
||||
detail.button.toggleAttribute(ATTRIBUT_ARIA_EXPANDED, false);
|
||||
detail.content.toggleAttribute(ATTRIBUT_HIDDEN, true);
|
||||
detail.button.toggleAttribute(ATTRIBUT_ARIA_EXPANDED, shouldOpen);
|
||||
detail.content.toggleAttribute(ATTRIBUT_HIDDEN, !shouldOpen);
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
const onDetailButtonClickHandler = Effect.fn("onDetailButtonClickHandler")(function*(
|
||||
evt: Event,
|
||||
): Effect.fn.Return<void, NoSuchElementError> {
|
||||
// Empêche la pollution de l'historique de navigation
|
||||
evt.preventDefault();
|
||||
const onDetailButtonClickHandler = Effect.fn("onDetailButtonClickHandler")(
|
||||
function*(evt: Event) {
|
||||
// Empêche la pollution de l'historique de navigation
|
||||
evt.preventDefault();
|
||||
|
||||
// La cible est connue.
|
||||
const target = evt.target as HTMLButtonElement;
|
||||
// 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)),
|
||||
);
|
||||
// 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)),
|
||||
Effect.fromOption,
|
||||
Effect.mapError(
|
||||
() => new IncoherentDOMError({ cause: "Un bouton de section ne correspond à aucun contenu." }),
|
||||
),
|
||||
);
|
||||
|
||||
// Sauvegarde l'état d'ouverture de la Section avant de toutes les fermer.
|
||||
const wasCurrentSection: boolean = target.getAttribute(ATTRIBUT_ARIA_EXPANDED) === "true";
|
||||
// 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();
|
||||
// Replie toutes les Sections.
|
||||
yield* toggleAllDetails(false);
|
||||
|
||||
// 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);
|
||||
|
||||
// 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 ProductVariations: ReadonlyArray<ProductVariation> = yield* pipe(
|
||||
JSON.parse(ProductRawJson.textContent)?.variations,
|
||||
json => Schema.decodeUnknownEffect(Schema.Array(ProductVariation))(json, { onExcessProperty: "ignore" }),
|
||||
Effect.mapError(error => SchemaIssue.makeFormatterStandardSchemaV1()(error.issue)),
|
||||
Effect.tapCause(Console.error),
|
||||
},
|
||||
Effect.tapError(Console.error),
|
||||
// Ouvre toutes les Sections en cas d'erreur.
|
||||
Effect.catch(() => toggleAllDetails(true)),
|
||||
);
|
||||
|
||||
const getChosenProductAttributesFromDOM = Effect.fn("getChosenProductAttributesFromDOM")(function*() {
|
||||
|
|
@ -133,60 +165,90 @@ class ProductPageDOM extends Context.Service<
|
|||
attribute: select.id,
|
||||
value: select.value,
|
||||
})),
|
||||
variations => Schema.decodeEffect(Schema.Array(ProductAttribute))(variations),
|
||||
Effect.mapError(error => SchemaIssue.makeFormatterDefault()(error.issue)),
|
||||
Effect.tapCause(Console.error),
|
||||
variations =>
|
||||
Schema.decodeEffect(Schema.Array(ProductVariationAttribute))(variations, {
|
||||
errors: "all",
|
||||
onExcessProperty: "error",
|
||||
}),
|
||||
Effect.mapError(IncoherentDOMError.fromSchemaError),
|
||||
Effect.tapError(Console.error),
|
||||
);
|
||||
});
|
||||
|
||||
const CurrentVariation = yield* Ref.make(Option.none<ProductVariation>());
|
||||
|
||||
const onVariationChangeHandler = Effect.fn("onVariationChangeHandler")(function*(): Effect.fn.Return<
|
||||
void,
|
||||
NoSuchElementError | string
|
||||
> {
|
||||
yield* Console.debug("onVariationChangeHandler");
|
||||
const onVariationChangeHandler = Effect.fn("onVariationChangeHandler")(function*() {
|
||||
yield* Console.log("onVariationChangeHandler");
|
||||
// Ne fais rien si le Formulaire n'est pas valide.
|
||||
if (VariationChoiceForm.checkValidity() === false) {
|
||||
yield* Console.debug("onVariationChangeHandler", "Le formulaire est invalide.");
|
||||
return yield* Effect.void;
|
||||
}
|
||||
|
||||
const equivalence = Schema.toEquivalence(Schema.Array(ProductAttribute));
|
||||
const equivalence = Schema.toEquivalence(Schema.Array(ProductVariationAttribute));
|
||||
const chosenProductAttributes = yield* getChosenProductAttributesFromDOM();
|
||||
const chosenVariation: ProductVariation = yield* FxArray.findFirst(
|
||||
ProductVariations,
|
||||
(variation: ProductVariation) => equivalence(variation.attributes, chosenProductAttributes),
|
||||
yield* Console.debug("onVariationChangeHandler", "chosenProductAttributes", chosenProductAttributes);
|
||||
const chosenVariation = yield* pipe(
|
||||
FxArray.findFirst(
|
||||
PageStates.product.variations,
|
||||
variation => equivalence(variation.attributes, chosenProductAttributes),
|
||||
),
|
||||
Effect.fromOption,
|
||||
Effect.mapError(error => new Error("Impossible de trouver la variation demandée.", { cause: error })),
|
||||
);
|
||||
yield* Console.debug("onVariationChangeHandler", "chosenVariation", chosenVariation);
|
||||
|
||||
// Met à jour la valeur de la Variation choisie dans le Service.
|
||||
yield* Ref.set(Option.some(chosenVariation))(CurrentVariation);
|
||||
yield* Ref.set(Option.some(chosenVariation))(CurrentProduct);
|
||||
|
||||
const newPrice = chosenVariation.price;
|
||||
const newPrice = String(chosenVariation.price);
|
||||
ProductPrice.textContent = `${newPrice}€`;
|
||||
|
||||
return yield* Effect.void;
|
||||
}, Effect.tapCause(Console.error));
|
||||
}, Effect.tapError(Console.error));
|
||||
|
||||
const onAddToCartButtonHandler = Effect.fn("onAddToCartButtonHandler")(function*() {
|
||||
const chosenVariation = yield* Ref.getUnsafe(CurrentVariation);
|
||||
const productDetails = yield* Schema.decodeEffect(AddProductToCart)(
|
||||
{
|
||||
id: chosenVariation.id,
|
||||
quantity: 1,
|
||||
variation: chosenVariation.attributes,
|
||||
},
|
||||
{ errors: "all" },
|
||||
/**
|
||||
* Déclenche une ajout du Produit demandé au Panier auprès du Backend.
|
||||
*/
|
||||
const addToCartButtonClickHandler = Effect.fn("addToCartButtonClickHandler")(function*() {
|
||||
yield* Console.log("addToCartButtonClickHandler");
|
||||
|
||||
// Créé le corps de la requête
|
||||
const requestBody: CartProduct = yield* pipe(
|
||||
Ref.get(CurrentProduct),
|
||||
Effect.flatMap(Effect.fromOption),
|
||||
// Pour un Produit simple, le Ref sera vide.
|
||||
Effect.orElseSucceed(() =>
|
||||
ProductVariation.make({
|
||||
attributes: [],
|
||||
id: PageStates.product.id,
|
||||
price: PageStates.product.price,
|
||||
})
|
||||
),
|
||||
Effect.map(({ id, attributes }) =>
|
||||
// Les données ont été validées en amont.
|
||||
Schema.decodeSync(CartProduct)({ id: id, quantity: 1, variation: attributes })
|
||||
),
|
||||
Effect.tap(body => Console.debug("addToCartButtonClickHandler", "requestBody", body)),
|
||||
);
|
||||
|
||||
console.debug(productDetails);
|
||||
// Désactive les interactions le temps de la requête.
|
||||
AddToCartButton.toggleAttribute(ATTRIBUT_DESACTIVE);
|
||||
AddToCartButton.toggleAttribute(ATTRIBUT_CHARGEMENT);
|
||||
lanceAnimationCycleLoading(AddToCartButton, 500);
|
||||
|
||||
const responseBody = yield* pipe(
|
||||
API.AddProductToCart(PageStates.nonce, requestBody),
|
||||
Effect.flatMap((response: Response) => Effect.tryPromise(async () => response.json())),
|
||||
Effect.tap((response: JSONValue) => Console.debug("addToCartButtonClickHandler", "response", response)),
|
||||
);
|
||||
|
||||
return responseBody;
|
||||
});
|
||||
|
||||
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.
|
||||
// S'('y a pas de stock, ne rien faire.
|
||||
if (isProductInStock === false) {
|
||||
return yield* Effect.void;
|
||||
}
|
||||
|
|
@ -199,32 +261,32 @@ class ProductPageDOM extends Context.Service<
|
|||
return yield* Effect.void;
|
||||
});
|
||||
|
||||
const initAddToCartButtonUpdates = Effect.fn("initAddToCartInteractionUpdates")(function*() {
|
||||
return yield* pipe(
|
||||
Stream.fromEventListener(VariationChoiceForm, "change"),
|
||||
Stream.tap(onFormChangeHandler),
|
||||
Stream.runDrain,
|
||||
);
|
||||
});
|
||||
|
||||
const initAddToCartButtonClicks = Effect.fn("initAddToCartButtonClicks")(function*() {
|
||||
return yield* pipe(
|
||||
Stream.fromEventListener(AddToCartButton, "click"),
|
||||
Stream.tap(onAddToCartButtonHandler),
|
||||
Stream.runDrain,
|
||||
);
|
||||
});
|
||||
|
||||
const initPriceUpdatesOnVariationChange = Effect.fn("initPriceUpdatesOnVariationChange")(
|
||||
function*(): Effect.fn.Return<void, NoSuchElementError | string> {
|
||||
const initAddToCartButtonUpdates = Effect.fn("initAddToCartInteractionUpdates")(
|
||||
function*(): Effect.fn.Return<void> {
|
||||
return yield* pipe(
|
||||
Stream.fromEventListener(VariationChoiceForm, "change"),
|
||||
Stream.tap(onVariationChangeHandler),
|
||||
Stream.tap(onFormChangeHandler),
|
||||
Stream.runDrain,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
const initAddToCartButtonClicks = Effect.fn("initAddToCartButtonClicks")(function*() {
|
||||
return yield* pipe(
|
||||
Stream.fromEventListener(AddToCartButton, "click"),
|
||||
Stream.tap(addToCartButtonClickHandler),
|
||||
Stream.runDrain,
|
||||
);
|
||||
});
|
||||
|
||||
const initPriceUpdatesOnVariationChange = Effect.fn("initPriceUpdatesOnVariationChange")(function*() {
|
||||
return yield* pipe(
|
||||
Stream.fromEventListener(VariationChoiceForm, "change"),
|
||||
Stream.tap(onVariationChangeHandler),
|
||||
Stream.runDrain,
|
||||
);
|
||||
});
|
||||
|
||||
const initDetailInteractions = Effect.fn("initDetailInteractions")(function*() {
|
||||
return yield* pipe(
|
||||
// Créé un Stream par Bouton de Section.
|
||||
|
|
@ -239,13 +301,13 @@ class ProductPageDOM extends Context.Service<
|
|||
});
|
||||
|
||||
return ProductPageDOM.of({
|
||||
CurrentVariation,
|
||||
ProductVariations,
|
||||
CurrentVariation: CurrentProduct,
|
||||
initAddToCartButtonClicks,
|
||||
initAddToCartButtonInitialState,
|
||||
initAddToCartButtonUpdates,
|
||||
initDetailInteractions,
|
||||
initPriceUpdatesOnVariationChange,
|
||||
PageStates,
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import {
|
|||
DOM_CONTENUS_ACCORDEON,
|
||||
DOM_PRIX_PRODUIT,
|
||||
} from "../constantes/dom.ts";
|
||||
import { IncoherentDOMError } from "./errors.ts";
|
||||
import type { DetailEnsemble } from "./types.d.ts";
|
||||
|
||||
class ProductPageElements extends Context.Service<
|
||||
|
|
@ -18,8 +19,8 @@ class ProductPageElements extends Context.Service<
|
|||
Details: HashMap.HashMap<string, DetailEnsemble>;
|
||||
DetailsButtons: NonEmptyReadonlyArray<HTMLButtonElement>;
|
||||
DetailsContents: NonEmptyReadonlyArray<HTMLDivElement>;
|
||||
PageStatesRawJson: HTMLScriptElement;
|
||||
ProductPrice: HTMLParagraphElement;
|
||||
ProductRawJson: HTMLScriptElement;
|
||||
VariationChoiceForm: HTMLFormElement;
|
||||
VariationSelectors: ReadonlyArray<HTMLSelectElement>;
|
||||
}
|
||||
|
|
@ -30,8 +31,8 @@ class ProductPageElements extends Context.Service<
|
|||
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 PageStatesRawJson = yield* getFirstSelectorFromDocument<HTMLScriptElement>("#page-states");
|
||||
const ProductPrice = yield* getFirstSelectorFromDocument<HTMLParagraphElement>(DOM_PRIX_PRODUIT);
|
||||
const ProductRawJson = yield* getFirstSelectorFromDocument<HTMLScriptElement>("#product-json");
|
||||
const VariationChoiceForm = yield* getFirstSelectorFromDocument<HTMLFormElement>("#variation-choice");
|
||||
const VariationSelectors = yield* pipe(
|
||||
getAllSelectorFromDocument<HTMLSelectElement>(".selecteur-produit select"),
|
||||
|
|
@ -58,12 +59,12 @@ class ProductPageElements extends Context.Service<
|
|||
Details,
|
||||
DetailsButtons,
|
||||
DetailsContents,
|
||||
PageStatesRawJson,
|
||||
ProductPrice,
|
||||
ProductRawJson,
|
||||
VariationChoiceForm,
|
||||
VariationSelectors,
|
||||
});
|
||||
}),
|
||||
}).pipe(Effect.mapError(IncoherentDOMError.fromNoSuchElementError)),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,4 +3,5 @@ type DetailEnsemble = {
|
|||
button: HTMLButtonElement;
|
||||
content: HTMLDivElement;
|
||||
};
|
||||
|
||||
export { DetailEnsemble };
|
||||
|
|
|
|||
|
|
@ -4,30 +4,17 @@ import { Console, Effect } from "effect";
|
|||
import ProductPageRuntime from "./page-produit/runtime.ts";
|
||||
import ProductPageDOM from "./page-produit/service-dom.ts";
|
||||
|
||||
/** États utiles pour les scripts de la page. */
|
||||
type EtatsPage = {
|
||||
/** L'ID en base de données du Produit. */
|
||||
idProduit: number;
|
||||
/** Un nonce pour l'authentification de requêtes API vers le backend WooCommerce. */
|
||||
nonce: string;
|
||||
};
|
||||
|
||||
// @ts-expect-error -- États injectés par le modèle PHP
|
||||
const ETATS_PAGE: EtatsPage = _etats;
|
||||
|
||||
document.addEventListener("DOMContentLoaded", (): void => {
|
||||
console.debug("oups");
|
||||
Effect.gen(function* () {
|
||||
Effect.gen(function*() {
|
||||
const DOM = yield* ProductPageDOM;
|
||||
console.debug("oups");
|
||||
|
||||
const effects = Effect.all(
|
||||
[
|
||||
DOM.initAddToCartButtonInitialState(),
|
||||
DOM.initAddToCartButtonUpdates(),
|
||||
DOM.initAddToCartButtonClicks(),
|
||||
DOM.initDetailInteractions(),
|
||||
DOM.initPriceUpdatesOnVariationChange(),
|
||||
DOM.initAddToCartButtonClicks(),
|
||||
],
|
||||
{
|
||||
concurrency: "unbounded",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue