2026-04-05

This commit is contained in:
gcch 2026-04-05 13:11:21 +02:00
commit 949195caf8
71 changed files with 535 additions and 458 deletions

View file

@ -8,5 +8,17 @@
"!vtsls", "!vtsls",
"..." "..."
], ],
"languages": {} "lsp": {
"oxlint": {
"initialization_options": {
"settings": {
"configPath": "cfg/oxlint.config.ts",
"run": "onType",
"disableNestedConfig": false,
"fixKind": "safe_fix",
"unusedDisableDirectives": "deny"
}
}
}
}
} }

View file

@ -7,9 +7,9 @@
"dependencies": { "dependencies": {
"@mobily/ts-belt": "v4.0.0-rc.5", "@mobily/ts-belt": "v4.0.0-rc.5",
"@sentry/browser": "^10.47.0", "@sentry/browser": "^10.47.0",
"a11y-dialog": "^8.1.4", "a11y-dialog": "^8.1.5",
"effect": "^4.0.0-beta.43", "effect": "^4.0.0-beta.43",
"lit-html": "^3.3.1", "lit-html": "^3.3.2",
"purify-ts": "2.1.2", "purify-ts": "2.1.2",
"ts-pattern": "^5.9.0", "ts-pattern": "^5.9.0",
"valibot": "1.1.0", "valibot": "1.1.0",
@ -17,17 +17,17 @@
"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#93dd909919", "@gcch/configuration-oxlint": "git+https://git.gcch.fr/gcch/configuration-oxlint#7e91de4cb259",
"@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",
"@types/bun": "^1.3.11", "@types/bun": "^1.3.11",
"@types/node": "^25.5.1", "@types/node": "^25.5.2",
"@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.30001784", "caniuse-lite": "^1.0.30001785",
"eslint": "^10.1.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",
"eslint-plugin-oxlint": "^1.58.0", "eslint-plugin-oxlint": "^1.58.0",
@ -53,7 +53,7 @@
"stylelint-declaration-block-no-ignored-properties": "^3.0.0", "stylelint-declaration-block-no-ignored-properties": "^3.0.0",
"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.48.1", "typescript-eslint": "^8.58.0",
"vite": "^8.0.3", "vite": "^8.0.3",
"vite-tsconfig-paths": "^6.1.1", "vite-tsconfig-paths": "^6.1.1",
}, },
@ -282,21 +282,21 @@
"@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="], "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="],
"@eslint/config-array": ["@eslint/config-array@0.23.3", "", { "dependencies": { "@eslint/object-schema": "^3.0.3", "debug": "^4.3.1", "minimatch": "^10.2.4" } }, "sha512-j+eEWmB6YYLwcNOdlwQ6L2OsptI/LO6lNBuLIqe5R7RetD658HLoF+Mn7LzYmAWWNNzdC6cqP+L6r8ujeYXWLw=="], "@eslint/config-array": ["@eslint/config-array@0.23.4", "", { "dependencies": { "@eslint/object-schema": "^3.0.4", "debug": "^4.3.1", "minimatch": "^10.2.4" } }, "sha512-lf19F24LSMfF8weXvW5QEtnLqW70u7kgit5e9PSx0MsHAFclGd1T9ynvWEMDT1w5J4Qt54tomGeAhdoAku1Xow=="],
"@eslint/config-helpers": ["@eslint/config-helpers@0.5.3", "", { "dependencies": { "@eslint/core": "^1.1.1" } }, "sha512-lzGN0onllOZCGroKJmRwY6QcEHxbjBw1gwB8SgRSqK8YbbtEXMvKynsXc3553ckIEBxsbMBU7oOZXKIPGZNeZw=="], "@eslint/config-helpers": ["@eslint/config-helpers@0.5.4", "", { "dependencies": { "@eslint/core": "^1.2.0" } }, "sha512-jJhqiY3wPMlWWO3370M86CPJ7pt8GmEwSLglMfQhjXal07RCvhmU0as4IuUEW5SJeunfItiEetHmSxCCe9lDBg=="],
"@eslint/core": ["@eslint/core@1.1.1", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-QUPblTtE51/7/Zhfv8BDwO0qkkzQL7P/aWWbqcf4xWLEYn1oKjdO0gglQBB4GAsu7u6wjijbCmzsUTy6mnk6oQ=="], "@eslint/core": ["@eslint/core@1.2.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-8FTGbNzTvmSlc4cZBaShkC6YvFMG0riksYWRFKXztqVdXaQbcZLXlFbSpC05s70sGEsXAw0qwhx69JiW7hQS7A=="],
"@eslint/js": ["@eslint/js@10.0.1", "", { "peerDependencies": { "eslint": "^10.0.0" }, "optionalPeers": ["eslint"] }, "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA=="], "@eslint/js": ["@eslint/js@10.0.1", "", { "peerDependencies": { "eslint": "^10.0.0" }, "optionalPeers": ["eslint"] }, "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA=="],
"@eslint/object-schema": ["@eslint/object-schema@3.0.3", "", {}, "sha512-iM869Pugn9Nsxbh/YHRqYiqd23AmIbxJOcpUMOuWCVNdoQJ5ZtwL6h3t0bcZzJUlC3Dq9jCFCESBZnX0GTv7iQ=="], "@eslint/object-schema": ["@eslint/object-schema@3.0.4", "", {}, "sha512-55lO/7+Yp0ISKRP0PsPtNTeNGapXaO085aELZmWCVc5SH3jfrqpuU6YgOdIxMS99ZHkQN1cXKE+cdIqwww9ptw=="],
"@eslint/plugin-kit": ["@eslint/plugin-kit@0.6.1", "", { "dependencies": { "@eslint/core": "^1.1.1", "levn": "^0.4.1" } }, "sha512-iH1B076HoAshH1mLpHMgwdGeTs0CYwL0SPMkGuSebZrwBp16v415e9NZXg2jtrqPVQjf6IANe2Vtlr5KswtcZQ=="], "@eslint/plugin-kit": ["@eslint/plugin-kit@0.7.0", "", { "dependencies": { "@eslint/core": "^1.2.0", "levn": "^0.4.1" } }, "sha512-ejvBr8MQCbVsWNZnCwDXjUKq40MDmHalq7cJ6e9s/qzTUFIIo/afzt1Vui9T97FM/V/pN4YsFVoed5NIa96RDg=="],
"@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#93dd9099199603d2fe2c334227e8051104b8f1a0", { "dependencies": { "globals": "^17.4.0", "oxlint": "^1.58.0", "oxlint-tsgolint": "^0.19.0" }, "optionalDependencies": { "eslint-plugin-astro": "^1.6.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.7.0", "eslint-plugin-sonarjs": "^4.0.2", "oxlint": "^1.58.0", "oxlint-tsgolint": "^0.19.0", "typescript": "^6.0.2" } }, "93dd9099199603d2fe2c334227e8051104b8f1a0"], "@gcch/configuration-oxlint": ["@gcch/configuration-oxlint@git+https://git.gcch.fr/gcch/configuration-oxlint#7e91de4cb25995eb3b98b139c15d2292eaad5e91", { "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" } }, "7e91de4cb25995eb3b98b139c15d2292eaad5e91"],
"@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"],
@ -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.30001784", "", {}, "sha512-WU346nBTklUV9YfUl60fqRbU5ZqyXlqvo1SgigE1OAXK5bFL8LL9q1K7aap3N739l4BvNqnkm3YrGHiY9sfUQw=="], "caniuse-lite": ["caniuse-lite@1.0.30001785", "", {}, "sha512-blhOL/WNR+Km1RI/LCVAvA73xplXA7ZbjzI4YkMK9pa6T/P3F2GxjNpEkyw5repTw9IvkyrjyHpwjnhZ5FOvYQ=="],
"change-case": ["change-case@5.4.4", "", {}, "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w=="], "change-case": ["change-case@5.4.4", "", {}, "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w=="],
@ -826,7 +826,7 @@
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
"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=="], "eslint": ["eslint@10.2.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.4", "@eslint/config-helpers": "^0.5.4", "@eslint/core": "^1.2.0", "@eslint/plugin-kit": "^0.7.0", "@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-+L0vBFYGIpSNIt/KWTpFonPrqYvgKw1eUI5Vn7mEogrQcWtWYtNQ7dNqC+px/J0idT3BAkiWrhfS7k+Tum8TUA=="],
"eslint-compat-utils": ["eslint-compat-utils@0.6.5", "", { "dependencies": { "semver": "^7.5.4" }, "peerDependencies": { "eslint": ">=6.0.0" } }, "sha512-vAUHYzue4YAa2hNACjB8HvUQj5yehAZgiClyFVVom9cP8z5NSFq3PwB/TtJslN2zAMgRX6FCFCjYBbQh71g5RQ=="], "eslint-compat-utils": ["eslint-compat-utils@0.6.5", "", { "dependencies": { "semver": "^7.5.4" }, "peerDependencies": { "eslint": ">=6.0.0" } }, "sha512-vAUHYzue4YAa2hNACjB8HvUQj5yehAZgiClyFVVom9cP8z5NSFq3PwB/TtJslN2zAMgRX6FCFCjYBbQh71g5RQ=="],
@ -1632,6 +1632,8 @@
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
"@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=="],
"@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=="],
@ -1644,6 +1646,8 @@
"babel-plugin-polyfill-corejs2/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], "babel-plugin-polyfill-corejs2/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
"browserslist/caniuse-lite": ["caniuse-lite@1.0.30001784", "", {}, "sha512-WU346nBTklUV9YfUl60fqRbU5ZqyXlqvo1SgigE1OAXK5bFL8LL9q1K7aap3N739l4BvNqnkm3YrGHiY9sfUQw=="],
"browserslist-to-esbuild/meow": ["meow@13.2.0", "", {}, "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA=="], "browserslist-to-esbuild/meow": ["meow@13.2.0", "", {}, "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA=="],
"cacheable/keyv": ["keyv@5.6.0", "", { "dependencies": { "@keyv/serialize": "^1.1.1" } }, "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw=="], "cacheable/keyv": ["keyv@5.6.0", "", { "dependencies": { "@keyv/serialize": "^1.1.1" } }, "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw=="],
@ -1690,6 +1694,14 @@
"vite/fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], "vite/fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
"@gcch/configuration-eslint/eslint/@eslint/config-array": ["@eslint/config-array@0.23.3", "", { "dependencies": { "@eslint/object-schema": "^3.0.3", "debug": "^4.3.1", "minimatch": "^10.2.4" } }, "sha512-j+eEWmB6YYLwcNOdlwQ6L2OsptI/LO6lNBuLIqe5R7RetD658HLoF+Mn7LzYmAWWNNzdC6cqP+L6r8ujeYXWLw=="],
"@gcch/configuration-eslint/eslint/@eslint/config-helpers": ["@eslint/config-helpers@0.5.3", "", { "dependencies": { "@eslint/core": "^1.1.1" } }, "sha512-lzGN0onllOZCGroKJmRwY6QcEHxbjBw1gwB8SgRSqK8YbbtEXMvKynsXc3553ckIEBxsbMBU7oOZXKIPGZNeZw=="],
"@gcch/configuration-eslint/eslint/@eslint/core": ["@eslint/core@1.1.1", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-QUPblTtE51/7/Zhfv8BDwO0qkkzQL7P/aWWbqcf4xWLEYn1oKjdO0gglQBB4GAsu7u6wjijbCmzsUTy6mnk6oQ=="],
"@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=="],
"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=="],
@ -1700,6 +1712,8 @@
"table/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], "table/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"@gcch/configuration-eslint/eslint/@eslint/config-array/@eslint/object-schema": ["@eslint/object-schema@3.0.3", "", {}, "sha512-iM869Pugn9Nsxbh/YHRqYiqd23AmIbxJOcpUMOuWCVNdoQJ5ZtwL6h3t0bcZzJUlC3Dq9jCFCESBZnX0GTv7iQ=="],
"eslint-plugin-jsx-a11y/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], "eslint-plugin-jsx-a11y/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
} }
} }

View file

@ -1,3 +1,11 @@
import config from "@gcch/configuration-oxlint"; import gcchConfig from "@gcch/configuration-oxlint";
import { OxlintConfig } from "oxlint";
const config: OxlintConfig = {
...gcchConfig,
globals: {
Bun: "readonly",
},
};
export default config; export default config;

View file

