2026-04-06

This commit is contained in:
gcch 2026-04-06 14:08:45 +02:00
commit 7baeb28fc1
36 changed files with 390 additions and 415 deletions

View file

@ -2,7 +2,9 @@
"language_servers": [ "language_servers": [
"!biome", "!biome",
"!deno", "!deno",
"!eslint",
"!intelephense", "!intelephense",
"!oxfmt",
"!prettier", "!prettier",
"!tailwindcss-language-server", "!tailwindcss-language-server",
"!vtsls", "!vtsls",

View file

@ -17,7 +17,7 @@
"devDependencies": { "devDependencies": {
"@effect/language-service": "^0.84.3", "@effect/language-service": "^0.84.3",
"@gcch/configuration-eslint": "git+https://git.gcch.fr/gcch/configuration-eslint#62ee424274", "@gcch/configuration-eslint": "git+https://git.gcch.fr/gcch/configuration-eslint#62ee424274",
"@gcch/configuration-oxlint": "git+https://git.gcch.fr/gcch/configuration-oxlint#3e49f5e2fb", "@gcch/configuration-oxlint": "git+https://git.gcch.fr/gcch/configuration-oxlint#bedd1fa23aff",
"@gcch/configuration-prettier": "git+https://git.gcch.fr/gcch/configuration-prettier#8de937e801", "@gcch/configuration-prettier": "git+https://git.gcch.fr/gcch/configuration-prettier#8de937e801",
"@playwright/test": "^1.59.1", "@playwright/test": "^1.59.1",
"@sentry/core": "^10.47.0", "@sentry/core": "^10.47.0",
@ -26,7 +26,7 @@
"@vitejs/plugin-legacy": "^8.0.1", "@vitejs/plugin-legacy": "^8.0.1",
"better-typescript-lib": "^2.12.0", "better-typescript-lib": "^2.12.0",
"browserslist": "^4.28.2", "browserslist": "^4.28.2",
"caniuse-lite": "^1.0.30001785", "caniuse-lite": "^1.0.30001786",
"eslint": "^10.2.0", "eslint": "^10.2.0",
"eslint-plugin-functional": "^9.0.4", "eslint-plugin-functional": "^9.0.4",
"eslint-plugin-jsx-a11y": "^6.10.2", "eslint-plugin-jsx-a11y": "^6.10.2",
@ -44,7 +44,7 @@
"playwright": "^1.59.1", "playwright": "^1.59.1",
"prettier": "^3.8.1", "prettier": "^3.8.1",
"prettier-plugin-pkg": "^0.22.1", "prettier-plugin-pkg": "^0.22.1",
"prettier-plugin-sh": "^0.18.0", "prettier-plugin-sh": "^0.18.1",
"sass-embedded": "^1.99.0", "sass-embedded": "^1.99.0",
"stylelint": "^17.6.0", "stylelint": "^17.6.0",
"stylelint-config-clean-order": "^8.0.1", "stylelint-config-clean-order": "^8.0.1",
@ -54,7 +54,7 @@
"stylelint-plugin-logical-css": "^2.1.0", "stylelint-plugin-logical-css": "^2.1.0",
"typescript": "6.0.2", "typescript": "6.0.2",
"typescript-eslint": "^8.58.0", "typescript-eslint": "^8.58.0",
"vite": "^8.0.3", "vite": "^8.0.5",
"vite-tsconfig-paths": "^6.1.1", "vite-tsconfig-paths": "^6.1.1",
}, },
}, },
@ -296,7 +296,7 @@
"@gcch/configuration-eslint": ["@gcch/configuration-eslint@git+https://git.gcch.fr/gcch/configuration-eslint#62ee424274f0bfebd5135a728960644f4b1cdcb8", { "dependencies": { "@eslint/js": "^10.0.1", "astro-eslint-parser": "^1.3.0", "eslint": "^10.0.3", "eslint-plugin-astro": "^1.6.0", "eslint-plugin-functional": "^9.0.4", "eslint-plugin-jsdoc": "^62.8.0", "eslint-plugin-jsx-a11y": "^6.10.2", "eslint-plugin-perfectionist": "^5.6.0", "eslint-plugin-sonarjs": "^4.0.2", "eslint-plugin-unicorn": "^63.0.0", "globals": "^17.4.0", "typescript-eslint": "^8.57.0" }, "peerDependencies": { "eslint": "^10.0.3", "typescript": "^6.0.1-rc" } }, "62ee424274f0bfebd5135a728960644f4b1cdcb8"], "@gcch/configuration-eslint": ["@gcch/configuration-eslint@git+https://git.gcch.fr/gcch/configuration-eslint#62ee424274f0bfebd5135a728960644f4b1cdcb8", { "dependencies": { "@eslint/js": "^10.0.1", "astro-eslint-parser": "^1.3.0", "eslint": "^10.0.3", "eslint-plugin-astro": "^1.6.0", "eslint-plugin-functional": "^9.0.4", "eslint-plugin-jsdoc": "^62.8.0", "eslint-plugin-jsx-a11y": "^6.10.2", "eslint-plugin-perfectionist": "^5.6.0", "eslint-plugin-sonarjs": "^4.0.2", "eslint-plugin-unicorn": "^63.0.0", "globals": "^17.4.0", "typescript-eslint": "^8.57.0" }, "peerDependencies": { "eslint": "^10.0.3", "typescript": "^6.0.1-rc" } }, "62ee424274f0bfebd5135a728960644f4b1cdcb8"],
"@gcch/configuration-oxlint": ["@gcch/configuration-oxlint@git+https://git.gcch.fr/gcch/configuration-oxlint#3e49f5e2fbfde01ad3d75acbff0da8023d5a3802", { "dependencies": { "eslint-plugin-astro": "^1.6.0", "eslint-plugin-functional": "^9.0.4", "eslint-plugin-jsx-a11y": "^6.10.2", "eslint-plugin-perfectionist": "^5.8.0", "eslint-plugin-sonarjs": "^4.0.2", "globals": "^17.4.0", "oxlint": "^1.58.0", "oxlint-tsgolint": "^0.19.0" }, "peerDependencies": { "eslint-plugin-astro": "^1.6.0", "eslint-plugin-functional": "^9.0.4", "eslint-plugin-jsx-a11y": "^6.10.2", "eslint-plugin-perfectionist": "^5.8.0", "eslint-plugin-sonarjs": "^4.0.2", "oxlint": "^1.58.0", "oxlint-tsgolint": "^0.19.0", "typescript": "^6.0.2" } }, "3e49f5e2fbfde01ad3d75acbff0da8023d5a3802"], "@gcch/configuration-oxlint": ["@gcch/configuration-oxlint@git+https://git.gcch.fr/gcch/configuration-oxlint#bedd1fa23affdb4c8ac4d3b39c4cc095791a3c05", { "dependencies": { "eslint-plugin-astro": "^1.6.0", "eslint-plugin-functional": "^9.0.4", "eslint-plugin-jsx-a11y": "^6.10.2", "eslint-plugin-perfectionist": "^5.8.0", "eslint-plugin-sonarjs": "^4.0.2", "globals": "^17.4.0", "oxlint": "^1.58.0", "oxlint-tsgolint": "^0.19.0" }, "peerDependencies": { "eslint-plugin-astro": "^1.6.0", "eslint-plugin-functional": "^9.0.4", "eslint-plugin-jsx-a11y": "^6.10.2", "eslint-plugin-perfectionist": "^5.8.0", "eslint-plugin-sonarjs": "^4.0.2", "oxlint": "^1.58.0", "oxlint-tsgolint": "^0.19.0", "typescript": "^6.0.2" } }, "bedd1fa23affdb4c8ac4d3b39c4cc095791a3c05"],
"@gcch/configuration-prettier": ["@gcch/configuration-prettier@git+https://git.gcch.fr/gcch/configuration-prettier#8de937e801bd44784ac91e0ff6e038d838f7eea1", { "dependencies": { "prettier": "^3.8.1", "prettier-plugin-curly": "^0.4.1", "prettier-plugin-ini": "^1.3.0", "prettier-plugin-jsdoc": "^1.8.0", "prettier-plugin-pkg": "^0.22.0", "prettier-plugin-sh": "^0.18.0", "prettier-plugin-sort-json": "^4.2.0" }, "peerDependencies": { "prettier": "^3.8.1" } }, "8de937e801bd44784ac91e0ff6e038d838f7eea1"], "@gcch/configuration-prettier": ["@gcch/configuration-prettier@git+https://git.gcch.fr/gcch/configuration-prettier#8de937e801bd44784ac91e0ff6e038d838f7eea1", { "dependencies": { "prettier": "^3.8.1", "prettier-plugin-curly": "^0.4.1", "prettier-plugin-ini": "^1.3.0", "prettier-plugin-jsdoc": "^1.8.0", "prettier-plugin-pkg": "^0.22.0", "prettier-plugin-sh": "^0.18.0", "prettier-plugin-sort-json": "^4.2.0" }, "peerDependencies": { "prettier": "^3.8.1" } }, "8de937e801bd44784ac91e0ff6e038d838f7eea1"],
@ -510,7 +510,7 @@
"@playwright/test": ["@playwright/test@1.59.1", "", { "dependencies": { "playwright": "1.59.1" }, "bin": { "playwright": "cli.js" } }, "sha512-PG6q63nQg5c9rIi4/Z5lR5IVF7yU5MqmKaPOe0HSc0O2cX1fPi96sUQu5j7eo4gKCkB2AnNGoWt7y4/Xx3Kcqg=="], "@playwright/test": ["@playwright/test@1.59.1", "", { "dependencies": { "playwright": "1.59.1" }, "bin": { "playwright": "cli.js" } }, "sha512-PG6q63nQg5c9rIi4/Z5lR5IVF7yU5MqmKaPOe0HSc0O2cX1fPi96sUQu5j7eo4gKCkB2AnNGoWt7y4/Xx3Kcqg=="],
"@reteps/dockerfmt": ["@reteps/dockerfmt@0.3.6", "", {}, "sha512-Tb5wIMvBf/nLejTQ61krK644/CEMB/cpiaIFXqGApfGqO3GwcR3qnI0DbmkFVCl2OyEp8LnLX3EkucoL0+tbFg=="], "@reteps/dockerfmt": ["@reteps/dockerfmt@0.5.2", "", {}, "sha512-Hbr7yen4fP5TxGM54ucXa4o5NwWXatJ6Bd9I8gp0PValYbI4Rug2Gu+rVv7K7o/efQc3F5ctqWJz47rYaa8zBw=="],
"@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-rc.12", "", { "os": "android", "cpu": "arm64" }, "sha512-pv1y2Fv0JybcykuiiD3qBOBdz6RteYojRFY1d+b95WVuzx211CRh+ytI/+9iVyWQ6koTh5dawe4S/yRfOFjgaA=="], "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-rc.12", "", { "os": "android", "cpu": "arm64" }, "sha512-pv1y2Fv0JybcykuiiD3qBOBdz6RteYojRFY1d+b95WVuzx211CRh+ytI/+9iVyWQ6koTh5dawe4S/yRfOFjgaA=="],
@ -726,7 +726,7 @@
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
"caniuse-lite": ["caniuse-lite@1.0.30001785", "", {}, "sha512-blhOL/WNR+Km1RI/LCVAvA73xplXA7ZbjzI4YkMK9pa6T/P3F2GxjNpEkyw5repTw9IvkyrjyHpwjnhZ5FOvYQ=="], "caniuse-lite": ["caniuse-lite@1.0.30001786", "", {}, "sha512-4oxTZEvqmLLrERwxO76yfKM7acZo310U+v4kqexI2TL1DkkUEMT8UijrxxcnVdxR3qkVf5awGRX+4Z6aPHVKrA=="],
"change-case": ["change-case@5.4.4", "", {}, "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w=="], "change-case": ["change-case@5.4.4", "", {}, "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w=="],
@ -1318,7 +1318,7 @@
"prettier-plugin-pkg": ["prettier-plugin-pkg@0.22.1", "", { "peerDependencies": { "prettier": "^3.0.3" } }, "sha512-l9qnxic48hgTXsvXi9yWWWzovpqkEYo/Ljf8Af7zSkDTNVmLwscXW63IEVMJXH94DyloO8YkogeLheJUzJh/ZQ=="], "prettier-plugin-pkg": ["prettier-plugin-pkg@0.22.1", "", { "peerDependencies": { "prettier": "^3.0.3" } }, "sha512-l9qnxic48hgTXsvXi9yWWWzovpqkEYo/Ljf8Af7zSkDTNVmLwscXW63IEVMJXH94DyloO8YkogeLheJUzJh/ZQ=="],
"prettier-plugin-sh": ["prettier-plugin-sh@0.18.0", "", { "dependencies": { "@reteps/dockerfmt": "^0.3.6", "sh-syntax": "^0.5.8" }, "peerDependencies": { "prettier": "^3.6.0" } }, "sha512-cW1XL27FOJQ/qGHOW6IHwdCiNWQsAgK+feA8V6+xUTaH0cD3Mh+tFAtBvEEWvuY6hTDzRV943Fzeii+qMOh7nQ=="], "prettier-plugin-sh": ["prettier-plugin-sh@0.18.1", "", { "dependencies": { "@reteps/dockerfmt": "^0.5.1", "sh-syntax": "^0.5.8" }, "peerDependencies": { "prettier": "^3.6.0" } }, "sha512-uZmU22wBMevjh3rmCatNQqiEer2+5KLa0xYCBX6zQQUQkcNzVL+s6FbPKK6ZSUNUbQk6jMAcQHrYPvuL2W6ihQ=="],
"prettier-plugin-sort-json": ["prettier-plugin-sort-json@4.2.0", "", { "peerDependencies": { "prettier": "^3.0.0" } }, "sha512-jK1w3/7otTvHtv1eoLji2U9mEoOGeyl7QQQ/afLnjht1YtRLSUUk8o0rIIC/HUVXhoGPCFe4SVZbRGYjjUVgvA=="], "prettier-plugin-sort-json": ["prettier-plugin-sort-json@4.2.0", "", { "peerDependencies": { "prettier": "^3.0.0" } }, "sha512-jK1w3/7otTvHtv1eoLji2U9mEoOGeyl7QQQ/afLnjht1YtRLSUUk8o0rIIC/HUVXhoGPCFe4SVZbRGYjjUVgvA=="],
@ -1588,7 +1588,7 @@
"varint": ["varint@6.0.0", "", {}, "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg=="], "varint": ["varint@6.0.0", "", {}, "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg=="],
"vite": ["vite@8.0.3", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.8", "rolldown": "1.0.0-rc.12", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.0", "esbuild": "^0.27.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-B9ifbFudT1TFhfltfaIPgjo9Z3mDynBTJSUYxTjOQruf/zHH+ezCQKcoqO+h7a9Pw9Nm/OtlXAiGT1axBgwqrQ=="], "vite": ["vite@8.0.5", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.8", "rolldown": "1.0.0-rc.12", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-nmu43Qvq9UopTRfMx2jOYW5l16pb3iDC1JH6yMuPkpVbzK0k+L7dfsEDH4jRgYFmsg0sTAqkojoZgzLMlwHsCQ=="],
"vite-tsconfig-paths": ["vite-tsconfig-paths@6.1.1", "", { "dependencies": { "debug": "^4.1.1", "globrex": "^0.1.2", "tsconfck": "^3.0.3" }, "peerDependencies": { "vite": "*" } }, "sha512-2cihq7zliibCCZ8P9cKJrQBkfgdvcFkOOc3Y02o3GWUDLgqjWsZudaoiuOwO/gzTzy17cS5F7ZPo4bsnS4DGkg=="], "vite-tsconfig-paths": ["vite-tsconfig-paths@6.1.1", "", { "dependencies": { "debug": "^4.1.1", "globrex": "^0.1.2", "tsconfck": "^3.0.3" }, "peerDependencies": { "vite": "*" } }, "sha512-2cihq7zliibCCZ8P9cKJrQBkfgdvcFkOOc3Y02o3GWUDLgqjWsZudaoiuOwO/gzTzy17cS5F7ZPo4bsnS4DGkg=="],
@ -1634,6 +1634,8 @@
"@gcch/configuration-eslint/eslint": ["eslint@10.1.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.3", "@eslint/config-helpers": "^0.5.3", "@eslint/core": "^1.1.1", "@eslint/plugin-kit": "^0.6.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^9.1.2", "eslint-visitor-keys": "^5.0.1", "espree": "^11.2.0", "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "minimatch": "^10.2.4", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-S9jlY/ELKEUwwQnqWDO+f+m6sercqOPSqXM5Go94l7DOmxHVDgmSFGWEzeE/gwgTAr0W103BWt0QLe/7mabIvA=="], "@gcch/configuration-eslint/eslint": ["eslint@10.1.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.3", "@eslint/config-helpers": "^0.5.3", "@eslint/core": "^1.1.1", "@eslint/plugin-kit": "^0.6.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^9.1.2", "eslint-visitor-keys": "^5.0.1", "espree": "^11.2.0", "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "minimatch": "^10.2.4", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-S9jlY/ELKEUwwQnqWDO+f+m6sercqOPSqXM5Go94l7DOmxHVDgmSFGWEzeE/gwgTAr0W103BWt0QLe/7mabIvA=="],
"@gcch/configuration-prettier/prettier-plugin-sh": ["prettier-plugin-sh@0.18.0", "", { "dependencies": { "@reteps/dockerfmt": "^0.3.6", "sh-syntax": "^0.5.8" }, "peerDependencies": { "prettier": "^3.6.0" } }, "sha512-cW1XL27FOJQ/qGHOW6IHwdCiNWQsAgK+feA8V6+xUTaH0cD3Mh+tFAtBvEEWvuY6hTDzRV943Fzeii+qMOh7nQ=="],
"@keyv/bigmap/keyv": ["keyv@5.6.0", "", { "dependencies": { "@keyv/serialize": "^1.1.1" } }, "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw=="], "@keyv/bigmap/keyv": ["keyv@5.6.0", "", { "dependencies": { "@keyv/serialize": "^1.1.1" } }, "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw=="],
"@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
@ -1702,6 +1704,8 @@
"@gcch/configuration-eslint/eslint/@eslint/plugin-kit": ["@eslint/plugin-kit@0.6.1", "", { "dependencies": { "@eslint/core": "^1.1.1", "levn": "^0.4.1" } }, "sha512-iH1B076HoAshH1mLpHMgwdGeTs0CYwL0SPMkGuSebZrwBp16v415e9NZXg2jtrqPVQjf6IANe2Vtlr5KswtcZQ=="], "@gcch/configuration-eslint/eslint/@eslint/plugin-kit": ["@eslint/plugin-kit@0.6.1", "", { "dependencies": { "@eslint/core": "^1.1.1", "levn": "^0.4.1" } }, "sha512-iH1B076HoAshH1mLpHMgwdGeTs0CYwL0SPMkGuSebZrwBp16v415e9NZXg2jtrqPVQjf6IANe2Vtlr5KswtcZQ=="],
"@gcch/configuration-prettier/prettier-plugin-sh/@reteps/dockerfmt": ["@reteps/dockerfmt@0.3.6", "", {}, "sha512-Tb5wIMvBf/nLejTQ61krK644/CEMB/cpiaIFXqGApfGqO3GwcR3qnI0DbmkFVCl2OyEp8LnLX3EkucoL0+tbFg=="],
"eslint-plugin-jsx-a11y/minimatch/brace-expansion": ["brace-expansion@1.1.13", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w=="], "eslint-plugin-jsx-a11y/minimatch/brace-expansion": ["brace-expansion@1.1.13", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w=="],
"stylelint/file-entry-cache/flat-cache": ["flat-cache@6.1.22", "", { "dependencies": { "cacheable": "^2.3.4", "flatted": "^3.4.2", "hookified": "^1.15.0" } }, "sha512-N2dnzVJIphnNsjHcrxGW7DePckJ6haPrSFqpsBUhHYgwtKGVq4JrBGielEGD2fCVnsGm1zlBVZ8wGhkyuetgug=="], "stylelint/file-entry-cache/flat-cache": ["flat-cache@6.1.22", "", { "dependencies": { "cacheable": "^2.3.4", "flatted": "^3.4.2", "hookified": "^1.15.0" } }, "sha512-N2dnzVJIphnNsjHcrxGW7DePckJ6haPrSFqpsBUhHYgwtKGVq4JrBGielEGD2fCVnsGm1zlBVZ8wGhkyuetgug=="],

View file

@ -81,7 +81,7 @@ watch-css:
# Compile TypeScript en JavaScript. # Compile TypeScript en JavaScript.
[group('js')] [group('js')]
build-js: build-js:
@bun --bun vite build --config "cfg/vite.config.ts" bun --bun vite build --config "cfg/vite.config.ts"
# Compile tout. # Compile tout.
[group('css')] [group('css')]
@ -94,7 +94,7 @@ build-all:
# Compile TypeScript à chaque changement de fichier. # Compile TypeScript à chaque changement de fichier.
[group('js')] [group('js')]
watch-js: watch-js:
bun vite build --watch bun --bun vite build --config "cfg/vite.config.ts" --watch
# Vérifie le code TypeScript avec des analyseurs statiques. # Vérifie le code TypeScript avec des analyseurs statiques.
[group('js')] [group('js')]

View file

@ -23,7 +23,7 @@
"devDependencies": { "devDependencies": {
"@effect/language-service": "^0.84.3", "@effect/language-service": "^0.84.3",
"@gcch/configuration-eslint": "git+https://git.gcch.fr/gcch/configuration-eslint#62ee424274", "@gcch/configuration-eslint": "git+https://git.gcch.fr/gcch/configuration-eslint#62ee424274",
"@gcch/configuration-oxlint": "git+https://git.gcch.fr/gcch/configuration-oxlint#3e49f5e2fb", "@gcch/configuration-oxlint": "git+https://git.gcch.fr/gcch/configuration-oxlint#bedd1fa23aff",
"@gcch/configuration-prettier": "git+https://git.gcch.fr/gcch/configuration-prettier#8de937e801", "@gcch/configuration-prettier": "git+https://git.gcch.fr/gcch/configuration-prettier#8de937e801",
"@playwright/test": "^1.59.1", "@playwright/test": "^1.59.1",
"@sentry/core": "^10.47.0", "@sentry/core": "^10.47.0",
@ -32,7 +32,7 @@
"@vitejs/plugin-legacy": "^8.0.1", "@vitejs/plugin-legacy": "^8.0.1",
"better-typescript-lib": "^2.12.0", "better-typescript-lib": "^2.12.0",
"browserslist": "^4.28.2", "browserslist": "^4.28.2",
"caniuse-lite": "^1.0.30001785", "caniuse-lite": "^1.0.30001786",
"eslint": "^10.2.0", "eslint": "^10.2.0",
"eslint-plugin-functional": "^9.0.4", "eslint-plugin-functional": "^9.0.4",
"eslint-plugin-jsx-a11y": "^6.10.2", "eslint-plugin-jsx-a11y": "^6.10.2",
@ -50,7 +50,7 @@
"playwright": "^1.59.1", "playwright": "^1.59.1",
"prettier": "^3.8.1", "prettier": "^3.8.1",
"prettier-plugin-pkg": "^0.22.1", "prettier-plugin-pkg": "^0.22.1",
"prettier-plugin-sh": "^0.18.0", "prettier-plugin-sh": "^0.18.1",
"sass-embedded": "^1.99.0", "sass-embedded": "^1.99.0",
"stylelint": "^17.6.0", "stylelint": "^17.6.0",
"stylelint-config-clean-order": "^8.0.1", "stylelint-config-clean-order": "^8.0.1",
@ -60,7 +60,7 @@
"stylelint-plugin-logical-css": "^2.1.0", "stylelint-plugin-logical-css": "^2.1.0",
"typescript": "6.0.2", "typescript": "6.0.2",
"typescript-eslint": "^8.58.0", "typescript-eslint": "^8.58.0",
"vite": "^8.0.3", "vite": "^8.0.5",
"vite-tsconfig-paths": "^6.1.1" "vite-tsconfig-paths": "^6.1.1"
}, },
"browserslist": [ "browserslist": [

View file

@ -21,6 +21,8 @@ parameters:
reportWrongPhpDocTypeInVarTag: true reportWrongPhpDocTypeInVarTag: true
# Setting treatPhpDocTypesAsCertain to false relaxes some of the rules around type-checking. # Setting treatPhpDocTypesAsCertain to false relaxes some of the rules around type-checking.
treatPhpDocTypesAsCertain: true treatPhpDocTypesAsCertain: true
# PHP silently casts array keys that look like decimal integers from string to int. This means array<string, mixed> cant guarantee that keys are actually strings at runtime.
reportUnsafeArrayStringKeyCasting: true
parallel: parallel:
jobSize: 20 jobSize: 20

View file

@ -31,7 +31,4 @@ function load_page_resources(): void {
add_action('wp_enqueue_scripts', load_page_resources(...)); add_action('wp_enqueue_scripts', load_page_resources(...));
Timber::render( Timber::render(data: $context, filenames: $templates);
data: $context,
filenames: $templates,
);

View file

@ -23,10 +23,7 @@ $templates = ['boutique.twig'];
/** @var list<WC_Product> $wc_products Les informations brutes des Produits. */ /** @var list<WC_Product> $wc_products Les informations brutes des Produits. */
$wc_products = wc_get_products(['limit' => 12, 'order' => 'DESC', 'orderby' => 'date', 'status' => 'publish']); $wc_products = wc_get_products(['limit' => 12, 'order' => 'DESC', 'orderby' => 'date', 'status' => 'publish']);
$products = array_map( $products = array_map(callback: Product::from_wc_product(...), array: $wc_products);
callback: Product::new(...),
array: $wc_products,
);
$context['products'] = $products; $context['products'] = $products;
add_action('wp_enqueue_scripts', function (): void { add_action('wp_enqueue_scripts', function (): void {
@ -44,7 +41,4 @@ add_action('wp_enqueue_scripts', function (): void {
); );
}); });
Timber::render( Timber::render(data: $context, filenames: $templates);
data: $context,
filenames: $templates,
);

View file

@ -27,7 +27,4 @@ add_action('wp_enqueue_scripts', function (): void {
); );
}); });
Timber::render( Timber::render(data: $context, filenames: $templates);
data: $context,
filenames: $templates,
);

View file

@ -23,12 +23,6 @@ Timber::$dirname = ['views'];
// Charge les Scripts du thème (report d'erreurs) // Charge les Scripts du thème (report d'erreurs)
function load_scripts(): void { function load_scripts(): void {
// wp_enqueue_script_module(
// id: 'haiku-atelier-2024-gaffe',
// deps: [],
// src: get_template_directory_uri() . '/assets/js/gaffe.js',
// version: filemtime(get_template_directory() . '/assets/js/gaffe.js'),
// );
wp_enqueue_script_module( wp_enqueue_script_module(
id: 'haiku-atelier-2024-bouton-panier', id: 'haiku-atelier-2024-bouton-panier',
deps: [], deps: [],

View file

@ -7,7 +7,4 @@ use Timber\Timber;
$context = Timber::context(); $context = Timber::context();
$templates = ['base.twig']; $templates = ['base.twig'];
Timber::render( Timber::render(data: $context, filenames: $templates);
data: $context,
filenames: $templates,
);

View file

@ -40,7 +40,4 @@ add_action('wp_enqueue_scripts', function (): void {
); );
}); });
Timber::render( Timber::render(data: $context, filenames: $templates);
data: $context,
filenames: $templates,
);

View file

@ -9,6 +9,7 @@ declare(strict_types=1);
namespace HaikuAtelier; namespace HaikuAtelier;
use HaikuAtelier\Data\Cart; use HaikuAtelier\Data\Cart;
use HaikuAtelier\Data\Product;
use HaikuAtelier\WP\Resource; use HaikuAtelier\WP\Resource;
use Illuminate\Support\Number; use Illuminate\Support\Number;
use Timber\Timber; use Timber\Timber;
@ -17,13 +18,8 @@ use WC_Shipping_Rate;
use function add_action; use function add_action;
use function collect; use function collect;
use function Crell\fp\pipe; use function Crell\fp\pipe;
use function genere_balise_img_multiformats;
use function recupere_et_formate_attributs_produit;
use function WC; use function WC;
// Importe la fonction pour récupérer les informations affichées des Produits dans le Panier
require_once __DIR__ . '/src/inc/TraitementInformations.php';
$context = Timber::context(); $context = Timber::context();
$templates = ['panier.twig']; $templates = ['panier.twig'];
@ -52,12 +48,12 @@ $shipping_subtotal = Cart::parse_cart_value($cart_totals['shipping_total'] ?? 0)
foreach (WC()->cart->get_cart() as $cle_panier => $article_panier) { foreach (WC()->cart->get_cart() as $cle_panier => $article_panier) {
$cart[$cle_panier] = [ $cart[$cle_panier] = [
'attributs' => $article_panier['data']?->get_type() === 'variation' 'attributs' => $article_panier['data']?->get_type() === 'variation'
? recupere_et_formate_attributs_produit($article_panier['data']?->get_attributes()) ? Product::recupere_et_formate_attributs_produit($article_panier['data']?->get_attributes())
: [], : [],
'cle' => $cle_panier, 'cle' => $cle_panier,
'id_produit' => $article_panier['product_id'], 'id_produit' => $article_panier['product_id'],
'id_variation' => $article_panier['variation_id'], 'id_variation' => $article_panier['variation_id'],
'image' => pipe($article_panier['data']?->get_image_id(), static fn($id): string => genere_balise_img_multiformats( 'image' => pipe($article_panier['data']?->get_image_id(), static fn($id): string => Resource::output_multi_formats_img_tag(
id: (string) $id, id: (string) $id,
lazy: true, lazy: true,
)), )),
@ -110,7 +106,4 @@ add_action('wp_enqueue_scripts', function (): void {
}); });
// Rendu // Rendu
Timber::render( Timber::render(filenames: $templates, data: $context);
filenames: $templates,
data: $context,
);

View file

@ -24,7 +24,4 @@ add_action('wp_enqueue_scripts', function (): void {
}); });
// Rendu // Rendu
Timber::render( Timber::render(filenames: $templates, data: $context);
filenames: $templates,
data: $context,
);

View file

@ -24,7 +24,4 @@ add_action('wp_enqueue_scripts', function (): void {
}); });
// Rendu // Rendu
Timber::render( Timber::render(filenames: $templates, data: $context);
filenames: $templates,
data: $context,
);

View file

@ -8,6 +8,7 @@ declare(strict_types=1);
namespace HaikuAtelier; namespace HaikuAtelier;
use HaikuAtelier\WP\Resource;
use Roots\WPConfig\Config; use Roots\WPConfig\Config;
use Stripe\Checkout\Session; use Stripe\Checkout\Session;
use Stripe\StripeClient; use Stripe\StripeClient;
@ -18,8 +19,6 @@ use WC_Order_Refund;
use function Crell\fp\pipe; use function Crell\fp\pipe;
require_once __DIR__ . '/src/inc/TraitementInformations.php';
/** @var string $url_accueil L'URL de la page d'Accueil. */ /** @var string $url_accueil L'URL de la page d'Accueil. */
$url_accueil = get_page_link(get_page_by_path('home')?->ID); $url_accueil = get_page_link(get_page_by_path('home')?->ID);
@ -87,7 +86,7 @@ try {
return [ return [
'attribut' => $attribut, 'attribut' => $attribut,
'id_produit' => $id_produit, 'id_produit' => $id_produit,
'image' => pipe($produit->get_image_id(), static fn($id): string => genere_balise_img_multiformats( 'image' => pipe($produit->get_image_id(), static fn($id): string => Resource::output_multi_formats_img_tag(
id: $id, id: $id,
lazy: true, lazy: true,
)), )),
@ -114,10 +113,7 @@ try {
add_action('wp_enqueue_scripts', 'charge_scripts_styles_page_succes_commande'); add_action('wp_enqueue_scripts', 'charge_scripts_styles_page_succes_commande');
// Rendu // Rendu
Timber::render( Timber::render(filenames: $templates, data: $context);
filenames: $templates,
data: $context,
);
} catch (Error $error) { } catch (Error $error) {
http_response_code(500); http_response_code(500);
echo json_encode(['error' => esc_html($error->getMessage())]); echo json_encode(['error' => esc_html($error->getMessage())]);

View file

@ -24,7 +24,4 @@ add_action('wp_enqueue_scripts', function (): void {
}); });
// Rendu // Rendu
Timber::render( Timber::render(filenames: $templates, data: $context);
filenames: $templates,
data: $context,
);

View file

@ -11,7 +11,9 @@ namespace HaikuAtelier;
use Exception; use Exception;
use HaikuAtelier\Data\Product; use HaikuAtelier\Data\Product;
use HaikuAtelier\WP\Resource; use HaikuAtelier\WP\Resource;
use HaikuAtelier\WP\Term;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
use Psl\Option\Option;
use stdClass; use stdClass;
use Timber\Timber; use Timber\Timber;
use WC_Product; use WC_Product;
@ -20,12 +22,9 @@ use function add_action;
use function assert; use function assert;
use function collect; use function collect;
use function is_array; use function is_array;
use function recupere_produits_meme_collection;
use function wc_get_product; use function wc_get_product;
use function wp_json_encode; use function wp_json_encode;
require_once __DIR__ . '/src/inc/TraitementInformations.php';
$context = Timber::context(); $context = Timber::context();
$templates = ['produit.twig']; $templates = ['produit.twig'];
@ -37,23 +36,41 @@ if ($raw_product === null || $raw_product === false) {
} }
// Assemble les données d'intérêt pour la page au sein d'une Classe. // Assemble les données d'intérêt pour la page au sein d'une Classe.
$product = Product::new($raw_product); $product = Product::from_wc_product($raw_product);
/** @var int $maximum_price Le prix de la Variation la plus chère */ /** @var int $maximum_price Le prix de la Variation la plus chère */
$maximum_price = collect($product->variations)->max('price'); $maximum_price = collect($product->variations)->max('price');
/** @var list<Product> Les Produits de la même collection que celui affiché dans la Page. */ /** @var list<Product> Les Produits de la même collection que celui affiché dans la Page. */
$same_collection_products = recupere_produits_meme_collection($product->collection)($product->id) $same_collection_products = Product::get_same_collection_products($product->collection)($product->id)
|> function (/** @var list<WC_Product>|stdClass */ mixed $products): array { |> function (/** @var list<WC_Product>|stdClass */ mixed $products): array {
assert(is_array($products), 'Les Produits de la même collection doivent être un tableau.'); assert(is_array($products), 'Les Produits de la même collection doivent être un tableau.');
return $products; return $products;
} }
|> (static fn(/** @var list<WC_Product> */ array $products): array => Arr::map($products, Product::new(...))); |> (static fn(/** @var list<WC_Product> */ array $products): array => Arr::map(
$products,
Product::from_wc_product(...),
));
$context['product'] = $product; $context['product'] = $product;
$context['product_json'] = wp_json_encode($product); $context['product_json'] = wp_json_encode($product);
$context['maximum_price'] = $maximum_price; $context['maximum_price'] = $maximum_price;
$context['same_collection_products'] = $same_collection_products; $context['same_collection_products'] = $same_collection_products;
$product_tags = $raw_product->get_tag_ids()
|> (static fn($tags_ids) => Arr::map($tags_ids, static fn($id) => Term::get_term_by_id(
id: $id,
taxonomy: 'product_tag',
)))
|> (static fn(/** @var list<Option<WC_Term>> */ $tags) => Arr::reject($tags, static fn($tag) => $tag->isNone()))
|> (static fn(/** @var list<Option<WC_Term>> */ $tags) => Arr::map($tags, static fn($tag) => $tag->unwrap()));
$tags = get_terms(['taxonomy' => 'product_tag', 'hide_empty' => true]);
echo '<pre>';
print_r($product_tags);
print_r($tags);
echo '</pre>';
exit();
add_action('wp_enqueue_scripts', function (): void { add_action('wp_enqueue_scripts', function (): void {
Resource::enqueue_script_module_file( Resource::enqueue_script_module_file(
@ -67,7 +84,4 @@ add_action('wp_enqueue_scripts', function (): void {
}); });
// Rendu // Rendu
Timber::render( Timber::render(filenames: $templates, data: $context);
filenames: $templates,
data: $context,
);

View file

@ -94,22 +94,11 @@ final class StarterSite extends Site {
// Récupère la Page courante // Récupère la Page courante
$url_courante = URLHelper::get_current_url(); $url_courante = URLHelper::get_current_url();
$context['page_courante'] = $url_courante; $context['page_courante'] = $url_courante;
$context['est_page_tous_produits'] = preg_match( $context['est_page_tous_produits'] = preg_match(pattern: '/(\bshop\b)/', subject: $url_courante);
pattern: '/(\bshop\b)/', $context['est_page_boutique'] =
subject: $url_courante, preg_match(pattern: '/(\bshop\b)/', subject: $url_courante)
); || preg_match(pattern: '/(\bproduct\b)/', subject: $url_courante)
$context['est_page_boutique'] = preg_match( || preg_match(pattern: '/(\bproduct-category\b)/', subject: $url_courante);
pattern: '/(\bshop\b)/',
subject: $url_courante,
)
|| preg_match(
pattern: '/(\bproduct\b)/',
subject: $url_courante,
)
|| preg_match(
pattern: '/(\bproduct-category\b)/',
subject: $url_courante,
);
// Politique de confidentialité // Politique de confidentialité
$politique_confidentialite_lien = pipe(get_privacy_policy_url(), esc_url(...)); $politique_confidentialite_lien = pipe(get_privacy_policy_url(), esc_url(...));
@ -130,10 +119,7 @@ final class StarterSite extends Site {
]; ];
$entrees_menu_categories = pipe( $entrees_menu_categories = pipe(
get_categories(['hide_empty' => false, 'orderby' => 'menu_order', 'taxonomy' => 'product_cat']), get_categories(['hide_empty' => false, 'orderby' => 'menu_order', 'taxonomy' => 'product_cat']),
static fn($categories): array => array_map( static fn($categories): array => array_map(callback: $cree_entree_menu, array: $categories),
callback: $cree_entree_menu,
array: $categories,
),
); );
$context['categories_produits'] = $entrees_menu_categories; $context['categories_produits'] = $entrees_menu_categories;

View file

@ -28,10 +28,6 @@ final readonly class Attribute {
/** @var list<AttributeOption> */ /** @var list<AttributeOption> */
$options = Arr::map($terms, AttributeOption::new(...)); $options = Arr::map($terms, AttributeOption::new(...));
return new self( return new self(name: $name, slug: $slug, options: $options);
name: $name,
slug: $slug,
options: $options,
);
} }
} }

View file

@ -18,10 +18,6 @@ final readonly class AttributeOption {
$name = $term->name; $name = $term->name;
$slug = $term->slug; $slug = $term->slug;
return new self( return new self(id: $id, name: $name, slug: $slug);
id: $id,
name: $name,
slug: $slug,
);
} }
} }

View file

@ -5,15 +5,17 @@ declare(strict_types=1);
namespace HaikuAtelier\Data; namespace HaikuAtelier\Data;
use HaikuAtelier\WP\HaikuProduct; use HaikuAtelier\WP\HaikuProduct;
use HaikuAtelier\WP\Resource;
use HaikuAtelier\WP\Term; use HaikuAtelier\WP\Term;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
use Psl\Option; use Psl\Option;
use stdClass;
use WC_Product; use WC_Product;
use WP_Term; use WP_Term;
use function HaikuAtelier\genere_balise_img_multiformats;
use function head; use function head;
use function Psl\Option\from_nullable; use function Psl\Option\from_nullable;
use function wc_get_products;
use function wpautop; use function wpautop;
final readonly class Product { final readonly class Product {
@ -41,16 +43,7 @@ final readonly class Product {
public string $url, public string $url,
) {} ) {}
/** public static function from_wc_product(WC_Product $product): self {
* @return list<Attribute>
*/
public static function get_attributes_for_product(WC_Product $product): array {
/** @var list<Attribute> */
return $product->get_attributes()
|> (static fn(array $attributes): array => Arr::map($attributes, Attribute::new(...)));
}
public static function new(WC_Product $product): self {
$attributes = self::get_attributes_for_product($product); $attributes = self::get_attributes_for_product($product);
/** @var lowercase-string */ /** @var lowercase-string */
$category = $product->get_id() |> wc_get_product_category_list(...) |> strtolower(...); $category = $product->get_id() |> wc_get_product_category_list(...) |> strtolower(...);
@ -73,8 +66,8 @@ final readonly class Product {
$left_column_photos = HaikuProduct::get_left_column_photos($id); $left_column_photos = HaikuProduct::get_left_column_photos($id);
/** @var list<string> */ /** @var list<string> */
$right_column_photos = HaikuProduct::get_right_column_photos($id); $right_column_photos = HaikuProduct::get_right_column_photos($id);
$default_photo = $left_column_photos[0] ?? genere_balise_img_multiformats('-1'); $default_photo = $left_column_photos[0] ?? Resource::output_multi_formats_img_tag('-1');
$hover_photo = $right_column_photos[0] ?? genere_balise_img_multiformats('-1', true); $hover_photo = $right_column_photos[0] ?? Resource::output_multi_formats_img_tag('-1', true);
$slug = $product->get_slug(); $slug = $product->get_slug();
$stock = $product->get_stock_quantity() ?? 1; $stock = $product->get_stock_quantity() ?? 1;
/** @var array<ProductVariation> */ /** @var array<ProductVariation> */
@ -104,4 +97,38 @@ final readonly class Product {
url: $url, url: $url,
); );
} }
/**
* @return list<Attribute>
*/
public static function get_attributes_for_product(WC_Product $product): array {
/** @var list<Attribute> */
return $product->get_attributes()
|> (static fn(array $attributes): array => Arr::map($attributes, Attribute::new(...)));
}
/**
* Récupère les informations utilisées pour la grille des Produits similaires (de la même
* collection) et les retourne sous forme de tableau associatif.
*
* Pour faciliter l'usage avec `array_map`, utilise une fonction avec curryfication.
*/
public static function get_same_collection_products(string $slug_collection): callable {
return static fn(int $id_produit): array|stdClass => wc_get_products([
'exclude' => [$id_produit],
'limit' => 4,
'order' => 'DESC',
'orderby' => 'date',
'status' => 'publish',
'tax_query' => [['taxonomy' => 'collection', 'field' => 'slug', 'terms' => $slug_collection]],
]);
}
public static 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],
];
}
} }

View file

@ -6,9 +6,9 @@
declare(strict_types=1); declare(strict_types=1);
use function Crell\fp\pipe; use HaikuAtelier\WP\Resource;
require_once 'TraitementInformations.php'; use function Crell\fp\pipe;
// Images du Produit // Images du Produit
@ -145,13 +145,10 @@ function genere_balises_img_dans_produit_dans_reponse_rest(
array: $metadata, array: $metadata,
callback: static fn($entree): bool => '_photos_colonne_gauche|||0|value' === $entree->key, callback: static fn($entree): bool => '_photos_colonne_gauche|||0|value' === $entree->key,
), ),
static fn($metadata): array => array_map( static fn($metadata): array => array_map(array: $metadata, callback: static fn($entree): string => Resource::output_multi_formats_img_tag(
array: $metadata,
callback: static fn($entree): string => genere_balise_img_multiformats(
id: $entree?->value, id: $entree?->value,
lazy: true, lazy: true,
), )),
),
static fn($image) => array_values(array: $image)[0], static fn($image) => array_values(array: $image)[0],
); );
@ -162,13 +159,10 @@ function genere_balises_img_dans_produit_dans_reponse_rest(
array: $metadata, array: $metadata,
callback: static fn($entree): bool => '_photos_colonne_droite|||0|value' === $entree->key, callback: static fn($entree): bool => '_photos_colonne_droite|||0|value' === $entree->key,
), ),
static fn($metadata): array => array_map( static fn($metadata): array => array_map(array: $metadata, callback: static fn($entree): string => Resource::output_multi_formats_img_tag(
array: $metadata,
callback: static fn($entree): string => genere_balise_img_multiformats(
id: $entree?->value, id: $entree?->value,
lazy: true, lazy: true,
), )),
),
static fn($image) => array_values(array: $image)[0], static fn($image) => array_values(array: $image)[0],
); );

View file

@ -1,116 +0,0 @@
<?php
declare(strict_types=1);
/**
* Fonctions pour le traitement d'informations.
*/
namespace HaikuAtelier;
use function Crell\fp\pipe;
// Page Shop
/**
* TODO.
*
* @param string $id TODO
* @param bool $lazy TODO
*
* @return string TODO
*/
function genere_balise_img_multiformats(string $id, bool $lazy = false): string {
$int_id = (int) $id;
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) : ['', ''];
$avif = $chemin ? realpath(pathinfo($chemin)['dirname'] . '/' . pathinfo($chemin)['filename'] . '.avif') : false;
$jxl = $chemin ? realpath(pathinfo($chemin)['dirname'] . '/' . pathinfo($chemin)['filename'] . '.jxl') : false;
// Génère un tableau avec les différents formats valides
$formats = pipe(
[$avif, $jxl],
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";
}
$loading = $lazy ? 'lazy' : 'eager';
return <<<EOD
{$sources}
<img
alt="{$alt}"
decoding="async"
height="{$dimensions[0]}"
loading="{$loading}"
onload="this.style.opacity=1"
src="{$url}"
width="{$dimensions[1]}"
/>
EOD;
}
// Page Produit
/**
* Récupère les informations utilisées pour la grille des Produits similaires (de la même
* collection) et les retourne sous forme de tableau associatif.
*
* Pour faciliter l'usage avec `array_map`, utilise une fonction avec curryfication.
*/
function recupere_produits_meme_collection(string $slug_collection): callable {
return static fn(int $id_produit): array|stdClass => 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],
];
}

View file

