Compare commits
15 commits
305fcce1ba
...
c61e631172
| Author | SHA1 | Date | |
|---|---|---|---|
| c61e631172 | |||
| 53d71ea183 | |||
| 50f2b67bc4 | |||
| de0a175624 | |||
| b643443b57 | |||
| c08717195a | |||
| 498ae877a1 | |||
| c66cf7faba | |||
| 4b5cadbc6e | |||
| 3e8982f11b | |||
| 0083e94084 | |||
| f0d1b4a3be | |||
| 8027006fde | |||
| 7f6f40cda4 | |||
| 869f8aeac4 |
34 changed files with 6995 additions and 2052 deletions
|
|
@ -11,10 +11,26 @@
|
||||||
"!oxlint",
|
"!oxlint",
|
||||||
"!prettier",
|
"!prettier",
|
||||||
"!tailwindcss-language-server",
|
"!tailwindcss-language-server",
|
||||||
|
"!tsgo",
|
||||||
"!vtsls",
|
"!vtsls",
|
||||||
"..."
|
"..."
|
||||||
],
|
],
|
||||||
"lsp": {
|
"lsp": {
|
||||||
|
"css-variables": {
|
||||||
|
"settings": {
|
||||||
|
"cssVariables": {
|
||||||
|
"blacklistFolders": [
|
||||||
|
"**/*.min.css",
|
||||||
|
"**/dist/**",
|
||||||
|
"**/node_modules/**"
|
||||||
|
],
|
||||||
|
"lookupFiles": [
|
||||||
|
"**/*.scss"
|
||||||
|
],
|
||||||
|
"undefinedVarFallback": "info"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"oxlint": {
|
"oxlint": {
|
||||||
"initialization_options": {
|
"initialization_options": {
|
||||||
"settings": {
|
"settings": {
|
||||||
|
|
|
||||||
6445
aube-lock.yaml
Normal file
6445
aube-lock.yaml
Normal file
File diff suppressed because it is too large
Load diff
6
aube-workspace.yaml
Normal file
6
aube-workspace.yaml
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
jailBuilds: false
|
||||||
|
nodeLinker: isolated
|
||||||
|
packageManagerStrict: true
|
||||||
|
packageManagerStrictVersion: true
|
||||||
|
trustPolicy: off
|
||||||
|
useBetaCli: true
|
||||||
|
|
@ -3,6 +3,8 @@ import type { OxlintConfig } from "oxlint";
|
||||||
|
|
||||||
const config: OxlintConfig = {
|
const config: OxlintConfig = {
|
||||||
...gcchConfig,
|
...gcchConfig,
|
||||||
|
// Désactive la configuration liée à Astro.
|
||||||
|
overrides: [],
|
||||||
globals: {
|
globals: {
|
||||||
Bun: "readonly",
|
Bun: "readonly",
|
||||||
},
|
},
|
||||||
|
|
|
||||||
56
composer.lock
generated
56
composer.lock
generated
|
|
@ -585,16 +585,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "illuminate/collections",
|
"name": "illuminate/collections",
|
||||||
"version": "v13.6.0",
|
"version": "v13.7.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/illuminate/collections.git",
|
"url": "https://github.com/illuminate/collections.git",
|
||||||
"reference": "8337eab46f512633d36df6b2f0ee54bf309c1da1"
|
"reference": "36cfc25fc23ca18714fc8ee6abc947ee99c18462"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/illuminate/collections/zipball/8337eab46f512633d36df6b2f0ee54bf309c1da1",
|
"url": "https://api.github.com/repos/illuminate/collections/zipball/36cfc25fc23ca18714fc8ee6abc947ee99c18462",
|
||||||
"reference": "8337eab46f512633d36df6b2f0ee54bf309c1da1",
|
"reference": "36cfc25fc23ca18714fc8ee6abc947ee99c18462",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
|
@ -640,11 +640,11 @@
|
||||||
"issues": "https://github.com/laravel/framework/issues",
|
"issues": "https://github.com/laravel/framework/issues",
|
||||||
"source": "https://github.com/laravel/framework"
|
"source": "https://github.com/laravel/framework"
|
||||||
},
|
},
|
||||||
"time": "2026-04-18T13:28:59+00:00"
|
"time": "2026-04-27T18:16:57+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "illuminate/conditionable",
|
"name": "illuminate/conditionable",
|
||||||
"version": "v13.6.0",
|
"version": "v13.7.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/illuminate/conditionable.git",
|
"url": "https://github.com/illuminate/conditionable.git",
|
||||||
|
|
@ -690,16 +690,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "illuminate/contracts",
|
"name": "illuminate/contracts",
|
||||||
"version": "v13.6.0",
|
"version": "v13.7.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/illuminate/contracts.git",
|
"url": "https://github.com/illuminate/contracts.git",
|
||||||
"reference": "022d9816b4ff052a3db5946a86be3cd2e224db0f"
|
"reference": "a5f08426a807faac42616f733113ba263779f2dd"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/illuminate/contracts/zipball/022d9816b4ff052a3db5946a86be3cd2e224db0f",
|
"url": "https://api.github.com/repos/illuminate/contracts/zipball/a5f08426a807faac42616f733113ba263779f2dd",
|
||||||
"reference": "022d9816b4ff052a3db5946a86be3cd2e224db0f",
|
"reference": "a5f08426a807faac42616f733113ba263779f2dd",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
|
@ -734,11 +734,11 @@
|
||||||
"issues": "https://github.com/laravel/framework/issues",
|
"issues": "https://github.com/laravel/framework/issues",
|
||||||
"source": "https://github.com/laravel/framework"
|
"source": "https://github.com/laravel/framework"
|
||||||
},
|
},
|
||||||
"time": "2026-04-12T17:46:48+00:00"
|
"time": "2026-04-24T14:55:56+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "illuminate/macroable",
|
"name": "illuminate/macroable",
|
||||||
"version": "v13.6.0",
|
"version": "v13.7.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/illuminate/macroable.git",
|
"url": "https://github.com/illuminate/macroable.git",
|
||||||
|
|
@ -784,7 +784,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "illuminate/reflection",
|
"name": "illuminate/reflection",
|
||||||
"version": "v13.6.0",
|
"version": "v13.7.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/illuminate/reflection.git",
|
"url": "https://github.com/illuminate/reflection.git",
|
||||||
|
|
@ -835,16 +835,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "illuminate/support",
|
"name": "illuminate/support",
|
||||||
"version": "v13.6.0",
|
"version": "v13.7.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/illuminate/support.git",
|
"url": "https://github.com/illuminate/support.git",
|
||||||
"reference": "5394e4b6008175f5a42c1227b5c4b959aa1be963"
|
"reference": "cc0d9d484ddd83fa54a0495cf15e7606e6a6f4b1"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/illuminate/support/zipball/5394e4b6008175f5a42c1227b5c4b959aa1be963",
|
"url": "https://api.github.com/repos/illuminate/support/zipball/cc0d9d484ddd83fa54a0495cf15e7606e6a6f4b1",
|
||||||
"reference": "5394e4b6008175f5a42c1227b5c4b959aa1be963",
|
"reference": "cc0d9d484ddd83fa54a0495cf15e7606e6a6f4b1",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
|
@ -910,7 +910,7 @@
|
||||||
"issues": "https://github.com/laravel/framework/issues",
|
"issues": "https://github.com/laravel/framework/issues",
|
||||||
"source": "https://github.com/laravel/framework"
|
"source": "https://github.com/laravel/framework"
|
||||||
},
|
},
|
||||||
"time": "2026-04-20T13:42:58+00:00"
|
"time": "2026-04-27T13:46:05+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "laravel/helpers",
|
"name": "laravel/helpers",
|
||||||
|
|
@ -4532,11 +4532,11 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpstan/phpstan",
|
"name": "phpstan/phpstan",
|
||||||
"version": "2.1.51",
|
"version": "2.1.53",
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/dc3b523c45e714c70de2ac5113b958223b55dc59",
|
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/ef67586798c003274797b288a68b221e4270dca7",
|
||||||
"reference": "dc3b523c45e714c70de2ac5113b958223b55dc59",
|
"reference": "ef67586798c003274797b288a68b221e4270dca7",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
|
@ -4581,7 +4581,7 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2026-04-21T18:22:01+00:00"
|
"time": "2026-04-28T16:09:00+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "psr/event-dispatcher",
|
"name": "psr/event-dispatcher",
|
||||||
|
|
@ -5215,12 +5215,12 @@
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/Roave/SecurityAdvisories.git",
|
"url": "https://github.com/Roave/SecurityAdvisories.git",
|
||||||
"reference": "08cd07f04fb07fb4d316e956801d57b700cf7096"
|
"reference": "87a281378fdad8f5926efe259f6ca72e7a395e68"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/08cd07f04fb07fb4d316e956801d57b700cf7096",
|
"url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/87a281378fdad8f5926efe259f6ca72e7a395e68",
|
||||||
"reference": "08cd07f04fb07fb4d316e956801d57b700cf7096",
|
"reference": "87a281378fdad8f5926efe259f6ca72e7a395e68",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"conflict": {
|
"conflict": {
|
||||||
|
|
@ -5836,7 +5836,7 @@
|
||||||
"phpoffice/common": "<0.2.9",
|
"phpoffice/common": "<0.2.9",
|
||||||
"phpoffice/math": "<=0.2",
|
"phpoffice/math": "<=0.2",
|
||||||
"phpoffice/phpexcel": "<=1.8.2",
|
"phpoffice/phpexcel": "<=1.8.2",
|
||||||
"phpoffice/phpspreadsheet": "<1.30|>=2,<2.1.12|>=2.2,<2.4|>=3,<3.10|>=4,<5",
|
"phpoffice/phpspreadsheet": "<=1.30.3|>=2,<=2.1.15|>=2.2,<=2.4.4|>=3,<=3.10.4|>=4,<=5.6",
|
||||||
"phppgadmin/phppgadmin": "<=7.13",
|
"phppgadmin/phppgadmin": "<=7.13",
|
||||||
"phpseclib/phpseclib": "<2.0.53|>=3,<3.0.51",
|
"phpseclib/phpseclib": "<2.0.53|>=3,<3.0.51",
|
||||||
"phpservermon/phpservermon": "<3.6",
|
"phpservermon/phpservermon": "<3.6",
|
||||||
|
|
@ -6264,7 +6264,7 @@
|
||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2026-04-24T17:22:29+00:00"
|
"time": "2026-04-28T23:21:55+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "sebastian/diff",
|
"name": "sebastian/diff",
|
||||||
|
|
@ -7576,5 +7576,5 @@
|
||||||
"php": ">=8.5"
|
"php": ">=8.5"
|
||||||
},
|
},
|
||||||
"platform-dev": {},
|
"platform-dev": {},
|
||||||
"plugin-api-version": "2.6.0"
|
"plugin-api-version": "2.9.0"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ declare(strict_types=1);
|
||||||
|
|
||||||
use Roots\WPConfig\Config;
|
use Roots\WPConfig\Config;
|
||||||
|
|
||||||
|
use function base64_encode;
|
||||||
use function Env\env;
|
use function Env\env;
|
||||||
|
|
||||||
Config::define('SAVEQUERIES', true);
|
Config::define('SAVEQUERIES', true);
|
||||||
|
|
@ -25,6 +26,10 @@ Config::define('DISALLOW_FILE_MODS', false);
|
||||||
// WooCommerce
|
// WooCommerce
|
||||||
Config::define('WOOCOMMERCE_API_CONSUMER_KEY', env('WOOCOMMERCE_API_CONSUMER_KEY'));
|
Config::define('WOOCOMMERCE_API_CONSUMER_KEY', env('WOOCOMMERCE_API_CONSUMER_KEY'));
|
||||||
Config::define('WOOCOMMERCE_API_CONSUMER_SECRET', env('WOOCOMMERCE_API_CONSUMER_SECRET'));
|
Config::define('WOOCOMMERCE_API_CONSUMER_SECRET', env('WOOCOMMERCE_API_CONSUMER_SECRET'));
|
||||||
|
Config::define(
|
||||||
|
'WOOCOMMERCE_API_AUTH_STRING',
|
||||||
|
base64_encode(env('WOOCOMMERCE_API_CONSUMER_KEY') . ':' . env('WOOCOMMERCE_API_CONSUMER_SECRET')),
|
||||||
|
);
|
||||||
|
|
||||||
// Stripe
|
// Stripe
|
||||||
Config::define('STRIPE_API_SECRET', env('STRIPE_API_SECRET'));
|
Config::define('STRIPE_API_SECRET', env('STRIPE_API_SECRET'));
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ declare(strict_types=1);
|
||||||
|
|
||||||
use Roots\WPConfig\Config;
|
use Roots\WPConfig\Config;
|
||||||
|
|
||||||
|
use function base64_encode;
|
||||||
use function Env\env;
|
use function Env\env;
|
||||||
|
|
||||||
Config::define('WP_DEBUG', true);
|
Config::define('WP_DEBUG', true);
|
||||||
|
|
@ -20,6 +21,10 @@ Config::define('DISALLOW_FILE_MODS', false);
|
||||||
|
|
||||||
Config::define('WOOCOMMERCE_API_CONSUMER_KEY', env('WOOCOMMERCE_API_CONSUMER_KEY'));
|
Config::define('WOOCOMMERCE_API_CONSUMER_KEY', env('WOOCOMMERCE_API_CONSUMER_KEY'));
|
||||||
Config::define('WOOCOMMERCE_API_CONSUMER_SECRET', env('WOOCOMMERCE_API_CONSUMER_SECRET'));
|
Config::define('WOOCOMMERCE_API_CONSUMER_SECRET', env('WOOCOMMERCE_API_CONSUMER_SECRET'));
|
||||||
|
Config::define(
|
||||||
|
'WOOCOMMERCE_API_AUTH_STRING',
|
||||||
|
base64_encode(env('WOOCOMMERCE_API_CONSUMER_KEY') . ':' . env('WOOCOMMERCE_API_CONSUMER_SECRET')),
|
||||||
|
);
|
||||||
|
|
||||||
// Stripe
|
// Stripe
|
||||||
Config::define('STRIPE_API_SECRET', env('STRIPE_API_SECRET'));
|
Config::define('STRIPE_API_SECRET', env('STRIPE_API_SECRET'));
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,13 @@ declare(strict_types=1);
|
||||||
|
|
||||||
use Roots\WPConfig\Config;
|
use Roots\WPConfig\Config;
|
||||||
|
|
||||||
|
use function base64_encode;
|
||||||
use function Env\env;
|
use function Env\env;
|
||||||
|
|
||||||
Config::define('DISALLOW_INDEXING', true);
|
Config::define('DISALLOW_INDEXING', true);
|
||||||
Config::define('WOOCOMMERCE_API_CONSUMER_KEY', env('WOOCOMMERCE_API_CONSUMER_KEY'));
|
Config::define('WOOCOMMERCE_API_CONSUMER_KEY', env('WOOCOMMERCE_API_CONSUMER_KEY'));
|
||||||
Config::define('WOOCOMMERCE_API_CONSUMER_SECRET', env('WOOCOMMERCE_API_CONSUMER_SECRET'));
|
Config::define('WOOCOMMERCE_API_CONSUMER_SECRET', env('WOOCOMMERCE_API_CONSUMER_SECRET'));
|
||||||
|
Config::define(
|
||||||
|
'WOOCOMMERCE_API_AUTH_STRING',
|
||||||
|
base64_encode(env('WOOCOMMERCE_API_CONSUMER_KEY') . ':' . env('WOOCOMMERCE_API_CONSUMER_SECRET')),
|
||||||
|
);
|
||||||
|
|
|
||||||
66
justfile
66
justfile
|
|
@ -1,7 +1,7 @@
|
||||||
set shell := ["fish", "-c"]
|
set shell := ["fish", "-c"]
|
||||||
|
|
||||||
# Recette par défaut.
|
# Recette par défaut.
|
||||||
default: dev
|
default: build-all
|
||||||
|
|
||||||
# Liste toutes les recettes
|
# Liste toutes les recettes
|
||||||
list:
|
list:
|
||||||
|
|
@ -12,12 +12,17 @@ list:
|
||||||
[group('php')]
|
[group('php')]
|
||||||
update:
|
update:
|
||||||
composer update
|
composer update
|
||||||
bun update
|
aube update
|
||||||
|
|
||||||
# Formatte avec Prettier et dprint.
|
# Formatte avec treefmt.
|
||||||
|
[group('qualité')]
|
||||||
|
treefmt:
|
||||||
|
treefmt --config-file ~/.config/treefmt/treefmt.toml .
|
||||||
|
|
||||||
|
# Formatte avec Prettier et treefmt.
|
||||||
[group('qualité')]
|
[group('qualité')]
|
||||||
format:
|
format:
|
||||||
bun prettier \
|
aube x prettier \
|
||||||
--cache --cache-location ".cache/prettiercache" \
|
--cache --cache-location ".cache/prettiercache" \
|
||||||
--config "cfg/prettier.config.ts" \
|
--config "cfg/prettier.config.ts" \
|
||||||
--ignore-path "cfg/prettierignore" \
|
--ignore-path "cfg/prettierignore" \
|
||||||
|
|
@ -36,40 +41,40 @@ format:
|
||||||
# Compile, minifie et optimise Sass vers CSS.
|
# Compile, minifie et optimise Sass vers CSS.
|
||||||
[group('css')]
|
[group('css')]
|
||||||
build-css:
|
build-css:
|
||||||
@bun sass \
|
@aube x sass \
|
||||||
--update \
|
--update \
|
||||||
"web/app/themes/haiku-atelier-2024/src/sass":"web/app/themes/haiku-atelier-2024/assets/css"
|
"web/app/themes/haiku-atelier-2024/src/sass":"web/app/themes/haiku-atelier-2024/assets/css"
|
||||||
@bun lightningcss \
|
@aube x lightningcss \
|
||||||
--bundle \
|
--bundle \
|
||||||
--minify \
|
--minify \
|
||||||
--output-file "web/app/themes/haiku-atelier-2024/assets/css/main.min.css" \
|
--output-file "web/app/themes/haiku-atelier-2024/assets/css/main.min.css" \
|
||||||
-- "web/app/themes/haiku-atelier-2024/assets/css/main.css"
|
-- "web/app/themes/haiku-atelier-2024/assets/css/main.css"
|
||||||
@bun lightningcss \
|
@aube x lightningcss \
|
||||||
--bundle \
|
--bundle \
|
||||||
--minify \
|
--minify \
|
||||||
--output-file "web/app/themes/haiku-atelier-2024/assets/css/pages/page-panier.min.css" \
|
--output-file "web/app/themes/haiku-atelier-2024/assets/css/pages/page-panier.min.css" \
|
||||||
-- "web/app/themes/haiku-atelier-2024/assets/css/pages/page-panier.css"
|
-- "web/app/themes/haiku-atelier-2024/assets/css/pages/page-panier.css"
|
||||||
@bun lightningcss \
|
@aube x lightningcss \
|
||||||
--bundle \
|
--bundle \
|
||||||
--minify \
|
--minify \
|
||||||
--output-file "web/app/themes/haiku-atelier-2024/assets/css/pages/page-accueil.min.css" \
|
--output-file "web/app/themes/haiku-atelier-2024/assets/css/pages/page-accueil.min.css" \
|
||||||
-- "web/app/themes/haiku-atelier-2024/assets/css/pages/page-accueil.css"
|
-- "web/app/themes/haiku-atelier-2024/assets/css/pages/page-accueil.css"
|
||||||
@bun lightningcss \
|
@aube x lightningcss \
|
||||||
--bundle \
|
--bundle \
|
||||||
--minify \
|
--minify \
|
||||||
--output-file "web/app/themes/haiku-atelier-2024/assets/css/pages/page-boutique.min.css" \
|
--output-file "web/app/themes/haiku-atelier-2024/assets/css/pages/page-boutique.min.css" \
|
||||||
-- "web/app/themes/haiku-atelier-2024/assets/css/pages/page-boutique.css"
|
-- "web/app/themes/haiku-atelier-2024/assets/css/pages/page-boutique.css"
|
||||||
@bun lightningcss \
|
@aube x lightningcss \
|
||||||
--bundle \
|
--bundle \
|
||||||
--minify \
|
--minify \
|
||||||
--output-file "web/app/themes/haiku-atelier-2024/assets/css/pages/page-a-propos.min.css" \
|
--output-file "web/app/themes/haiku-atelier-2024/assets/css/pages/page-a-propos.min.css" \
|
||||||
-- "web/app/themes/haiku-atelier-2024/assets/css/pages/page-a-propos.css"
|
-- "web/app/themes/haiku-atelier-2024/assets/css/pages/page-a-propos.css"
|
||||||
@bun lightningcss \
|
@aube x lightningcss \
|
||||||
--bundle \
|
--bundle \
|
||||||
--minify \
|
--minify \
|
||||||
--output-file "web/app/themes/haiku-atelier-2024/assets/css/pages/page-modele-simple.min.css" \
|
--output-file "web/app/themes/haiku-atelier-2024/assets/css/pages/page-modele-simple.min.css" \
|
||||||
-- "web/app/themes/haiku-atelier-2024/assets/css/pages/page-modele-simple.css"
|
-- "web/app/themes/haiku-atelier-2024/assets/css/pages/page-modele-simple.css"
|
||||||
@bun lightningcss \
|
@aube x lightningcss \
|
||||||
--bundle \
|
--bundle \
|
||||||
--minify \
|
--minify \
|
||||||
--output-file "web/app/themes/haiku-atelier-2024/assets/css/pages/page-succes-commande.min.css" \
|
--output-file "web/app/themes/haiku-atelier-2024/assets/css/pages/page-succes-commande.min.css" \
|
||||||
|
|
@ -83,7 +88,12 @@ watch-css:
|
||||||
# Compile TypeScript en JavaScript.
|
# Compile TypeScript en JavaScript.
|
||||||
[group('js')]
|
[group('js')]
|
||||||
build-js:
|
build-js:
|
||||||
bun --bun vite build --config "cfg/vite.config.ts"
|
aube x vite build --config "cfg/vite.config.ts"
|
||||||
|
|
||||||
|
# Compile TypeScript à chaque changement de fichier.
|
||||||
|
[group('js')]
|
||||||
|
watch-js:
|
||||||
|
@watchexec -w "web/app/themes/haiku-atelier-2024/src/scripts" -w "web/app/themes/haiku-atelier-2024/src/scripts-effect" -- just build-js treefmt
|
||||||
|
|
||||||
# Compile tout.
|
# Compile tout.
|
||||||
[group('css')]
|
[group('css')]
|
||||||
|
|
@ -93,22 +103,17 @@ build-all:
|
||||||
@just build-js
|
@just build-js
|
||||||
@just format
|
@just format
|
||||||
|
|
||||||
# Compile TypeScript à chaque changement de fichier.
|
|
||||||
[group('js')]
|
|
||||||
watch-js:
|
|
||||||
bun --bun vite build --config "cfg/vite.config.ts" --watch
|
|
||||||
|
|
||||||
# Vérifie le code TypeScript avec des analyseurs statiques.
|
# Vérifie le code TypeScript avec des analyseurs statiques.
|
||||||
[group('js')]
|
[group('js')]
|
||||||
[group('qualité')]
|
[group('qualité')]
|
||||||
lint-js:
|
lint-js:
|
||||||
-bun eslint "web/app/themes/haiku-atelier-2024/src/scripts"
|
-aube x eslint "web/app/themes/haiku-atelier-2024/src/scripts"
|
||||||
bun --bun oxlint \
|
-aube x oxlint \
|
||||||
--config cfg/oxlint.config.ts \
|
--config cfg/oxlint.config.ts \
|
||||||
--format stylish
|
--format stylish
|
||||||
|
|
||||||
fix-js:
|
fix-js:
|
||||||
bun --bun oxlint \
|
aube x oxlint \
|
||||||
--config cfg/oxlint.config.ts \
|
--config cfg/oxlint.config.ts \
|
||||||
--format stylish \
|
--format stylish \
|
||||||
--fix --fix-suggestions --fix-dangerously
|
--fix --fix-suggestions --fix-dangerously
|
||||||
|
|
@ -117,13 +122,13 @@ fix-js:
|
||||||
[group('css')]
|
[group('css')]
|
||||||
[group('qualité')]
|
[group('qualité')]
|
||||||
lint-css:
|
lint-css:
|
||||||
-bun stylelint --config "cfg/stylelint.config.ts" "web/app/themes/haiku-atelier-2024/src/sass/" --fix
|
-aube x stylelint --config "cfg/stylelint.config.ts" "web/app/themes/haiku-atelier-2024/src/sass/" --fix
|
||||||
|
|
||||||
# Vérifie le code TypeScript mort avec knip
|
# Vérifie le code TypeScript mort avec knip
|
||||||
[group('js')]
|
[group('js')]
|
||||||
[group('qualité')]
|
[group('qualité')]
|
||||||
lint-code-mort:
|
lint-code-mort:
|
||||||
-bun knip
|
-aube x knip
|
||||||
|
|
||||||
# Fusionne tous les changements actuels dans le commit précédent et pousse sur le répertoire distant avec Jujetsu.
|
# Fusionne tous les changements actuels dans le commit précédent et pousse sur le répertoire distant avec Jujetsu.
|
||||||
[group('vcs')]
|
[group('vcs')]
|
||||||
|
|
@ -138,19 +143,6 @@ lint-build-format-css:
|
||||||
-just build-css
|
-just build-css
|
||||||
-just format
|
-just format
|
||||||
|
|
||||||
# Lance un navigateur de développement.
|
|
||||||
[group('développement')]
|
|
||||||
dev:
|
|
||||||
@/opt/cromite/chrome --remote-debugging-address=127.0.0.1 --remote-debugging-port=9222 --profile-directory=Guest "https://haikuatelier.gcch.local" &
|
|
||||||
|
|
||||||
# Recharge le premier onglet du navigateur de développement.
|
|
||||||
[group('développement')]
|
|
||||||
reload-tab:
|
|
||||||
#!/usr/bin/fish
|
|
||||||
set -f WSURL (curl -s http://127.1:9222/json | fx '.[0].webSocketDebuggerUrl')
|
|
||||||
set -f REQUEST '{ "id": 2, "method": "Page.reload", "params": { "ignoreCache": true, "scriptToEvaluateOnLoad": "" } }'
|
|
||||||
echo $REQUEST | websocat $WSURL
|
|
||||||
|
|
||||||
# Créé l'image OCI.
|
# Créé l'image OCI.
|
||||||
[group('container')]
|
[group('container')]
|
||||||
build-wordpress-container:
|
build-wordpress-container:
|
||||||
|
|
@ -174,7 +166,7 @@ restart-services:
|
||||||
# Met à jour les conteneurs images des conteneurs.
|
# Met à jour les conteneurs images des conteneurs.
|
||||||
[group('container')]
|
[group('container')]
|
||||||
pull-images:
|
pull-images:
|
||||||
bun "scripts/pull-container-images.ts"
|
bun run "scripts/pull-container-images.ts"
|
||||||
|
|
||||||
export_production_db:
|
export_production_db:
|
||||||
fish "scripts/déclenche-sauvegarde-bdd-production.fish"
|
fish "scripts/déclenche-sauvegarde-bdd-production.fish"
|
||||||
|
|
|
||||||
17
package.json
Executable file → Normal file
17
package.json
Executable file → Normal file
|
|
@ -15,7 +15,8 @@
|
||||||
"@mobily/ts-belt": "v4.0.0-rc.5",
|
"@mobily/ts-belt": "v4.0.0-rc.5",
|
||||||
"@sentry/browser": "^10.50.0",
|
"@sentry/browser": "^10.50.0",
|
||||||
"a11y-dialog": "^8.1.5",
|
"a11y-dialog": "^8.1.5",
|
||||||
"effect": "^4.0.0-beta.57",
|
"effect": "^4.0.0-beta.59",
|
||||||
|
"html-template-tag": "^5.0.0",
|
||||||
"lit-html": "^3.3.2",
|
"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",
|
||||||
|
|
@ -23,15 +24,15 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@effect/language-service": "^0.85.1",
|
"@effect/language-service": "^0.85.1",
|
||||||
"@effect/tsgo": "0.5.1",
|
"@effect/tsgo": "^0.5.1",
|
||||||
"@gcch/configuration-eslint": "git+https://git.gcch.fr/gcch/configuration-eslint#888eb4aa54e5",
|
"@gcch/configuration-eslint": "git+https://git.gcch.fr/gcch/configuration-eslint#888eb4aa54",
|
||||||
"@gcch/configuration-oxlint": "git+https://git.gcch.fr/gcch/configuration-oxlint#83547fc1ebfd",
|
"@gcch/configuration-oxlint": "git+https://git.gcch.fr/gcch/configuration-oxlint#83547fc1ebfd",
|
||||||
"@gcch/configuration-prettier": "git+https://git.gcch.fr/gcch/configuration-prettier#d267d6dc5ee8",
|
"@gcch/configuration-prettier": "git+https://git.gcch.fr/gcch/configuration-prettier#d267d6dc5e",
|
||||||
"@playwright/test": "^1.59.1",
|
"@playwright/test": "^1.59.1",
|
||||||
"@sentry/core": "^10.50.0",
|
"@sentry/core": "^10.50.0",
|
||||||
"@types/bun": "^1.3.13",
|
"@types/bun": "^1.3.13",
|
||||||
"@types/node": "^25.6.0",
|
"@types/node": "^25.6.0",
|
||||||
"@typescript/native-preview": "7.0.0-dev.20260427.1",
|
"@typescript/native-preview": "7.0.0-dev.20260429.1",
|
||||||
"@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",
|
||||||
|
|
@ -44,15 +45,19 @@
|
||||||
"fdir": "^6.5.0",
|
"fdir": "^6.5.0",
|
||||||
"globals": "^17.5.0",
|
"globals": "^17.5.0",
|
||||||
"jiti": "^2.6.1",
|
"jiti": "^2.6.1",
|
||||||
"knip": "^6.7.0",
|
"knip": "^6.8.0",
|
||||||
"lightningcss": "^1.32.0",
|
"lightningcss": "^1.32.0",
|
||||||
"lightningcss-cli": "^1.32.0",
|
"lightningcss-cli": "^1.32.0",
|
||||||
"oxlint": "^1.62.0",
|
"oxlint": "^1.62.0",
|
||||||
"oxlint-tsgolint": "^0.22.1",
|
"oxlint-tsgolint": "^0.22.1",
|
||||||
"playwright": "^1.59.1",
|
"playwright": "^1.59.1",
|
||||||
"prettier": "^3.8.3",
|
"prettier": "^3.8.3",
|
||||||
|
"prettier-plugin-curly": "^0.4.1",
|
||||||
|
"prettier-plugin-ini": "^1.3.0",
|
||||||
|
"prettier-plugin-jsdoc": "^1.8.0",
|
||||||
"prettier-plugin-pkg": "^0.22.1",
|
"prettier-plugin-pkg": "^0.22.1",
|
||||||
"prettier-plugin-sh": "^0.18.1",
|
"prettier-plugin-sh": "^0.18.1",
|
||||||
|
"prettier-plugin-sort-json": "^4.2.0",
|
||||||
"sass-embedded": "^1.99.0",
|
"sass-embedded": "^1.99.0",
|
||||||
"stylelint": "^17.9.1",
|
"stylelint": "^17.9.1",
|
||||||
"stylelint-config-clean-order": "^8.0.1",
|
"stylelint-config-clean-order": "^8.0.1",
|
||||||
|
|
|
||||||
|
|
@ -10,12 +10,17 @@ namespace HaikuAtelier;
|
||||||
|
|
||||||
use HaikuAtelier\Data\Product;
|
use HaikuAtelier\Data\Product;
|
||||||
use HaikuAtelier\WP\Resource;
|
use HaikuAtelier\WP\Resource;
|
||||||
|
use Roots\WPConfig\Config;
|
||||||
use Timber\Timber;
|
use Timber\Timber;
|
||||||
use WC_Product;
|
use WC_Product;
|
||||||
|
|
||||||
use function add_action;
|
use function add_action;
|
||||||
use function array_map;
|
use function array_map;
|
||||||
|
use function assert;
|
||||||
|
use function is_string;
|
||||||
use function wc_get_products;
|
use function wc_get_products;
|
||||||
|
use function wp_create_nonce;
|
||||||
|
use function wp_json_encode;
|
||||||
|
|
||||||
$context = Timber::context();
|
$context = Timber::context();
|
||||||
$templates = ['boutique.twig'];
|
$templates = ['boutique.twig'];
|
||||||
|
|
@ -26,6 +31,12 @@ $wc_products = wc_get_products(['limit' => 12, 'order' => 'DESC', 'orderby' => '
|
||||||
$products = array_map(callback: Product::from_wc_product(...), array: $wc_products);
|
$products = array_map(callback: Product::from_wc_product(...), array: $wc_products);
|
||||||
$context['products'] = $products;
|
$context['products'] = $products;
|
||||||
|
|
||||||
|
// Injecte les états initiaux des données du Produit sous forme de JSON dans le contexte.
|
||||||
|
$page_states = ['authString' => Config::get('WOOCOMMERCE_API_AUTH_STRING'), 'nonce' => wp_create_nonce('wc_store_api')]
|
||||||
|
|> wp_json_encode(...);
|
||||||
|
assert(is_string($page_states));
|
||||||
|
$context['page_states'] = $page_states;
|
||||||
|
|
||||||
add_action('wp_enqueue_scripts', function (): void {
|
add_action('wp_enqueue_scripts', function (): void {
|
||||||
Resource::enqueue_style_file(
|
Resource::enqueue_style_file(
|
||||||
handle: 'haiku-atelier-2024-styles-page-boutique',
|
handle: 'haiku-atelier-2024-styles-page-boutique',
|
||||||
|
|
|
||||||
|
|
@ -462,8 +462,8 @@ input[type="checkbox"], input[type="radio"] {
|
||||||
transition: 0.2s background;
|
transition: 0.2s background;
|
||||||
}
|
}
|
||||||
input[type="checkbox"]:checked, input[type="radio"]:checked {
|
input[type="checkbox"]:checked, input[type="radio"]:checked {
|
||||||
color: var(--couleur-blanc);
|
color: var(--couleur-noir);
|
||||||
background: var(--couleur-gris-fonce);
|
background: var(--arriere-plan-points);
|
||||||
}
|
}
|
||||||
input[type="checkbox"]:checked::before, input[type="radio"]:checked::before {
|
input[type="checkbox"]:checked::before, input[type="radio"]:checked::before {
|
||||||
content: "x";
|
content: "x";
|
||||||
|
|
@ -492,7 +492,7 @@ input[type="radio"] + label {
|
||||||
}
|
}
|
||||||
@media (hover: hover) {
|
@media (hover: hover) {
|
||||||
input[type="checkbox"]:hover, input[type="radio"]:hover {
|
input[type="checkbox"]:hover, input[type="radio"]:hover {
|
||||||
background: var(--couleur-gris-fonce);
|
background: var(--arriere-plan-points);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -654,7 +654,7 @@ video {
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* * Styles pour un bandeau défilant. */
|
/* Styles pour un bandeau défilant. */
|
||||||
.bandeau {
|
.bandeau {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -131,7 +131,8 @@ function retire_merdes_wc(): void {
|
||||||
*/
|
*/
|
||||||
function genere_balises_img_dans_produit_dans_reponse_rest(
|
function genere_balises_img_dans_produit_dans_reponse_rest(
|
||||||
WP_REST_Response $response,
|
WP_REST_Response $response,
|
||||||
mixed $_product,
|
WC_Data $_product,
|
||||||
|
WP_REST_Request $_request,
|
||||||
): WP_REST_Response {
|
): WP_REST_Response {
|
||||||
// Vérifie que la Réponse a des données
|
// Vérifie que la Réponse a des données
|
||||||
if (empty($response->data)) {
|
if (empty($response->data)) {
|
||||||
|
|
@ -169,37 +170,34 @@ function genere_balises_img_dans_produit_dans_reponse_rest(
|
||||||
return $response;
|
return $response;
|
||||||
}
|
}
|
||||||
|
|
||||||
add_filter('woocommerce_rest_prepare_product_object', 'genere_balises_img_dans_produit_dans_reponse_rest', 10, 2);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO.
|
* TODO.
|
||||||
*/
|
*/
|
||||||
function genere_prix_maximal_produit_variable_dans_reponse_rest(
|
function genere_prix_maximal_produit_variable_dans_reponse_rest(
|
||||||
WP_REST_Response $reponse,
|
WP_REST_Response $response,
|
||||||
WC_Data $_produit,
|
WC_Data $_product,
|
||||||
|
WP_REST_Request $_request,
|
||||||
): WP_REST_Response {
|
): WP_REST_Response {
|
||||||
// Vérifie que la Réponse a des données
|
// Vérifie que la Réponse a des données
|
||||||
if (empty($reponse->data)) {
|
if (empty($response->data)) {
|
||||||
return $reponse;
|
return $response;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Si le Produit n'est pas Variable, assigner le prix du Produit comme prix maximal
|
// Si le Produit n'est pas Variable, assigner le prix du Produit comme prix maximal
|
||||||
if ('variable' !== $reponse->data['type']) {
|
if ('variable' !== $response->data['type']) {
|
||||||
$reponse->data['prix_maximal'] = $reponse->data['regular_price'];
|
$response->data['prix_maximal'] = $response->data['regular_price'];
|
||||||
|
|
||||||
return $reponse;
|
return $response;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assigne le prix de la Variation la plus chère dans la Réponse
|
// Assigne le prix de la Variation la plus chère dans la Réponse
|
||||||
$reponse->data['prix_maximal'] = collect($reponse->data['variations'])
|
$response->data['prix_maximal'] = collect($response->data['variations'])
|
||||||
->map(wc_get_product(...))
|
->map(wc_get_product(...))
|
||||||
->map(static fn($p) => $p->get_price())->max();
|
->map(static fn($p) => $p->get_price())->max();
|
||||||
|
|
||||||
return $reponse;
|
return $response;
|
||||||
}
|
}
|
||||||
|
|
||||||
add_filter('woocommerce_rest_prepare_product_object', 'genere_prix_maximal_produit_variable_dans_reponse_rest', 10, 2);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retire la propagande commerciale de WooCommerce du menu.
|
* Retire la propagande commerciale de WooCommerce du menu.
|
||||||
*/
|
*/
|
||||||
|
|
@ -216,3 +214,8 @@ add_action('init', 'retire_script_galerie');
|
||||||
add_action('template_redirect', 'retire_merdes_wc');
|
add_action('template_redirect', 'retire_merdes_wc');
|
||||||
add_action('wp_enqueue_scripts', 'dequeue_woocommerce_styles_scripts');
|
add_action('wp_enqueue_scripts', 'dequeue_woocommerce_styles_scripts');
|
||||||
add_filter('woocommerce_enqueue_styles', '__return_empty_array');
|
add_filter('woocommerce_enqueue_styles', '__return_empty_array');
|
||||||
|
add_filter('woocommerce_rest_prepare_product_object', 'genere_balises_img_dans_produit_dans_reponse_rest', 10, 3);
|
||||||
|
add_filter('woocommerce_rest_prepare_product_object', 'genere_prix_maximal_produit_variable_dans_reponse_rest', 10, 3);
|
||||||
|
|
||||||
|
// DEBUG
|
||||||
|
// add_filter('woocommerce_store_api_disable_nonce_check', '__return_true');
|
||||||
|
|
|
||||||
|
|
@ -66,8 +66,8 @@ input[type="checkbox"], input[type="radio"] {
|
||||||
transition: 0.2s background;
|
transition: 0.2s background;
|
||||||
|
|
||||||
&:checked {
|
&:checked {
|
||||||
color: var(--couleur-blanc);
|
color: var(--couleur-noir);
|
||||||
background: var(--couleur-gris-fonce);
|
background: var(--arriere-plan-points);
|
||||||
|
|
||||||
// TODO: Utiliser un SVG plutôt qu'un « x » ?
|
// TODO: Utiliser un SVG plutôt qu'un « x » ?
|
||||||
&::before {
|
&::before {
|
||||||
|
|
@ -98,7 +98,7 @@ input[type="checkbox"], input[type="radio"] {
|
||||||
|
|
||||||
@media (hover: hover) {
|
@media (hover: hover) {
|
||||||
&:hover {
|
&:hover {
|
||||||
background: var(--couleur-gris-fonce);
|
background: var(--arriere-plan-points);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
/* * Styles pour un bandeau défilant. */
|
/* Styles pour un bandeau défilant. */
|
||||||
|
|
||||||
.bandeau {
|
.bandeau {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
import { Context, Effect, Layer, Schema } from "effect";
|
|
||||||
import { ENTETE_WC_NONCE, ROUTE_API_AJOUTE_ARTICLE_PANIER } from "../scripts/constantes/api.ts";
|
|
||||||
import { CartProduct } from "./schemas/api.ts";
|
|
||||||
|
|
||||||
/** Délai par défaut pour la réalisation d'une Requête. */
|
|
||||||
const REQUEST_TIMEOUT = 5_000;
|
|
||||||
|
|
||||||
/** Représente un soucis lors de l'exécution d'une Requête auprès de l'API WooCommerce. */
|
|
||||||
class APIError extends Schema.TaggedErrorClass<APIError>()("APIError", {
|
|
||||||
cause: Schema.Defect,
|
|
||||||
}) {}
|
|
||||||
|
|
||||||
class WooCommerceAPI extends Context.Service<
|
|
||||||
WooCommerceAPI,
|
|
||||||
{
|
|
||||||
AddProductToCart: (nonce: string, requestBody: CartProduct) => Effect.Effect<Response, APIError>;
|
|
||||||
}
|
|
||||||
>()("haikuatelier.fr/WooCommerceAPI") {
|
|
||||||
static readonly layer = Layer.effect(
|
|
||||||
WooCommerceAPI,
|
|
||||||
// oxlint-disable-next-line require-yield
|
|
||||||
Effect.gen(function*() {
|
|
||||||
const AddProductToCart = Effect.fn("AddProductToCart")(function*(nonce: string, product: CartProduct) {
|
|
||||||
const response = yield* Effect.tryPromise({
|
|
||||||
catch: error => new APIError({ cause: error }),
|
|
||||||
try: async () =>
|
|
||||||
fetch(ROUTE_API_AJOUTE_ARTICLE_PANIER, {
|
|
||||||
// Convertis en chaîne de caractères (équivalent à JSON.stringify).
|
|
||||||
body: Schema.encodeSync(Schema.fromJsonString(CartProduct))(product),
|
|
||||||
credentials: "same-origin",
|
|
||||||
headers: {
|
|
||||||
Accept: "application/json",
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
[ENTETE_WC_NONCE]: nonce,
|
|
||||||
},
|
|
||||||
method: "POST",
|
|
||||||
mode: "same-origin",
|
|
||||||
signal: AbortSignal.timeout(REQUEST_TIMEOUT),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
return response;
|
|
||||||
});
|
|
||||||
|
|
||||||
return WooCommerceAPI.of({
|
|
||||||
AddProductToCart,
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export { WooCommerceAPI };
|
|
||||||
|
|
@ -1,14 +1,17 @@
|
||||||
import { Console, Context, Effect, Layer, Match, pipe, Schedule, Schema, SchemaIssue } from "effect";
|
import { Console, Context, Effect, Layer, Match, pipe, References, Schedule, Schema, SchemaIssue } from "effect";
|
||||||
import { SchemaError } from "effect/Schema";
|
import type { SchemaError } from "effect/Schema";
|
||||||
|
import type {
|
||||||
|
HttpClientError,
|
||||||
|
} from "effect/unstable/http";
|
||||||
import {
|
import {
|
||||||
FetchHttpClient,
|
FetchHttpClient,
|
||||||
HttpClient,
|
HttpClient,
|
||||||
HttpClientError,
|
|
||||||
HttpClientRequest,
|
HttpClientRequest,
|
||||||
HttpClientResponse,
|
HttpClientResponse,
|
||||||
} from "effect/unstable/http";
|
} from "effect/unstable/http";
|
||||||
import { HttpClientErrorSchema } from "effect/unstable/http/HttpClientError";
|
import { HttpClientErrorSchema } from "effect/unstable/http/HttpClientError";
|
||||||
import type { CartProduct } from "../schemas/api.ts";
|
import type { CartProduct, GetProducts } from "../schemas/api.ts";
|
||||||
|
import { Product } from "../schemas/api.ts";
|
||||||
import { WooCommerceCart } from "../schemas/cart.ts";
|
import { WooCommerceCart } from "../schemas/cart.ts";
|
||||||
|
|
||||||
/** Le nombre maximal d'essais pour une Requête. */
|
/** Le nombre maximal d'essais pour une Requête. */
|
||||||
|
|
@ -16,20 +19,21 @@ const MAX_RETRIES = 3;
|
||||||
/** Le temps d'attente avant de réessayer une Requête. */
|
/** Le temps d'attente avant de réessayer une Requête. */
|
||||||
const RETRY_WAIT_TIME = "1 seconds";
|
const RETRY_WAIT_TIME = "1 seconds";
|
||||||
|
|
||||||
|
type APIError = APIRequestError | APIResponseError;
|
||||||
|
|
||||||
|
type APIResponse<T> = T | WooCommerceError;
|
||||||
|
|
||||||
/** Décrit une Erreur survenue au traitement d'une `Request`. */
|
/** Décrit une Erreur survenue au traitement d'une `Request`. */
|
||||||
class APIRequestError extends Schema.TaggedErrorClass<APIRequestError>()("APIRequestError", {
|
class APIRequestError extends Schema.TaggedErrorClass<APIRequestError>()("APIRequestError", {
|
||||||
message: Schema.String,
|
|
||||||
cause: Schema.Union([Schema.Defect, HttpClientErrorSchema]),
|
cause: Schema.Union([Schema.Defect, HttpClientErrorSchema]),
|
||||||
|
message: Schema.String,
|
||||||
}) {}
|
}) {}
|
||||||
|
|
||||||
/** Décrit une Erreur survenue au traitement d'une `Response`. */
|
/** Décrit une Erreur survenue au traitement d'une `Response`. */
|
||||||
class APIResponseError extends Schema.TaggedErrorClass<APIResponseError>()("APIResponseError", {
|
class APIResponseError extends Schema.TaggedErrorClass<APIResponseError>()("APIResponseError", {
|
||||||
message: Schema.String,
|
|
||||||
cause: Schema.Union([Schema.Defect, HttpClientErrorSchema]),
|
cause: Schema.Union([Schema.Defect, HttpClientErrorSchema]),
|
||||||
|
message: Schema.String,
|
||||||
}) {}
|
}) {}
|
||||||
|
|
||||||
type APIError = APIRequestError | APIResponseError;
|
|
||||||
|
|
||||||
class WooCommerceErrorBody extends Schema.Class<WooCommerceErrorBody>("WooCommerceErrorBody")({
|
class WooCommerceErrorBody extends Schema.Class<WooCommerceErrorBody>("WooCommerceErrorBody")({
|
||||||
code: Schema.String,
|
code: Schema.String,
|
||||||
data: Schema.Struct({
|
data: Schema.Struct({
|
||||||
|
|
@ -37,20 +41,19 @@ class WooCommerceErrorBody extends Schema.Class<WooCommerceErrorBody>("WooCommer
|
||||||
}),
|
}),
|
||||||
message: Schema.String,
|
message: Schema.String,
|
||||||
}) {}
|
}) {}
|
||||||
|
|
||||||
class WooCommerceError extends Schema.Class<WooCommerceError>("WooCommerceError")({
|
class WooCommerceError extends Schema.Class<WooCommerceError>("WooCommerceError")({
|
||||||
body: WooCommerceErrorBody,
|
body: WooCommerceErrorBody,
|
||||||
status: Schema.Number,
|
status: Schema.Number,
|
||||||
}) {}
|
}) {}
|
||||||
|
|
||||||
type APIResponse<T> = T | WooCommerceError;
|
|
||||||
|
|
||||||
/** Client `fetch` contenant les options et en-têtes de Requêtes pré-renseignées. */
|
/** Client `fetch` contenant les options et en-têtes de Requêtes pré-renseignées. */
|
||||||
const APIFetchClient = FetchHttpClient.layer.pipe(
|
const APIFetchClient = FetchHttpClient.layer.pipe(
|
||||||
Layer.provide(
|
Layer.provide(
|
||||||
Layer.succeed(
|
Layer.succeed(
|
||||||
FetchHttpClient.RequestInit,
|
FetchHttpClient.RequestInit,
|
||||||
{
|
{
|
||||||
credentials: "same-origin",
|
credentials: "include",
|
||||||
headers: {
|
headers: {
|
||||||
Accept: "application/json",
|
Accept: "application/json",
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
|
|
@ -81,22 +84,22 @@ class APIClient extends Context.Service<APIClient>()("haikuatelier.fr/APIClient"
|
||||||
const matchAPIError = (error: HttpClientError.HttpClientError | SchemaError): APIError => {
|
const matchAPIError = (error: HttpClientError.HttpClientError | SchemaError): APIError => {
|
||||||
if (error._tag === "SchemaError") {
|
if (error._tag === "SchemaError") {
|
||||||
return new APIRequestError({
|
return new APIRequestError({
|
||||||
message: `Erreur lors du parsage du corps de la Requête :${SchemaIssue.makeFormatterDefault()(error.issue)}`,
|
|
||||||
cause: error,
|
cause: error,
|
||||||
|
message: `Erreur lors du parsage du corps de la Requête :${SchemaIssue.makeFormatterDefault()(error.issue)}`,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return Match.typeTags<HttpClientError.HttpClientErrorReason, APIError>()({
|
return Match.typeTags<HttpClientError.HttpClientErrorReason, APIError>()({
|
||||||
|
DecodeError: cause => new APIResponseError({ cause, message: "Le corps de la Réponse ne peut être lu" }),
|
||||||
|
EmptyBodyError: cause => new APIResponseError({ cause, message: "Un corps vide ne peut être lu" }),
|
||||||
|
EncodeError: cause => new APIRequestError({ cause, message: "Le corps de la Requête ne peut être lu" }),
|
||||||
|
InvalidUrlError: cause => new APIRequestError({ cause, message: "L'URL de la Requête n'est pas valide" }),
|
||||||
|
StatusCodeError: cause =>
|
||||||
|
new APIResponseError({ cause, message: "Le code HTTP de la Réponse correspond à un échec" }),
|
||||||
TransportError: (cause): APIError =>
|
TransportError: (cause): APIError =>
|
||||||
new APIRequestError({
|
new APIRequestError({
|
||||||
cause,
|
cause,
|
||||||
message: "Un problème réseau empêche l'exécution de la Requête",
|
message: "Un problème réseau empêche l'exécution de la Requête",
|
||||||
}),
|
}),
|
||||||
EncodeError: cause => new APIRequestError({ cause, message: "Le corps de la Requête ne peut être lu" }),
|
|
||||||
InvalidUrlError: cause => new APIRequestError({ cause, message: "L'URL de la Requête n'est pas valide" }),
|
|
||||||
StatusCodeError: cause =>
|
|
||||||
new APIResponseError({ cause, message: "Le code HTTP de la Réponse correspond à un échec" }),
|
|
||||||
DecodeError: cause => new APIResponseError({ cause, message: "Le corps de la Réponse ne peut être lu" }),
|
|
||||||
EmptyBodyError: cause => new APIResponseError({ cause, message: "Un corps vide ne peut être lu" }),
|
|
||||||
})(error.reason);
|
})(error.reason);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -109,7 +112,7 @@ class APIClient extends Context.Service<APIClient>()("haikuatelier.fr/APIClient"
|
||||||
return yield* Effect.succeed(error);
|
return yield* Effect.succeed(error);
|
||||||
});
|
});
|
||||||
|
|
||||||
const AddProductToCart = Effect.fn("AppClient.AddProductToCart")(
|
const AddProductToCart = Effect.fn("APIClient.AddProductToCart")(
|
||||||
function*(nonce: string, productToAdd: CartProduct): Effect.fn.Return<APIResponse<WooCommerceCart>, APIError> {
|
function*(nonce: string, productToAdd: CartProduct): Effect.fn.Return<APIResponse<WooCommerceCart>, APIError> {
|
||||||
const request = pipe(
|
const request = pipe(
|
||||||
HttpClientRequest.post(`/wp-json/wc/store/cart/add-item`),
|
HttpClientRequest.post(`/wp-json/wc/store/cart/add-item`),
|
||||||
|
|
@ -132,7 +135,32 @@ class APIClient extends Context.Service<APIClient>()("haikuatelier.fr/APIClient"
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
return { AddProductToCart };
|
const GetProducts = Effect.fn("APIClient.GetProducts")(
|
||||||
|
function*(nonce: string, queryParams: GetProducts) {
|
||||||
|
const request = pipe(
|
||||||
|
HttpClientRequest.get(`/wp-json/wc/v3/products`),
|
||||||
|
HttpClientRequest.setHeader("Nonce", nonce),
|
||||||
|
// TODO: Utiliser l'environnement
|
||||||
|
HttpClientRequest.basicAuth(
|
||||||
|
"ck_eded693107df0dbc19dab937e0c71325db810a4a",
|
||||||
|
"cs_a68c0f3e711c4a21be51495d09e6fe807649bbfb",
|
||||||
|
),
|
||||||
|
// Le corps de la Requête a été validée en amont, on peut utiliser Unsafe.
|
||||||
|
HttpClientRequest.setUrlParams(queryParams),
|
||||||
|
);
|
||||||
|
|
||||||
|
const response = yield* pipe(
|
||||||
|
haikuHTTPClient.execute(request),
|
||||||
|
Effect.flatMap(HttpClientResponse.schemaBodyJson(Schema.Array(Product))),
|
||||||
|
Effect.mapError(error => matchAPIError(error)),
|
||||||
|
Effect.tapError(error => printErrorAsSuccinctMessage(error)),
|
||||||
|
);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return { AddProductToCart, GetProducts };
|
||||||
}),
|
}),
|
||||||
}) {
|
}) {
|
||||||
static readonly Live = Layer.effect(this, this.make).pipe(Layer.provide(APIFetchClient));
|
static readonly Live = Layer.effect(this, this.make).pipe(Layer.provide(APIFetchClient));
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ const getAllSelectorFromParent =
|
||||||
pipe(
|
pipe(
|
||||||
parent.querySelectorAll<E>(selector),
|
parent.querySelectorAll<E>(selector),
|
||||||
// Convertis NodeListOf en Array.
|
// Convertis NodeListOf en Array.
|
||||||
(xs: NodeListOf<E>) => Array.from<E>(xs),
|
(xs: NodeListOf<E>) => [...xs],
|
||||||
(xs: Array<E>) => Option.liftPredicate(FxArray.isReadonlyArrayNonEmpty)(xs),
|
(xs: Array<E>) => Option.liftPredicate(FxArray.isReadonlyArrayNonEmpty)(xs),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { Effect } from "effect";
|
||||||
|
import { ATTRIBUT_CHARGEMENT, ATTRIBUT_DESACTIVE } from "../../scripts/constantes/dom.ts";
|
||||||
|
|
||||||
|
const setLoadingState = Effect.fn("setLoadingState")(function*(element: HTMLElement, isLoading: boolean) {
|
||||||
|
element.toggleAttribute(ATTRIBUT_DESACTIVE, isLoading);
|
||||||
|
element.toggleAttribute(ATTRIBUT_CHARGEMENT, isLoading);
|
||||||
|
});
|
||||||
|
|
||||||
|
export { setLoadingState };
|
||||||
|
|
@ -1,18 +1,18 @@
|
||||||
import { Context, Effect, flow, Layer, pipe, Schedule, Schema } from "effect";
|
import { Context, Effect, flow, Layer, pipe, Schedule, Schema } from "effect";
|
||||||
import { FetchHttpClient, HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http";
|
import { FetchHttpClient, HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http";
|
||||||
import { HttpClientError } from "effect/unstable/http/HttpClientError";
|
import type { HttpClientError } from "effect/unstable/http/HttpClientError";
|
||||||
|
|
||||||
class Todo extends Schema.Class<Todo>("Todo")({
|
|
||||||
userId: Schema.Number,
|
|
||||||
id: Schema.Number,
|
|
||||||
title: Schema.String,
|
|
||||||
completed: Schema.Boolean,
|
|
||||||
}) {}
|
|
||||||
|
|
||||||
class FetchClientError extends Schema.TaggedErrorClass<FetchClientError>()("FetchClientError", {
|
class FetchClientError extends Schema.TaggedErrorClass<FetchClientError>()("FetchClientError", {
|
||||||
cause: Schema.Defect,
|
cause: Schema.Defect,
|
||||||
}) {}
|
}) {}
|
||||||
|
|
||||||
|
class Todo extends Schema.Class<Todo>("Todo")({
|
||||||
|
completed: Schema.Boolean,
|
||||||
|
id: Schema.Number,
|
||||||
|
title: Schema.String,
|
||||||
|
userId: Schema.Number,
|
||||||
|
}) {}
|
||||||
|
|
||||||
class FetchClientExample extends Context.Service<FetchClientExample, {
|
class FetchClientExample extends Context.Service<FetchClientExample, {
|
||||||
readonly allTodos: Effect.Effect<ReadonlyArray<Todo>, FetchClientError>;
|
readonly allTodos: Effect.Effect<ReadonlyArray<Todo>, FetchClientError>;
|
||||||
createTodo(todo: Omit<Todo, "id">): Effect.Effect<Todo, FetchClientError>;
|
createTodo(todo: Omit<Todo, "id">): Effect.Effect<Todo, FetchClientError>;
|
||||||
|
|
@ -75,8 +75,8 @@ class FetchClientExample extends Context.Service<FetchClientExample, {
|
||||||
|
|
||||||
return FetchClientExample.of({
|
return FetchClientExample.of({
|
||||||
allTodos,
|
allTodos,
|
||||||
getTodo,
|
|
||||||
createTodo,
|
createTodo,
|
||||||
|
getTodo,
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
).pipe(
|
).pipe(
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { Schema } from "effect";
|
import { Schema } from "effect";
|
||||||
import { ProductId, ProductQuantity, ProductVariationAttribute } from "./product.ts";
|
import { ProductId, ProductQuantity, ProductStatus, ProductVariationAttribute } from "./product.ts";
|
||||||
|
|
||||||
class CartProduct extends Schema.Class<CartProduct>("CartProduct")({
|
class CartProduct extends Schema.Class<CartProduct>("CartProduct")({
|
||||||
id: ProductId,
|
id: ProductId,
|
||||||
|
|
@ -7,4 +7,58 @@ class CartProduct extends Schema.Class<CartProduct>("CartProduct")({
|
||||||
variation: Schema.Array(ProductVariationAttribute),
|
variation: Schema.Array(ProductVariationAttribute),
|
||||||
}) {}
|
}) {}
|
||||||
|
|
||||||
export { CartProduct };
|
class GetProducts extends Schema.Class<GetProducts>("GetProducts")({
|
||||||
|
/** L'ID de la Catégorie de Produits demandé. */
|
||||||
|
category: Schema.Int.pipe(Schema.optional),
|
||||||
|
/** Le numéro de page demandé. */
|
||||||
|
page: Schema.Int,
|
||||||
|
/** Le nombre de Produits par page demandé. */
|
||||||
|
per_page: Schema.Int,
|
||||||
|
/** Le statut demandé des Produits. */
|
||||||
|
status: ProductStatus,
|
||||||
|
}) {}
|
||||||
|
|
||||||
|
class Product extends Schema.Class<Product>("Product")({
|
||||||
|
attributes: Schema.Unknown,
|
||||||
|
brands: Schema.Unknown,
|
||||||
|
// TODO: Pourrait être une énumération.
|
||||||
|
catalog_visibility: Schema.String,
|
||||||
|
categories: Schema.Unknown,
|
||||||
|
description: Schema.String,
|
||||||
|
dimensions: Schema.Unknown,
|
||||||
|
featured: Schema.Boolean,
|
||||||
|
grouped_products: Schema.Unknown,
|
||||||
|
has_options: Schema.Boolean,
|
||||||
|
id: Schema.Int,
|
||||||
|
// NOTE: Non-standard, injecté dans la Réponse.
|
||||||
|
image_repos: Schema.String,
|
||||||
|
// NOTE: Non-standard, injecté dans la Réponse.
|
||||||
|
image_survol: Schema.String,
|
||||||
|
images: Schema.Unknown,
|
||||||
|
low_stock_amount: Schema.Union([Schema.Number, Schema.Null]),
|
||||||
|
menu_order: Schema.Int,
|
||||||
|
meta_data: Schema.Unknown,
|
||||||
|
name: Schema.String,
|
||||||
|
on_sale: Schema.Boolean,
|
||||||
|
parent_id: Schema.Int,
|
||||||
|
permalink: Schema.URLFromString,
|
||||||
|
price: Schema.String,
|
||||||
|
// NOTE: Non-standard, injecté dans la Réponse.
|
||||||
|
prix_maximal: Schema.String,
|
||||||
|
regular_price: Schema.String,
|
||||||
|
sale_price: Schema.String,
|
||||||
|
short_description: Schema.String,
|
||||||
|
sku: Schema.String,
|
||||||
|
slug: Schema.String,
|
||||||
|
sold_individually: Schema.Boolean,
|
||||||
|
stock_quantity: Schema.Union([Schema.Int, Schema.Null]),
|
||||||
|
// TODO: Pourrait être une énumération.
|
||||||
|
stock_status: Schema.String,
|
||||||
|
tags: Schema.Unknown,
|
||||||
|
type: Schema.Literals(["external", "grouped", "simple", "variable"]),
|
||||||
|
variations: Schema.Array(Schema.Int),
|
||||||
|
virtual: Schema.Boolean,
|
||||||
|
weight: Schema.String,
|
||||||
|
}) {}
|
||||||
|
|
||||||
|
export { CartProduct, GetProducts, Product };
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@
|
||||||
import { Effect, Option, pipe, Schema, SchemaIssue, SchemaTransformation } from "effect";
|
import { Effect, Option, pipe, Schema, SchemaIssue, SchemaTransformation } from "effect";
|
||||||
import type { SchemaError } from "effect/Schema";
|
import type { SchemaError } from "effect/Schema";
|
||||||
|
|
||||||
|
const ProductStatus = Schema.Literals(["any", "draft", "future", "pending", "private", "publish", "trash"]);
|
||||||
|
|
||||||
/** Représente l'identifiant numérique unique d'un Produit. */
|
/** Représente l'identifiant numérique unique d'un Produit. */
|
||||||
const ProductId = Schema.Int.pipe(Schema.brand("ProductId")).check(Schema.isGreaterThan(0));
|
const ProductId = Schema.Int.pipe(Schema.brand("ProductId")).check(Schema.isGreaterThan(0));
|
||||||
|
|
||||||
|
|
@ -78,6 +80,7 @@ export {
|
||||||
ProductId,
|
ProductId,
|
||||||
ProductQuantity,
|
ProductQuantity,
|
||||||
ProductQuantityFromString,
|
ProductQuantityFromString,
|
||||||
|
ProductStatus,
|
||||||
ProductVariation,
|
ProductVariation,
|
||||||
ProductVariationAttribute,
|
ProductVariationAttribute,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { Console, Layer, ManagedRuntime, pipe } from "effect";
|
||||||
|
|
||||||
|
import { APIClient } from "../../scripts-effect/lib/api.ts";
|
||||||
|
import ShopPageDOM from "./service-dom.ts";
|
||||||
|
import ShopPageElements from "./service-elements.ts";
|
||||||
|
import ShopPageMessages from "./service-messages.ts";
|
||||||
|
|
||||||
|
const ShopPageRuntime = ManagedRuntime.make(
|
||||||
|
pipe(
|
||||||
|
ShopPageDOM.Live,
|
||||||
|
Layer.provideMerge(ShopPageMessages.Live),
|
||||||
|
Layer.provideMerge(ShopPageElements.Live),
|
||||||
|
Layer.provide(APIClient.Live),
|
||||||
|
Layer.tapError(error => Console.error("ProductPageRuntime", "Impossible de créer le Layer :", error)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
export default ShopPageRuntime;
|
||||||
|
|
@ -0,0 +1,163 @@
|
||||||
|
import {
|
||||||
|
Array as FxArray,
|
||||||
|
Console,
|
||||||
|
Context,
|
||||||
|
Effect,
|
||||||
|
Layer,
|
||||||
|
Option,
|
||||||
|
pipe,
|
||||||
|
Ref,
|
||||||
|
Schema,
|
||||||
|
SchemaIssue,
|
||||||
|
Stream,
|
||||||
|
SubscriptionRef,
|
||||||
|
} from "effect";
|
||||||
|
import { SchemaError } from "effect/Schema";
|
||||||
|
import html from "html-template-tag";
|
||||||
|
import { APIClient } from "../../scripts-effect/lib/api.ts";
|
||||||
|
import { setLoadingState } from "../../scripts-effect/lib/elements.ts";
|
||||||
|
import { GetProducts, Product } from "../../scripts-effect/schemas/api.ts";
|
||||||
|
import { ATTRIBUT_HIDDEN, ATTRIBUT_ID_CATEGORIE_PRODUITS, ATTRIBUT_PAGE } from "../constantes/dom.ts";
|
||||||
|
import ShopPageElements from "./service-elements.ts";
|
||||||
|
import ShopPageMessages from "./service-messages.ts";
|
||||||
|
|
||||||
|
/** Le nombre de Produits à afficher par « page ». */
|
||||||
|
const PRODUCTS_PER_PAGE = 18;
|
||||||
|
|
||||||
|
/** Forme attendue des données injectées dans la page sous forme de JSON. */
|
||||||
|
class PageStates extends Schema.Opaque<PageStates>()(
|
||||||
|
Schema.Struct({
|
||||||
|
authString: Schema.NonEmptyString,
|
||||||
|
nonce: Schema.NonEmptyString,
|
||||||
|
}),
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/** Représente une Erreur liée à un état de page invalide ou incohérent empêchant la poursuite des interactions/de la navigation. */
|
||||||
|
class InvalidShopPageStateError
|
||||||
|
extends Schema.TaggedErrorClass<InvalidShopPageStateError>()("InvalidShopPageStateError", {
|
||||||
|
cause: Schema.String,
|
||||||
|
})
|
||||||
|
{
|
||||||
|
/** Créé une `InvalidShopPageStateError` depuis une `SchemaError` levée suite à une validation. */
|
||||||
|
static readonly fromSchemaError = (schemaError: SchemaError): InvalidShopPageStateError =>
|
||||||
|
new InvalidShopPageStateError({
|
||||||
|
cause: SchemaIssue.makeFormatterDefault()(schemaError.issue),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class ShopPageDOM extends Context.Service<ShopPageDOM>()("haikuatelier.fr/Shop/ShopPageDOM", {
|
||||||
|
make: Effect.gen(function*() {
|
||||||
|
const { PageStatesRawJson, ProductsGrid, ShowMoreButton } = yield* ShopPageElements;
|
||||||
|
const { ShowMoreButtonText } = yield* ShopPageMessages;
|
||||||
|
const API = yield* APIClient;
|
||||||
|
|
||||||
|
const { authString, nonce } = yield* pipe(
|
||||||
|
PageStatesRawJson.textContent,
|
||||||
|
(textContent: string) =>
|
||||||
|
Schema.decodeUnknownEffect(Schema.fromJsonString(PageStates))(textContent, { errors: "all" }),
|
||||||
|
Effect.mapError(InvalidShopPageStateError.fromSchemaError),
|
||||||
|
);
|
||||||
|
|
||||||
|
/** ID de la Catégorie des Produits de la Page, si la Page courante est une Archive. */
|
||||||
|
const ProductsCategoryId = yield* pipe(
|
||||||
|
ProductsGrid.getAttribute(ATTRIBUT_ID_CATEGORIE_PRODUITS),
|
||||||
|
Number,
|
||||||
|
Option.fromNullishOr,
|
||||||
|
Ref.make,
|
||||||
|
);
|
||||||
|
|
||||||
|
// TODO: Créer une SubscriptionRef mettant à jour le DOM au changement de valeur.
|
||||||
|
const PageNumber = yield* Ref.make(1);
|
||||||
|
|
||||||
|
const createProductDOM = (product: Product): HTMLElement => {
|
||||||
|
const article = document.createElement("article");
|
||||||
|
article.classList.add("produit");
|
||||||
|
article.innerHTML = html`<figure>
|
||||||
|
<a href="/product/${product.slug}">
|
||||||
|
<picture class="produit__illustration produit__illustration__principale">
|
||||||
|
$${product.image_repos}
|
||||||
|
</picture>
|
||||||
|
|
||||||
|
<picture class="produit__illustration produit__illustration__survol">
|
||||||
|
$${product.image_survol}
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<figcaption class="produit__textuel">
|
||||||
|
<h3 class="produit__textuel__titre">
|
||||||
|
<a href="$${product.permalink.toString()}">${product.name}</a>
|
||||||
|
</h3>
|
||||||
|
<p class="produit__textuel__prix">
|
||||||
|
${product.prix_maximal}€
|
||||||
|
</p>
|
||||||
|
</figcaption>
|
||||||
|
</figure>
|
||||||
|
`;
|
||||||
|
return article;
|
||||||
|
};
|
||||||
|
const createNewPageDOM = (products: ReadonlyArray<Product>) => {
|
||||||
|
const fragment: DocumentFragment = document.createDocumentFragment();
|
||||||
|
|
||||||
|
// Ajoute le HTML des cartes des Produits au fragment.
|
||||||
|
pipe(
|
||||||
|
FxArray.take(products, PRODUCTS_PER_PAGE),
|
||||||
|
FxArray.forEach(product => {
|
||||||
|
const productHTML = createProductDOM(product);
|
||||||
|
fragment.append(productHTML);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
return fragment;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onMoreProductedWantedHandler = Effect.fn("onMoreProductedWantedHandler")(function*() {
|
||||||
|
yield* Console.debug("onMoreProductedWantedHandler");
|
||||||
|
|
||||||
|
/** Le numéro de page souhaitée. */
|
||||||
|
const newPageNumber = yield* Ref.updateAndGet(PageNumber, pageNumber => pageNumber + 1);
|
||||||
|
/** L'ID de la Catégorie de Produits affichée dans la page si elle existe. */
|
||||||
|
const categoryId = pipe(yield* Ref.get(ProductsCategoryId), Option.getOrUndefined);
|
||||||
|
|
||||||
|
const requestBody = yield* GetProducts.makeEffect({
|
||||||
|
page: newPageNumber,
|
||||||
|
per_page: PRODUCTS_PER_PAGE,
|
||||||
|
status: "publish",
|
||||||
|
...(categoryId && { category: categoryId }),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Désactive les interactions et affiche un texte de chargement le temps de la requête.
|
||||||
|
yield* setLoadingState(ShowMoreButton, true);
|
||||||
|
yield* SubscriptionRef.set(ShowMoreButtonText, "Getting Products...");
|
||||||
|
|
||||||
|
const newProducts = yield* API.GetProducts(nonce, requestBody);
|
||||||
|
yield* Console.debug("onMoreProductedWantedHandler", newProducts);
|
||||||
|
|
||||||
|
// Rétablis le texte du Bouton et réactive les interactions.
|
||||||
|
yield* SubscriptionRef.set(ShowMoreButtonText, "Show more");
|
||||||
|
yield* setLoadingState(ShowMoreButton, false);
|
||||||
|
|
||||||
|
// Cache le bouton s'il y a moins de Produits disponibles que PRODUCTS_PER_PAGE (que l'on est donc à la dernière page).
|
||||||
|
ShowMoreButton.toggleAttribute(ATTRIBUT_HIDDEN, newProducts.length < PRODUCTS_PER_PAGE);
|
||||||
|
|
||||||
|
// Ajoute les nouveaux Produits dans le DOM.
|
||||||
|
ProductsGrid.append(fragment);
|
||||||
|
ProductsGrid.setAttribute(ATTRIBUT_PAGE, String(newPageNumber));
|
||||||
|
});
|
||||||
|
|
||||||
|
const initLoadMoreProductsOnButtonClick = Effect.fn("initLoadMoreProductsOnButtonClick")(function*() {
|
||||||
|
return yield* pipe(
|
||||||
|
Stream.fromEventListener(ShowMoreButton, "click"),
|
||||||
|
Stream.tap(onMoreProductedWantedHandler),
|
||||||
|
Stream.runDrain,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
ProductsCategoryId,
|
||||||
|
initLoadMoreProductsOnButtonClick,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
}) {
|
||||||
|
static readonly Live = Layer.effect(this, this.make);
|
||||||
|
}
|
||||||
|
export default ShopPageDOM;
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { Context, Effect, Layer } from "effect";
|
||||||
|
import { getFirstSelectorFromDocument } from "../../scripts-effect/lib/dom.ts";
|
||||||
|
import { IncoherentDOMError } from "../page-produit/errors.ts";
|
||||||
|
|
||||||
|
class ShopPageElements extends Context.Service<ShopPageElements>()("haikuatelier.fr/Shop/ShopPageElements", {
|
||||||
|
make: Effect.gen(function*() {
|
||||||
|
const PageStatesRawJson = yield* getFirstSelectorFromDocument<HTMLScriptElement>("#page-states");
|
||||||
|
|
||||||
|
/** Le Bouton « Show more » pour afficher plus de Produits à la suite de la Grille. */
|
||||||
|
const ShowMoreButton = yield* getFirstSelectorFromDocument<HTMLButtonElement>(
|
||||||
|
"#page-boutique #bouton-plus-de-produits",
|
||||||
|
);
|
||||||
|
|
||||||
|
/** Le conteneur de la Grille des Produits. */
|
||||||
|
const ProductsGrid = yield* getFirstSelectorFromDocument<HTMLDivElement>("#page-boutique .grille-produits");
|
||||||
|
|
||||||
|
return {
|
||||||
|
PageStatesRawJson,
|
||||||
|
ProductsGrid,
|
||||||
|
ShowMoreButton,
|
||||||
|
};
|
||||||
|
}).pipe(Effect.mapErrorEager(IncoherentDOMError.fromNoSuchElementError)),
|
||||||
|
}) {
|
||||||
|
static readonly Live = Layer.effect(this, this.make);
|
||||||
|
}
|
||||||
|
export default ShopPageElements;
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
import { Context, Effect, Layer, pipe, Stream, SubscriptionRef } from "effect";
|
||||||
|
|
||||||
|
import ShopPageElements from "./service-elements.ts";
|
||||||
|
|
||||||
|
class ShopPageMessages extends Context.Service<ShopPageMessages>()("haikuatelier.fr/Shop/Messages", {
|
||||||
|
make: Effect.gen(function*() {
|
||||||
|
const { ShowMoreButton } = yield* ShopPageElements;
|
||||||
|
|
||||||
|
const ShowMoreButtonText = yield* SubscriptionRef.make("Show more");
|
||||||
|
// Const ShowMoreErrorText = yield* SubscriptionRef.make<Option.Option<string>>(Option.none());
|
||||||
|
|
||||||
|
const initShowMoreButtonUpdates = Effect.fn("initShowMoreButtonUpdates")(function*() {
|
||||||
|
return yield* pipe(
|
||||||
|
SubscriptionRef.changes(ShowMoreButtonText),
|
||||||
|
Stream.tap(newText => {
|
||||||
|
ShowMoreButton.textContent = newText;
|
||||||
|
return Effect.succeed(newText);
|
||||||
|
}),
|
||||||
|
Stream.runDrain,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return { ShowMoreButtonText, initShowMoreButtonUpdates };
|
||||||
|
}),
|
||||||
|
}) {
|
||||||
|
static readonly Live = Layer.effect(this, this.make);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ShopPageMessages;
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
|
// oxlint-disable typescript/dot-notation
|
||||||
import type { SchemaError } from "effect/Schema";
|
import type { SchemaError } from "effect/Schema";
|
||||||
|
|
||||||
// oxlint-disable typescript/dot-notation
|
|
||||||
import {
|
import {
|
||||||
Array as FxArray,
|
Array as FxArray,
|
||||||
Console,
|
Console,
|
||||||
|
|
@ -21,6 +21,7 @@ import type { APIError } from "../../scripts-effect/lib/api.ts";
|
||||||
import type { DetailEnsemble } from "./types.d.ts";
|
import type { DetailEnsemble } from "./types.d.ts";
|
||||||
|
|
||||||
import { APIClient } from "../../scripts-effect/lib/api.ts";
|
import { APIClient } from "../../scripts-effect/lib/api.ts";
|
||||||
|
import { setLoadingState } from "../../scripts-effect/lib/elements.ts";
|
||||||
import { CartProduct } from "../../scripts-effect/schemas/api.ts";
|
import { CartProduct } from "../../scripts-effect/schemas/api.ts";
|
||||||
import { WooCommerceCart } from "../../scripts-effect/schemas/cart.ts";
|
import { WooCommerceCart } from "../../scripts-effect/schemas/cart.ts";
|
||||||
import { Product, ProductVariation, ProductVariationAttribute } from "../../scripts-effect/schemas/product.ts";
|
import { Product, ProductVariation, ProductVariationAttribute } from "../../scripts-effect/schemas/product.ts";
|
||||||
|
|
@ -98,11 +99,6 @@ class ProductPageDOM extends Context.Service<ProductPageDOM>()(
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const setLoadingState = Effect.fn("setLoadingState")(function*(element: HTMLElement, isLoading: boolean) {
|
|
||||||
element.toggleAttribute(ATTRIBUT_DESACTIVE, isLoading);
|
|
||||||
element.toggleAttribute(ATTRIBUT_CHARGEMENT, isLoading);
|
|
||||||
});
|
|
||||||
|
|
||||||
const detailButtonClickHandler = Effect.fn("detailButtonClickHandler")(
|
const detailButtonClickHandler = Effect.fn("detailButtonClickHandler")(
|
||||||
function*(evt: Event) {
|
function*(evt: Event) {
|
||||||
// Empêche la pollution de l'historique de navigation
|
// Empêche la pollution de l'historique de navigation
|
||||||
|
|
@ -232,7 +228,7 @@ class ProductPageDOM extends Context.Service<ProductPageDOM>()(
|
||||||
// Désactive les interactions le temps de la requête.
|
// Désactive les interactions le temps de la requête.
|
||||||
yield* setLoadingState(AddToCartButton, true);
|
yield* setLoadingState(AddToCartButton, true);
|
||||||
yield* SubscriptionRef.set(AddToCartButtonText, "Adding Product...");
|
yield* SubscriptionRef.set(AddToCartButtonText, "Adding Product...");
|
||||||
// lanceAnimationCycleLoading(AddToCartButton, 500);
|
// LanceAnimationCycleLoading(AddToCartButton, 500);
|
||||||
|
|
||||||
// Exécute la Requête auprès du backend.
|
// Exécute la Requête auprès du backend.
|
||||||
const newCart = yield* API.AddProductToCart(PageStates.nonce, requestBody);
|
const newCart = yield* API.AddProductToCart(PageStates.nonce, requestBody);
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ class ProductPageMessages extends Context.Service<ProductPageMessages>()("haikua
|
||||||
const { AddToCartButton } = yield* ProductPageElements;
|
const { AddToCartButton } = yield* ProductPageElements;
|
||||||
|
|
||||||
const AddToCartButtonText = yield* SubscriptionRef.make("Add to cart");
|
const AddToCartButtonText = yield* SubscriptionRef.make("Add to cart");
|
||||||
// const AddToCartErrorText = yield* SubscriptionRef.make<Option.Option<string>>(Option.none());
|
// Const AddToCartErrorText = yield* SubscriptionRef.make<Option.Option<string>>(Option.none());
|
||||||
|
|
||||||
const initAddToCartButtonUpdates = Effect.fn("initAddToCartButtonUpdates")(function*() {
|
const initAddToCartButtonUpdates = Effect.fn("initAddToCartButtonUpdates")(function*() {
|
||||||
return yield* pipe(
|
return yield* pipe(
|
||||||
|
|
|
||||||
|
|
@ -2,75 +2,24 @@
|
||||||
* Scripts pour les fonctionnalités de la page Boutique.
|
* Scripts pour les fonctionnalités de la page Boutique.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { pipe } from "@mobily/ts-belt";
|
import { Console, Effect } from "effect";
|
||||||
import { tap } from "@mobily/ts-belt/Function";
|
import ShopPageRuntime from "./page-boutique/runtime.ts";
|
||||||
import { EitherAsync } from "purify-ts";
|
import ShopPageDOM from "./page-boutique/service-dom.ts";
|
||||||
import { match, P } from "ts-pattern";
|
import ShopPageElements from "./page-boutique/service-elements.ts";
|
||||||
import { ValiError } from "valibot";
|
import ShopPageMessages from "./page-boutique/service-messages.ts";
|
||||||
|
|
||||||
import type { APIFetchErrors } from "./lib/types/api/erreurs";
|
|
||||||
import type { WCV3Products, WCV3ProductsArgs } from "./lib/types/api/v3/products.ts";
|
|
||||||
import type { GenericPageState } from "./lib/types/pages";
|
|
||||||
|
|
||||||
import { ROUTE_API_NOUVELLE_PRODUCTS } from "./constantes/api.ts";
|
|
||||||
import { PRODUCT_STATUTES } from "./constantes/api/products.ts";
|
|
||||||
import {
|
|
||||||
ATTRIBUT_CHARGEMENT,
|
|
||||||
ATTRIBUT_DESACTIVE,
|
|
||||||
ATTRIBUT_HIDDEN,
|
|
||||||
ATTRIBUT_ID_CATEGORIE_PRODUITS,
|
|
||||||
ATTRIBUT_PAGE,
|
|
||||||
DOM_BOUTON_PLUS_PRODUITS,
|
|
||||||
DOM_GRILLE_PRODUITS,
|
|
||||||
} from "./constantes/dom.ts";
|
|
||||||
import { lanceAnimationCycleLoading } from "./lib/animations.ts";
|
|
||||||
import { html, mustGetEleInDocument } from "./lib/dom.ts";
|
|
||||||
import { BadRequestError, reporteErreur, ServerError } from "./lib/erreurs.ts";
|
|
||||||
import { getBackendAvecParametresUrl, newPartialResponse } from "./lib/reseau.ts";
|
|
||||||
import { WCV3ProductsArgsSchema, WCV3ProductsSchema } from "./lib/schemas/api/v3/products.ts";
|
|
||||||
import { safeSchemaParse } from "./lib/validation.ts";
|
|
||||||
|
|
||||||
type APIProductsErrors =
|
|
||||||
| APIFetchErrors
|
|
||||||
| ValiError<typeof WCV3ProductsArgsSchema>
|
|
||||||
| ValiError<typeof WCV3ProductsSchema>;
|
|
||||||
|
|
||||||
// @ts-expect-error -- États injectés par le modèle PHP
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- États injectés par le modèle PHP
|
|
||||||
const ETATS_PAGE: GenericPageState = _etats;
|
|
||||||
|
|
||||||
// Numéros magiques
|
|
||||||
const PRODUCTS_PER_PAGE = 12;
|
|
||||||
|
|
||||||
// Éléments d'intérêt
|
|
||||||
const E = {
|
|
||||||
BOUTON_PLUS_DE_PRODUITS: mustGetEleInDocument<HTMLButtonElement>(DOM_BOUTON_PLUS_PRODUITS),
|
|
||||||
GRILLE_PRODUITS: mustGetEleInDocument<HTMLDivElement>(DOM_GRILLE_PRODUITS),
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TODO
|
|
||||||
*/
|
|
||||||
const initialisePageBoutique = (): void => {
|
|
||||||
/** ID de la Catégorie de Produits si la Page courante est l'Archive d'une Catégorie. */
|
|
||||||
const idCategorieProduits: null | string = E.GRILLE_PRODUITS.getAttribute(ATTRIBUT_ID_CATEGORIE_PRODUITS);
|
|
||||||
|
|
||||||
E.BOUTON_PLUS_DE_PRODUITS.addEventListener("click", (): void => {
|
|
||||||
/** Le numéro de page demandée par l'Utilisateur. */
|
|
||||||
const nouveauNumeroPage = Number(E.GRILLE_PRODUITS.getAttribute(ATTRIBUT_PAGE)) + 1;
|
|
||||||
/** Les arguments passés à la requête auprès Backend pour la nouvelle page de Produits. */
|
|
||||||
const args: WCV3ProductsArgs = {
|
|
||||||
page: nouveauNumeroPage,
|
|
||||||
per_page: PRODUCTS_PER_PAGE,
|
|
||||||
status: PRODUCT_STATUTES.PUBLISH,
|
|
||||||
// Ajoute conditionnellement la Catégorie de Produits
|
|
||||||
...(idCategorieProduits && { category: idCategorieProduits }),
|
|
||||||
};
|
|
||||||
|
|
||||||
undefined;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", (): void => {
|
document.addEventListener("DOMContentLoaded", (): void => {
|
||||||
initialisePageBoutique();
|
console.debug("scripts-page-boutique");
|
||||||
|
// initialisePageBoutique();
|
||||||
|
ShopPageRuntime.runFork(Effect.gen(function*() {
|
||||||
|
const Elements = yield* ShopPageElements;
|
||||||
|
const DOM = yield* ShopPageDOM;
|
||||||
|
const Messages = yield* ShopPageMessages;
|
||||||
|
|
||||||
|
yield* Effect.all([DOM.initLoadMoreProductsOnButtonClick(), Messages.initShowMoreButtonUpdates()], {
|
||||||
|
concurrency: "unbounded",
|
||||||
|
}).pipe(Effect.tapError(Console.error));
|
||||||
|
|
||||||
|
console.debug(Elements.ProductsGrid);
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -11,15 +11,20 @@ namespace HaikuAtelier;
|
||||||
use HaikuAtelier\Data\Product;
|
use HaikuAtelier\Data\Product;
|
||||||
use HaikuAtelier\WP\Resource;
|
use HaikuAtelier\WP\Resource;
|
||||||
use Illuminate\Support\Arr;
|
use Illuminate\Support\Arr;
|
||||||
|
use Roots\WPConfig\Config;
|
||||||
use Timber\Timber;
|
use Timber\Timber;
|
||||||
use WC_Product;
|
use WC_Product;
|
||||||
use WP_Term;
|
use WP_Term;
|
||||||
|
|
||||||
use function add_action;
|
use function add_action;
|
||||||
use function assert;
|
use function assert;
|
||||||
|
use function base64_encode;
|
||||||
use function get_queried_object;
|
use function get_queried_object;
|
||||||
use function is_array;
|
use function is_array;
|
||||||
|
use function is_string;
|
||||||
use function wc_get_products;
|
use function wc_get_products;
|
||||||
|
use function wp_create_nonce;
|
||||||
|
use function wp_json_encode;
|
||||||
|
|
||||||
$context = Timber::context();
|
$context = Timber::context();
|
||||||
$templates = ['boutique.twig'];
|
$templates = ['boutique.twig'];
|
||||||
|
|
@ -48,6 +53,17 @@ $products = wc_get_products([
|
||||||
$context['products'] = $products;
|
$context['products'] = $products;
|
||||||
$context['category_id'] = $current_term->term_id;
|
$context['category_id'] = $current_term->term_id;
|
||||||
|
|
||||||
|
// Injecte les états initiaux des données du Produit sous forme de JSON dans le contexte.
|
||||||
|
$page_states = [
|
||||||
|
'nonce' => wp_create_nonce('wc_store_api'),
|
||||||
|
'authString' => base64_encode(
|
||||||
|
Config::get('WOOCOMMERCE_API_CONSUMER_KEY') . ':' . Config::get('WOOCOMMERCE_API_CONSUMER_SECRET'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|> wp_json_encode(...);
|
||||||
|
assert(is_string($page_states));
|
||||||
|
$context['page_states'] = $page_states;
|
||||||
|
|
||||||
add_action('wp_enqueue_scripts', function (): void {
|
add_action('wp_enqueue_scripts', function (): void {
|
||||||
Resource::enqueue_style_file(
|
Resource::enqueue_style_file(
|
||||||
handle: 'haiku-atelier-2024-styles-page-boutique',
|
handle: 'haiku-atelier-2024-styles-page-boutique',
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,12 @@
|
||||||
{% block head %}
|
{% block head %}
|
||||||
{{ include('parts/en-tetes-backend.twig') }}
|
{{ include('parts/en-tetes-backend.twig') }}
|
||||||
|
|
||||||
<script id="injection">
|
<!-- markup-fmt-ignore -->
|
||||||
// dprint-ignore-file
|
<script
|
||||||
// Injection d'états pour les Scripts de la page.
|
id="page-states"
|
||||||
|
type="application/json"
|
||||||
const _etats = {
|
>
|
||||||
authString: "{{ auth_string }}",
|
{{ page_states }}
|
||||||
nonce: "{{ nonce_wc }}",
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
{% endblock head %}
|
{% endblock head %}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue