From 498ae877a1178b17c13bc037401bc6bf4939dbc7 Mon Sep 17 00:00:00 2001 From: gcch Date: Wed, 29 Apr 2026 10:45:49 +0200 Subject: [PATCH] =?UTF-8?q?corv=C3=A9e(lint)=20applique=20des=20suggestion?= =?UTF-8?q?s=20de=20lint=20Oxlint?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/scripts-effect/lib/api.ts | 61 +++++++++++++------ .../src/scripts-effect/lib/dom.ts | 2 +- .../src/scripts-effect/lib/elements.ts | 9 +++ .../src/scripts-effect/lib/fetch.ts | 18 +++--- .../src/scripts/page-produit/service-dom.ts | 10 +-- .../scripts/page-produit/service-messages.ts | 2 +- 6 files changed, 65 insertions(+), 37 deletions(-) create mode 100644 web/app/themes/haiku-atelier-2024/src/scripts-effect/lib/elements.ts diff --git a/web/app/themes/haiku-atelier-2024/src/scripts-effect/lib/api.ts b/web/app/themes/haiku-atelier-2024/src/scripts-effect/lib/api.ts index 06995c4c..dc58274a 100644 --- a/web/app/themes/haiku-atelier-2024/src/scripts-effect/lib/api.ts +++ b/web/app/themes/haiku-atelier-2024/src/scripts-effect/lib/api.ts @@ -1,14 +1,16 @@ import { Console, Context, Effect, Layer, Match, pipe, Schedule, Schema, SchemaIssue } from "effect"; -import { SchemaError } from "effect/Schema"; +import type { SchemaError } from "effect/Schema"; +import type { + HttpClientError, +} from "effect/unstable/http"; import { FetchHttpClient, HttpClient, - HttpClientError, HttpClientRequest, HttpClientResponse, } from "effect/unstable/http"; import { HttpClientErrorSchema } from "effect/unstable/http/HttpClientError"; -import type { CartProduct } from "../schemas/api.ts"; +import type { CartProduct, GetProducts } from "../schemas/api.ts"; import { WooCommerceCart } from "../schemas/cart.ts"; /** Le nombre maximal d'essais pour une Requête. */ @@ -16,20 +18,21 @@ const MAX_RETRIES = 3; /** Le temps d'attente avant de réessayer une Requête. */ const RETRY_WAIT_TIME = "1 seconds"; +type APIError = APIRequestError | APIResponseError; + +type APIResponse = T | WooCommerceError; + /** Décrit une Erreur survenue au traitement d'une `Request`. */ class APIRequestError extends Schema.TaggedErrorClass()("APIRequestError", { - message: Schema.String, cause: Schema.Union([Schema.Defect, HttpClientErrorSchema]), + message: Schema.String, }) {} /** Décrit une Erreur survenue au traitement d'une `Response`. */ class APIResponseError extends Schema.TaggedErrorClass()("APIResponseError", { - message: Schema.String, cause: Schema.Union([Schema.Defect, HttpClientErrorSchema]), + message: Schema.String, }) {} - -type APIError = APIRequestError | APIResponseError; - class WooCommerceErrorBody extends Schema.Class("WooCommerceErrorBody")({ code: Schema.String, data: Schema.Struct({ @@ -37,13 +40,12 @@ class WooCommerceErrorBody extends Schema.Class("WooCommer }), message: Schema.String, }) {} + class WooCommerceError extends Schema.Class("WooCommerceError")({ body: WooCommerceErrorBody, status: Schema.Number, }) {} -type APIResponse = T | WooCommerceError; - /** Client `fetch` contenant les options et en-têtes de Requêtes pré-renseignées. */ const APIFetchClient = FetchHttpClient.layer.pipe( Layer.provide( @@ -81,22 +83,22 @@ class APIClient extends Context.Service()("haikuatelier.fr/APIClient" const matchAPIError = (error: HttpClientError.HttpClientError | SchemaError): APIError => { if (error._tag === "SchemaError") { return new APIRequestError({ - message: `Erreur lors du parsage du corps de la Requête :${SchemaIssue.makeFormatterDefault()(error.issue)}`, cause: error, + message: `Erreur lors du parsage du corps de la Requête :${SchemaIssue.makeFormatterDefault()(error.issue)}`, }); } else { return Match.typeTags()({ + DecodeError: cause => new APIResponseError({ cause, message: "Le corps de la Réponse ne peut être lu" }), + EmptyBodyError: cause => new APIResponseError({ cause, message: "Un corps vide ne peut être lu" }), + EncodeError: cause => new APIRequestError({ cause, message: "Le corps de la Requête ne peut être lu" }), + InvalidUrlError: cause => new APIRequestError({ cause, message: "L'URL de la Requête n'est pas valide" }), + StatusCodeError: cause => + new APIResponseError({ cause, message: "Le code HTTP de la Réponse correspond à un échec" }), TransportError: (cause): APIError => new APIRequestError({ cause, message: "Un problème réseau empêche l'exécution de la Requête", }), - EncodeError: cause => new APIRequestError({ cause, message: "Le corps de la Requête ne peut être lu" }), - InvalidUrlError: cause => new APIRequestError({ cause, message: "L'URL de la Requête n'est pas valide" }), - StatusCodeError: cause => - new APIResponseError({ cause, message: "Le code HTTP de la Réponse correspond à un échec" }), - DecodeError: cause => new APIResponseError({ cause, message: "Le corps de la Réponse ne peut être lu" }), - EmptyBodyError: cause => new APIResponseError({ cause, message: "Un corps vide ne peut être lu" }), })(error.reason); } }; @@ -109,7 +111,7 @@ class APIClient extends Context.Service()("haikuatelier.fr/APIClient" return yield* Effect.succeed(error); }); - const AddProductToCart = Effect.fn("AppClient.AddProductToCart")( + const AddProductToCart = Effect.fn("APIClient.AddProductToCart")( function*(nonce: string, productToAdd: CartProduct): Effect.fn.Return, APIError> { const request = pipe( HttpClientRequest.post(`/wp-json/wc/store/cart/add-item`), @@ -132,7 +134,28 @@ class APIClient extends Context.Service()("haikuatelier.fr/APIClient" }, ); - return { AddProductToCart }; + const GetProducts = Effect.fn("APIClient.GetProducts")( + function*(nonce: string, authString: string, queryParams: GetProducts) { + const request = pipe( + HttpClientRequest.get(`/wp-json/wc/store/products`), + HttpClientRequest.setHeader("Nonce", nonce), + HttpClientRequest.bearerToken(authString), + // Le corps de la Requête a été validée en amont, on peut utiliser Unsafe. + HttpClientRequest.setUrlParams(queryParams), + ); + + const response = yield* pipe( + haikuHTTPClient.execute(request), + Effect.flatMap(HttpClientResponse.schemaBodyJson(Schema.Unknown)), + Effect.mapError(error => matchAPIError(error)), + Effect.tapError(error => printErrorAsSuccinctMessage(error)), + ); + + return response; + }, + ); + + return { AddProductToCart, GetProducts }; }), }) { static readonly Live = Layer.effect(this, this.make).pipe(Layer.provide(APIFetchClient)); diff --git a/web/app/themes/haiku-atelier-2024/src/scripts-effect/lib/dom.ts b/web/app/themes/haiku-atelier-2024/src/scripts-effect/lib/dom.ts index d7f48c4a..6bcb6e78 100644 --- a/web/app/themes/haiku-atelier-2024/src/scripts-effect/lib/dom.ts +++ b/web/app/themes/haiku-atelier-2024/src/scripts-effect/lib/dom.ts @@ -31,7 +31,7 @@ const getAllSelectorFromParent = pipe( parent.querySelectorAll(selector), // Convertis NodeListOf en Array. - (xs: NodeListOf) => Array.from(xs), + (xs: NodeListOf) => [...xs], (xs: Array) => Option.liftPredicate(FxArray.isReadonlyArrayNonEmpty)(xs), ); diff --git a/web/app/themes/haiku-atelier-2024/src/scripts-effect/lib/elements.ts b/web/app/themes/haiku-atelier-2024/src/scripts-effect/lib/elements.ts new file mode 100644 index 00000000..b2abe17a --- /dev/null +++ b/web/app/themes/haiku-atelier-2024/src/scripts-effect/lib/elements.ts @@ -0,0 +1,9 @@ +import { Effect } from "effect"; +import { ATTRIBUT_CHARGEMENT, ATTRIBUT_DESACTIVE } from "../../scripts/constantes/dom.ts"; + +const setLoadingState = Effect.fn("setLoadingState")(function*(element: HTMLElement, isLoading: boolean) { + element.toggleAttribute(ATTRIBUT_DESACTIVE, isLoading); + element.toggleAttribute(ATTRIBUT_CHARGEMENT, isLoading); +}); + +export { setLoadingState }; diff --git a/web/app/themes/haiku-atelier-2024/src/scripts-effect/lib/fetch.ts b/web/app/themes/haiku-atelier-2024/src/scripts-effect/lib/fetch.ts index f97ac7e5..6c6b70df 100644 --- a/web/app/themes/haiku-atelier-2024/src/scripts-effect/lib/fetch.ts +++ b/web/app/themes/haiku-atelier-2024/src/scripts-effect/lib/fetch.ts @@ -1,18 +1,18 @@ import { Context, Effect, flow, Layer, pipe, Schedule, Schema } from "effect"; import { FetchHttpClient, HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http"; -import { HttpClientError } from "effect/unstable/http/HttpClientError"; - -class Todo extends Schema.Class("Todo")({ - userId: Schema.Number, - id: Schema.Number, - title: Schema.String, - completed: Schema.Boolean, -}) {} +import type { HttpClientError } from "effect/unstable/http/HttpClientError"; class FetchClientError extends Schema.TaggedErrorClass()("FetchClientError", { cause: Schema.Defect, }) {} +class Todo extends Schema.Class("Todo")({ + completed: Schema.Boolean, + id: Schema.Number, + title: Schema.String, + userId: Schema.Number, +}) {} + class FetchClientExample extends Context.Service, FetchClientError>; createTodo(todo: Omit): Effect.Effect; @@ -75,8 +75,8 @@ class FetchClientExample extends Context.Service()( ); }); - const setLoadingState = Effect.fn("setLoadingState")(function*(element: HTMLElement, isLoading: boolean) { - element.toggleAttribute(ATTRIBUT_DESACTIVE, isLoading); - element.toggleAttribute(ATTRIBUT_CHARGEMENT, isLoading); - }); - const detailButtonClickHandler = Effect.fn("detailButtonClickHandler")( function*(evt: Event) { // Empêche la pollution de l'historique de navigation @@ -232,7 +228,7 @@ class ProductPageDOM extends Context.Service()( // Désactive les interactions le temps de la requête. yield* setLoadingState(AddToCartButton, true); yield* SubscriptionRef.set(AddToCartButtonText, "Adding Product..."); - // lanceAnimationCycleLoading(AddToCartButton, 500); + // LanceAnimationCycleLoading(AddToCartButton, 500); // Exécute la Requête auprès du backend. const newCart = yield* API.AddProductToCart(PageStates.nonce, requestBody); diff --git a/web/app/themes/haiku-atelier-2024/src/scripts/page-produit/service-messages.ts b/web/app/themes/haiku-atelier-2024/src/scripts/page-produit/service-messages.ts index aa110e38..3663a91a 100644 --- a/web/app/themes/haiku-atelier-2024/src/scripts/page-produit/service-messages.ts +++ b/web/app/themes/haiku-atelier-2024/src/scripts/page-produit/service-messages.ts @@ -7,7 +7,7 @@ class ProductPageMessages extends Context.Service()("haikua const { AddToCartButton } = yield* ProductPageElements; const AddToCartButtonText = yield* SubscriptionRef.make("Add to cart"); - // const AddToCartErrorText = yield* SubscriptionRef.make>(Option.none()); + // Const AddToCartErrorText = yield* SubscriptionRef.make>(Option.none()); const initAddToCartButtonUpdates = Effect.fn("initAddToCartButtonUpdates")(function*() { return yield* pipe(