@ -7,7 +7,6 @@ namespace HaikuAtelier\WP;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
use function carbon_get_post_meta; use function carbon_get_post_meta;
use function HaikuAtelier\genere_balise_img_multiformats;
use function is_array; use function is_array;
use function is_string; use function is_string;
@ -19,7 +18,7 @@ final readonly class HaikuProduct {
/** @var list<string> */ /** @var list<string> */
return Post::get_post_meta_array($post_id, '_photos_colonne_gauche|||0|value')->unwrapOr([]) return Post::get_post_meta_array($post_id, '_photos_colonne_gauche|||0|value')->unwrapOr([])
|> (static fn(array $meta) => Arr::where($meta, static fn($meta): bool => is_string($meta))) |> (static fn(array $meta) => Arr::where($meta, static fn($meta): bool => is_string($meta)))
|> (static fn(array $array) => Arr::map($array, genere_balise_img_multiformats(...))); |> (static fn(array $array) => Arr::map($array, Resource::output_multi_formats_img_tag(...)));
} }
/** /**
@ -31,7 +30,7 @@ final readonly class HaikuProduct {
if (is_array($meta)) { if (is_array($meta)) {
/** @var list<string> */ /** @var list<string> */
return Arr::where($meta, static fn($meta): bool => is_string($meta)) return Arr::where($meta, static fn($meta): bool => is_string($meta))
|> (static fn(array $array) => Arr::map($array, genere_balise_img_multiformats(...))); |> (static fn(array $array) => Arr::map($array, Resource::output_multi_formats_img_tag(...)));
} }
return []; return [];

View file

@ -6,6 +6,7 @@ namespace HaikuAtelier\WP;
use Exception; use Exception;
use function Crell\fp\pipe;
use function filemtime; use function filemtime;
use function get_template_directory; use function get_template_directory;
use function get_template_directory_uri; use function get_template_directory_uri;
@ -29,12 +30,7 @@ final readonly class Resource {
$version = (string) $file_mtime; $version = (string) $file_mtime;
wp_enqueue_script_module( wp_enqueue_script_module(id: $id, src: $file_uri, deps: [], version: $version);
id: $id,
src: $file_uri,
deps: [],
version: $version,
);
} }
/** /**
@ -52,12 +48,74 @@ final readonly class Resource {
$ver = (string) $file_mtime; $ver = (string) $file_mtime;
wp_enqueue_style( wp_enqueue_style(handle: $handle, src: $file_uri, deps: [], ver: $ver, media: 'all');
handle: $handle, }
src: $file_uri,
deps: [], /**
ver: $ver, * TODO.
media: 'all', *
* @param string $id TODO
* @param bool $lazy TODO
*
* @return string TODO
*/
public static function output_multi_formats_img_tag(string $id, bool $lazy = false): string {
$int_id = (int) $id;
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) : ['', ''];
$avif = $chemin ? realpath(pathinfo($chemin)['dirname'] . '/' . pathinfo($chemin)['filename'] . '.avif') : false;
$jxl = $chemin ? realpath(pathinfo($chemin)['dirname'] . '/' . pathinfo($chemin)['filename'] . '.jxl') : false;
// Génère un tableau avec les différents formats valides
$formats = pipe(
[$avif, $jxl],
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";
}
$loading = $lazy ? 'lazy' : 'eager';
return <<<EOD
{$sources}
<img
alt="{$alt}"
decoding="async"
height="{$dimensions[0]}"
loading="{$loading}"
onload="this.style.opacity=1"
src="{$url}"
width="{$dimensions[1]}"
/>
EOD;
} }
} }

View file

@ -12,6 +12,19 @@ use function Psl\Option\none;
use function Psl\Option\some; use function Psl\Option\some;
final readonly class Term { final readonly class Term {
/**
* @return Option\Option<WP_Term>
*/
public static function get_term_by_id(int $id, string $taxonomy): Option\Option {
$term_data = get_term(term: $id, taxonomy: $taxonomy);
if ($term_data instanceof WP_Term) {
return some($term_data);
} else {
return none();
}
}
/** /**
* @return Option\Option<list<WP_Term>> * @return Option\Option<list<WP_Term>>
*/ */

View file

@ -1,27 +1,34 @@
import { Option, pipe } from "effect"; import { Array as FxArray, Option, pipe } from "effect";
import { Array as FxArray } from "effect"; import type { NonEmptyReadonlyArray } from "effect/Array";
import { NonEmptyReadonlyArray } from "effect/Array";
import { getOptionOrThrowWithError } from "./utils"; import { getOptionOrThrowWithError } from "./utils.ts";
/** Type union des parents possibles pour un `querySelector`. */ /** Type union des parents possibles pour un `querySelector`. */
export type ParentElement = Document | Element; type ParentElement = Document | Element;
export const getFirstSelectorFromParent = const getFirstSelectorFromParent =
(parent: ParentElement) => (parent: ParentElement) =>
<E extends Element = Element>(selector: string): Option.Option<NonNullable<E>> => <E extends Element = Element>(selector: string): Option.Option<NonNullable<E>> =>
Option.fromNullishOr(parent.querySelector<E>(selector)); Option.fromNullishOr(parent.querySelector<E>(selector));
export const getFirstSelectorFromDocument = <E extends Element = Element>( const getFirstSelectorFromParentOrThrow =
selector: string, (parent: ParentElement) =>
): Option.Option<NonNullable<E>> => getFirstSelectorFromParent(document)<E>(selector); <E extends Element = Element>(selector: string): NonNullable<E> =>
pipe(
getFirstSelectorFromParent(parent)<E>(selector),
getOptionOrThrowWithError(`Il n'y a pas d'Élément dans le parent avec le sélecteur suivant : ${selector}.`),
);
export const getFirstSelectorFromDocumentOrThrow = <E extends Element = Element>(selector: string): NonNullable<E> => const getFirstSelectorFromDocument = <E extends Element = Element>(selector: string): Option.Option<NonNullable<E>> =>
getFirstSelectorFromParent(document)<E>(selector);
const getFirstSelectorFromDocumentOrThrow = <E extends Element = Element>(selector: string): NonNullable<E> =>
pipe( pipe(
getFirstSelectorFromDocument<E>(selector), getFirstSelectorFromDocument<E>(selector),
getOptionOrThrowWithError(`Il n'y a pas d'Élément dans le Document avec le sélecteur suivant : ${selector}.`), getOptionOrThrowWithError(`Il n'y a pas d'Élément dans le Document avec le sélecteur suivant : ${selector}.`),
); );
export const getAllSelectorFromParent = const getAllSelectorFromParent =
(parent: ParentElement) => (parent: ParentElement) =>
<E extends Element = Element>(selector: string): Option.Option<NonEmptyReadonlyArray<E>> => <E extends Element = Element>(selector: string): Option.Option<NonEmptyReadonlyArray<E>> =>
pipe( pipe(
@ -31,14 +38,23 @@ export const getAllSelectorFromParent =
(xs: Array<E>) => Option.liftPredicate(FxArray.isReadonlyArrayNonEmpty)(xs), (xs: Array<E>) => Option.liftPredicate(FxArray.isReadonlyArrayNonEmpty)(xs),
); );
export const getAllSelectorFromDocument = <E extends Element = Element>( const getAllSelectorFromDocument = <E extends Element = Element>(
selector: string, selector: string,
): Option.Option<NonEmptyReadonlyArray<E>> => getAllSelectorFromParent(document)<E>(selector); ): Option.Option<NonEmptyReadonlyArray<E>> => getAllSelectorFromParent(document)<E>(selector);
export const getAllSelectorFromDocumentOrThrow = <E extends Element = Element>( const getAllSelectorFromDocumentOrThrow = <E extends Element = Element>(selector: string): NonEmptyReadonlyArray<E> =>
selector: string,
): NonEmptyReadonlyArray<E> =>
pipe( pipe(
getAllSelectorFromDocument<E>(selector), getAllSelectorFromDocument<E>(selector),
getOptionOrThrowWithError(`Il n'y a pas d'Éléments dans le Document avec le sélecteur suivant : ${selector}.`), getOptionOrThrowWithError(`Il n'y a pas d'Éléments dans le Document avec le sélecteur suivant : ${selector}.`),
); );
export {
getAllSelectorFromDocument,
getAllSelectorFromDocumentOrThrow,
getAllSelectorFromParent,
getFirstSelectorFromDocument,
getFirstSelectorFromDocumentOrThrow,
getFirstSelectorFromParent,
getFirstSelectorFromParentOrThrow,
type ParentElement,
};

View file

@ -13,5 +13,5 @@ export const WCStoreCartAddItemArgsSchema = v.object({
/** Quantity of this item to add to the basket. */ /** Quantity of this item to add to the basket. */
quantity: v.optional(v.number()), quantity: v.optional(v.number()),
/** Chosen attributes (for variations). */ /** Chosen attributes (for variations). */
variation: v.optional(v.array(WCStoreCartAddItemArgsItemsSchema)), variation: v.pipe(v.optional(v.array(WCStoreCartAddItemArgsItemsSchema)), v.readonly()),
}); });

View file