@ -2,24 +2,6 @@ import { defineConfig, devices } from "@playwright/test";
export default defineConfig({ export default defineConfig({
fullyParallel: true, fullyParallel: true,
reporter: "list",
retries: 1,
testDir: "../tests",
timeout: 10_000,
workers: "100%",
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: "https://haikuatelier.gcch.local",
trace: "retry-with-trace",
clientCertificates: [
{
origin: "https://haikuatelier.gcch.local",
certPath: "../containers/data/certs/_wildcard.gcch.local.pem",
keyPath: "../containers/data/certs/_wildcard.gcch.local-key.pem",
},
],
ignoreHTTPSErrors: true,
},
projects: [ projects: [
{ {
name: "desktop-chromium-1920", name: "desktop-chromium-1920",
@ -62,4 +44,22 @@ export default defineConfig({
// use: { ...devices["Pixel 7 landscape"] }, // use: { ...devices["Pixel 7 landscape"] },
// }, // },
], ],
reporter: "list",
retries: 1,
testDir: "../tests",
timeout: 10_000,
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: "https://haikuatelier.gcch.local",
trace: "retry-with-trace",
clientCertificates: [
{
origin: "https://haikuatelier.gcch.local",
certPath: "../containers/data/certs/_wildcard.gcch.local.pem",
keyPath: "../containers/data/certs/_wildcard.gcch.local-key.pem",
},
],
ignoreHTTPSErrors: true,
},
workers: "100%",
}); });

View file

@ -1,40 +1,59 @@
// @ts-expect-error -- La dépendance ne dispose pas de types. import { pipe, Array as FxArray } from "effect";
import type stylelint from "stylelint";
import { propertyGroups } from "stylelint-config-clean-order"; import { propertyGroups } from "stylelint-config-clean-order";
/** @type {string[][]} */ /**
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument -- Impossible de typer correctement avec JSDoc. * Définition d'un groupe de Propriétés _CSS_ du plugin `stylelint-config-clean-order` pour _Stylelint_.
const groupesProprietes = Array.from(/** @type {GroupeProprietes} */ propertyGroups); */
type StylelintConfigCleanOrderPropertyGroup = {
emptyLineBefore: "never" | "threshold";
noEmptyLineBetween: boolean;
properties: ReadonlyArray<string> | string;
};
/** @import { StylelintConfigCleanOrderPropertyGroup } from "./lib/stylelint" */ const ordreProprietes: ReadonlyArray<StylelintConfigCleanOrderPropertyGroup> = pipe(
/** @type {Array<StylelintConfigCleanOrderPropertyGroup>} */ Array.from(propertyGroups),
const ordreProprietes = groupesProprietes.map((properties) => ({ FxArray.map((properties: ReadonlyArray<string>) => ({
emptyLineBefore: "never", emptyLineBefore: "never",
noEmptyLineBetween: true, noEmptyLineBetween: true,
properties, properties,
})); })),
);
/** @type {import("stylelint").Config} */ const stylelintConfig: stylelint.Config = {
export default {
extends: ["stylelint-config-standard-scss", "stylelint-config-sass-guidelines", "stylelint-config-clean-order"], extends: ["stylelint-config-standard-scss", "stylelint-config-sass-guidelines", "stylelint-config-clean-order"],
plugins: ["stylelint-declaration-block-no-ignored-properties"], plugins: ["stylelint-declaration-block-no-ignored-properties"],
rules: { rules: {
"@stylistic/function-parentheses-space-inside": null, "@stylistic/function-parentheses-space-inside": undefined,
"@stylistic/selector-list-comma-newline-after": null, "@stylistic/selector-list-comma-newline-after": undefined,
"@stylistic/string-quotes": null, "@stylistic/string-quotes": undefined,
"custom-property-pattern": null, "custom-property-pattern": undefined,
"declaration-block-no-duplicate-custom-properties": true, "declaration-block-no-duplicate-custom-properties": true,
"declaration-block-no-duplicate-properties": true, "declaration-block-no-duplicate-properties": true,
"declaration-block-no-redundant-longhand-properties": true, "declaration-block-no-redundant-longhand-properties": true,
"declaration-block-no-shorthand-property-overrides": true, "declaration-block-no-shorthand-property-overrides": true,
"max-nesting-depth": null, "max-nesting-depth": undefined,
"no-descending-specificity": null, "no-descending-specificity": undefined,
"no-duplicate-selectors": [true, { disallowInList: false }], "no-duplicate-selectors": [
"order/properties-order": [ordreProprietes, { severity: "error", unspecified: "bottomAlphabetical" }], true,
{
disallowInList: false,
},
],
"order/properties-order": [
ordreProprietes,
{
severity: "error",
unspecified: "bottomAlphabetical",
},
],
"plugin/declaration-block-no-ignored-properties": true, "plugin/declaration-block-no-ignored-properties": true,
"selector-class-pattern": null, "selector-class-pattern": undefined,
"selector-id-pattern": null, "selector-id-pattern": undefined,
"selector-max-compound-selectors": null, "selector-max-compound-selectors": undefined,
"selector-max-id": null, "selector-max-id": undefined,
"selector-no-qualifying-type": null, "selector-no-qualifying-type": undefined,
}, },
}; };
export default stylelintConfig;

View file

@ -17,7 +17,6 @@ export default defineConfig(({ mode }) => {
return { return {
base: "/", base: "/",
cacheDir: ".cache/vite",
build: { build: {
assetsDir: ".", assetsDir: ".",
cssMinify: "lightningcss", cssMinify: "lightningcss",
@ -40,6 +39,7 @@ export default defineConfig(({ mode }) => {
target: "es2020", target: "es2020",
write: true, write: true,
}, },
cacheDir: ".cache/vite",
css: { css: {
devSourcemap: true, devSourcemap: true,
transformer: "lightningcss", transformer: "lightningcss",

View file

@ -25,7 +25,8 @@
"curryfication", "curryfication",
"giftcard", "giftcard",
"taplo", "taplo",
"phpactor" "phpactor",
"gcch"
], ],
"words": [ "words": [
"GLITCHTIP", "GLITCHTIP",

8
lib/stylelint.d.ts vendored
View file

@ -1,8 +0,0 @@
/**
* Définition d'un groupe de Propriétés _CSS_ du plugin `stylelint-config-clean-order` pour _Stylelint_.
*/
export type stylelintconfigcleanorderpropertygroup = {
emptyLineBefore: "never" | "threshold";
noEmptyLineBetween: boolean;
properties: string | array<string>;
};

View file

@ -13,9 +13,9 @@
"dependencies": { "dependencies": {
"@mobily/ts-belt": "v4.0.0-rc.5", "@mobily/ts-belt": "v4.0.0-rc.5",
"@sentry/browser": "^10.47.0", "@sentry/browser": "^10.47.0",
"a11y-dialog": "^8.1.4", "a11y-dialog": "^8.1.5",
"effect": "^4.0.0-beta.43", "effect": "^4.0.0-beta.43",
"lit-html": "^3.3.1", "lit-html": "^3.3.2",
"purify-ts": "2.1.2", "purify-ts": "2.1.2",
"ts-pattern": "^5.9.0", "ts-pattern": "^5.9.0",
"valibot": "1.1.0" "valibot": "1.1.0"
@ -23,17 +23,17 @@
"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#93dd909919", "@gcch/configuration-oxlint": "git+https://git.gcch.fr/gcch/configuration-oxlint#7e91de4cb259",
"@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",
"@types/bun": "^1.3.11", "@types/bun": "^1.3.11",
"@types/node": "^25.5.1", "@types/node": "^25.5.2",
"@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.30001784", "caniuse-lite": "^1.0.30001785",
"eslint": "^10.1.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",
"eslint-plugin-oxlint": "^1.58.0", "eslint-plugin-oxlint": "^1.58.0",
@ -59,7 +59,7 @@
"stylelint-declaration-block-no-ignored-properties": "^3.0.0", "stylelint-declaration-block-no-ignored-properties": "^3.0.0",
"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.48.1", "typescript-eslint": "^8.58.0",
"vite": "^8.0.3", "vite": "^8.0.3",
"vite-tsconfig-paths": "^6.1.1" "vite-tsconfig-paths": "^6.1.1"
}, },

View file

@ -1,45 +1,69 @@
import { YAML } from "bun"; import { YAML } from "bun";
import { Array as EffectArray, Console, Data, Effect, pipe, Record, Schema, SchemaIssue } from "effect"; import { Console, Data, Effect, Array as EffectArray, pipe, Record, Schema, SchemaIssue } from "effect";
import { NoSuchElementError } from "effect/Cause";
import { type ReadonlyRecord } from "effect/Record";
import { SchemaError } from "effect/Schema"; import { SchemaError } from "effect/Schema";
const COMPOSE_PATH = "compose.yaml"; const COMPOSE_PATH = "compose.yaml";
const DEFAULT_CMD_TIMEOUT = 10_000;
class Compose extends Schema.Class<Compose>("Compose")({
name: Schema.String,
services: Schema.Record(Schema.String, Schema.Unknown),
}) {}
class ScriptError extends Data.TaggedError("ScriptError")<{ cause: unknown }> {} class ScriptError extends Data.TaggedError("ScriptError")<{ cause: unknown }> {}
const ComposeSchema = Schema.Record(Schema.Union([Schema.String, Schema.Symbol]), Schema.Any); // Const composeSchema = Schema.Record(Schema.Union([Schema.String, Schema.Symbol]), Schema.Unknown);
// type YamlRecord = ReadonlyRecord<string | symbol, unknown>;
type ComposeYaml = ReadonlyRecord<string | symbol, any>; /* */
const getObjectKey = (key: string, yaml: ComposeYaml): Effect.Effect<ReadonlyArray<string>, ScriptError> => /**
pipe( * Retourne les noms des services déclarés dans un ficher _Compose_.
Record.get(key)(yaml), * @param compose Le fichier _Compose_ sous forme d'objet.
Effect.fromOption, * @returns Les noms des Services sous forme de tableau.
Effect.map((yaml: any) => Record.keys(yaml)), */
Effect.mapError((e: NoSuchElementError) => new ScriptError({ cause: e })), const getServicesFromComposeYaml: (compose: Compose) => ReadonlyArray<string> = (compose) =>
); Record.keys(compose.services);
const getFileContent = Effect.fn("getFileContent")(function* (filePath: string) { /**
const fileRef = Bun.file(filePath); * Récupère le contenu texte d'un fichier pour un chemin donné.
*
* @param filePath Le chemin du fichier dont on souhaite le contenu.
* @returns Le contenu textuel du fichier sous forme de chaîne de caractères.
*/
const getFileContent: (filePath: string) => Effect.Effect<string, ScriptError> = Effect.fn("getFileContent")(
function* (filePath) {
const fileRef: Bun.BunFile = Bun.file(filePath);
yield* Effect.tryPromise({ yield* Effect.tryPromise({
try: () => fileRef.exists(), catch: (_): ScriptError => new ScriptError({ cause: "The wanted file does not exist." }),
catch: (_) => new ScriptError({ cause: "The wanted file does not exist." }), try: async (): Promise<boolean> => fileRef.exists(),
}); });
return yield* Effect.tryPromise({ return yield* Effect.tryPromise({
try: () => fileRef.text(), catch: (_): ScriptError => new ScriptError({ cause: "Can't retrieve the file's text content." }),
catch: (_) => new ScriptError({ cause: "Can't retrieve the file's text content." }), try: async (): Promise<string> => fileRef.text(),
}); });
}); },
);
const getComposeYaml = <A>(filePath: string, schema: Schema.Schema<A>) => /**
pipe( * Récupère le contenu _YAML_ d'un fichier _Compose_ sous forme de `Record`.
getFileContent(filePath), *
Effect.map((text: string) => YAML.parse(text)), * @param path Le chemin du fichier _Compose_.
Effect.flatMap((yaml: unknown) => Schema.decodeUnknownEffect(schema)(yaml, { errors: "all" })), * @param schema Le `Schema` utilisée pour le parsage des données du fichier.
Effect.mapError((error) => { * @returns Un `Record` des données du fichier.
*/
const getComposeYaml: <ComposeSchema>(
path: string,
schema: Schema.Schema<ComposeSchema>,
) => Effect.Effect<ComposeSchema, ScriptError, unknown> = Effect.fn("getComposeYaml")(function* (path, schema) {
return yield* pipe(
getFileContent(path),
Effect.map((text: string): unknown => YAML.parse(text)),
Effect.flatMap((yaml: unknown) =>
Schema.decodeUnknownEffect(schema)(yaml, { errors: "all", onExcessProperty: "ignore" }),
),
Effect.mapError((error): ScriptError => {
if (error instanceof SchemaError) { if (error instanceof SchemaError) {
return new ScriptError({ cause: SchemaIssue.makeFormatterStandardSchemaV1()(error.issue) }); return new ScriptError({ cause: SchemaIssue.makeFormatterStandardSchemaV1()(error.issue) });
} else { } else {
@ -47,14 +71,15 @@ const getComposeYaml = <A>(filePath: string, schema: Schema.Schema<A>) =>
} }
}), }),
); );
});
const program: Effect.Effect<ReadonlyArray<string>, ScriptError> = pipe( const program: Effect.Effect<ReadonlyArray<string>, ScriptError> = pipe(
getComposeYaml(COMPOSE_PATH, ComposeSchema), getComposeYaml(COMPOSE_PATH, Compose),
Effect.flatMap((yaml: ComposeYaml) => getObjectKey("services", yaml)), Effect.map((compose: Compose) => getServicesFromComposeYaml(compose)),
Effect.map((keys: ReadonlyArray<string>) => EffectArray.filter(keys, (key) => key !== "wordpress")), Effect.map((keys: ReadonlyArray<string>) => EffectArray.filter(keys, (key) => key !== "wordpress")),
Effect.orElseSucceed(() => [""]), Effect.orElseSucceed(() => [""]),
Effect.tap((services: ReadonlyArray<string>) => { Effect.tap((services: ReadonlyArray<string>) => {
Bun.spawn({ cmd: ["podman", "compose", "pull", ...services], timeout: 10000 }); Bun.spawn({ cmd: ["podman", "compose", "pull", ...services], timeout: DEFAULT_CMD_TIMEOUT });
return Effect.succeed(services); return Effect.succeed(services);
}), }),
Effect.tapCause(Console.error), Effect.tapCause(Console.error),

View file

@ -1,4 +1,5 @@
import { expect, type Page, test } from "@playwright/test"; import { expect, test } from "@playwright/test";
import type { Page } from "@playwright/test";
type TestPage = { type TestPage = {
pageName: string; pageName: string;

View file

@ -24,8 +24,8 @@ type ProductsFixture = {
type ProductsKinds = { type ProductsKinds = {
allProducts: WCV3Products; allProducts: WCV3Products;
simpleProducts: WCV3Products; simpleProducts: WCV3Products;
simpleProductsWithStock: WCV3Products;
simpleProductsWithoutStock: WCV3Products; simpleProductsWithoutStock: WCV3Products;
simpleProductsWithStock: WCV3Products;
}; };
export const test = base.extend<ProductsFixture>({ export const test = base.extend<ProductsFixture>({
@ -34,7 +34,7 @@ export const test = base.extend<ProductsFixture>({
const backendHeaders: BackendHeaders = await getBackendHeadersFromHtml(page); const backendHeaders: BackendHeaders = await getBackendHeadersFromHtml(page);
const response = await request.get("/wp-json/wc/v3/products?page=1&per_page=100&status=publish", { const response = await request.get("/wp-json/wc/v3/products?page=1&per_page=100&status=publish", {
headers: { Nonce: backendHeaders.nonce, Authorization: `Basic ${backendHeaders.authString}` }, headers: { Authorization: `Basic ${backendHeaders.authString}`, Nonce: backendHeaders.nonce },
}); });
expect(response.ok(), "The API returned the list of every Product").toBeTruthy(); expect(response.ok(), "The API returned the list of every Product").toBeTruthy();
@ -50,15 +50,15 @@ export const test = base.extend<ProductsFixture>({
const kinds = { const kinds = {
allProducts, allProducts,
simpleProducts, simpleProducts,
simpleProductsWithStock,
simpleProductsWithoutStock, simpleProductsWithoutStock,
simpleProductsWithStock,
} satisfies ProductsKinds; } satisfies ProductsKinds;
await use(kinds); await use(kinds);
}, },
}); });
test("can add a Product without variation with stock to the Cart", async ({ products, page }) => { test("can add a Product without variation with stock to the Cart", async ({ page, products }) => {
// Prend un produit au hasard. // Prend un produit au hasard.
const randomProductIndex = getRandomIntInclusive(0, products.simpleProductsWithStock.length - 1); const randomProductIndex = getRandomIntInclusive(0, products.simpleProductsWithStock.length - 1);
const randomProduct = products.simpleProductsWithStock.at(randomProductIndex); const randomProduct = products.simpleProductsWithStock.at(randomProductIndex);
@ -71,7 +71,7 @@ test("can add a Product without variation with stock to the Cart", async ({ prod
await page.goto(randomProduct.permalink); await page.goto(randomProduct.permalink);
// Vérifie le bon état du bouton de l'ajout au Panier. // Vérifie le bon état du bouton de l'ajout au Panier.
const addToCartButton = page.getByRole("button", { name: "Add to cart", disabled: false }); const addToCartButton = page.getByRole("button", { disabled: false, name: "Add to cart" });
await addToCartButton.scrollIntoViewIfNeeded(); await addToCartButton.scrollIntoViewIfNeeded();
await expect(addToCartButton, "The add to cart button must be visible").toBeVisible(); await expect(addToCartButton, "The add to cart button must be visible").toBeVisible();
await expect(addToCartButton, "The add to cart button must be enabled").toBeEnabled(); await expect(addToCartButton, "The add to cart button must be enabled").toBeEnabled();
@ -91,7 +91,7 @@ test("can add a Product without variation with stock to the Cart", async ({ prod
expect(cartLink, "The cart items' indicator has been incremented").toBeDefined(); expect(cartLink, "The cart items' indicator has been incremented").toBeDefined();
}); });
test("can't add a Product without variation without stock to the Cart", async ({ products, page }) => { test("can't add a Product without variation without stock to the Cart", async ({ page, products }) => {
// Prend un produit au hasard. // Prend un produit au hasard.
const randomProductIndex = getRandomIntInclusive(0, products.simpleProductsWithoutStock.length - 1); const randomProductIndex = getRandomIntInclusive(0, products.simpleProductsWithoutStock.length - 1);
const randomProduct = products.simpleProductsWithoutStock.at(randomProductIndex); const randomProduct = products.simpleProductsWithoutStock.at(randomProductIndex);
@ -104,7 +104,7 @@ test("can't add a Product without variation without stock to the Cart", async ({
await page.goto(randomProduct.permalink); await page.goto(randomProduct.permalink);
// Vérifie le bon état du bouton de l'ajout au Panier. // Vérifie le bon état du bouton de l'ajout au Panier.
const outOfStockButton = page.getByRole("button", { name: "Out of stock", disabled: true }); const outOfStockButton = page.getByRole("button", { disabled: true, name: "Out of stock" });
await outOfStockButton.scrollIntoViewIfNeeded(); await outOfStockButton.scrollIntoViewIfNeeded();
await expect(outOfStockButton, "The add to cart button must be visible").toBeVisible(); await expect(outOfStockButton, "The add to cart button must be visible").toBeVisible();
await expect(outOfStockButton, "The add to cart button must be disabled").toBeDisabled(); await expect(outOfStockButton, "The add to cart button must be disabled").toBeDisabled();

View file

@ -1,8 +1,8 @@
import { test, expect, Page, Locator, Response, APIRequestContext } from "@playwright/test"; import { APIRequestContext, expect, Locator, Page, Response, test } from "@playwright/test";
import { WCV3Products } from "../../web/app/themes/haiku-atelier-2024/src/scripts/lib/types/api/v3/products"; import { WCV3Products } from "../../web/app/themes/haiku-atelier-2024/src/scripts/lib/types/api/v3/products";
import { BackendHeaders, getBackendHeadersFromHtml } from "./utils.ts"; import { BackendHeaders, getBackendHeadersFromHtml } from "./utils.ts";
test.describe.configure({ mode: "parallel", timeout: 60000 }); test.describe.configure({ mode: "parallel", timeout: 60_000 });
test("can scroll to the end of the grid", async ({ page }): Promise<void> => { test("can scroll to the end of the grid", async ({ page }): Promise<void> => {
await scrollToGridsEnd(page); await scrollToGridsEnd(page);
@ -22,12 +22,10 @@ test.skip("can access all Products' pages", async ({ page, request }): Promise<v
const getAllProductsLinks = async (page: Page, request: APIRequestContext): Promise<Array<string>> => { const getAllProductsLinks = async (page: Page, request: APIRequestContext): Promise<Array<string>> => {
const backendHeaders: BackendHeaders = await getBackendHeadersFromHtml(page); const backendHeaders: BackendHeaders = await getBackendHeadersFromHtml(page);
const response = await request.get("/wp-json/wc/v3/products?page=1&per_page=100&status=publish", { const response = await request.get("/wp-json/wc/v3/products?page=1&per_page=100&status=publish", {
headers: { Nonce: backendHeaders.nonce, Authorization: `Basic ${backendHeaders.authString}` }, headers: { Authorization: `Basic ${backendHeaders.authString}`, Nonce: backendHeaders.nonce },
}); });
const json = (await response.json()) as WCV3Products; const json = (await response.json()) as WCV3Products;
const links = json.map((p) => p.permalink); return json.map((p) => p.permalink);
return links;
}; };
const scrollToGridsEnd = async (page: Page): Promise<void> => { const scrollToGridsEnd = async (page: Page): Promise<void> => {

View file

@ -25,7 +25,6 @@
"noImplicitReturns": true, "noImplicitReturns": true,
"noImplicitThis": true, "noImplicitThis": true,
"noPropertyAccessFromIndexSignature": true, "noPropertyAccessFromIndexSignature": true,
"noStrictGenericChecks": false,
"noUncheckedIndexedAccess": true, "noUncheckedIndexedAccess": true,
"noUncheckedSideEffectImports": true, "noUncheckedSideEffectImports": true,
"noUnusedLocals": true, "noUnusedLocals": true,

View file

@ -22,7 +22,8 @@ $templates = ['404.twig'];
* *
* @throws Exception une exception est levée s'il est impossible d'obtenir la date de modification du fichier à charger * @throws Exception une exception est levée s'il est impossible d'obtenir la date de modification du fichier à charger
*/ */
function load_page_resources(): void { function load_page_resources(): void
{
Resource::enqueue_style_file( Resource::enqueue_style_file(
handle: 'haiku-atelier-2024-styles-page-a-propos', handle: 'haiku-atelier-2024-styles-page-a-propos',
path: '/assets/css/pages/page-modele-simple.css', path: '/assets/css/pages/page-modele-simple.css',
@ -31,7 +32,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

@ -26,10 +26,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::new(...), array: $wc_products);
callback: Product::new(...),
array: $wc_products,
);
$context['products'] = $products; $context['products'] = $products;
/** /**
@ -37,7 +34,8 @@ $context['products'] = $products;
* *
* @throws Exception une exception est levée s'il est impossible d'obtenir la date de modification du fichier à charger * @throws Exception une exception est levée s'il est impossible d'obtenir la date de modification du fichier à charger
*/ */
function load_page_resources(): void { function load_page_resources(): void
{
Resource::enqueue_style_file( Resource::enqueue_style_file(
handle: 'haiku-atelier-2024-styles-page-boutique', handle: 'haiku-atelier-2024-styles-page-boutique',
path: '/assets/css/pages/page-boutique.css', path: '/assets/css/pages/page-boutique.css',
@ -54,7 +52,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

@ -11,18 +11,18 @@ jQuery(document).ready(function ($) {
$(".customize-control-tinymce-editor").each(function () { $(".customize-control-tinymce-editor").each(function () {
// Get the toolbar strings that were passed from the PHP Class // Get the toolbar strings that were passed from the PHP Class
var tinyMCEToolbar1String = _wpCustomizeSettings.controls[$(this).attr("id")].skyrockettinymcetoolbar1; const tinyMCEToolbar1String = _wpCustomizeSettings.controls[$(this).attr("id")].skyrockettinymcetoolbar1;
var tinyMCEToolbar2String = _wpCustomizeSettings.controls[$(this).attr("id")].skyrockettinymcetoolbar2; const tinyMCEToolbar2String = _wpCustomizeSettings.controls[$(this).attr("id")].skyrockettinymcetoolbar2;
var tinyMCEMediaButtons = _wpCustomizeSettings.controls[$(this).attr("id")].skyrocketmediabuttons; const tinyMCEMediaButtons = _wpCustomizeSettings.controls[$(this).attr("id")].skyrocketmediabuttons;
wp.editor.initialize($(this).attr("id"), { wp.editor.initialize($(this).attr("id"), {
mediaButtons: tinyMCEMediaButtons,
quicktags: true,
tinymce: { tinymce: {
wpautop: true, wpautop: true,
toolbar1: tinyMCEToolbar1String, toolbar1: tinyMCEToolbar1String,
toolbar2: tinyMCEToolbar2String, toolbar2: tinyMCEToolbar2String,
}, },
quicktags: true,
mediaButtons: tinyMCEMediaButtons,
}); });
}); });
$(document).on("tinymce-editor-init", function (event, editor) { $(document).on("tinymce-editor-init", function (event, editor) {

View file

@ -22,7 +22,8 @@ $templates = ['accueil.twig'];
* *
* @throws Exception une exception est levée s'il est impossible d'obtenir la date de modification du fichier à charger * @throws Exception une exception est levée s'il est impossible d'obtenir la date de modification du fichier à charger
*/ */
function load_page_resources(): void { function load_page_resources(): void
{
Resource::enqueue_style_file( Resource::enqueue_style_file(
handle: 'haiku-atelier-2024-styles-page-accueil', handle: 'haiku-atelier-2024-styles-page-accueil',
path: '/assets/css/pages/page-accueil.css', path: '/assets/css/pages/page-accueil.css',
@ -35,7 +36,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

@ -22,7 +22,8 @@ Timber::init();
Timber::$dirname = ['views']; 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( // wp_enqueue_script_module(
// id: 'haiku-atelier-2024-gaffe', // id: 'haiku-atelier-2024-gaffe',
// deps: [], // deps: [],
@ -52,7 +53,8 @@ function load_scripts(): void {
add_action('wp_enqueue_scripts', 'load_scripts'); add_action('wp_enqueue_scripts', 'load_scripts');
// Charge les styles du thème // Charge les styles du thème
function charge_styles_haiku_atelier_2024(): void { function charge_styles_haiku_atelier_2024(): void
{
wp_enqueue_style( wp_enqueue_style(
handle: 'haiku-atelier-2024-styles', handle: 'haiku-atelier-2024-styles',
src: get_template_directory_uri() . '/assets/css/main.css', src: get_template_directory_uri() . '/assets/css/main.css',
@ -70,7 +72,8 @@ new StarterSite();
/** /**
* Personnalisation du thème. * Personnalisation du thème.
*/ */
function enregistre_personnalisations_theme(mixed $wp_customize): void { function enregistre_personnalisations_theme(mixed $wp_customize): void
{
// Section « Réseaux sociaux » // Section « Réseaux sociaux »
$wp_customize->add_section('liens_reseaux_sociaux', [ $wp_customize->add_section('liens_reseaux_sociaux', [
'title' => __('Liens des réseaux sociaux'), 'title' => __('Liens des réseaux sociaux'),
@ -144,7 +147,8 @@ function enregistre_personnalisations_theme(mixed $wp_customize): void {
add_action('customize_register', 'enregistre_personnalisations_theme'); add_action('customize_register', 'enregistre_personnalisations_theme');
function retire_tailles_image_par_defaut(mixed $sizes): mixed { function retire_tailles_image_par_defaut(mixed $sizes): mixed
{
/** @var list<string> */ /** @var list<string> */
$targets = ['full', 'thumbnail']; $targets = ['full', 'thumbnail'];
@ -177,7 +181,8 @@ add_filter('should_load_remote_block_patterns', '__return_false');
*/ */
// Charge Carbon Fields // Charge Carbon Fields
function charge_carbon_fields(): void { function charge_carbon_fields(): void
{
// Nécessaire pour que Carbon Fields trouve ses ressources // Nécessaire pour que Carbon Fields trouve ses ressources
define('Carbon_Fields\URL', home_url('/vendor/htmlburger/carbon-fields')); define('Carbon_Fields\URL', home_url('/vendor/htmlburger/carbon-fields'));
Carbon_Fields::boot(); Carbon_Fields::boot();

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

@ -34,7 +34,8 @@ $context['image_dimensions'] = $image_dimensions;
* *
* @throws Exception une exception est levée s'il est impossible d'obtenir la date de modification du fichier à charger * @throws Exception une exception est levée s'il est impossible d'obtenir la date de modification du fichier à charger
*/ */
function load_page_resources(): void { function load_page_resources(): void
{
Resource::enqueue_style_file( Resource::enqueue_style_file(
handle: 'haiku-atelier-2024-styles-page-a-propos', handle: 'haiku-atelier-2024-styles-page-a-propos',
path: '/assets/css/pages/page-a-propos.css', path: '/assets/css/pages/page-a-propos.css',
@ -47,7 +48,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

@ -104,7 +104,8 @@ $context['methodes_livraison'] = $methodes_livraison;
* *
* @throws Exception une exception est levée s'il est impossible d'obtenir la date de modification du fichier à charger * @throws Exception une exception est levée s'il est impossible d'obtenir la date de modification du fichier à charger
*/ */
function load_page_resources(): void { function load_page_resources(): void
{
Resource::enqueue_style_file( Resource::enqueue_style_file(
handle: 'haiku-atelier-2024-styles-page-panier', handle: 'haiku-atelier-2024-styles-page-panier',
path: '/assets/css/pages/page-panier.css', path: '/assets/css/pages/page-panier.css',
@ -118,7 +119,4 @@ function load_page_resources(): void {
add_action('wp_enqueue_scripts', load_page_resources(...)); add_action('wp_enqueue_scripts', load_page_resources(...));
// Rendu // Rendu
Timber::render( Timber::render(filenames: $templates, data: $context);
filenames: $templates,
data: $context,
);

View file

@ -22,7 +22,8 @@ use WC_Session_Handler;
header('Content-Type: application/json; charset=utf-8'); header('Content-Type: application/json; charset=utf-8');
// TODO: Appliquer le bon calcul pour les montants vs. percentages // TODO: Appliquer le bon calcul pour les montants vs. percentages
function get_discount_amount(WC_Coupon $coupon) { function get_discount_amount(WC_Coupon $coupon)
{
if ($coupon->get_discount_type() === 'fixed_cart') { if ($coupon->get_discount_type() === 'fixed_cart') {
return $coupon->get_amount() * 100; return $coupon->get_amount() * 100;
} else { } else {
@ -30,7 +31,8 @@ function get_discount_amount(WC_Coupon $coupon) {
} }
} }
function get_discount_duration(WC_Coupon $coupon): string { function get_discount_duration(WC_Coupon $coupon): string
{
if ($coupon->get_discount_type() === 'fixed_cart') { if ($coupon->get_discount_type() === 'fixed_cart') {
return 'once'; return 'once';
} else { } else {

View file

@ -22,7 +22,8 @@ $templates = ['contact.twig'];
* *
* @throws Exception une exception est levée s'il est impossible d'obtenir la date de modification du fichier à charger * @throws Exception une exception est levée s'il est impossible d'obtenir la date de modification du fichier à charger
*/ */
function load_page_resources(): void { function load_page_resources(): void
{
Resource::enqueue_style_file( Resource::enqueue_style_file(
handle: 'haiku-atelier-2024-styles-page-contact', handle: 'haiku-atelier-2024-styles-page-contact',
path: '/assets/css/pages/page-contact.css', path: '/assets/css/pages/page-contact.css',
@ -32,7 +33,4 @@ function load_page_resources(): void {
add_action('wp_enqueue_scripts', load_page_resources(...)); add_action('wp_enqueue_scripts', load_page_resources(...));
// Rendu // Rendu
Timber::render( Timber::render(filenames: $templates, data: $context);
filenames: $templates,
data: $context,
);

View file

@ -22,7 +22,8 @@ $templates = ['echec-commande.twig'];
* *
* @throws Exception une exception est levée s'il est impossible d'obtenir la date de modification du fichier à charger * @throws Exception une exception est levée s'il est impossible d'obtenir la date de modification du fichier à charger
*/ */
function load_page_resources(): void { function load_page_resources(): void
{
Resource::enqueue_style_file( Resource::enqueue_style_file(
handle: 'haiku-atelier-2024-styles-page-modele-simple', handle: 'haiku-atelier-2024-styles-page-modele-simple',
path: '/assets/css/pages/page-modele-simple.css', path: '/assets/css/pages/page-modele-simple.css',
@ -32,7 +33,4 @@ function load_page_resources(): void {
add_action('wp_enqueue_scripts', load_page_resources(...)); add_action('wp_enqueue_scripts', load_page_resources(...));
// Rendu // Rendu
Timber::render( Timber::render(filenames: $templates, data: $context);
filenames: $templates,
data: $context,
);

View file

@ -101,7 +101,8 @@ try {
$context['produits'] = $produits; $context['produits'] = $produits;
// Charge les scripts et styles de la page // Charge les scripts et styles de la page
function charge_scripts_styles_page_succes_commande(): void { function charge_scripts_styles_page_succes_commande(): void
{
wp_enqueue_style( wp_enqueue_style(
handle: 'haiku-atelier-2024-styles-page-succes-commande', handle: 'haiku-atelier-2024-styles-page-succes-commande',
src: get_template_directory_uri() . '/assets/css/pages/page-succes-commande.css', src: get_template_directory_uri() . '/assets/css/pages/page-succes-commande.css',
@ -114,10 +115,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

@ -20,7 +20,8 @@ $templates = ['cgv.twig'];
* *
* @throws Exception une exception est levée s'il est impossible d'obtenir la date de modification du fichier à charger * @throws Exception une exception est levée s'il est impossible d'obtenir la date de modification du fichier à charger
*/ */
function load_page_resources(): void { function load_page_resources(): void
{
Resource::enqueue_style_file( Resource::enqueue_style_file(
handle: '/assets/css/pages/page-modele-simple.css', handle: '/assets/css/pages/page-modele-simple.css',
path: '/assets/css/pages/page-modele-simple.css', path: '/assets/css/pages/page-modele-simple.css',
@ -30,7 +31,4 @@ function load_page_resources(): void {
add_action('wp_enqueue_scripts', load_page_resources(...)); add_action('wp_enqueue_scripts', load_page_resources(...));
// Rendu // Rendu
Timber::render( Timber::render(filenames: $templates, data: $context);
filenames: $templates,
data: $context,
);

View file

@ -61,7 +61,8 @@ $context['same_collection_products'] = $same_collection_products;
* *
* @throws Exception une exception est levée s'il est impossible d'obtenir la date de modification du fichier à charger * @throws Exception une exception est levée s'il est impossible d'obtenir la date de modification du fichier à charger
*/ */
function load_page_resources(): void { function load_page_resources(): void
{
Resource::enqueue_script_module_file( Resource::enqueue_script_module_file(
id: 'haiku-atelier-2024-scripts-page-produit', id: 'haiku-atelier-2024-scripts-page-produit',
path: '/assets/js/scripts-page-produit.js', path: '/assets/js/scripts-page-produit.js',
@ -75,7 +76,4 @@ function load_page_resources(): void {
add_action('wp_enqueue_scripts', load_page_resources(...)); add_action('wp_enqueue_scripts', load_page_resources(...));
// Rendu // Rendu
Timber::render( Timber::render(filenames: $templates, data: $context);
filenames: $templates,
data: $context,
);

View file

@ -28,8 +28,10 @@ use function wp_create_nonce;
use function wp_get_attachment_image_src; use function wp_get_attachment_image_src;
use function wpautop; use function wpautop;
final class StarterSite extends Site { final class StarterSite extends Site
public function __construct() { {
public function __construct()
{
add_action('after_setup_theme', $this->defini_fonctionnalites_theme(...)); add_action('after_setup_theme', $this->defini_fonctionnalites_theme(...));
add_action('after_setup_theme', [$this, 'charge_traductions_theme']); add_action('after_setup_theme', [$this, 'charge_traductions_theme']);
@ -44,7 +46,8 @@ final class StarterSite extends Site {
* *
* @return array<int,mixed> * @return array<int,mixed>
*/ */
public function ajoute_au_contexte_twig(array $context): array { public function ajoute_au_contexte_twig(array $context): array
{
$context['site'] = $this; $context['site'] = $this;
$context['environnement'] = env('WP_ENV'); $context['environnement'] = env('WP_ENV');
@ -94,22 +97,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 +122,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;
@ -159,7 +148,8 @@ final class StarterSite extends Site {
return $context; return $context;
} }
public function defini_fonctionnalites_theme(): void { public function defini_fonctionnalites_theme(): void
{
// Laisse WordPress gérer le titre de la page // Laisse WordPress gérer le titre de la page
add_theme_support('title-tag'); add_theme_support('title-tag');
@ -181,9 +171,11 @@ final class StarterSite extends Site {
* *
* @param array $options un tableau avec les options d'environnement * @param array $options un tableau avec les options d'environnement
*/ */
public function maj_environnement_twig(array $options): array { public function maj_environnement_twig(array $options): array
{
return $options; return $options;
} }
// public function charge_traductions_theme(): void { // public function charge_traductions_theme(): void {
// load_theme_textdomain("haiku-atelier-2024", get_template_directory() . "/languages"); // load_theme_textdomain("haiku-atelier-2024", get_template_directory() . "/languages");
// } // }

View file

@ -9,7 +9,8 @@ declare(strict_types=1);
use Carbon_Fields\Container; use Carbon_Fields\Container;
use Carbon_Fields\Field; use Carbon_Fields\Field;
function cree_champs_personnalises_produit(): void { function cree_champs_personnalises_produit(): void
{
Container::make('post_meta', "Product's Details") Container::make('post_meta', "Product's Details")
->where('post_type', '=', 'product') ->where('post_type', '=', 'product')
->add_fields([ ->add_fields([
@ -26,7 +27,8 @@ function cree_champs_personnalises_produit(): void {
]); ]);
} }
function cree_champ_personnalise_commande($order): void { function cree_champ_personnalise_commande($order): void
{
woocommerce_wp_text_input([ woocommerce_wp_text_input([
'id' => 'tracking_number', 'id' => 'tracking_number',
'label' => 'Tracking Number:', 'label' => 'Tracking Number:',
@ -35,7 +37,8 @@ function cree_champ_personnalise_commande($order): void {
]); ]);
} }
function maj_champ_personnalise_commande($order_id): void { function maj_champ_personnalise_commande($order_id): void
{
$order = wc_get_order($order_id); $order = wc_get_order($order_id);
$order->update_meta_data('tracking_number', wc_clean($_POST['tracking_number'])); $order->update_meta_data('tracking_number', wc_clean($_POST['tracking_number']));
$order->save(); $order->save();

View file

@ -8,7 +8,8 @@
declare(strict_types=1); declare(strict_types=1);
function enregistre_controle_personnalise_tinymce(): void { function enregistre_controle_personnalise_tinymce(): void
{
/** /**
* TinyMCE Custom Control. * TinyMCE Custom Control.
* *
@ -17,14 +18,16 @@ function enregistre_controle_personnalise_tinymce(): void {
* *
* @see https://github.com/maddisondesigns * @see https://github.com/maddisondesigns
*/ */
final class ControlesPersonnalises extends WP_Customize_Control { final class ControlesPersonnalises extends WP_Customize_Control
{
/** The type of control being rendered. */ /** The type of control being rendered. */
public $type = 'editeur_tinymce'; public $type = 'editeur_tinymce';
/** /**
* Enqueue our scripts and styles. * Enqueue our scripts and styles.
*/ */
public function enqueue(): void { public function enqueue(): void
{
wp_enqueue_script( wp_enqueue_script(
handle: 'controle-personnalise-tinymce', handle: 'controle-personnalise-tinymce',
src: get_template_directory_uri() . '/assets/vendor/controle-personnalise-tinymce.js', src: get_template_directory_uri() . '/assets/vendor/controle-personnalise-tinymce.js',
@ -38,7 +41,8 @@ function enregistre_controle_personnalise_tinymce(): void {
/** /**
* Render the control in the customizer. * Render the control in the customizer.
*/ */
public function render_content(): void { ?> public function render_content(): void
{ ?>
<div class="tinymce-control"> <div class="tinymce-control">
<span class="customize-control-title"><?php echo esc_html($this->label); ?></span> <span class="customize-control-title"><?php echo esc_html($this->label); ?></span>
<?php if (!empty($this->description)) { ?> <?php if (!empty($this->description)) { ?>
@ -55,7 +59,8 @@ function enregistre_controle_personnalise_tinymce(): void {
/** /**
* Pass our TinyMCE toolbar string to JavaScript. * Pass our TinyMCE toolbar string to JavaScript.
*/ */
public function to_json(): void { public function to_json(): void
{
parent::to_json(); parent::to_json();
$this->json['skyrockettinymcetoolbar1'] = isset($this->input_attrs['toolbar1']) $this->json['skyrockettinymcetoolbar1'] = isset($this->input_attrs['toolbar1'])

View file

@ -8,7 +8,8 @@ use Illuminate\Support\Arr;
use WC_Product_Attribute; use WC_Product_Attribute;
use WP_Term; use WP_Term;
final readonly class Attribute { final readonly class Attribute
{
/** /**
* @param list<AttributeOption> $options * @param list<AttributeOption> $options
*/ */
@ -18,7 +19,8 @@ final readonly class Attribute {
public array $options, public array $options,
) {} ) {}
public static function new(WC_Product_Attribute $attribute): self { public static function new(WC_Product_Attribute $attribute): self
{
$name = wc_attribute_label($attribute->get_name()); $name = wc_attribute_label($attribute->get_name());
$slug = $attribute->get_name(); $slug = $attribute->get_name();
/** @var list<WP_Term> */ /** @var list<WP_Term> */
@ -26,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

@ -6,22 +6,20 @@ namespace HaikuAtelier\Data;
use WP_Term; use WP_Term;
final readonly class AttributeOption { final readonly class AttributeOption
{
public function __construct( public function __construct(
public int $id, public int $id,
public string $name, public string $name,
public string $slug, public string $slug,
) {} ) {}
public static function new(WP_Term $term): self { public static function new(WP_Term $term): self
{
$id = $term->term_taxonomy_id; $id = $term->term_taxonomy_id;
$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

@ -11,7 +11,8 @@ use function is_float;
use function is_int; use function is_int;
use function is_string; use function is_string;
final readonly class Cart { final readonly class Cart
{
public function __construct() {} public function __construct() {}
/** La valeur par défaut d'une donnée invalide du Panier. */ /** La valeur par défaut d'une donnée invalide du Panier. */
@ -22,7 +23,8 @@ final readonly class Cart {
* *
* @return array<int,string> * @return array<int,string>
*/ */
public static function get_allowed_countries(): array { public static function get_allowed_countries(): array
{
return [ return [
'AD', 'AD',
'AL', 'AL',
@ -95,7 +97,8 @@ final readonly class Cart {
]; ];
} }
public static function parse_cart_value(int|float|string|bool $cart_value): string { public static function parse_cart_value(int|float|string|bool $cart_value): string
{
if (is_int($cart_value) || is_float($cart_value)) { if (is_int($cart_value) || is_float($cart_value)) {
return self::format_number($cart_value); return self::format_number($cart_value);
} }
@ -110,7 +113,8 @@ final readonly class Cart {
return '0.00'; return '0.00';
} }
private static function format_number(int|float $number): string { private static function format_number(int|float $number): string
{
$formatted_number = Number::format( $formatted_number = Number::format(
number: $number, number: $number,
// precision et max_precision sont mutuellement exclusifs. // precision et max_precision sont mutuellement exclusifs.

View file

@ -13,7 +13,8 @@ use WP_Term;
use function Psl\Option\from_nullable; use function Psl\Option\from_nullable;
final readonly class Product { final readonly class Product
{
/** /**
* @param list<Attribute> $attributes * @param list<Attribute> $attributes
* @param list<string> $left_column_photos * @param list<string> $left_column_photos
@ -41,12 +42,14 @@ final readonly class Product {
/** /**
* @return list<Attribute> * @return list<Attribute>
*/ */
public static function get_attributes_for_product(WC_Product $product): array { public static function get_attributes_for_product(WC_Product $product): array
{
/** @var list<Attribute> */ /** @var list<Attribute> */
return $product->get_attributes() |> (static fn($attributes) => Arr::map($attributes, Attribute::new(...))); return $product->get_attributes() |> (static fn($attributes) => Arr::map($attributes, Attribute::new(...)));
} }
public static function new(WC_Product $product): self { public static function new(WC_Product $product): self
{
/** @var list<Attribute> */ /** @var list<Attribute> */
$attributes = self::get_attributes_for_product($product); $attributes = self::get_attributes_for_product($product);
/** @var lowercase-string */ /** @var lowercase-string */

View file

@ -6,7 +6,8 @@ namespace HaikuAtelier\Data;
use WC_Product; use WC_Product;
final readonly class ProductVariation { final readonly class ProductVariation
{
/** /**
* @param int $id L'ID de la Variation * @param int $id L'ID de la Variation
* @param string $price Le prix de la Variation * @param string $price Le prix de la Variation
@ -21,7 +22,8 @@ final readonly class ProductVariation {
/** /**
* Créé une nouvelle instance de `ProductVariation` à partir d'un `WC_Product`. * Créé une nouvelle instance de `ProductVariation` à partir d'un `WC_Product`.
*/ */
public static function new(WC_Product $product): self { public static function new(WC_Product $product): self
{
$id = $product->get_id(); $id = $product->get_id();
$price = $product->get_price(); $price = $product->get_price();
/** @var list<ProductVariationAttribute> */ /** @var list<ProductVariationAttribute> */

View file

@ -4,7 +4,8 @@ declare(strict_types=1);
namespace HaikuAtelier\Data; namespace HaikuAtelier\Data;
final readonly class ProductVariationAttribute { final readonly class ProductVariationAttribute
{
/** /**
* @param string $attribute Le slug de l'Attribut * @param string $attribute Le slug de l'Attribut
* @param string $value Le slug de la valeur de l'Attribut * @param string $value Le slug de la valeur de l'Attribut

View file

@ -7,7 +7,8 @@
declare(strict_types=1); declare(strict_types=1);
// Désactive divers transformations du contenu par WordPress // Désactive divers transformations du contenu par WordPress
function desactive_wpautop(): void { function desactive_wpautop(): void
{
remove_filter('the_content', 'wpautop'); remove_filter('the_content', 'wpautop');
} }
@ -18,7 +19,8 @@ function desactive_wpautop(): void {
* *
* @return array<string, bool> le même tableau avec des configurations en plus * @return array<string, bool> le même tableau avec des configurations en plus
*/ */
function desactive_transformation_contenu_tinymce(array $configuration): array { function desactive_transformation_contenu_tinymce(array $configuration): array
{
// Ne supprime pas les retours à la ligne // Ne supprime pas les retours à la ligne
$configuration['remove_linebreaks'] = false; $configuration['remove_linebreaks'] = false;
// Convertis les caractères de retours à la ligne en <br> // Convertis les caractères de retours à la ligne en <br>
@ -37,18 +39,21 @@ function desactive_transformation_contenu_tinymce(array $configuration): array {
* *
* @return array<string, string> le même tableau avec SVG en plus * @return array<string, string> le même tableau avec SVG en plus
*/ */
function autorise_import_svg_mediatheque(array $file_types): array { function autorise_import_svg_mediatheque(array $file_types): array
{
$new_filetypes = []; $new_filetypes = [];
$new_filetypes['svg'] = 'image/svg+xml'; $new_filetypes['svg'] = 'image/svg+xml';
return array_merge($file_types, $new_filetypes); return array_merge($file_types, $new_filetypes);
} }
function retire_motifs_blocs_gutenberg(): void { function retire_motifs_blocs_gutenberg(): void
{
remove_theme_support('core-block-patterns'); remove_theme_support('core-block-patterns');
} }
function retire_styles_core_block(): void { function retire_styles_core_block(): void
{
wp_dequeue_style('core-block-supports'); wp_dequeue_style('core-block-supports');
} }

View file

@ -16,7 +16,8 @@ require_once 'TraitementInformations.php';
* Désactive les fonctionnalités liées aux images Produit WooCommerce, empêchant le chargement de * Désactive les fonctionnalités liées aux images Produit WooCommerce, empêchant le chargement de
* scripts et styles inutiles. * scripts et styles inutiles.
*/ */
function desactive_images_produit_woocommerce(): void { function desactive_images_produit_woocommerce(): void
{
remove_action('woocommerce_before_shop_loop_item_title', 'woocommerce_template_loop_product_thumbnail', 10); remove_action('woocommerce_before_shop_loop_item_title', 'woocommerce_template_loop_product_thumbnail', 10);
remove_action('woocommerce_before_single_product_summary', 'woocommerce_show_product_images', 20); remove_action('woocommerce_before_single_product_summary', 'woocommerce_show_product_images', 20);
remove_action('woocommerce_product_thumbnails', 'woocommerce_show_product_thumbnails', 20); remove_action('woocommerce_product_thumbnails', 'woocommerce_show_product_thumbnails', 20);
@ -25,7 +26,8 @@ function desactive_images_produit_woocommerce(): void {
/** /**
* Désactive les champs liés aux images Produit de l'administration. * Désactive les champs liés aux images Produit de l'administration.
*/ */
function desactive_champs_admin_images_produit_woocommerce(): void { function desactive_champs_admin_images_produit_woocommerce(): void
{
// Désactive le champ « Galerie d'images du Produit » // Désactive le champ « Galerie d'images du Produit »
remove_meta_box('woocommerce-product-images', 'product', 'side'); remove_meta_box('woocommerce-product-images', 'product', 'side');
} }
@ -35,7 +37,8 @@ function desactive_champs_admin_images_produit_woocommerce(): void {
/** /**
* Désactive tous les scripts et styles WooCommerce. * Désactive tous les scripts et styles WooCommerce.
*/ */
function dequeue_woocommerce_styles_scripts(): void { function dequeue_woocommerce_styles_scripts(): void
{
// Styles // Styles
wp_dequeue_style('photoswipe-default-skin-css'); wp_dequeue_style('photoswipe-default-skin-css');
wp_dequeue_style('photoswipe-default-skin'); wp_dequeue_style('photoswipe-default-skin');
@ -82,7 +85,8 @@ function dequeue_woocommerce_styles_scripts(): void {
/** /**
* Désactive scripts et styles liés aux blocs Gutenberg. * Désactive scripts et styles liés aux blocs Gutenberg.
*/ */
function desactive_blocs_gutenberg_woocommerce(): void { function desactive_blocs_gutenberg_woocommerce(): void
{
wp_deregister_style('wc-blocks-style'); wp_deregister_style('wc-blocks-style');
wp_dequeue_style('wc-blocks-style'); wp_dequeue_style('wc-blocks-style');
} }
@ -92,7 +96,8 @@ function desactive_blocs_gutenberg_woocommerce(): void {
* *
* Cette fonction surcharge son homonyme et désactive les fonctionnalités non désirées. * Cette fonction surcharge son homonyme et désactive les fonctionnalités non désirées.
*/ */
function woocommerce_photoswipe(): void { function woocommerce_photoswipe(): void
{
remove_theme_support('wc-product-gallery-zoom'); remove_theme_support('wc-product-gallery-zoom');
remove_theme_support('wc-product-gallery-lightbox'); remove_theme_support('wc-product-gallery-lightbox');
remove_theme_support('wc-product-gallery-slider'); remove_theme_support('wc-product-gallery-slider');
@ -101,18 +106,21 @@ function woocommerce_photoswipe(): void {
/** /**
* Désactive l'Attribution des Commandes WooCommerce. * Désactive l'Attribution des Commandes WooCommerce.
*/ */
function desactive_attribution_commande_woocommerce(): void { function desactive_attribution_commande_woocommerce(): void
{
update_option('woocommerce_feature_order_attribution_enabled', ''); update_option('woocommerce_feature_order_attribution_enabled', '');
} }
/** /**
* Retire d'autres merdes WooCommerce. * Retire d'autres merdes WooCommerce.
*/ */
function retire_script_galerie(): void { function retire_script_galerie(): void
{
remove_action('wp_head', 'wc_gallery_noscript'); remove_action('wp_head', 'wc_gallery_noscript');
} }
function retire_merdes_wc(): void { function retire_merdes_wc(): void
{
// remove WC generator tag // remove WC generator tag
remove_filter('get_the_generator_html', 'wc_generator_tag', 10, 2); remove_filter('get_the_generator_html', 'wc_generator_tag', 10, 2);
remove_filter('get_the_generator_xhtml', 'wc_generator_tag', 10, 2); remove_filter('get_the_generator_xhtml', 'wc_generator_tag', 10, 2);
@ -145,13 +153,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 => genere_balise_img_multiformats(
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 +167,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 => genere_balise_img_multiformats(
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],
); );
@ -209,7 +211,8 @@ add_filter('woocommerce_rest_prepare_product_object', 'genere_prix_maximal_produ
/** /**
* Retire la propagande commerciale de WooCommerce du menu. * Retire la propagande commerciale de WooCommerce du menu.
*/ */
function remove_payments_ad_tab(): void { function remove_payments_ad_tab(): void
{
remove_menu_page('admin.php?page=wc-settings&tab=checkout'); remove_menu_page('admin.php?page=wc-settings&tab=checkout');
} }

View file

@ -9,7 +9,8 @@ declare(strict_types=1);
/** /**
* Enregistre la Taxonomie « Collection ». * Enregistre la Taxonomie « Collection ».
*/ */
function enregistre_taxonomie_collection(): void { function enregistre_taxonomie_collection(): void
{
$labels = [ $labels = [
'add_new_item' => __('Add New Collection'), 'add_new_item' => __('Add New Collection'),
'all_items' => __('All Collections'), 'all_items' => __('All Collections'),

View file

@ -21,7 +21,8 @@ use function Crell\fp\pipe;
* *
* @return string TODO * @return string TODO
*/ */
function genere_balise_img_multiformats(string $id, bool $lazy = false): string { function genere_balise_img_multiformats(string $id, bool $lazy = false): string
{
$int_id = (int) $id; $int_id = (int) $id;
if (-1 === $int_id) { if (-1 === $int_id) {
@ -43,9 +44,7 @@ function genere_balise_img_multiformats(string $id, bool $lazy = false): string
array: $tableau, array: $tableau,
callback: static fn($chemin_format): bool => false !== $chemin_format, callback: static fn($chemin_format): bool => false !== $chemin_format,
), ),
static fn($tableau): array => array_map( static fn($tableau): array => array_map(array: $tableau, callback: static fn($chemin_format): array => [
array: $tableau,
callback: static fn($chemin_format): array => [
'format' => pathinfo((string) $chemin_format)['extension'], 'format' => pathinfo((string) $chemin_format)['extension'],
'taille' => filesize($chemin_format), 'taille' => filesize($chemin_format),
'url' => 'url' =>
@ -54,13 +53,9 @@ function genere_balise_img_multiformats(string $id, bool $lazy = false): string
. pathinfo($url)['filename'] . pathinfo($url)['filename']
. '.' . '.'
. pathinfo((string) $chemin_format)['extension'], . pathinfo((string) $chemin_format)['extension'],
], ]),
),
);
usort(
array: $formats,
callback: static fn($a, $b): int => $a['taille'] <=> $b['taille'],
); );
usort(array: $formats, callback: static fn($a, $b): int => $a['taille'] <=> $b['taille']);
// Construis les balises <source> avec les formats valides // Construis les balises <source> avec les formats valides
$sources = ''; $sources = '';
@ -90,7 +85,8 @@ function genere_balise_img_multiformats(string $id, bool $lazy = false): string
/** /**
* TODO. * TODO.
*/ */
function tri_variations_par_prix_descendant(WC_Product $a, WC_Product $b): int { function tri_variations_par_prix_descendant(WC_Product $a, WC_Product $b): int
{
return $b->get_price() <=> $a->get_price(); return $b->get_price() <=> $a->get_price();
} }
@ -100,16 +96,14 @@ function tri_variations_par_prix_descendant(WC_Product $a, WC_Product $b): int {
* *
* @return mixed un tableau avec uniquement les informations pour la Grille de Produits * @return mixed un tableau avec uniquement les informations pour la Grille de Produits
*/ */
function recupere_informations_produit_shop(WC_Product $produit): mixed { function recupere_informations_produit_shop(WC_Product $produit): mixed
{
/** @var int $prix_maximal Le prix maximal du Produit. */ /** @var int $prix_maximal Le prix maximal du Produit. */
$prix_maximal = pipe( $prix_maximal = pipe(
// Récupère les Variations // Récupère les Variations
$produit->get_children(), $produit->get_children(),
// Récupère les informations de chaque Variation // Récupère les informations de chaque Variation
static fn($enfants): array => array_map( static fn($enfants): array => array_map(callback: wc_get_product(...), array: $enfants),
callback: wc_get_product(...),
array: $enfants,
),
// Trie les Variations par prix descendant // Trie les Variations par prix descendant
static fn($variations): array => array_map( static fn($variations): array => array_map(
callback: static fn($variation) => $variation->get_price(), callback: static fn($variation) => $variation->get_price(),
@ -153,7 +147,8 @@ function recupere_informations_produit_shop(WC_Product $produit): mixed {
/** /**
* Retourne un tableau associatif des informations affichées sur la page Produit depuis les données brutes d'un Produit. * Retourne un tableau associatif des informations affichées sur la page Produit depuis les données brutes d'un Produit.
*/ */
function recupere_informations_produit_page_produit(WC_Product $product): mixed { function recupere_informations_produit_page_produit(WC_Product $product): mixed
{
/** @var list<Attribute> */ /** @var list<Attribute> */
$attributs = Product::get_attributes_for_product($product); $attributs = Product::get_attributes_for_product($product);
@ -205,7 +200,8 @@ function recupere_informations_produit_page_produit(WC_Product $product): mixed
* *
* Pour faciliter l'usage avec `array_map`, utilise une fonction avec curryfication. * Pour faciliter l'usage avec `array_map`, utilise une fonction avec curryfication.
*/ */
function recupere_produits_meme_collection(string $slug_collection): callable { function recupere_produits_meme_collection(string $slug_collection): callable
{
return static fn(int $id_produit): array|stdClass => wc_get_products([ return static fn(int $id_produit): array|stdClass => wc_get_products([
'exclude' => [$id_produit], 'exclude' => [$id_produit],
'limit' => 4, 'limit' => 4,
@ -218,7 +214,8 @@ function recupere_produits_meme_collection(string $slug_collection): callable {
// Page Panier // Page Panier
function recupere_et_formate_attributs_produit(mixed $attributs_produit): mixed { function recupere_et_formate_attributs_produit(mixed $attributs_produit): mixed
{
return [ return [
'taille' => ['nom' => 'Size', 'valeur' => $attributs_produit['pa_size'] ?? false], 'taille' => ['nom' => 'Size', 'valeur' => $attributs_produit['pa_size'] ?? false],
'pierre' => ['nom' => 'Stone', 'valeur' => $attributs_produit['pa_stone'] ?? false], 'pierre' => ['nom' => 'Stone', 'valeur' => $attributs_produit['pa_stone'] ?? false],

View file

@ -9,11 +9,13 @@ use Illuminate\Support\Arr;
use function is_array; use function is_array;
use function is_string; use function is_string;
final readonly class HaikuProduct { final readonly class HaikuProduct
{
/** /**
* @return list<string> * @return list<string>
*/ */
public static function get_left_column_photos(int $post_id): array { public static function get_left_column_photos(int $post_id): array
{
/** @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)))
@ -23,7 +25,8 @@ final readonly class HaikuProduct {
/** /**
* @return list<string> * @return list<string>
*/ */
public static function get_right_column_photos(int $post_id): array { public static function get_right_column_photos(int $post_id): array
{
$meta = carbon_get_post_meta($post_id, 'photos_colonne_droite'); $meta = carbon_get_post_meta($post_id, 'photos_colonne_droite');
if (is_array($meta)) { if (is_array($meta)) {

View file

@ -12,11 +12,13 @@ use function is_array;
use function Psl\Option\none; use function Psl\Option\none;
use function Psl\Option\some; use function Psl\Option\some;
final readonly class Post { final readonly class Post
{
/** /**
* @return Option\Option<mixed> * @return Option\Option<mixed>
*/ */
public static function get_post_meta(int $post_id, string $key): Option\Option { public static function get_post_meta(int $post_id, string $key): Option\Option
{
/** @var false|mixed|string */ /** @var false|mixed|string */
$value = get_post_meta($post_id, $key, true); $value = get_post_meta($post_id, $key, true);
@ -30,7 +32,8 @@ final readonly class Post {
/** /**
* @return Option\Option<array<mixed>> * @return Option\Option<array<mixed>>
*/ */
public static function get_post_meta_array(int $post_id, string $key): Option\Option { public static function get_post_meta_array(int $post_id, string $key): Option\Option
{
/** @var array<mixed>|false */ /** @var array<mixed>|false */
$value = get_post_meta($post_id, $key, false); $value = get_post_meta($post_id, $key, false);
@ -44,7 +47,8 @@ final readonly class Post {
/** /**
* @return Option\Option<array<mixed>> * @return Option\Option<array<mixed>>
*/ */
public static function get_terms(int $post_id, string $taxonomy_name): Option\Option { public static function get_terms(int $post_id, string $taxonomy_name): Option\Option
{
/** @var false|list<WP_Term>|WP_Error */ /** @var false|list<WP_Term>|WP_Error */
$terms = get_the_terms($post_id, $taxonomy_name); $terms = get_the_terms($post_id, $taxonomy_name);

View file

@ -8,8 +8,10 @@ use Exception;
use function is_bool; use function is_bool;
final readonly class Resource { final readonly class Resource
public static function enqueue_script_module_file(string $path, string $id): void { {
public static function enqueue_script_module_file(string $path, string $id): void
{
$file_uri = get_template_directory_uri() . $path; $file_uri = get_template_directory_uri() . $path;
$file_path = get_template_directory() . $path; $file_path = get_template_directory() . $path;
@ -21,15 +23,11 @@ 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,
);
} }
public static function enqueue_style_file(string $path, string $handle): void { public static function enqueue_style_file(string $path, string $handle): void
{
$file_uri = get_template_directory_uri() . $path; $file_uri = get_template_directory_uri() . $path;
$file_path = get_template_directory() . $path; $file_path = get_template_directory() . $path;
@ -41,12 +39,6 @@ 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,
media: 'all',
);
} }
} }

View file

@ -11,11 +11,13 @@ use function is_array;
use function Psl\Option\none; 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<list<WP_Term>> * @return Option\Option<list<WP_Term>>
*/ */
public static function get_terms(int $post_id, string $taxonomy_name): Option\Option { public static function get_terms(int $post_id, string $taxonomy_name): Option\Option
{
$terms = get_the_terms($post_id, $taxonomy_name); $terms = get_the_terms($post_id, $taxonomy_name);
if (is_array($terms)) { if (is_array($terms)) {

View file

@ -1,4 +1,4 @@
import { pipe, Option } from "effect"; import { Option, pipe } from "effect";
export const getOptionOrThrowWithError = export const getOptionOrThrowWithError =
(message: string) => (message: string) =>

View file

@ -3,18 +3,18 @@
import { ATTRIBUT_CHARGEMENT } from "../constantes/dom"; import { ATTRIBUT_CHARGEMENT } from "../constantes/dom";
// Types // Types
interface AnimationCycleTexte { type AnimationCycleTexte = {
callback: () => void; callback: () => void;
etapes: Array<string>; etapes: Array<string>;
index: number; index: number;
interval: NodeJS.Timeout; interval: NodeJS.Timeout;
} };
interface ParametresAnimationCycleTexte { type ParametresAnimationCycleTexte = {
attribut: string; attribut: string;
element: HTMLElement; element: HTMLElement;
etapes: Array<string>; etapes: Array<string>;
} };
/** /**
* Créer le nécessaire pour une animation s'exécutant jusqu'à ce que un interval soit manuellement arrêté. L'animation * Créer le nécessaire pour une animation s'exécutant jusqu'à ce que un interval soit manuellement arrêté. L'animation
@ -39,7 +39,7 @@ const lanceAnimationCycleTexte = (args: ParametresAnimationCycleTexte): Animatio
}, },
etapes: args.etapes, etapes: args.etapes,
index: 0, index: 0,
interval: setInterval(() => {}, 2147483647), interval: setInterval(() => {}, 2_147_483_647),
}; };
return animation; return animation;

View file

@ -123,8 +123,12 @@ export const reporteEtLeveErreur = <E extends Error>(erreur: E): never => {
export const reporteEtJournaliseErreur = <E extends Error>(erreur: E): void => { export const reporteEtJournaliseErreur = <E extends Error>(erreur: E): void => {
reporteErreur(erreur); reporteErreur(erreur);
console.error(erreur); console.error(erreur);
if (erreur instanceof ValiError) console.error(erreur.issues); if (erreur instanceof ValiError) {
if (erreur instanceof ErreurAdresseInvalide) console.error(erreur.problemes); console.error(erreur.issues);
}
if (erreur instanceof ErreurAdresseInvalide) {
console.error(erreur.problemes);
}
}; };
export const reporteEtRetourneErreur = <E extends Error>(erreur: E): E => { export const reporteEtRetourneErreur = <E extends Error>(erreur: E): E => {

View file

@ -7,12 +7,12 @@ export const CODE_PROMO_MAJ_EVENT = new CustomEvent(CODE_PROMO_MAJ, {});
// Interfaces // Interfaces
export interface UpdatedShippingRatesEvent extends Event { export type UpdatedShippingRatesEvent = {
detail: { refresh_methods: boolean; shipping_rates: ReadonlyArray<WCStoreShippingRateShippingRate> }; detail: { refresh_methods: boolean; shipping_rates: ReadonlyArray<WCStoreShippingRateShippingRate> };
} } & Event;
export interface UpdatedTotalsEvent extends Event { export type UpdatedTotalsEvent = {
detail: { totals: WCStoreCartTotals }; detail: { totals: WCStoreCartTotals };
} } & Event;
// Méthodes // Méthodes

View file

@ -1,6 +1,7 @@
import { pipe } from "@mobily/ts-belt"; import { pipe } from "@mobily/ts-belt";
import { Either } from "purify-ts"; import { Either } from "purify-ts";
import { parse, type ValiError } from "valibot"; import { parse } from "valibot";
import type { ValiError } from "valibot";
import type { import type {
MessageMajBoutonPanier, MessageMajBoutonPanier,

View file

@ -36,7 +36,7 @@ type ArgumentsPostBackendWC = {
route: string; route: string;
}; };
// fetch // Fetch
export const getBackend = (args: ArgumentsGetBackendWC): Promise<Response> => export const getBackend = (args: ArgumentsGetBackendWC): Promise<Response> =>
fetch(args.route, { fetch(args.route, {
@ -120,19 +120,16 @@ export const safeFetch = (f: Promise<Response>): EitherAsync<DOMException | Type
EitherAsync<DOMException | TypeError, Response>(async () => await f); EitherAsync<DOMException | TypeError, Response>(async () => await f);
// Réponses Simplifiées // Réponses Simplifiées
export const newPartialResponse = async (reponse: Response): Promise<SimplifiedResponse> => { export const newPartialResponse = async (reponse: Response): Promise<SimplifiedResponse> => ({
return {
body: await reponse.json(), body: await reponse.json(),
status: reponse.status, status: reponse.status,
}; });
};
export const traiteErreursBackendWooCommerce = (rs: SimplifiedResponse): HttpCodeErrors => { export const traiteErreursBackendWooCommerce = (rs: SimplifiedResponse): HttpCodeErrors =>
return match(rs) match(rs)
.with({ status: 400 }, () => new BadRequestError()) .with({ status: 400 }, () => new BadRequestError())
.with({ status: 401 }, () => new UnauthorizedError()) .with({ status: 401 }, () => new UnauthorizedError())
.with({ status: 403 }, () => new ForbiddenError()) .with({ status: 403 }, () => new ForbiddenError())
.with({ status: 404 }, () => new NotFoundError()) .with({ status: 404 }, () => new NotFoundError())
.with({ status: 500 }, () => new ServerError()) .with({ status: 500 }, () => new ServerError())
.otherwise((rs) => new Error(String(rs.status))); .otherwise((rs) => new Error(String(rs.status)));
};

View file

@ -6,7 +6,7 @@ export const WCStoreBillingAddressSchema = v.object({
city: v.string(), city: v.string(),
company: v.string(), company: v.string(),
country: v.string(), country: v.string(),
// email: v.optional(v.pipe(v.string(), v.email())), // Email: v.optional(v.pipe(v.string(), v.email())),
email: v.string(), email: v.string(),
first_name: v.string(), first_name: v.string(),
last_name: v.string(), last_name: v.string(),

View file

@ -3,7 +3,8 @@ import type { GenericSchema, InferOutput, ValiError } from "valibot";
import { Either, Maybe } from "purify-ts"; import { Either, Maybe } from "purify-ts";
import { safeJsonParse } from "./dom.ts"; import { safeJsonParse } from "./dom.ts";
import { ErreurEntreeInexistante, type NonExistingKeyError } from "./erreurs.ts"; import { ErreurEntreeInexistante } from "./erreurs.ts";
import type { NonExistingKeyError } from "./erreurs.ts";
import { safeSchemaParse, safeSchemaParseCurried } from "./validation.ts"; import { safeSchemaParse, safeSchemaParseCurried } from "./validation.ts";
export type GetSessionStorage<S extends GenericSchema> = Either<ErreursGetSessionStorage<S>, InferOutput<S>>; export type GetSessionStorage<S extends GenericSchema> = Either<ErreursGetSessionStorage<S>, InferOutput<S>>;

View file

@ -4,7 +4,7 @@ export type FetchErrors = DOMException | Error | TypeError;
export type HttpCodeErrors = BadRequestError | Error | ForbiddenError | NotFoundError | ServerError | UnauthorizedError; export type HttpCodeErrors = BadRequestError | Error | ForbiddenError | NotFoundError | ServerError | UnauthorizedError;
export interface SimplifiedResponse { export type SimplifiedResponse = {
body: unknown; body: unknown;
status: number; status: number;
} };

View file

@ -1,5 +1,6 @@
import { D } from "@mobily/ts-belt"; import { D } from "@mobily/ts-belt";
import { type Either, Maybe } from "purify-ts"; import { Maybe } from "purify-ts";
import type { Either } from "purify-ts";
import { CleNonTrouveError } from "./erreurs"; import { CleNonTrouveError } from "./erreurs";

View file

@ -3,7 +3,8 @@
*/ */
import { Either } from "purify-ts"; import { Either } from "purify-ts";
import { type GenericSchema, type InferOutput, parse, type ValiError } from "valibot"; import { parse } from "valibot";
import type { GenericSchema, InferOutput, ValiError } from "valibot";
export const safeSchemaParse = <Schema extends GenericSchema>( export const safeSchemaParse = <Schema extends GenericSchema>(
valeur: unknown, valeur: unknown,

View file

@ -4,7 +4,8 @@ import { map as dictMap, values as dictValues } from "@mobily/ts-belt/Dict";
import { trim as stringTrim } from "@mobily/ts-belt/String"; import { trim as stringTrim } from "@mobily/ts-belt/String";
import { EitherAsync, Maybe } from "purify-ts"; import { EitherAsync, Maybe } from "purify-ts";
import { match, P } from "ts-pattern"; import { match, P } from "ts-pattern";
import { type AnySchema, ValiError } from "valibot"; import { ValiError } 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";
import type { WCStoreCart, WCStoreShippingRate, WCStoreShippingRateShippingRate } from "../lib/types/api/cart"; import type { WCStoreCart, WCStoreShippingRate, WCStoreShippingRateShippingRate } from "../lib/types/api/cart";
@ -43,10 +44,10 @@ import { safeSchemaParse } from "../lib/validation";
import { E } from "./scripts-page-panier-elements"; import { E } from "./scripts-page-panier-elements";
import { getShippingRatesLS } from "./scripts-page-panier-local-storage"; import { getShippingRatesLS } from "./scripts-page-panier-local-storage";
interface Addresses { type Addresses = {
billing_address: WCStoreBillingAddress; billing_address: WCStoreBillingAddress;
shipping_address: WCStoreShippingAddress; shipping_address: WCStoreShippingAddress;
} };
// @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 // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- États injectés par le modèle PHP
@ -65,8 +66,7 @@ export const initCartFormEventEmitters = (): void => {
}); });
}; };
export const getAddressesFromForm = (formFields: Record<string, string>, areAddressesMerged: boolean): Addresses => { export const getAddressesFromForm = (formFields: Record<string, string>, areAddressesMerged: boolean): Addresses => ({
return {
billing_address: { billing_address: {
address_1: formFields["facturation-adresse"] ?? formFields["livraison-adresse"] ?? "", address_1: formFields["facturation-adresse"] ?? formFields["livraison-adresse"] ?? "",
address_2: "", address_2: "",
@ -92,8 +92,7 @@ export const getAddressesFromForm = (formFields: Record<string, string>, areAddr
postcode: formFields["livraison-code-postal"] ?? "", postcode: formFields["livraison-code-postal"] ?? "",
state: formFields["livraison-region-etat"] ?? "", state: formFields["livraison-region-etat"] ?? "",
}, },
}; });
};
export const initShippingCalculationButton = (): void => { export const initShippingCalculationButton = (): void => {
// Déclenche au clic sur le Bouton de soumission du Formulaire la requête pour le calcul des frais de livraison // Déclenche au clic sur le Bouton de soumission du Formulaire la requête pour le calcul des frais de livraison
@ -119,11 +118,11 @@ export const initShippingCalculationButton = (): void => {
void EitherAsync.liftEither(safeSchemaParse(formArgs, WCStoreCartUpdateCustomerArgsSchema)) void EitherAsync.liftEither(safeSchemaParse(formArgs, WCStoreCartUpdateCustomerArgsSchema))
// Désactive le Bouton pour empêcher des requêtes concurrentes // Désactive le Bouton pour empêcher des requêtes concurrentes
.ifRight((): void => setButtonLoadingState(E.BOUTON_ACTIONS_FORMULAIRE, true)) .ifRight((): void => setButtonLoadingState(E.BOUTON_ACTIONS_FORMULAIRE, true))
.chain((args: WCStoreCartUpdateCustomerArgs) => { .chain((args: WCStoreCartUpdateCustomerArgs) =>
return safeFetch(postBackend(ROUTE_API_MAJ_CLIENT, JSON.stringify(args), false)); safeFetch(postBackend(ROUTE_API_MAJ_CLIENT, JSON.stringify(args), false)),
}) )
.chain((rs: Response) => { .chain((rs: Response) =>
return EitherAsync<ErreurAdresseInvalide | HttpCodeErrors, unknown>( EitherAsync<ErreurAdresseInvalide | HttpCodeErrors, unknown>(
async ({ throwE }): Promise<unknown> => async ({ throwE }): Promise<unknown> =>
match(await newPartialResponse(rs)) match(await newPartialResponse(rs))
.with({ status: 200 }, (rs): unknown => rs.body) .with({ status: 200 }, (rs): unknown => rs.body)
@ -135,8 +134,8 @@ export const initShippingCalculationButton = (): void => {
(rs): never => throwE(new ErreurAdresseInvalide(rs.body.data.params)), (rs): never => throwE(new ErreurAdresseInvalide(rs.body.data.params)),
) )
.otherwise((rs): never => throwE(traiteErreursBackendWooCommerce(rs))), .otherwise((rs): never => throwE(traiteErreursBackendWooCommerce(rs))),
); ),
}) )
.chain((b: unknown) => EitherAsync.liftEither(safeSchemaParse(b, WCStoreCartSchema))) .chain((b: unknown) => EitherAsync.liftEither(safeSchemaParse(b, WCStoreCartSchema)))
.ifRight((cart: WCStoreCart): void => { .ifRight((cart: WCStoreCart): void => {
/** La méthode de livraison sélectionnée dans le SessionStorage */ /** La méthode de livraison sélectionnée dans le SessionStorage */
@ -299,9 +298,9 @@ export const initOrderCreationButton = (): void => {
void EitherAsync.liftEither(safeSchemaParse(formArgs, WCV3OrdersArgsSchema)) void EitherAsync.liftEither(safeSchemaParse(formArgs, WCV3OrdersArgsSchema))
// Désactive le Bouton pour empêcher des requêtes concurrentes // Désactive le Bouton pour empêcher des requêtes concurrentes
.ifRight((): void => setButtonLoadingState(E.BOUTON_ACTIONS_FORMULAIRE, true)) .ifRight((): void => setButtonLoadingState(E.BOUTON_ACTIONS_FORMULAIRE, true))
.chain((args: WCV3OrdersArgs) => { .chain((args: WCV3OrdersArgs) =>
return safeFetch(postBackend(ROUTE_API_NOUVELLE_COMMANDES, JSON.stringify(args), true)); safeFetch(postBackend(ROUTE_API_NOUVELLE_COMMANDES, JSON.stringify(args), true)),
}) )
.chain((rs: Response) => .chain((rs: Response) =>
EitherAsync<HttpCodeErrors, unknown>( EitherAsync<HttpCodeErrors, unknown>(
async ({ throwE }): Promise<unknown> => async ({ throwE }): Promise<unknown> =>

View file

@ -130,7 +130,7 @@ export const initialiseElementsCodePromo = (): void => {
); );
window.dispatchEvent(CODE_PROMO_MAJ_EVENT); window.dispatchEvent(CODE_PROMO_MAJ_EVENT);
// emetUniqueMessageBroadcastChannel(NOM_CANAL_REVALIDATION_LIVRAISON, true); // EmetUniqueMessageBroadcastChannel(NOM_CANAL_REVALIDATION_LIVRAISON, true);
}) })
.ifLeft((erreur) => { .ifLeft((erreur) => {
// Rétablis le texte d'origine // Rétablis le texte d'origine
@ -194,7 +194,9 @@ export const initialiseElementsCodePromo = (): void => {
) )
.chain((reponse: Response) => .chain((reponse: Response) =>
EitherAsync<ServerError, unknown>(async ({ throwE }) => { EitherAsync<ServerError, unknown>(async ({ throwE }) => {
if (estReponse500(reponse)) throwE(new ServerError("500 server Error")); if (estReponse500(reponse)) {
throwE(new ServerError("500 server Error"));
}
return await reponse.json(); return await reponse.json();
}), }),
) )

View file

@ -1,5 +1,6 @@
import { forEach as arrayForEach, map as arrayMap } from "@mobily/ts-belt/Array"; import { forEach as arrayForEach, map as arrayMap } from "@mobily/ts-belt/Array";
import { html, render, type TemplateResult } from "lit-html"; import { html, render } from "lit-html";
import type { TemplateResult } from "lit-html";
import type { WCStoreCartTotals, WCStoreShippingRateShippingRate } from "../lib/types/api/cart"; import type { WCStoreCartTotals, WCStoreShippingRateShippingRate } from "../lib/types/api/cart";
import type { WCStoreShippingRateShippingRates } from "../lib/types/api/couts-livraison"; import type { WCStoreShippingRateShippingRates } from "../lib/types/api/couts-livraison";
@ -66,8 +67,9 @@ export const generateShippingRatesHTML = (
getDOMElementsWithSelector(container)("div[data-methode-initiale]").ifRight(arrayForEach((div) => div.remove())); getDOMElementsWithSelector(container)("div[data-methode-initiale]").ifRight(arrayForEach((div) => div.remove()));
const selectedShippingRate: string = shippingRates.find((sr) => sr.selected)?.method_id ?? ""; const selectedShippingRate: string = shippingRates.find((sr) => sr.selected)?.method_id ?? "";
const shippingRatesHTML: ReadonlyArray<TemplateResult> = arrayMap(shippingRates, (methode) => { const shippingRatesHTML: ReadonlyArray<TemplateResult> = arrayMap(
return html` <div> shippingRates,
(methode) => html` <div>
<input <input
id="methode-livraison-${methode.method_id}" id="methode-livraison-${methode.method_id}"
name="choix-methode-livraison" name="choix-methode-livraison"
@ -78,8 +80,8 @@ export const generateShippingRatesHTML = (
<label for="methode-livraison-${methode.method_id}" <label for="methode-livraison-${methode.method_id}"
>${methode.name} (${formateEnEuros(methode.price)})</label >${methode.name} (${formateEnEuros(methode.price)})</label
> >
</div>`; </div>`,
}); );
// Ajoute les nouveaux Produits dans le DOM // Ajoute les nouveaux Produits dans le DOM
container.removeAttribute(ATTRIBUT_HIDDEN); container.removeAttribute(ATTRIBUT_HIDDEN);

View file

@ -4,7 +4,8 @@ import { pipe } from "@mobily/ts-belt";
import { forEach as arrayForEach, map as arrayMap } from "@mobily/ts-belt/Array"; import { forEach as arrayForEach, map as arrayMap } from "@mobily/ts-belt/Array";
import { EitherAsync, Maybe } from "purify-ts"; import { EitherAsync, Maybe } from "purify-ts";
import { match, P } from "ts-pattern"; import { match, P } from "ts-pattern";
import { type AnySchema, ValiError } from "valibot"; import { ValiError } from "valibot";
import type { AnySchema } from "valibot";
import type { WCStoreCart } from "../lib/types/api/cart"; import type { WCStoreCart } from "../lib/types/api/cart";
import type { WCStoreCartRemoveItemArgs } from "../lib/types/api/cart-remove-item"; import type { WCStoreCartRemoveItemArgs } from "../lib/types/api/cart-remove-item";

View file

@ -59,9 +59,13 @@ const initialiseObservationFenetre = (): void => {
// Met à jour la valeur du défilement vertical dans la page // Met à jour la valeur du défilement vertical dans la page
defilementY = majDefilementY(); defilementY = majDefilementY();
// Vérifie que le Ratio soit le bon // Vérifie que le Ratio soit le bon
if (ratioActuel < RATIO_MINIMUM_PAGE_PAR_FENETRE) return; if (ratioActuel < RATIO_MINIMUM_PAGE_PAR_FENETRE) {
return;
}
// Attend la prochaine étape // Attend la prochaine étape
if (etapePlanifiee) return; if (etapePlanifiee) {
return;
}
etapePlanifiee = true; etapePlanifiee = true;
requestAnimationFrame((): void => requestAnimationFrame((): void =>

View file

@ -17,12 +17,16 @@ document.addEventListener("DOMContentLoaded", (): void => {
// Créé un nouvel Observer pour la première et dernière entrée. // Créé un nouvel Observer pour la première et dernière entrée.
EffectArray.forEach(firstAndLastEntries, (menuEntry, _index) => { EffectArray.forEach(firstAndLastEntries, (menuEntry, _index) => {
if (Predicate.isUndefined(menuEntry)) return; if (Predicate.isUndefined(menuEntry)) {
return;
}
new IntersectionObserver( new IntersectionObserver(
EffectArray.forEach((intersectionEntry) => { EffectArray.forEach((intersectionEntry) => {
// Ne déclenche rien si le scroll n'est pas horizontal // Ne déclenche rien si le scroll n'est pas horizontal
if (intersectionEntry.boundingClientRect.top <= 0) return; if (intersectionEntry.boundingClientRect.top <= 0) {
return;
}
Match.value([intersectionEntry.isIntersecting]).pipe( Match.value([intersectionEntry.isIntersecting]).pipe(
Match.when([true, 0], () => productsCategoriesMenu.removeAttribute("data-entrees-presentes-debut")), Match.when([true, 0], () => productsCategoriesMenu.removeAttribute("data-entrees-presentes-debut")),
@ -32,7 +36,7 @@ document.addEventListener("DOMContentLoaded", (): void => {
Match.orElse(() => {}), Match.orElse(() => {}),
); );
}), }),
{ root: null, threshold: 0.9 }, { root: undefined, threshold: 0.9 },
).observe(menuEntry); ).observe(menuEntry);
}); });
}); });

View file

@ -102,7 +102,7 @@ const initGestionAnimation = (): void => {
A.at(E.IMAGES_STORYTELLING, 0), A.at(E.IMAGES_STORYTELLING, 0),
O.tap((img) => { O.tap((img) => {
const options: IntersectionObserverInit = { const options: IntersectionObserverInit = {
root: null, root: undefined,
rootMargin: "0px", rootMargin: "0px",
threshold: 0, threshold: 0,
}; };

View file

@ -92,13 +92,13 @@ const initialisePageBoutique = (): void => {
) )
// 4. Traite les cas d'Erreurs et récupère le Corps de la Réponse // 4. Traite les cas d'Erreurs et récupère le Corps de la Réponse
.chain((reponse: Response) => .chain((reponse: Response) =>
EitherAsync<APIFetchErrors, unknown>(async ({ throwE }) => { EitherAsync<APIFetchErrors, unknown>(async ({ throwE }) =>
return 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 Server Error"))) .with({ status: 400 }, () => throwE(new BadRequestError("400 Server Error")))
.with({ status: 200 }, (r) => r.body) .with({ status: 200 }, (r) => r.body)
.run(); .run(),
}), ),
) )
// 5. Vérifie le Schéma de la Réponse // 5. Vérifie le Schéma de la Réponse
.chain((corpsReponse: unknown) => EitherAsync.liftEither(safeSchemaParse(corpsReponse, WCV3ProductsSchema))) .chain((corpsReponse: unknown) => EitherAsync.liftEither(safeSchemaParse(corpsReponse, WCV3ProductsSchema)))
@ -139,12 +139,12 @@ const initialisePageBoutique = (): void => {
</figure> </figure>
</article> </article>
`, `,
tap((article) => fragment.appendChild(article)), tap((article) => fragment.append(article)),
); );
} }
// Ajoute les nouveaux Produits dans le DOM // Ajoute les nouveaux Produits dans le DOM
E.GRILLE_PRODUITS.appendChild(fragment); E.GRILLE_PRODUITS.append(fragment);
E.GRILLE_PRODUITS.setAttribute(ATTRIBUT_PAGE, String(nouveauNumeroPage)); E.GRILLE_PRODUITS.setAttribute(ATTRIBUT_PAGE, String(nouveauNumeroPage));
E.BOUTON_PLUS_DE_PRODUITS.textContent = "Show more"; E.BOUTON_PLUS_DE_PRODUITS.textContent = "Show more";

View file

@ -21,7 +21,8 @@ import {
} from "./constantes/dom.ts"; } from "./constantes/dom.ts";
import { NOM_CANAL_BOUTON_PANIER, NOM_CANAL_CONTENU_PANIER } from "./constantes/messages.ts"; import { NOM_CANAL_BOUTON_PANIER, NOM_CANAL_CONTENU_PANIER } from "./constantes/messages.ts";
import { getDOMElementsWithSelector, recupereElementAvecSelecteur, recupereElementOuLeve } from "./lib/dom.ts"; import { getDOMElementsWithSelector, recupereElementAvecSelecteur, recupereElementOuLeve } from "./lib/dom.ts";
import { type CleNonTrouveError, reporteErreur } from "./lib/erreurs.ts"; import { reporteErreur } from "./lib/erreurs.ts";
import type { CleNonTrouveError } from "./lib/erreurs.ts";
import { valideMessageMajBoutonPanier, valideMessageMajContenuPanier } from "./lib/messages.ts"; import { valideMessageMajBoutonPanier, valideMessageMajContenuPanier } from "./lib/messages.ts";
import { arrondisADeuxDecimales, diviseParCent, formateEnEuros, inverseNombre } from "./lib/nombres.ts"; import { arrondisADeuxDecimales, diviseParCent, formateEnEuros, inverseNombre } from "./lib/nombres.ts";
import { propEither } from "./lib/utils.ts"; import { propEither } from "./lib/utils.ts";

View file

@ -7,7 +7,8 @@ import { tap as optionTap } from "@mobily/ts-belt/Option";
import { pipe as epipe } from "effect"; import { pipe as epipe } from "effect";
import { EitherAsync, Maybe } from "purify-ts"; import { EitherAsync, Maybe } from "purify-ts";
import { match, P } from "ts-pattern"; import { match, P } from "ts-pattern";
import { type AnySchema, ValiError } from "valibot"; import { ValiError } from "valibot";
import type { AnySchema } from "valibot";
import type { WCStoreCart } from "./lib/types/api/cart"; import type { WCStoreCart } from "./lib/types/api/cart";
import type { WCStoreCartAddItemArgs, WCStoreCartAddItemArgsItems } from "./lib/types/api/cart-add-item.ts"; import type { WCStoreCartAddItemArgs, WCStoreCartAddItemArgsItems } from "./lib/types/api/cart-add-item.ts";
@ -79,8 +80,12 @@ const gereAccordeonDetailsProduit = (): void => {
const idContenu: null | string = bouton.getAttribute(ATTRIBUT_ARIA_CONTROLS); const idContenu: null | string = bouton.getAttribute(ATTRIBUT_ARIA_CONTROLS);
const sectionCorrespondante: HTMLDivElement | undefined = E.CONTENUS_ACCORDEON[index]; const sectionCorrespondante: HTMLDivElement | undefined = E.CONTENUS_ACCORDEON[index];
if (!idContenu) throw new Error("Le lien ne dispose pas d'ID !"); if (!idContenu) {
if (!sectionCorrespondante) throw new Error("Le lien ne dispose pas de section correspondante !"); throw new Error("Le lien ne dispose pas d'ID !");
}
if (!sectionCorrespondante) {
throw new Error("Le lien ne dispose pas de section correspondante !");
}
contenus.set(idContenu, [bouton, sectionCorrespondante]); contenus.set(idContenu, [bouton, sectionCorrespondante]);
@ -93,7 +98,9 @@ const gereAccordeonDetailsProduit = (): void => {
pipe(contenus.values(), Array.from<EnsembleLienContenu>, deplieToutesSections); pipe(contenus.values(), Array.from<EnsembleLienContenu>, deplieToutesSections);
// Ne fais rien de plus si l'onglet sélectionné était le courant // Ne fais rien de plus si l'onglet sélectionné était le courant
if (estAncienContenuDeplie) return; if (estAncienContenuDeplie) {
return;
}
// Ouvre le nouvel onglet sélectionné // Ouvre le nouvel onglet sélectionné
bouton.setAttribute(ATTRIBUT_ARIA_EXPANDED, "true"); bouton.setAttribute(ATTRIBUT_ARIA_EXPANDED, "true");
@ -130,14 +137,14 @@ const getAttributesFromDom = (): ReadonlyArray<WCStoreCartAddItemArgsItems> => {
document.querySelectorAll<HTMLSelectElement>(".selecteur-produit select"), document.querySelectorAll<HTMLSelectElement>(".selecteur-produit select"),
Array.from<HTMLSelectElement>, Array.from<HTMLSelectElement>,
); );
if (selectElements.length === 0) return []; if (selectElements.length === 0) {
return [];
}
const attributes = selectElements.map((select: HTMLSelectElement) => { const attributes = selectElements.map((select: HTMLSelectElement) => ({
return {
attribute: select.id, attribute: select.id,
value: select.value, value: select.value,
} satisfies WCStoreCartAddItemArgsItems; }));
});
return attributes; return attributes;
}; };
@ -176,9 +183,9 @@ 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, // Id: ETATS_PAGE.idProduit,
quantity: 1, quantity: 1,
// variation: getAttributeValuesFromDom(), // Variation: getAttributeValuesFromDom(),
}; };
// Réalise la Requête et traite sa Réponse // Réalise la Requête et traite sa Réponse

View file

@ -1,7 +1,7 @@
/// <reference types="vite/client" /> /// <reference types="vite/client" />
interface ImportMeta { type ImportMeta = {
readonly env: ImportMetaEnv; readonly env: ImportMetaEnv;
} };
interface ImportMetaEnv {} type ImportMetaEnv = {};

View file

@ -47,7 +47,8 @@ $context['products_category_id'] = $products_category_id;
* *
* @throws Exception une exception est levée s'il est impossible d'obtenir la date de modification du fichier à charger * @throws Exception une exception est levée s'il est impossible d'obtenir la date de modification du fichier à charger
*/ */
function load_page_resources(): void { function load_page_resources(): void
{
Resource::enqueue_style_file( Resource::enqueue_style_file(
handle: 'haiku-atelier-2024-styles-page-boutique', handle: 'haiku-atelier-2024-styles-page-boutique',
path: '/assets/css/pages/page-boutique.css', path: '/assets/css/pages/page-boutique.css',
@ -65,7 +66,4 @@ function load_page_resources(): void {
add_action('wp_enqueue_scripts', load_page_resources(...)); add_action('wp_enqueue_scripts', load_page_resources(...));
// 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,
);