@ -7,42 +7,44 @@ import { match, P } from "ts-pattern";
import { ValiError } from "valibot"; import { ValiError } from "valibot";
import type { AnySchema } from "valibot"; import type { AnySchema } from "valibot";
import type { WCStoreBillingAddress, WCStoreShippingAddress } from "../lib/types/api/adresses"; import type { WCStoreBillingAddress, WCStoreShippingAddress } from "../lib/types/api/adresses.ts";
import type { WCStoreCart, WCStoreShippingRate, WCStoreShippingRateShippingRate } from "../lib/types/api/cart"; import type { WCStoreCart, WCStoreShippingRate, WCStoreShippingRateShippingRate } from "../lib/types/api/cart.ts";
import type { WCStoreCartUpdateCustomerArgs } from "../lib/types/api/cart-update-customer"; import type { WCStoreCartUpdateCustomerArgs } from "../lib/types/api/cart-update-customer.ts";
import type { WCV3Order, WCV3OrdersArgs } from "../lib/types/api/v3/orders"; import type { WCV3Order, WCV3OrdersArgs } from "../lib/types/api/v3/orders.ts";
import type { GenericPageState } from "../lib/types/pages"; import type { GenericPageState } from "../lib/types/pages.ts";
import type { FetchErrors, HttpCodeErrors } from "../lib/types/reseau"; import type { FetchErrors, HttpCodeErrors } from "../lib/types/reseau.ts";
import { ROUTE_API_MAJ_CLIENT, ROUTE_API_NOUVELLE_COMMANDES } from "../constantes/api"; import { ROUTE_API_MAJ_CLIENT, ROUTE_API_NOUVELLE_COMMANDES } from "../constantes/api.ts";
import { ATTRIBUT_CHARGEMENT, ATTRIBUT_LIVRAISON_VALIDEE } from "../constantes/dom"; import { ATTRIBUT_CHARGEMENT, ATTRIBUT_LIVRAISON_VALIDEE } from "../constantes/dom.ts";
import { NOM_CANAL_REVALIDATION_LIVRAISON } from "../constantes/messages"; import { NOM_CANAL_REVALIDATION_LIVRAISON } from "../constantes/messages.ts";
import { import {
ERREUR_ADRESSE_GENERIQUE, ERREUR_ADRESSE_GENERIQUE,
ERREUR_ADRESSE_MAUVAIS_CODE_POSTAL, ERREUR_ADRESSE_MAUVAIS_CODE_POSTAL,
ERREUR_GENERIQUE_CREATION_COMMANDE, ERREUR_GENERIQUE_CREATION_COMMANDE,
ERREUR_GENERIQUE_RESEAU, ERREUR_GENERIQUE_RESEAU,
ERREUR_GENERIQUE_SOUMISSION_ADRESSES, ERREUR_GENERIQUE_SOUMISSION_ADRESSES,
} from "../constantes/messages-utilisateur"; } from "../constantes/messages-utilisateur.ts";
import { estErreurFetch, estErreurHttp, setButtonLoadingState } from "../lib/dom"; import { estErreurFetch, estErreurHttp, setButtonLoadingState } from "../lib/dom.ts";
import { reporteEtJournaliseErreur } from "../lib/erreurs"; import { reporteEtJournaliseErreur } from "../lib/erreurs.ts";
import { ErreurAdresseInvalide } from "../lib/erreurs/adresses"; import { ErreurAdresseInvalide } from "../lib/erreurs/adresses.ts";
import { import {
ADRESSES_MAJ_EVENT, ADRESSES_MAJ_EVENT,
createUpdatedShippingRatesEvent, createUpdatedShippingRatesEvent,
createUpdatedTotalsEvent, createUpdatedTotalsEvent,
} from "../lib/evenements/panier"; } from "../lib/evenements/panier.ts";
import { emetUniqueMessageBroadcastChannel } from "../lib/messages"; import { emetUniqueMessageBroadcastChannel } from "../lib/messages.ts";
import { diviseParCent } from "../lib/nombres"; import { diviseParCent } from "../lib/nombres.ts";
import { newPartialResponse, prefilledPostBackend, safeFetch, traiteErreursBackendWooCommerce } from "../lib/reseau"; import { newPartialResponse, prefilledPostBackend, safeFetch, traiteErreursBackendWooCommerce } from "../lib/reseau.ts";
import { find, first } from "../lib/safe-arrays"; import { find, first } from "../lib/safe-arrays.ts";
import { WCStoreCartSchema } from "../lib/schemas/api/cart"; import { WCStoreCartSchema } from "../lib/schemas/api/cart.ts";
import { WCStoreCartUpdateCustomerArgsSchema } from "../lib/schemas/api/cart-update-customer"; import { WCStoreCartUpdateCustomerArgsSchema } from "../lib/schemas/api/cart-update-customer.ts";
import { estWCAddressError } from "../lib/schemas/api/erreurs"; import { estWCAddressError } from "../lib/schemas/api/erreurs.ts";
import { WCV3OrdersArgsSchema, WCV3OrderSchema } from "../lib/schemas/api/v3/orders"; import { WCV3OrdersArgsSchema, WCV3OrderSchema } from "../lib/schemas/api/v3/orders.ts";
import { safeSchemaParse } from "../lib/validation"; import { safeSchemaParse } from "../lib/validation.ts";
import { E } from "./scripts-page-panier-elements"; import { E } from "./scripts-page-panier-elements.ts";
import { getShippingRatesLS } from "./scripts-page-panier-local-storage"; import { getShippingRatesLS } from "./scripts-page-panier-local-storage.ts";
import { ReadonlyRecord } from "effect/Record";
import { Console, Effect, Stream } from "effect";
type Addresses = { type Addresses = {
billing_address: WCStoreBillingAddress; billing_address: WCStoreBillingAddress;
@ -50,23 +52,44 @@ type Addresses = {
}; };
// @ts-expect-error -- États injectés par le modèle PHP // @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; const ETATS_PAGE: GenericPageState = _etats;
const postBackend = prefilledPostBackend(ETATS_PAGE.nonce, ETATS_PAGE.authString); const postBackend = prefilledPostBackend(ETATS_PAGE.nonce, ETATS_PAGE.authString);
/** /**
* Initialise les Émetteurs d'Événements sur divers parties du Panier. * Initialise les Émetteurs d'Événements sur divers parties du Panier.
* *
* @returns void * @returns Un `Effect` ne retournant rien et ne pouvant échouer.
*/ */
export const initCartFormEventEmitters = (): void => { export const initCartFormEventEmitters = Effect.fn("initCartFormEventEmitters")(function* () {
E.FORMULAIRE_PANIER.addEventListener("change", (): void => { return yield* pipe(
Maybe.fromFalsy(E.FORMULAIRE_PANIER.checkValidity()).ifJust((): boolean => Stream.fromEventListener(E.FORMULAIRE_PANIER, "change"),
window.dispatchEvent(ADRESSES_MAJ_EVENT), Stream.tap((event: Event) => {
// La cible ne peut qu'être un Formulaire.
const target = event.target as HTMLFormElement;
if (target.checkValidity()) {
window.dispatchEvent(ADRESSES_MAJ_EVENT);
}
return Effect.void;
}),
Stream.runDrain,
); );
}); });
};
export const getAddressesFromForm = (formFields: Record<string, string>, areAddressesMerged: boolean): Addresses => ({ /**
* Récupère les valeurs des adresses renseignées dans les Formulaires correspondants.
*
* @param formFields Les champs du Formulaire sous forme d'Objet.
* @param areAddressesMerged Est-ce que l'utilisateur utilise la même adresse pour livraison et facturation.
* @returns Les Adresses proprement renseignées.
*
* TODO: Utiliser un Schéma pour parser.
*/
export const getAddressesFromForm = (
formFields: ReadonlyRecord<string, string>,
areAddressesMerged: boolean,
): Addresses => ({
billing_address: { billing_address: {
address_1: formFields["facturation-adresse"] ?? formFields["livraison-adresse"] ?? "", address_1: formFields["facturation-adresse"] ?? formFields["livraison-adresse"] ?? "",
address_2: "", address_2: "",

View file

@ -7,13 +7,13 @@ import { match, P } from "ts-pattern";
import { ValiError } from "valibot"; import { ValiError } from "valibot";
import type { AnySchema } from "valibot"; import type { AnySchema } from "valibot";
import type { WCStoreCart } from "../lib/types/api/cart"; import type { WCStoreCart } from "../lib/types/api/cart.ts";
import type { WCStoreCartRemoveItemArgs } from "../lib/types/api/cart-remove-item"; import type { WCStoreCartRemoveItemArgs } from "../lib/types/api/cart-remove-item.ts";
import type { WCStoreCartUpdateItemArgs } from "../lib/types/api/cart-update-item"; import type { WCStoreCartUpdateItemArgs } from "../lib/types/api/cart-update-item.ts";
import type { GenericPageState } from "../lib/types/pages"; import type { GenericPageState } from "../lib/types/pages.ts";
import type { FetchErrors, HttpCodeErrors } from "../lib/types/reseau"; import type { FetchErrors, HttpCodeErrors } from "../lib/types/reseau.ts";
import { ROUTE_API_MAJ_ARTICLE_PANIER, ROUTE_API_RETIRE_ARTICLE_PANIER } from "../constantes/api"; import { ROUTE_API_MAJ_ARTICLE_PANIER, ROUTE_API_RETIRE_ARTICLE_PANIER } from "../constantes/api.ts";
import { import {
ATTRIBUT_CLE_PANIER, ATTRIBUT_CLE_PANIER,
ATTRIBUT_DESACTIVE, ATTRIBUT_DESACTIVE,
@ -21,25 +21,24 @@ import {
DOM_BOUTON_SOUSTRACTION_QUANTITE, DOM_BOUTON_SOUSTRACTION_QUANTITE,
DOM_BOUTON_SUPPRESSION_PANIER, DOM_BOUTON_SUPPRESSION_PANIER,
DOM_CHAMP_QUANTITE_LIGNE_PANIER, DOM_CHAMP_QUANTITE_LIGNE_PANIER,
} from "../constantes/dom"; } from "../constantes/dom.ts";
import { NOM_CANAL_REVALIDATION_LIVRAISON } from "../constantes/messages"; import { NOM_CANAL_REVALIDATION_LIVRAISON } from "../constantes/messages.ts";
import { mustGetEleInParent } from "../lib/dom"; import { BadRequestError, reporteErreur, ServerError } from "../lib/erreurs.ts";
import { BadRequestError, reporteErreur, ServerError } from "../lib/erreurs";
import { import {
emetMessageMajBoutonPanier, emetMessageMajBoutonPanier,
emetMessageMajContenuPanier, emetMessageMajContenuPanier,
emetUniqueMessageBroadcastChannel, emetUniqueMessageBroadcastChannel,
} from "../lib/messages"; } from "../lib/messages.ts";
import { diviseParCent } from "../lib/nombres"; import { diviseParCent } from "../lib/nombres.ts";
import { newPartialResponse, postBackend, safeFetch, traiteErreursBackendWooCommerce } from "../lib/reseau"; import { newPartialResponse, postBackend, safeFetch, traiteErreursBackendWooCommerce } from "../lib/reseau.ts";
import { WCStoreCartSchema } from "../lib/schemas/api/cart"; import { WCStoreCartSchema } from "../lib/schemas/api/cart.ts";
import { WCStoreCartRemoveItemArgsSchema } from "../lib/schemas/api/cart-remove-item"; import { WCStoreCartRemoveItemArgsSchema } from "../lib/schemas/api/cart-remove-item.ts";
import { WCStoreCartUpdateItemArgsSchema } from "../lib/schemas/api/cart-update-item"; import { WCStoreCartUpdateItemArgsSchema } from "../lib/schemas/api/cart-update-item.ts";
import { safeSchemaParse } from "../lib/validation"; import { safeSchemaParse } from "../lib/validation.ts";
import { E } from "./scripts-page-panier-elements"; import { E } from "./scripts-page-panier-elements.ts";
import { getFirstSelectorFromParentOrThrow } from "../../scripts-effect/lib/dom.ts";
// @ts-expect-error -- États injectés par le modèle PHP // @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 PAGE_STATE: GenericPageState = _etats; const PAGE_STATE: GenericPageState = _etats;
type CartEntryInteractiveElements = { type CartEntryInteractiveElements = {
@ -49,31 +48,35 @@ type CartEntryInteractiveElements = {
substractionButton: HTMLButtonElement; substractionButton: HTMLButtonElement;
}; };
const getCartEntryInteractiveEles = (entree: HTMLElement): CartEntryInteractiveElements => { const getCartEntryInteractiveEles = (entry: HTMLElement): CartEntryInteractiveElements => {
const mustGetEle = mustGetEleInParent(entree); const entrySelector = getFirstSelectorFromParentOrThrow(entry);
return { return {
additionButton: mustGetEle<HTMLButtonElement>(DOM_BOUTON_ADDITION_QUANTITE), additionButton: entrySelector<HTMLButtonElement>(DOM_BOUTON_ADDITION_QUANTITE),
deletionButton: mustGetEle<HTMLButtonElement>(DOM_BOUTON_SUPPRESSION_PANIER), deletionButton: entrySelector<HTMLButtonElement>(DOM_BOUTON_SUPPRESSION_PANIER),
quantityInput: mustGetEle<HTMLInputElement>(DOM_CHAMP_QUANTITE_LIGNE_PANIER), quantityInput: entrySelector<HTMLInputElement>(DOM_CHAMP_QUANTITE_LIGNE_PANIER),
substractionButton: mustGetEle<HTMLButtonElement>(DOM_BOUTON_SOUSTRACTION_QUANTITE), substractionButton: entrySelector<HTMLButtonElement>(DOM_BOUTON_SOUSTRACTION_QUANTITE),
}; };
}; };
/** /**
* Met à jour l'état d'activation des Boutons d'action sur chaque Entrée du Panier. * Met à jour l'état d'activation des Boutons d'action pour toutes les Entrées du Panier.
* @param activated Le nouvel état d'activation (activé/désactivé). * @param activated Le nouvel état d'activation (activé/désactivé).
* @returns Rien. * @returns Rien.
*/ */
export const toggleCartEntryButtons = const toggleCartEntryButtons =
(activated: boolean) => (activated: boolean) =>
(cartEntries: ReadonlyArray<CartEntryInteractiveElements>): void => (cartEntries: ReadonlyArray<CartEntryInteractiveElements>): void => {
arrayForEach(cartEntries, (e: CartEntryInteractiveElements): void => { arrayForEach(cartEntries, (e: CartEntryInteractiveElements): void => {
if (activated) { if (activated) {
// Active les Boutons // Active les Boutons
Number(e.quantityInput.value) === 1 const entryQuantity = Number(e.quantityInput.value);
? e.substractionButton.setAttribute(ATTRIBUT_DESACTIVE, "") // Empêche la réducation de la quantité si on se trouve au minimum.
: e.substractionButton.removeAttribute(ATTRIBUT_DESACTIVE); if (entryQuantity === 1) {
e.substractionButton.setAttribute(ATTRIBUT_DESACTIVE, "");
} else {
e.substractionButton.removeAttribute(ATTRIBUT_DESACTIVE);
}
e.additionButton.removeAttribute(ATTRIBUT_DESACTIVE); e.additionButton.removeAttribute(ATTRIBUT_DESACTIVE);
e.deletionButton.removeAttribute(ATTRIBUT_DESACTIVE); e.deletionButton.removeAttribute(ATTRIBUT_DESACTIVE);
e.deletionButton.textContent = "Remove"; e.deletionButton.textContent = "Remove";
@ -85,22 +88,29 @@ export const toggleCartEntryButtons =
e.deletionButton.textContent = "Loading"; e.deletionButton.textContent = "Loading";
} }
}); });
};
export const initialiseActionsEntreesPanier = (): void => { const initActionsOnCartEntries = (): void => {
// Initialise des actions uniquement si des Entrées dans le Panier existent // Initialise des actions uniquement si des Entrées dans le Panier existent
E.ENTREES_PANIER.ifRight((cartEntries: Array<HTMLElement>) => E.ENTREES_PANIER.ifRight((cartEntries: Array<HTMLElement>) => {
arrayForEach(cartEntries, (entry: HTMLElement): void => { arrayForEach(cartEntries, (entry: HTMLElement): void => {
// Retire l'entrée du DOM si la clé Panier n'existe pas puis arrête précocement // Retire l'entrée du DOM si la clé Panier n'existe pas puis arrête précocement
const entryKey: string = Maybe.fromNullable(entry.getAttribute(ATTRIBUT_CLE_PANIER)) const entryKey: string = Maybe.fromNullable(entry.getAttribute(ATTRIBUT_CLE_PANIER))
.ifNothing(() => entry.remove()) .ifNothing(() => {
entry.remove();
})
.orDefault("CLE_PANIER_INEXISTANTE"); .orDefault("CLE_PANIER_INEXISTANTE");
const entryButtons: CartEntryInteractiveElements = getCartEntryInteractiveEles(entry); const entryButtons: CartEntryInteractiveElements = getCartEntryInteractiveEles(entry);
entry.addEventListener("click", (event: Event): void => { entry.addEventListener("click", (event: Event): void => {
// Discrimine en fonction de l'Élément cliqué (délégation d'Événements) // Discrimine en fonction de l'Élément cliqué (délégation d'Événements)
match(event.target) match(event.target)
[
// Cas impossible // Cas impossible
.with(P.nullish, () => console.error(event.target)) "with"
](P.nullish, () => {
console.error(event.target);
})
// Clic sur le Bouton d'addition // Clic sur le Bouton d'addition
.when( .when(
(target: EventTarget) => (target as HTMLElement).matches(DOM_BOUTON_ADDITION_QUANTITE), (target: EventTarget) => (target as HTMLElement).matches(DOM_BOUTON_ADDITION_QUANTITE),
@ -116,7 +126,9 @@ export const initialiseActionsEntreesPanier = (): void => {
safeSchemaParse({ key: entryKey, quantity: q + 1 }, WCStoreCartUpdateItemArgsSchema), safeSchemaParse({ key: entryKey, quantity: q + 1 }, WCStoreCartUpdateItemArgsSchema),
), ),
) )
.ifRight(() => pipe(cartEntries, arrayMap(getCartEntryInteractiveEles), toggleCartEntryButtons(false))) .ifRight(() => {
pipe(cartEntries, arrayMap(getCartEntryInteractiveEles), toggleCartEntryButtons(false));
})
.chain((args: WCStoreCartUpdateItemArgs) => .chain((args: WCStoreCartUpdateItemArgs) =>
safeFetch( safeFetch(
postBackend({ postBackend({
@ -129,7 +141,7 @@ export const initialiseActionsEntreesPanier = (): void => {
.chain((r: Response) => .chain((r: Response) =>
EitherAsync<ServerError, unknown>(async ({ throwE }) => EitherAsync<ServerError, unknown>(async ({ throwE }) =>
match(await newPartialResponse(r)) match(await newPartialResponse(r))
.with({ status: 200 }, (r) => r.body) ["with"]({ status: 200 }, (r) => r.body)
.otherwise((rs): never => throwE(traiteErreursBackendWooCommerce(rs))), .otherwise((rs): never => throwE(traiteErreursBackendWooCommerce(rs))),
), ),
) )
@ -149,24 +161,24 @@ export const initialiseActionsEntreesPanier = (): void => {
}) })
.ifLeft((err: FetchErrors | HttpCodeErrors | ValiError<AnySchema>): void => { .ifLeft((err: FetchErrors | HttpCodeErrors | ValiError<AnySchema>): void => {
match(err) match(err)
.with(P.instanceOf(ValiError), (e) => { ["with"](P.instanceOf(ValiError), (e) => {
reporteErreur(e); reporteErreur(e);
console.error(e.issues); console.error(e.issues);
// E.MESSAGE_ADRESSES.textContent = ERREUR_GENERIQUE_SOUMISSION_ADRESSES; // E.MESSAGE_ADRESSES.textContent = ERREUR_GENERIQUE_SOUMISSION_ADRESSES;
}) })
.with(P.instanceOf(ServerError), P.instanceOf(BadRequestError), (e) => { ["with"](P.instanceOf(ServerError), P.instanceOf(BadRequestError), (e) => {
reporteErreur(e); reporteErreur(e);
console.error(e); console.error(e);
// E.MESSAGE_ADRESSES.textContent = ERREUR_GENERIQUE_SOUMISSION_ADRESSES; // E.MESSAGE_ADRESSES.textContent = ERREUR_GENERIQUE_SOUMISSION_ADRESSES;
}) })
.with(P.instanceOf(DOMException), P.instanceOf(TypeError), P.instanceOf(Error), (e) => { ["with"](P.instanceOf(DOMException), P.instanceOf(TypeError), P.instanceOf(Error), (e) => {
reporteErreur(e); reporteErreur(e);
console.error(e); console.error(e);
// E.MESSAGE_ADRESSES.textContent = ERREUR_GENERIQUE_RESEAU; // E.MESSAGE_ADRESSES.textContent = ERREUR_GENERIQUE_RESEAU;
}) })
.exhaustive(); .exhaustive();
}) })
.finally(() => { ["finally"](() => {
pipe(cartEntries, arrayMap(getCartEntryInteractiveEles), toggleCartEntryButtons(true)); pipe(cartEntries, arrayMap(getCartEntryInteractiveEles), toggleCartEntryButtons(true));
}) })
.run(); .run();
@ -188,9 +200,9 @@ export const initialiseActionsEntreesPanier = (): void => {
safeSchemaParse({ key: entryKey, quantity: valeur - 1 }, WCStoreCartUpdateItemArgsSchema), safeSchemaParse({ key: entryKey, quantity: valeur - 1 }, WCStoreCartUpdateItemArgsSchema),
) )
// 2. Exécute un Effet pour empêcher les requêtes concurrentes // 2. Exécute un Effet pour empêcher les requêtes concurrentes
.ifRight(() => .ifRight(() => {
pipe(cartEntries, arrayMap(getCartEntryInteractiveEles), toggleCartEntryButtons(false)), pipe(cartEntries, arrayMap(getCartEntryInteractiveEles), toggleCartEntryButtons(false));
) })
// 3. Exécute la requête via fetch sous forme d'EitherAsync // 3. Exécute la requête via fetch sous forme d'EitherAsync
.chain((args: WCStoreCartUpdateItemArgs) => .chain((args: WCStoreCartUpdateItemArgs) =>
safeFetch( safeFetch(
@ -206,9 +218,9 @@ export const initialiseActionsEntreesPanier = (): void => {
EitherAsync<BadRequestError | Error | ServerError, unknown>(async ({ throwE }) => EitherAsync<BadRequestError | Error | ServerError, unknown>(async ({ throwE }) =>
// Simplifie les données à matcher // Simplifie les données à matcher
match(await newPartialResponse(reponse)) match(await newPartialResponse(reponse))
.with({ status: 500 }, () => throwE(new ServerError("500 Server Error"))) ["with"]({ status: 500 }, () => throwE(new ServerError("500 Server Error")))
.with({ status: 400 }, () => throwE(new BadRequestError("400 Bad Request Error"))) ["with"]({ status: 400 }, () => throwE(new BadRequestError("400 Bad Request Error")))
.with({ status: 200 }, (r) => r.body) ["with"]({ status: 200 }, (r) => r.body)
.otherwise((erreur) => throwE(new Error(`Erreur inconnue ${String(erreur.status)}`))), .otherwise((erreur) => throwE(new Error(`Erreur inconnue ${String(erreur.status)}`))),
), ),
) )
@ -231,24 +243,24 @@ export const initialiseActionsEntreesPanier = (): void => {
// 7. Traite les Erreurs et affiche un message à l'Utilisateur // 7. Traite les Erreurs et affiche un message à l'Utilisateur
.ifLeft((erreur: BadRequestError | FetchErrors | ServerError | ValiError<AnySchema>): void => { .ifLeft((erreur: BadRequestError | FetchErrors | ServerError | ValiError<AnySchema>): void => {
match(erreur) match(erreur)
.with(P.instanceOf(ValiError), (e) => { ["with"](P.instanceOf(ValiError), (e) => {
reporteErreur(e); reporteErreur(e);
console.error(e.issues); console.error(e.issues);
// E.MESSAGE_ADRESSES.textContent = ERREUR_GENERIQUE_SOUMISSION_ADRESSES; // E.MESSAGE_ADRESSES.textContent = ERREUR_GENERIQUE_SOUMISSION_ADRESSES;
}) })
.with(P.instanceOf(ServerError), P.instanceOf(BadRequestError), (e) => { ["with"](P.instanceOf(ServerError), P.instanceOf(BadRequestError), (e) => {
reporteErreur(e); reporteErreur(e);
console.error(e); console.error(e);
// E.MESSAGE_ADRESSES.textContent = ERREUR_GENERIQUE_SOUMISSION_ADRESSES; // E.MESSAGE_ADRESSES.textContent = ERREUR_GENERIQUE_SOUMISSION_ADRESSES;
}) })
.with(P.instanceOf(DOMException), P.instanceOf(TypeError), P.instanceOf(Error), (e) => { ["with"](P.instanceOf(DOMException), P.instanceOf(TypeError), P.instanceOf(Error), (e) => {
reporteErreur(e); reporteErreur(e);
console.error(e); console.error(e);
// E.MESSAGE_ADRESSES.textContent = ERREUR_GENERIQUE_RESEAU; // E.MESSAGE_ADRESSES.textContent = ERREUR_GENERIQUE_RESEAU;
}) })
.exhaustive(); .exhaustive();
}) })
.finally(() => { ["finally"](() => {
// Réactive les Boutons // Réactive les Boutons
pipe(cartEntries, arrayMap(getCartEntryInteractiveEles), toggleCartEntryButtons(true)); pipe(cartEntries, arrayMap(getCartEntryInteractiveEles), toggleCartEntryButtons(true));
}) })
@ -269,9 +281,9 @@ export const initialiseActionsEntreesPanier = (): void => {
// 1. Valide les Arguments de la Requête // 1. Valide les Arguments de la Requête
.liftEither(safeSchemaParse({ key: entryKey }, WCStoreCartRemoveItemArgsSchema)) .liftEither(safeSchemaParse({ key: entryKey }, WCStoreCartRemoveItemArgsSchema))
// 2. Exécute un Effet pour empêcher les requêtes concurrentes // 2. Exécute un Effet pour empêcher les requêtes concurrentes
.ifRight(() => .ifRight(() => {
pipe(cartEntries, arrayMap(getCartEntryInteractiveEles), toggleCartEntryButtons(false)), pipe(cartEntries, arrayMap(getCartEntryInteractiveEles), toggleCartEntryButtons(false));
) })
// 3. Exécute la requête via fetch sous forme d'EitherAsync // 3. Exécute la requête via fetch sous forme d'EitherAsync
.chain((args: WCStoreCartRemoveItemArgs) => .chain((args: WCStoreCartRemoveItemArgs) =>
safeFetch( safeFetch(
@ -287,9 +299,9 @@ export const initialiseActionsEntreesPanier = (): void => {
EitherAsync<BadRequestError | Error | ServerError, unknown>(async ({ throwE }) => EitherAsync<BadRequestError | Error | ServerError, unknown>(async ({ throwE }) =>
// Simplifie les données à matcher // Simplifie les données à matcher
match(await newPartialResponse(reponse)) match(await newPartialResponse(reponse))
.with({ status: 500 }, () => throwE(new ServerError("500 Server Error"))) ["with"]({ status: 500 }, () => throwE(new ServerError("500 Server Error")))
.with({ status: 400 }, () => throwE(new BadRequestError("400 Bad Request Error"))) ["with"]({ status: 400 }, () => throwE(new BadRequestError("400 Bad Request Error")))
.with({ status: 200 }, (r) => r.body) ["with"]({ status: 200 }, (r) => r.body)
.otherwise((erreur) => throwE(new Error(`Erreur inconnue ${String(erreur.status)}`))), .otherwise((erreur) => throwE(new Error(`Erreur inconnue ${String(erreur.status)}`))),
), ),
) )
@ -315,24 +327,24 @@ export const initialiseActionsEntreesPanier = (): void => {
// 7. Traite les Erreurs et affiche un message à l'Utilisateur // 7. Traite les Erreurs et affiche un message à l'Utilisateur
.ifLeft((erreur: BadRequestError | FetchErrors | ServerError | ValiError<AnySchema>): void => { .ifLeft((erreur: BadRequestError | FetchErrors | ServerError | ValiError<AnySchema>): void => {
match(erreur) match(erreur)
.with(P.instanceOf(ValiError), (e) => { ["with"](P.instanceOf(ValiError), (e) => {
reporteErreur(e); reporteErreur(e);
console.error(e.issues); console.error(e.issues);
// E.MESSAGE_ADRESSES.textContent = ERREUR_GENERIQUE_SOUMISSION_ADRESSES; // E.MESSAGE_ADRESSES.textContent = ERREUR_GENERIQUE_SOUMISSION_ADRESSES;
}) })
.with(P.instanceOf(ServerError), P.instanceOf(BadRequestError), (e) => { ["with"](P.instanceOf(ServerError), P.instanceOf(BadRequestError), (e) => {
reporteErreur(e); reporteErreur(e);
console.error(e); console.error(e);
// E.MESSAGE_ADRESSES.textContent = ERREUR_GENERIQUE_SOUMISSION_ADRESSES; // E.MESSAGE_ADRESSES.textContent = ERREUR_GENERIQUE_SOUMISSION_ADRESSES;
}) })
.with(P.instanceOf(DOMException), P.instanceOf(TypeError), P.instanceOf(Error), (e) => { ["with"](P.instanceOf(DOMException), P.instanceOf(TypeError), P.instanceOf(Error), (e) => {
reporteErreur(e); reporteErreur(e);
console.error(e); console.error(e);
// E.MESSAGE_ADRESSES.textContent = ERREUR_GENERIQUE_RESEAU; // E.MESSAGE_ADRESSES.textContent = ERREUR_GENERIQUE_RESEAU;
}) })
.exhaustive(); .exhaustive();
}) })
.finally(() => { ["finally"](() => {
// Réactive les Boutons // Réactive les Boutons
pipe(cartEntries, arrayMap(getCartEntryInteractiveEles), toggleCartEntryButtons(true)); pipe(cartEntries, arrayMap(getCartEntryInteractiveEles), toggleCartEntryButtons(true));
}) })
@ -342,6 +354,8 @@ export const initialiseActionsEntreesPanier = (): void => {
) )
.otherwise((_) => {}); .otherwise((_) => {});
}); });
}), });
); });
}; };
export { toggleCartEntryButtons, initActionsOnCartEntries as initialiseActionsEntreesPanier };

View file

@ -36,6 +36,7 @@ import { E } from "./page-panier/scripts-page-panier-elements.ts";
import { souscrisEvenementsPanier } from "./page-panier/scripts-page-panier-evenement.ts"; import { souscrisEvenementsPanier } from "./page-panier/scripts-page-panier-evenement.ts";
import { initShippingRatesChoicesActions } from "./page-panier/scripts-page-panier-methodes-livraison.ts"; import { initShippingRatesChoicesActions } from "./page-panier/scripts-page-panier-methodes-livraison.ts";
import { initialiseActionsEntreesPanier } from "./page-panier/scripts-page-panier-panneau-produits.ts"; import { initialiseActionsEntreesPanier } from "./page-panier/scripts-page-panier-panneau-produits.ts";
import { Effect } from "effect";
type ElementsEntreePanier = { type ElementsEntreePanier = {
boutonAddition: HTMLButtonElement; boutonAddition: HTMLButtonElement;
@ -176,7 +177,7 @@ const initialiseMajFormulairesPanier = (): void => {
}; };
document.addEventListener("DOMContentLoaded", (): void => { document.addEventListener("DOMContentLoaded", (): void => {
initCartFormEventEmitters(); Effect.runFork(initCartFormEventEmitters());
souscrisEvenementsPanier(); souscrisEvenementsPanier();
initialiseActionsEntreesPanier(); initialiseActionsEntreesPanier();
initShippingRatesChoicesActions(); initShippingRatesChoicesActions();

View file

@ -10,7 +10,7 @@ import { match, P } from "ts-pattern";
import { ValiError } from "valibot"; import { ValiError } from "valibot";
import type { AnySchema } from "valibot"; import type { AnySchema } from "valibot";
import type { WCStoreCart } from "./lib/types/api/cart"; import type { WCStoreCart } from "./lib/types/api/cart.ts";
import type { WCStoreCartAddItemArgs, WCStoreCartAddItemArgsItems } from "./lib/types/api/cart-add-item.ts"; import type { WCStoreCartAddItemArgs, WCStoreCartAddItemArgsItems } from "./lib/types/api/cart-add-item.ts";
import type { FetchErrors } from "./lib/types/reseau.ts"; import type { FetchErrors } from "./lib/types/reseau.ts";
@ -39,6 +39,7 @@ import { WCStoreCartSchema } from "./lib/schemas/api/cart.ts";
import { safeSchemaParse } from "./lib/validation"; import { safeSchemaParse } from "./lib/validation";
type EnsembleLienContenu = [HTMLAnchorElement, HTMLElement]; type EnsembleLienContenu = [HTMLAnchorElement, HTMLElement];
/** États utiles pour les scripts de la page. */ /** États utiles pour les scripts de la page. */
type EtatsPage = { type EtatsPage = {
/** L'ID en base de données du Produit. */ /** L'ID en base de données du Produit. */
@ -48,7 +49,6 @@ type EtatsPage = {
}; };
// @ts-expect-error -- États injectés par le modèle PHP // @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: EtatsPage = _etats; const ETATS_PAGE: EtatsPage = _etats;
/** /**
@ -149,18 +149,18 @@ const getAttributesFromDom = (): ReadonlyArray<WCStoreCartAddItemArgsItems> => {
return attributes; return attributes;
}; };
function areArraysEqual<T>(array1: Array<T>, array2: Array<T>): boolean { const areArraysEqual = <T>(array1: Array<T>, array2: Array<T>): boolean => {
if (array1 !== array2) { if (array1 !== array2) {
const a1 = JSON.stringify(array1.toSorted()); const a1 = JSON.stringify(array1.toSorted());
const a2 = JSON.stringify(array2.toSorted()); const a2 = JSON.stringify(array2.toSorted());
return a1 === a2; return a1 === a2;
} }
return true; return true;
} };
const updatePriceOnAttributeChange = (): void => { const updatePriceOnAttributeChange = (): void => {
E.VARIATION_CHOICE_FORM.addEventListener("change", (): void => { E.VARIATION_CHOICE_FORM.addEventListener("change", (): void => {
if (!E.VARIATION_CHOICE_FORM.checkValidity()) { if (E.VARIATION_CHOICE_FORM.checkValidity() === false) {
return; return;
} }
@ -183,9 +183,8 @@ const ajouteProduitAuPanier = (event: MouseEvent): void => {
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 // Récupère l'ID du Produit de la Page pour les Produits simples
.orDefault(ETATS_PAGE.idProduit), .orDefault(ETATS_PAGE.idProduit),
// Id: ETATS_PAGE.idProduit,
quantity: 1, quantity: 1,
// Variation: getAttributeValuesFromDom(), variation: getAttributesFromDom(),
}; };
// Réalise la Requête et traite sa Réponse // Réalise la Requête et traite sa Réponse

View file

@ -39,7 +39,10 @@ $products = wc_get_products([
assert(is_array($products), 'Les Produits de la Catégorie doivent être un tableau.'); assert(is_array($products), 'Les Produits de la Catégorie doivent être un tableau.');
return $products; return $products;
} }
|> (static fn(/** @var list<WC_Product> */ array $products): array => Arr::map($products, Product::new(...))); |> (static fn(/** @var list<WC_Product> */ array $products): array => Arr::map(
$products,
Product::from_wc_product(...),
));
$context['products'] = $products; $context['products'] = $products;
$context['category_id'] = $current_term->term_id; $context['category_id'] = $current_term->term_id;
@ -60,7 +63,4 @@ add_action('wp_enqueue_scripts', function (): void {
}); });
// Rendu // Rendu
Timber::render( Timber::render(filenames: $templates, data: $context);
filenames: $templates,
data: $context,
);

View file

@ -41,7 +41,4 @@ $email = [
$context['commande'] = $email; $context['commande'] = $email;
// Rendu // Rendu
Timber::render( Timber::render(filenames: $templates, data: $context);
filenames: $templates,
data: $context,
);

View file

@ -76,7 +76,4 @@ $email['adresses']['facturation']['country'] = WC()->countries->countries[$comma
$context['commande'] = $email; $context['commande'] = $email;
// Rendu // Rendu
Timber::render( Timber::render(filenames: $templates, data: $context);
filenames: $templates,
data: $context,
);

View file

@ -73,7 +73,4 @@ $email['adresses']['facturation']['country'] = WC()->countries->countries[$comma
$context['commande'] = $email; $context['commande'] = $email;
// Rendu // Rendu
Timber::render( Timber::render(filenames: $templates, data: $context);
filenames: $templates,
data: $context,
);