Compare commits
No commits in common. "d81acac380db122adfa04ae95ab3ba54896a7bed" and "61178d4d82381c89ecc3267bfc70476333b21a12" have entirely different histories.
d81acac380
...
61178d4d82
52 changed files with 1079 additions and 18600 deletions
|
|
@ -7,7 +7,7 @@
|
|||
"/vendor/composer/**/*"
|
||||
],
|
||||
"language_server.diagnostic_outsource_timeout": 5,
|
||||
"language_server.diagnostics_on_update": true,
|
||||
"language_server.diagnostics_on_update": false,
|
||||
"language_server.diagnostics_on_save": true,
|
||||
"language_server_highlight.enabled": true,
|
||||
"language_server_php_cs_fixer.enabled": true,
|
||||
|
|
|
|||
47
compose.yaml
47
compose.yaml
|
|
@ -25,6 +25,33 @@ services:
|
|||
restart: "unless-stopped"
|
||||
volumes:
|
||||
- "db-data:/var/lib/mysql:rw"
|
||||
jaeger:
|
||||
container_name: "haikuatelier.fr-jaeger"
|
||||
environment:
|
||||
- "COLLECTOR_OTLP_ENABLED=true"
|
||||
healthcheck:
|
||||
interval: "5s"
|
||||
retries: 3
|
||||
start_period: "5s"
|
||||
test:
|
||||
- "CMD"
|
||||
- "wget"
|
||||
- "--spider"
|
||||
- "http://localhost:16686"
|
||||
timeout: "2s"
|
||||
image: "cr.jaegertracing.io/jaegertracing/jaeger:latest"
|
||||
networks:
|
||||
- "haiku-network"
|
||||
ports:
|
||||
- "6831:6831/udp"
|
||||
- "6832:6832/udp"
|
||||
- "5778:5778"
|
||||
- "16686:16686"
|
||||
- "4317:4317"
|
||||
- "4318:4318"
|
||||
- "14250:14250"
|
||||
- "14268:14268"
|
||||
- "14269:14269"
|
||||
proxy:
|
||||
container_name: "haikuatelier.fr-proxy"
|
||||
depends_on:
|
||||
|
|
@ -78,10 +105,30 @@ services:
|
|||
- "./containers/data/certs:/etc/certs/:ro"
|
||||
- "./containers/data/traefik/logs:/var/log/traefik:rw"
|
||||
- "/var/run/user/1000/podman/podman.sock:/var/run/docker.sock:ro"
|
||||
valkey:
|
||||
command: "valkey-server /usr/local/etc/valkey/valkey.conf"
|
||||
container_name: "haikuatelier.fr-valkey"
|
||||
env_file:
|
||||
- path: "./.env"
|
||||
required: true
|
||||
healthcheck:
|
||||
interval: "10s"
|
||||
retries: 3
|
||||
test:
|
||||
- "CMD-SHELL"
|
||||
- "valkey-cli ping | grep PONG"
|
||||
timeout: "5s"
|
||||
image: "docker.io/valkey/valkey:9-alpine"
|
||||
restart: "unless-stopped"
|
||||
sysctls:
|
||||
- "net.core.somaxconn=512"
|
||||
volumes:
|
||||
- "./containers/conf/valkey.conf:/usr/local/etc/valkey/valkey.conf:ro"
|
||||
wordpress:
|
||||
container_name: "haikuatelier.fr-wordpress"
|
||||
depends_on:
|
||||
- "db"
|
||||
- "valkey"
|
||||
- "traefik"
|
||||
env_file:
|
||||
- path: "./.env"
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@
|
|||
"roots/bedrock-disallow-indexing": "^2.0",
|
||||
"roots/wordpress": "^6.8.1",
|
||||
"roots/wp-config": "^1.0",
|
||||
"stripe/stripe-php": "^19",
|
||||
"stripe/stripe-php": "^16.3",
|
||||
"symfony/uid": "^8",
|
||||
"timber/timber": "^2.3",
|
||||
"vlucas/phpdotenv": "^5.6.1",
|
||||
|
|
|
|||
572
composer.lock
generated
572
composer.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -36,8 +36,7 @@ VOLUME /var/www/wordpress
|
|||
WORKDIR /var/www/wordpress
|
||||
|
||||
# Récupère les fichiers du projet.
|
||||
COPY --from=repo --chmod=775 "/tmp/repo/" .
|
||||
RUN chown www-data: -R .
|
||||
COPY --from=repo --chmod=777 "/tmp/repo/" .
|
||||
|
||||
# Installe les dépendences Composer.
|
||||
RUN composer install
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ include /etc/angie/modules-enabled/*.conf;
|
|||
pcre_jit on;
|
||||
pid /run/angie.pid;
|
||||
error_log /dev/stdout info;
|
||||
error_log /var/log/angie/angie.log warn;
|
||||
|
||||
events {
|
||||
worker_connections 2048;
|
||||
|
|
|
|||
|
|
@ -17,5 +17,4 @@ fastcgi_param SERVER_PORT $server_port;
|
|||
fastcgi_param SERVER_PROTOCOL $server_protocol;
|
||||
fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;
|
||||
|
||||
fastcgi_hide_header X-Powered-By;
|
||||
fastcgi_index index.php;
|
||||
|
|
|
|||
|
|
@ -2,12 +2,15 @@ server {
|
|||
listen 80;
|
||||
server_name _;
|
||||
|
||||
root /var/www/wordpress/web/;
|
||||
root /var/www/wordpress/web;
|
||||
index index.html index.php;
|
||||
|
||||
access_log /var/log/angie/haikuatelier-access.log;
|
||||
error_log /var/log/angie/haikuatelier-error.log;
|
||||
|
||||
# Remove X-Powered-By, which is an information leak
|
||||
fastcgi_hide_header X-Powered-By;
|
||||
|
||||
# Pour éviter des erreurs liés à des requêtes trop lourdes.
|
||||
fastcgi_buffers 16 32k;
|
||||
fastcgi_buffer_size 64k;
|
||||
|
|
@ -30,27 +33,25 @@ server {
|
|||
access_log off;
|
||||
}
|
||||
|
||||
location ~ \.php$ {
|
||||
fastcgi_pass wordpress:9000;
|
||||
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||
include /etc/angie/fastcgi.conf;
|
||||
try_files $uri =404;
|
||||
}
|
||||
|
||||
location ~* .(jpg|jpeg|png|gif|ico|css|js)$ {
|
||||
expires 365d;
|
||||
}
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.php?$args;
|
||||
}
|
||||
|
||||
location ~ \.php$ {
|
||||
include /etc/angie/fastcgi.conf;
|
||||
fastcgi_pass wordpress:9000;
|
||||
fastcgi_intercept_errors on;
|
||||
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||
}
|
||||
|
||||
location ~* \.(?:ico|svg|css|js|gif|jpe?g|png|avif|jxl|webp|avif|woff2?)$ {
|
||||
access_log off;
|
||||
expires max;
|
||||
add_header "Cache-Control" "public, immutable";
|
||||
}
|
||||
|
||||
location * {
|
||||
add_header "Access-Control-Allow-Methods" "GET, POST, OPTIONS";
|
||||
add_header "Access-Control-Allow-Origin" "*";
|
||||
add_header "Cross-Origin-Opener-Policy" "unsafe-none";
|
||||
add_header "Cross-Origin-Resource-Policy" "cross-origin";
|
||||
add_header "Access-Control-Allow-Methods" "GET, POST, OPTIONS";
|
||||
add_header "Access-Control-Allow-Origin" "*";
|
||||
add_header "Cross-Origin-Opener-Policy" "unsafe-none";
|
||||
add_header "Cross-Origin-Resource-Policy" "cross-origin";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,4 +12,3 @@ memory_limit = 1024M
|
|||
post_max_size = 32M
|
||||
register_globals = Off
|
||||
upload_max_filesize = 32M
|
||||
open_basedir = "/"
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -1,9 +1,5 @@
|
|||
# Journal de développement
|
||||
|
||||
## 2026-01-09
|
||||
|
||||
- Faire un modèle _Twig_ pour l'injection de données _JSON_ dans le _HTML_ d'une page.
|
||||
|
||||
## 2025-06-13
|
||||
|
||||
### Informations produit sous forme de grille
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
- Produits
|
||||
- Aller sur tous les Produits
|
||||
- La page doit correctement se charger
|
||||
- Il est possible d'ajouter chaque variation au Panier
|
||||
- Il n'est pas possible d'ajouter un Produit sans stock au Panier
|
||||
- Le backend renvoie une erreur quand une demande d'ajout au Panier pour un Produit sans stock est malgré tout effectuée
|
||||
|
|
@ -1,9 +1,3 @@
|
|||
## 2026-02-19
|
||||
|
||||
- Créer un _timer_ et _service_ `systemd` adossés à un script réalisant un export de la BDD de production du site Haiku toutes les semaines dans le dossier `db` du répertoire.
|
||||
|
||||
---
|
||||
|
||||
- PAGE PANIER
|
||||
- [-] Bouton « Réinitialiser » pour les Articles
|
||||
- [-] Bouton « Réinitialiser » pour les Adresses
|
||||
|
|
|
|||
12
dprint.json
12
dprint.json
|
|
@ -76,13 +76,13 @@
|
|||
},
|
||||
"newLineKind": "lf",
|
||||
"plugins": [
|
||||
"https://plugins.dprint.dev/typescript-0.95.15.wasm",
|
||||
"https://plugins.dprint.dev/json-0.21.1.wasm",
|
||||
"https://plugins.dprint.dev/markdown-0.21.1.wasm",
|
||||
"https://plugins.dprint.dev/typescript-0.95.13.wasm",
|
||||
"https://plugins.dprint.dev/json-0.21.0.wasm",
|
||||
"https://plugins.dprint.dev/markdown-0.20.0.wasm",
|
||||
"https://plugins.dprint.dev/toml-0.7.0.wasm",
|
||||
"https://plugins.dprint.dev/g-plane/malva-v0.15.2.wasm",
|
||||
"https://plugins.dprint.dev/g-plane/markup_fmt-v0.26.0.wasm",
|
||||
"https://plugins.dprint.dev/g-plane/pretty_yaml-v0.6.0.wasm",
|
||||
"https://plugins.dprint.dev/g-plane/malva-v0.15.1.wasm",
|
||||
"https://plugins.dprint.dev/g-plane/markup_fmt-v0.25.3.wasm",
|
||||
"https://plugins.dprint.dev/g-plane/pretty_yaml-v0.5.1.wasm",
|
||||
"https://plugins.dprint.dev/exec-0.6.0.json@a054130d458f124f9b5c91484833828950723a5af3f8ff2bd1523bd47b83b364"
|
||||
],
|
||||
"toml": {
|
||||
|
|
|
|||
3
justfile
3
justfile
|
|
@ -172,6 +172,3 @@ restart-services:
|
|||
[group('container')]
|
||||
pull-images:
|
||||
bun "scripts/pull-container-images.ts"
|
||||
|
||||
export_production_db:
|
||||
fish "scripts/déclenche-sauvegarde-bdd-production.fish"
|
||||
|
|
|
|||
72
mago.toml
72
mago.toml
|
|
@ -10,77 +10,77 @@ threads = 8
|
|||
|
||||
[formatter]
|
||||
# Brace style for classes, traits, etc.
|
||||
classlike-brace-style = "same_line"
|
||||
classlike-brace-style = "same_line"
|
||||
# Brace style for closures.
|
||||
closure-brace-style = "same_line"
|
||||
closure-brace-style = "same_line"
|
||||
# Brace style for control structures.
|
||||
control-brace-style = "same_line"
|
||||
control-brace-style = "same_line"
|
||||
# Brace style for functions.
|
||||
function-brace-style = "same_line"
|
||||
function-brace-style = "same_line"
|
||||
# Brace style for methods.
|
||||
method-brace-style = "same_line"
|
||||
method-brace-style = "same_line"
|
||||
# Maximum line length that the printer will wrap on.
|
||||
print-width = 120
|
||||
print-width = 120
|
||||
# Remove the trailing PHP close tag (?>) from files.
|
||||
remove-trailing-close-tag = true
|
||||
remove-trailing-close-tag = true
|
||||
# Prefer single quotes over double quotes for strings.
|
||||
single-quote = true
|
||||
single-quote = true
|
||||
# Number of spaces per indentation level.
|
||||
tab-width = 4
|
||||
tab-width = 4
|
||||
# Add a trailing comma to multi-line arrays, parameter lists, etc.
|
||||
trailing-comma = true
|
||||
trailing-comma = true
|
||||
# Use tabs instead of spaces for indentation.
|
||||
use-tabs = false
|
||||
use-tabs = false
|
||||
# Place empty control structure bodies on the same line.
|
||||
inline-empty-control-braces = true
|
||||
inline-empty-control-braces = true
|
||||
# Place empty function bodies on the same line.
|
||||
inline-empty-function-braces = true
|
||||
inline-empty-function-braces = true
|
||||
# Place empty constructor bodies on the same line.
|
||||
inline-empty-constructor-braces = true
|
||||
inline-empty-constructor-braces = true
|
||||
# Place empty method bodies on the same line.
|
||||
inline-empty-method-braces = true
|
||||
inline-empty-method-braces = true
|
||||
# Place empty anonymous class bodies on the same line.
|
||||
inline-empty-anonymous-class-braces = true
|
||||
inline-empty-anonymous-class-braces = true
|
||||
# How to break method chains.
|
||||
method-chain-breaking-style = "next_line"
|
||||
method-chain-breaking-style = "next_line"
|
||||
# Preserve existing line breaks in method chains.
|
||||
preserve-breaking-member-access-chain = false
|
||||
preserve-breaking-member-access-chain = false
|
||||
# Preserve existing line breaks in argument lists.
|
||||
preserve-breaking-argument-list = false
|
||||
preserve-breaking-argument-list = false
|
||||
# Preserve existing line breaks in array-like structures.
|
||||
preserve-breaking-array-like = false
|
||||
preserve-breaking-array-like = false
|
||||
# Preserve existing line breaks in parameter lists.
|
||||
preserve-breaking-parameter-list = false
|
||||
preserve-breaking-parameter-list = false
|
||||
# Preserve existing line breaks in attribute lists.
|
||||
preserve-breaking-attribute-list = false
|
||||
preserve-breaking-attribute-list = false
|
||||
# Preserve existing line breaks in ternary expressions.
|
||||
preserve-breaking-conditional-expression = false
|
||||
preserve-breaking-conditional-expression = false
|
||||
# Always break parameter lists with promoted properties.
|
||||
break-promoted-properties-list = true
|
||||
break-promoted-properties-list = true
|
||||
# Place the binary operator on the next line when breaking.
|
||||
line-before-binary-operator = true
|
||||
line-before-binary-operator = true
|
||||
# Always break named argument lists into multiple lines.
|
||||
always-break-named-arguments-list = true
|
||||
always-break-named-arguments-list = true
|
||||
# Always break named argument lists in attributes.
|
||||
always-break-attribute-named-argument-lists = true
|
||||
always-break-attribute-named-argument-lists = true
|
||||
# Use table-style alignment for arrays.
|
||||
array-table-style-alignment = true
|
||||
array-table-style-alignment = true
|
||||
# Sort use statements alphabetically.
|
||||
sort-uses = true
|
||||
sort-uses = true
|
||||
# Insert a blank line between different types of use statements.
|
||||
separate-use-types = true
|
||||
separate-use-types = true
|
||||
# Expand grouped use statements into individual statements.
|
||||
expand-use-groups = true
|
||||
expand-use-groups = true
|
||||
# How to format null type hints (null|T vs ?T).
|
||||
null-type-hint = "null_pipe"
|
||||
null-type-hint = "null_pipe"
|
||||
# Add parentheses around new in member access ((new Foo)->bar()).
|
||||
parentheses-around-new-in-member-access = false
|
||||
parentheses-around-new-in-member-access = false
|
||||
# Add parentheses to new expressions without arguments (new Foo()).
|
||||
parentheses-in-new-expression = true
|
||||
parentheses-in-new-expression = true
|
||||
# Add parentheses to exit and die constructs.
|
||||
parentheses-in-exit-and-die = true
|
||||
parentheses-in-exit-and-die = true
|
||||
# Add parentheses to attributes without arguments.
|
||||
parentheses-in-attribute = true
|
||||
parentheses-in-attribute = true
|
||||
# Add a space before arrow function parameters.
|
||||
space-before-arrow-function-parameter-list-parenthesis = false
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
[tools]
|
||||
"cargo:mago" = "latest"
|
||||
70
package.json
70
package.json
|
|
@ -7,18 +7,16 @@
|
|||
"license": "ISC",
|
||||
"main": "index.js",
|
||||
"keywords": [],
|
||||
"scripts": {
|
||||
"knip": "knip"
|
||||
},
|
||||
"scripts": { "knip": "knip" },
|
||||
"dependencies": {
|
||||
"@effect/language-service": "^0.75.1",
|
||||
"@logtape/logtape": "^1.3.7",
|
||||
"@effect/language-service": "^0.60.0",
|
||||
"@logtape/logtape": "^1.2.2",
|
||||
"@mobily/ts-belt": "v4.0.0-rc.5",
|
||||
"@sentry/browser": "^10.40.0",
|
||||
"a11y-dialog": "^8.1.5",
|
||||
"@sentry/browser": "^10.29.0",
|
||||
"a11y-dialog": "^8.1.4",
|
||||
"chalk": "^5.6.2",
|
||||
"effect": "^3.19.19",
|
||||
"lit-html": "^3.3.2",
|
||||
"effect": "^3.19.9",
|
||||
"lit-html": "^3.3.1",
|
||||
"loglevel": "^1.9.2",
|
||||
"loglevel-plugin-prefix": "^0.8.4",
|
||||
"optics-ts": "^2.4.1",
|
||||
|
|
@ -27,45 +25,44 @@
|
|||
"valibot": "1.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^2.4.4",
|
||||
"@biomejs/biome": "^2.3.8",
|
||||
"@cspell/dict-fr-fr": "^2.3.2",
|
||||
"@eslint/js": "^10.0.1",
|
||||
"@playwright/test": "^1.58.2",
|
||||
"@eslint/js": "^9.39.1",
|
||||
"@playwright/test": "^1.57.0",
|
||||
"@prettier/plugin-xml": "^3.4.2",
|
||||
"@sentry/core": "^10.40.0",
|
||||
"@sentry/core": "^10.29.0",
|
||||
"@swc/cli": "0.7.8",
|
||||
"@types/eslint__js": "^9.14.0",
|
||||
"@types/node": "^25.3.1",
|
||||
"@types/node": "^24.10.1",
|
||||
"@vitejs/plugin-legacy": "^7.2.1",
|
||||
"better-typescript-lib": "^2.12.0",
|
||||
"browserslist": "^4.28.1",
|
||||
"caniuse-lite": "^1.0.30001774",
|
||||
"eslint": "^10.0.2",
|
||||
"eslint-plugin-oxlint": "^1.50.0",
|
||||
"eslint-plugin-perfectionist": "^5.6.0",
|
||||
"caniuse-lite": "^1.0.30001759",
|
||||
"eslint": "^9.39.1",
|
||||
"eslint-plugin-oxlint": "^1.31.0",
|
||||
"eslint-plugin-perfectionist": "^4.15.1",
|
||||
"fdir": "^6.5.0",
|
||||
"globals": "^17.3.0",
|
||||
"knip": "^5.85.0",
|
||||
"lightningcss-cli": "^1.31.1",
|
||||
"oxlint": "^1.50.0",
|
||||
"globals": "^16.5.0",
|
||||
"knip": "^5.71.0",
|
||||
"lightningcss-cli": "^1.30.2",
|
||||
"oxlint": "^1.31.0",
|
||||
"picomatch": "^4.0.3",
|
||||
"playwright": "^1.58.2",
|
||||
"playwright": "^1.57.0",
|
||||
"prettier": "^4.0.0-alpha.13",
|
||||
"prettier-plugin-pkg": "^0.21.2",
|
||||
"prettier-plugin-sh": "^0.18.0",
|
||||
"sass-embedded": "^1.97.3",
|
||||
"stylelint": "^17.4.0",
|
||||
"stylelint-config-clean-order": "^8.0.1",
|
||||
"stylelint-config-sass-guidelines": "^13.0.0",
|
||||
"stylelint-config-standard-scss": "^17.0.0",
|
||||
"stylelint-declaration-block-no-ignored-properties": "^3.0.0",
|
||||
"stylelint-plugin-logical-css": "^2.0.2",
|
||||
"sass-embedded": "^1.93.3",
|
||||
"stylelint": "^16.26.1",
|
||||
"stylelint-config-clean-order": "^8.0.0",
|
||||
"stylelint-config-sass-guidelines": "^12.1.0",
|
||||
"stylelint-config-standard-scss": "^16.0.0",
|
||||
"stylelint-declaration-block-no-ignored-properties": "^2.8.0",
|
||||
"stylelint-plugin-logical-css": "^1.2.3",
|
||||
"typescript": "5.9.3",
|
||||
"typescript-eslint": "^8.56.1",
|
||||
"vite": "^8.0.0-beta.0",
|
||||
"typescript-eslint": "^8.48.1",
|
||||
"vite": "^7.2.6",
|
||||
"vite-plugin-valibot-env": "^1.0.1",
|
||||
"vite-tsconfig-paths": "^6.1.1",
|
||||
"vitest": "^4.0.18",
|
||||
"vite-tsconfig-paths": "^5.1.4",
|
||||
"wp-types": "^4.69.0"
|
||||
},
|
||||
"browserslist": [
|
||||
|
|
@ -81,8 +78,5 @@
|
|||
"entry": ["web/app/themes/haiku-atelier-2024/src/scripts/*.ts"],
|
||||
"project": ["web/app/themes/haiku-atelier-2024/src/scripts/**/*.{js,ts,d.ts}"]
|
||||
},
|
||||
"trustedDependencies": ["@biomejs/biome", "@parcel/watcher", "@swc/core", "core-js", "esbuild", "lightningcss-cli"],
|
||||
"overrides": {
|
||||
"vite": "8.0.0-beta.0"
|
||||
}
|
||||
"trustedDependencies": ["@biomejs/biome", "@parcel/watcher", "@swc/core", "core-js", "esbuild", "lightningcss-cli"]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,22 +21,20 @@ export default defineConfig({
|
|||
retries: process.env.CI ? 2 : 0,
|
||||
/* Opt out of parallel tests on CI. */
|
||||
workers: process.env.CI ? 1 : undefined,
|
||||
// Définis un délai d'exécution.
|
||||
timeout: 30000,
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: "list",
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
use: {
|
||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||
baseURL: "https://haikuatelier.gcch.local",
|
||||
// baseURL: 'http://localhost:3000',
|
||||
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
trace: "on-first-retry",
|
||||
clientCertificates: [
|
||||
{
|
||||
origin: "https://haikuatelier.gcch.local",
|
||||
certPath: "./containers/data/certs/_wildcard.gcch.local.pem",
|
||||
keyPath: "./containers/data/certs/_wildcard.gcch.local-key.pem",
|
||||
certPath: "../certs/_wildcard.gcch.local.pem",
|
||||
keyPath: "../certs/_wildcard.gcch.local-key.pem",
|
||||
},
|
||||
],
|
||||
ignoreHTTPSErrors: true,
|
||||
|
|
@ -48,42 +46,42 @@ export default defineConfig({
|
|||
name: "desktop-chromium-1920",
|
||||
use: { ...devices["Desktop Chrome"], viewport: { width: 1920, height: 1080 } },
|
||||
},
|
||||
// {
|
||||
// name: "desktop-chromium-1536",
|
||||
// use: { ...devices["Desktop Chrome"], viewport: { width: 1536, height: 864 } },
|
||||
// },
|
||||
// {
|
||||
// name: "desktop-chromium-1366",
|
||||
// use: { ...devices["Desktop Chrome"], viewport: { width: 1366, height: 768 } },
|
||||
// },
|
||||
{
|
||||
name: "desktop-chromium-1536",
|
||||
use: { ...devices["Desktop Chrome"], viewport: { width: 1536, height: 864 } },
|
||||
},
|
||||
{
|
||||
name: "desktop-chromium-1366",
|
||||
use: { ...devices["Desktop Chrome"], viewport: { width: 1366, height: 768 } },
|
||||
},
|
||||
{
|
||||
name: "desktop-firefox-1920",
|
||||
use: { ...devices["Desktop Firefox"], viewport: { width: 1920, height: 1080 } },
|
||||
},
|
||||
// {
|
||||
// name: "desktop-firefox-1536",
|
||||
// use: { ...devices["Desktop Firefox"], viewport: { width: 1536, height: 864 } },
|
||||
// },
|
||||
// {
|
||||
// name: "desktop-firefox-1366",
|
||||
// use: { ...devices["Desktop Firefox"], viewport: { width: 1366, height: 768 } },
|
||||
// },
|
||||
// {
|
||||
// name: "tablet-chromium-portrait",
|
||||
// use: { ...devices["Galaxy Tab S9"] },
|
||||
// },
|
||||
// {
|
||||
// name: "tablet-chromium-landscape",
|
||||
// use: { ...devices["Galaxy Tab S9 landscape"] },
|
||||
// },
|
||||
// {
|
||||
// name: "mobile-chromium-portrait",
|
||||
// use: { ...devices["Pixel 7"] },
|
||||
// },
|
||||
// {
|
||||
// name: "mobile-chromium-landscape",
|
||||
// use: { ...devices["Pixel 7 landscape"] },
|
||||
// },
|
||||
{
|
||||
name: "desktop-firefox-1536",
|
||||
use: { ...devices["Desktop Firefox"], viewport: { width: 1536, height: 864 } },
|
||||
},
|
||||
{
|
||||
name: "desktop-firefox-1366",
|
||||
use: { ...devices["Desktop Firefox"], viewport: { width: 1366, height: 768 } },
|
||||
},
|
||||
{
|
||||
name: "tablet-chromium-portrait",
|
||||
use: { ...devices["Galaxy Tab S9"] },
|
||||
},
|
||||
{
|
||||
name: "tablet-chromium-landscape",
|
||||
use: { ...devices["Galaxy Tab S9 landscape"] },
|
||||
},
|
||||
{
|
||||
name: "mobile-chromium-portrait",
|
||||
use: { ...devices["Pixel 7"] },
|
||||
},
|
||||
{
|
||||
name: "mobile-chromium-landscape",
|
||||
use: { ...devices["Pixel 7 landscape"] },
|
||||
},
|
||||
],
|
||||
/* Run your local dev server before starting the tests */
|
||||
// webServer: {
|
||||
|
|
|
|||
|
|
@ -1,2 +0,0 @@
|
|||
ssh ade -- fish /srv/haikuatelier.com/scripts/sauvegarde-bdd-production.fish
|
||||
rclone copy --check-first --progress --multi-thread-streams 8 ade:/srv/haikuatelier.com/db /home/gcch/Répertoires/git.gcch.fr/gcch/haiku-atelier-2024/db
|
||||
|
|
@ -1,10 +1,5 @@
|
|||
set -f fichiers_toml (fd --glob "*.toml")
|
||||
set -f fichiers_angie (fd --glob "*.conf" containers/conf/angie)
|
||||
|
||||
for toml in $fichiers_toml
|
||||
tombi format "$toml"
|
||||
end
|
||||
|
||||
for angie in $angie
|
||||
nginxfmt "$angie"
|
||||
taplo format "$toml"
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,4 +0,0 @@
|
|||
cd /srv/haikuatelier.com/web
|
||||
sudo -S wp-cli --allow-root db export
|
||||
sudo -S mv -v /srv/haikuatelier.com/web/*.sql ../db
|
||||
sudo -S chown www-data: ../db
|
||||
|
|
@ -38,7 +38,7 @@ Array.from<TestPage>([
|
|||
url: "https://haikuatelier.gcch.local/product/fuyou-long-earrings-silver/",
|
||||
},
|
||||
]).forEach(({ pageName, url }) => {
|
||||
test.skip(pageName, async ({ page }, testInfo) => {
|
||||
test(pageName, async ({ page }, testInfo) => {
|
||||
await page.goto(url);
|
||||
|
||||
const projectName = testInfo.project.name;
|
||||
|
|
@ -1,120 +0,0 @@
|
|||
import { test as base, expect, Response } from "@playwright/test";
|
||||
import {
|
||||
WCV3Product,
|
||||
WCV3Products,
|
||||
} from "../../web/app/themes/haiku-atelier-2024/src/scripts/lib/types/api/v3/products";
|
||||
import { BackendHeaders, getBackendHeadersFromHtml } from "./utils.ts";
|
||||
import { pipe } from "effect";
|
||||
import { not } from "effect/Boolean";
|
||||
|
||||
/*
|
||||
* Faire un premier test simple où l'on clic sur la première carte du shop
|
||||
* On doit pouvoir naviguer sur la page
|
||||
* Le produit ne DOIT pas avoir de variations (pour l'instant)
|
||||
* Le bouton d'ajout du panier doit être visible
|
||||
* (Si le produit n'est pas en stock) On ne doit pas pouvoir ajouter le produit au panier
|
||||
* Le bouton doit afficher le texte "Out of stock"
|
||||
* Le bouton doit être désactivé
|
||||
*/
|
||||
|
||||
type ProductsFixture = {
|
||||
products: ProductsKinds;
|
||||
};
|
||||
|
||||
type ProductsKinds = {
|
||||
allProducts: WCV3Products;
|
||||
simpleProducts: WCV3Products;
|
||||
simpleProductsWithStock: WCV3Products;
|
||||
simpleProductsWithoutStock: WCV3Products;
|
||||
};
|
||||
|
||||
export const test = base.extend<ProductsFixture>({
|
||||
products: async ({ page, request }, use) => {
|
||||
await page.goto("/shop");
|
||||
const backendHeaders: BackendHeaders = await getBackendHeadersFromHtml(page);
|
||||
|
||||
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}` },
|
||||
});
|
||||
expect(response.ok(), "The API returned the list of every Product").toBeTruthy();
|
||||
|
||||
const isSimpleProduct = (product: WCV3Product) => product.type === "simple";
|
||||
const hasStock = (product: WCV3Product) => (product.stock_quantity ?? 0) > 0;
|
||||
const hasNoStock = (product: WCV3Product) => pipe(hasStock(product), not);
|
||||
|
||||
const allProducts = await response.json() as WCV3Products;
|
||||
const simpleProducts = allProducts.filter(isSimpleProduct);
|
||||
const simpleProductsWithStock = simpleProducts.filter(hasStock);
|
||||
const simpleProductsWithoutStock = simpleProducts.filter(hasNoStock);
|
||||
|
||||
const kinds = {
|
||||
allProducts,
|
||||
simpleProducts,
|
||||
simpleProductsWithStock,
|
||||
simpleProductsWithoutStock,
|
||||
} satisfies ProductsKinds;
|
||||
|
||||
await use(kinds);
|
||||
},
|
||||
});
|
||||
|
||||
test("can add a Product without variation with stock to the Cart", async ({ products, page }) => {
|
||||
// Prend un produit au hasard.
|
||||
const randomProductIndex = getRandomIntInclusive(0, products.simpleProductsWithStock.length - 1);
|
||||
const randomProduct = products.simpleProductsWithStock.at(randomProductIndex);
|
||||
expect(randomProduct, "The selected random Product must exist").toBeTruthy();
|
||||
if (randomProduct === undefined) {
|
||||
throw new Error("The random product can't be undefined");
|
||||
}
|
||||
|
||||
// Va à la page du Produit.
|
||||
await page.goto(randomProduct.permalink);
|
||||
|
||||
// Vérifie le bon état du bouton de l'ajout au Panier.
|
||||
const addToCartButton = page.getByRole("button", { name: "Add to cart", disabled: false });
|
||||
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 enabled").toBeEnabled();
|
||||
|
||||
// Vérifie qu'au clic sur le bouton, l'ajout au Panier retourne un succès.
|
||||
const addToCartResponse: Promise<Response> = page.waitForResponse(
|
||||
new RegExp(".*/wp-json/wc/store/cart/add-item"),
|
||||
);
|
||||
await addToCartButton.click();
|
||||
const addToCartStatus = (await addToCartResponse).ok();
|
||||
expect(addToCartStatus, "The cart addition must succeed").toBeTruthy();
|
||||
|
||||
// Vérifie que le bouton ait changé de texte.
|
||||
const addedToCartButton = page.getByRole("button", { name: "Added to cart!" });
|
||||
expect(addedToCartButton, "The add to cart button's text has changed").toBeDefined();
|
||||
|
||||
// Vérifie que le compteur d'articles dans le Panier soit incrémenté.
|
||||
const cartLink = page.getByRole("link", { name: "cart (1)" });
|
||||
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 }) => {
|
||||
// Prend un produit au hasard.
|
||||
const randomProductIndex = getRandomIntInclusive(0, products.simpleProductsWithoutStock.length - 1);
|
||||
const randomProduct = products.simpleProductsWithoutStock.at(randomProductIndex);
|
||||
expect(randomProduct, "The selected random Product must exist").toBeTruthy();
|
||||
if (randomProduct === undefined) {
|
||||
throw new Error("The random product can't be undefined");
|
||||
}
|
||||
|
||||
// Va à la page du Produit.
|
||||
await page.goto(randomProduct.permalink);
|
||||
|
||||
// Vérifie le bon état du bouton de l'ajout au Panier.
|
||||
const outOfStockButton = page.getByRole("button", { name: "Out of stock", disabled: true });
|
||||
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 disabled").toBeDisabled();
|
||||
});
|
||||
|
||||
const getRandomIntInclusive = (min: number, max: number): number => {
|
||||
const minCeiled = Math.ceil(min);
|
||||
const maxFloored = Math.floor(max);
|
||||
|
||||
return Math.floor(Math.random() * (maxFloored - minCeiled + 1) + minCeiled);
|
||||
};
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
import { test, expect, Page, Locator, Response, APIRequestContext } from "@playwright/test";
|
||||
import { WCV3Products } from "../../web/app/themes/haiku-atelier-2024/src/scripts/lib/types/api/v3/products";
|
||||
import { BackendHeaders, getBackendHeadersFromHtml } from "./utils.ts";
|
||||
|
||||
test.describe.configure({ mode: "parallel", timeout: 60000 });
|
||||
|
||||
test("can scroll to the end of the grid", async ({ page }): Promise<void> => {
|
||||
await scrollToGridsEnd(page);
|
||||
});
|
||||
|
||||
test("can access all Products' pages", async ({ page, request }): Promise<void> => {
|
||||
await page.goto("https://haikuatelier.gcch.local/shop/");
|
||||
const links = await getAllProductsLinks(page, request);
|
||||
|
||||
for (const link of links) {
|
||||
// Vérifie que le lien de la page retourne OK.
|
||||
const req = await request.get(link as string);
|
||||
expect(req, "The Product's page is accessible").toBeOK();
|
||||
}
|
||||
});
|
||||
|
||||
const getAllProductsLinks = async (page: Page, request: APIRequestContext): Promise<Array<string>> => {
|
||||
const backendHeaders: BackendHeaders = await getBackendHeadersFromHtml(page);
|
||||
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}` },
|
||||
});
|
||||
const json = await response.json() as WCV3Products;
|
||||
const links = json.map(p => p.permalink);
|
||||
|
||||
return links;
|
||||
};
|
||||
|
||||
const scrollToGridsEnd = async (page: Page): Promise<void> => {
|
||||
await page.goto("https://haikuatelier.gcch.local/shop/");
|
||||
|
||||
let hasMoreProducts = true;
|
||||
let currentPageNumber = "1";
|
||||
|
||||
const productsGrid: Locator = page.locator(".grille-produits");
|
||||
await expect(productsGrid, "The Product's grid is visible").toBeVisible();
|
||||
expect(await (productsGrid.getAttribute("data-page")), "The initial page number attribute is correct").toBe(
|
||||
currentPageNumber,
|
||||
);
|
||||
|
||||
const showMoreButton: Locator = page.getByRole("button", { name: "Show more" });
|
||||
await expect(showMoreButton, "The 'Show more' button is visible").toBeVisible();
|
||||
|
||||
while (hasMoreProducts) {
|
||||
const newProductsResponse: Promise<Response> = page.waitForResponse(
|
||||
new RegExp(".*wp-json\/wc\/v3\/products.*"),
|
||||
);
|
||||
await showMoreButton.click();
|
||||
await newProductsResponse;
|
||||
|
||||
const newPageNumber = String(Number(currentPageNumber) + 1);
|
||||
// Créé un nouveau Locator que l'on attend pour s'assurer que l'attribut soit bien mis à jour.
|
||||
const gridWithNewPageNumber = page.locator(`.grille-produits[data-page="${newPageNumber}"]`);
|
||||
await gridWithNewPageNumber.waitFor();
|
||||
|
||||
// Redondance pour expliciter la raison de l'assertion.
|
||||
expect(await (productsGrid.getAttribute("data-page")), "The page number attribute is incremented").toBe(
|
||||
newPageNumber,
|
||||
);
|
||||
currentPageNumber = newPageNumber;
|
||||
|
||||
// La fin de la grille est atteint.
|
||||
if (await showMoreButton.isHidden()) {
|
||||
hasMoreProducts = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
import { Option, pipe } from "effect";
|
||||
import { Page } from "playwright/test";
|
||||
|
||||
export type BackendHeaders = {
|
||||
authString: string;
|
||||
nonce: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* @throws Lève une exception si la balise du JSON est introuvable.
|
||||
*/
|
||||
export const getBackendHeadersFromHtml = async (page: Page): Promise<BackendHeaders> => {
|
||||
const backendHeaders: BackendHeaders | undefined = pipe(
|
||||
Option.fromNullable(await page.locator("#injection-v2").textContent()),
|
||||
Option.andThen(j => JSON.parse(j) as BackendHeaders),
|
||||
Option.getOrUndefined,
|
||||
);
|
||||
|
||||
if (backendHeaders === undefined) {
|
||||
throw new Error("The JSON of the backend headers in the page's HTML can't be null.");
|
||||
}
|
||||
|
||||
return backendHeaders;
|
||||
};
|
||||
|
|
@ -44,5 +44,5 @@
|
|||
"useUnknownInCatchVariables": true
|
||||
},
|
||||
"exclude": ["vendor", "web/app/plugins", "web/wp"],
|
||||
"include": ["*.js", "lib", "web/app/themes/haiku-atelier-2024/src", "vite.config.ts", "tests"]
|
||||
"include": ["*.js", "lib", "web/app/themes/haiku-atelier-2024/src", "vite.config.ts"]
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -1 +1 @@
|
|||
#page-a-propos{--images-longueur-maximale:1300px;--page-marges-bloc-debut:var(--en-tete-hauteur);--images-marges-ligne:var(--espace-xl) * 2;margin-top:var(--page-marges-bloc-debut);flex-flow:column;display:flex}#page-a-propos .storytelling{padding:var(--espace-xl) 0;color:var(--couleur-gris-fonce)}#page-a-propos .storytelling__conteneur{width:min(var(--images-longueur-maximale), 100% - var(--images-marges-ligne));margin:auto;position:relative}#page-a-propos .storytelling picture{position:relative}#page-a-propos .storytelling picture:before{content:"";filter:opacity(0%);background-image:url(/app/themes/haiku-atelier-2024/assets/img/icons/cloud-gris.svg);background-position:50%;background-repeat:space;background-size:contain;width:100%;height:100%;animation:1s linear infinite alternate both clignotement;position:absolute;top:0;left:0}#page-a-propos .storytelling img{object-fit:contain;background:0 0;width:max-content;height:auto;position:relative}#page-a-propos .storytelling .epingle{position:absolute;right:46.5%}#page-a-propos .storytelling .epingle img{pointer-events:none;width:1.75rem;display:block}#page-a-propos .storytelling .epingle[data-id-ensemble-epingle-boite="1"]{top:1%;right:60%}#page-a-propos .storytelling .epingle[data-id-ensemble-epingle-boite="2"]{top:25%;right:70%}#page-a-propos .storytelling .epingle[data-id-ensemble-epingle-boite="3"]{top:37%;right:20%}#page-a-propos .storytelling .epingle[data-id-ensemble-epingle-boite="4"]{top:58%;right:70%}#page-a-propos .storytelling .epingle[data-id-ensemble-epingle-boite="5"]{top:76%;right:14%}#page-a-propos .storytelling .epingle[data-id-ensemble-epingle-boite="6"]{top:95.5%;right:75%}#page-a-propos .storytelling .boite-texte{top:0;right:calc(46.5% - (15rem + var(--espace-l)) / 2);padding:var(--espace-l);border:1px solid var(--couleur-noir);color:var(--couleur-noir);visibility:hidden;opacity:0;background:var(--couleur-fond);flex-flow:column;font-size:.8rem;font-style:italic;transition:opacity .2s,visibility .2s;display:flex;position:absolute}#page-a-propos .storytelling .boite-texte button{top:0;right:calc(-1.5rem - var(--espace-m) - var(--espace-xs));padding:var(--espace-xs);align-self:end;position:absolute}#page-a-propos .storytelling .boite-texte button img{pointer-events:none;aspect-ratio:1;width:1.5rem}#page-a-propos .storytelling .boite-texte button:active{background:var(--couleur-jaune)}#page-a-propos .storytelling .boite-texte p{max-width:15rem}#page-a-propos .storytelling .boite-texte p+p{margin-top:var(--espace-m)}#page-a-propos .storytelling .boite-texte[data-ensemble-epingle-boite-actif]{visibility:visible;opacity:1}#page-a-propos .storytelling .boite-texte[data-id-ensemble-epingle-boite="2"]{--hauteur-boite:calc(7lh + 1rem + var(--espace-l) * 2);top:calc(23% - var(--hauteur-boite) / 2)}#page-a-propos .storytelling .boite-texte[data-id-ensemble-epingle-boite="3"]{--hauteur-boite:calc(7lh + 1rem + var(--espace-l) * 2);top:calc(35% - var(--hauteur-boite) / 2)}#page-a-propos .storytelling .boite-texte[data-id-ensemble-epingle-boite="3"] button{left:calc(-1.5rem - var(--espace-m) - var(--espace-xs))}#page-a-propos .storytelling .boite-texte[data-id-ensemble-epingle-boite="4"]{--hauteur-boite:calc(8lh + 2rem + var(--espace-l) * 2);top:calc(58% - var(--hauteur-boite) / 2)}#page-a-propos .storytelling .boite-texte[data-id-ensemble-epingle-boite="5"]{--hauteur-boite:calc(12lh + 2rem + var(--espace-l) * 2);top:calc(76% - var(--hauteur-boite) / 2)}#page-a-propos .storytelling .boite-texte[data-id-ensemble-epingle-boite="6"]{--hauteur-boite:calc(7lh + 1rem + var(--espace-l) * 2);top:calc(95.5% - var(--hauteur-boite) / 2)}#page-a-propos .storytelling .boite-texte[data-id-ensemble-epingle-boite="6"] button{left:calc(-1.5rem - var(--espace-m) - var(--espace-xs))}@media (width<=700px){#page-a-propos{--images-marges-ligne:var(--espace-m)}}@keyframes clignotement{to{filter:opacity(30%)}}
|
||||
#page-a-propos{--images-longueur-maximale:1300px;--page-marges-bloc-debut:var(--en-tete-hauteur);--images-marges-ligne:var(--espace-xl)*2;margin-top:var(--page-marges-bloc-debut);flex-flow:column;display:flex}#page-a-propos .storytelling{padding:var(--espace-xl)0;color:var(--couleur-gris-fonce)}#page-a-propos .storytelling__conteneur{width:min(var(--images-longueur-maximale),100% - var(--images-marges-ligne));margin:auto;position:relative}#page-a-propos .storytelling picture{position:relative}#page-a-propos .storytelling picture:before{content:"";filter:opacity(0%);background-image:url(/app/themes/haiku-atelier-2024/assets/img/icons/cloud-gris.svg);background-position:50%;background-repeat:space;background-size:contain;width:100%;height:100%;animation:1s linear infinite alternate both clignotement;position:absolute;top:0;left:0}#page-a-propos .storytelling img{object-fit:contain;background:0 0;width:max-content;height:auto;position:relative}#page-a-propos .storytelling .epingle{position:absolute;right:46.5%}#page-a-propos .storytelling .epingle img{pointer-events:none;width:1.75rem;display:block}#page-a-propos .storytelling .epingle[data-id-ensemble-epingle-boite="1"]{top:1%;right:60%}#page-a-propos .storytelling .epingle[data-id-ensemble-epingle-boite="2"]{top:25%;right:70%}#page-a-propos .storytelling .epingle[data-id-ensemble-epingle-boite="3"]{top:37%;right:20%}#page-a-propos .storytelling .epingle[data-id-ensemble-epingle-boite="4"]{top:58%;right:70%}#page-a-propos .storytelling .epingle[data-id-ensemble-epingle-boite="5"]{top:76%;right:14%}#page-a-propos .storytelling .epingle[data-id-ensemble-epingle-boite="6"]{top:95.5%;right:75%}#page-a-propos .storytelling .boite-texte{top:0;right:calc(46.5% - (15rem + var(--espace-l))/2);padding:var(--espace-l);border:1px solid var(--couleur-noir);color:var(--couleur-noir);visibility:hidden;opacity:0;background:var(--couleur-fond);flex-flow:column;font-size:.8rem;font-style:italic;transition:opacity .2s,visibility .2s;display:flex;position:absolute}#page-a-propos .storytelling .boite-texte button{top:0;right:calc(-1.5rem - var(--espace-m) - var(--espace-xs));padding:var(--espace-xs);align-self:end;position:absolute}#page-a-propos .storytelling .boite-texte button img{pointer-events:none;aspect-ratio:1;width:1.5rem}#page-a-propos .storytelling .boite-texte button:active{background:var(--couleur-jaune)}#page-a-propos .storytelling .boite-texte p{max-width:15rem}#page-a-propos .storytelling .boite-texte p+p{margin-top:var(--espace-m)}#page-a-propos .storytelling .boite-texte[data-ensemble-epingle-boite-actif]{visibility:visible;opacity:1}#page-a-propos .storytelling .boite-texte[data-id-ensemble-epingle-boite="2"]{--hauteur-boite:calc(7lh + 1rem + var(--espace-l)*2);top:calc(23% - var(--hauteur-boite)/2)}#page-a-propos .storytelling .boite-texte[data-id-ensemble-epingle-boite="3"]{--hauteur-boite:calc(7lh + 1rem + var(--espace-l)*2);top:calc(35% - var(--hauteur-boite)/2)}#page-a-propos .storytelling .boite-texte[data-id-ensemble-epingle-boite="3"] button{left:calc(-1.5rem - var(--espace-m) - var(--espace-xs))}#page-a-propos .storytelling .boite-texte[data-id-ensemble-epingle-boite="4"]{--hauteur-boite:calc(8lh + 2rem + var(--espace-l)*2);top:calc(58% - var(--hauteur-boite)/2)}#page-a-propos .storytelling .boite-texte[data-id-ensemble-epingle-boite="5"]{--hauteur-boite:calc(12lh + 2rem + var(--espace-l)*2);top:calc(76% - var(--hauteur-boite)/2)}#page-a-propos .storytelling .boite-texte[data-id-ensemble-epingle-boite="6"]{--hauteur-boite:calc(7lh + 1rem + var(--espace-l)*2);top:calc(95.5% - var(--hauteur-boite)/2)}#page-a-propos .storytelling .boite-texte[data-id-ensemble-epingle-boite="6"] button{left:calc(-1.5rem - var(--espace-m) - var(--espace-xs))}@media (width<=700px){#page-a-propos{--images-marges-ligne:var(--espace-m)}}@keyframes clignotement{to{filter:opacity(30%)}}
|
||||
|
|
@ -1 +1 @@
|
|||
#page-accueil{--hauteur-conteneur:var(--contenu-page-hauteur-minimale-sans-categories);--page-marges-bloc-debut:var(--en-tete-hauteur);--conteneur-marges-internes-ligne:var(--espace-xl);min-block-size:var(--hauteur-conteneur);max-block-size:var(--hauteur-conteneur);margin-top:var(--page-marges-bloc-debut);flex-flow:column;display:flex;overflow:hidden}#page-accueil .storytelling{overscroll-behavior:none;min-block-size:inherit;max-block-size:inherit;overflow-y:scroll}#page-accueil .storytelling__conteneur{overscroll-behavior:inherit;min-block-size:calc(var(--hauteur-conteneur) * 13);padding:0 var(--conteneur-marges-internes-ligne);flex-flow:column;place-items:center;display:flex}#page-accueil .storytelling__animation{--hauteur-animation:90px;--taille-police:calc(var(--espace-xl) * 2.5);pointer-events:none;z-index:3;visibility:visible;opacity:1;block-size:100%;mask-image:linear-gradient(var(--mask-direction,to right), #0000, #000 20%, #000 80%, #0000);place-content:center;place-items:center;margin:auto;transition:opacity 1s ease-in-out,visibility 1s ease-in-out;display:grid;position:absolute;top:0;left:0;right:0;overflow:hidden}#page-accueil .storytelling__animation[hidden]{visibility:hidden;opacity:0;transition:opacity 1s ease-in-out,visibility 1s ease-in-out;display:grid!important}#page-accueil .storytelling__animation.no-js{visibility:hidden;opacity:0;transition:opacity 1s ease-in-out,visibility 1s ease-in-out}#page-accueil .storytelling__animation .animation-conteneur{width:120vw;block-size:var(--hauteur-animation);overflow:visible}#page-accueil .storytelling__animation .animation-texte{font-size:var(--taille-police);text-shadow:4px 4px 0 var(--couleur-blanc);text-transform:uppercase;letter-spacing:var(--espacement-inter-lettres-rapproche-s);font-weight:600;overflow:visible}#page-accueil .storytelling__image{inline-size:max-content;max-inline-size:100%;min-block-size:var(--hauteur-conteneur);max-block-size:var(--hauteur-conteneur);align-content:center;position:sticky;top:0}#page-accueil .storytelling__image[data-caché]{display:none!important}#page-accueil .storytelling__image picture{max-block-size:inherit}#page-accueil .storytelling__image img{max-block-size:inherit;object-fit:contain;background:0 0;margin:auto;scale:.95}@media (scripting:none){#page-accueil .storytelling__animation{visibility:hidden}}@media (width<=700px){#page-accueil{--conteneur-marges-internes-ligne:var(--espace-l)}}@media (width<=500px){#page-accueil{--conteneur-marges-internes-ligne:var(--espace-m)}}@supports ((-moz-appearance:none)){#page-accueil .storytelling__animation{--taille-police:calc(var(--espace-xl) * 2.2)}}
|
||||
#page-accueil{--hauteur-conteneur:var(--contenu-page-hauteur-minimale-sans-categories);--page-marges-bloc-debut:var(--en-tete-hauteur);--conteneur-marges-internes-ligne:var(--espace-xl);min-block-size:var(--hauteur-conteneur);max-block-size:var(--hauteur-conteneur);margin-top:var(--page-marges-bloc-debut);flex-flow:column;display:flex;overflow:hidden}#page-accueil .storytelling{overscroll-behavior:none;min-block-size:inherit;max-block-size:inherit;overflow-y:scroll}#page-accueil .storytelling__conteneur{overscroll-behavior:inherit;min-block-size:calc(var(--hauteur-conteneur)*13);padding:0 var(--conteneur-marges-internes-ligne);flex-flow:column;place-items:center;display:flex}#page-accueil .storytelling__animation{--hauteur-animation:90px;--taille-police:calc(var(--espace-xl)*2.5);pointer-events:none;z-index:3;visibility:visible;opacity:1;block-size:100%;mask-image:linear-gradient(var(--mask-direction,to right),#0000,#000 20%,#000 80%,#0000);place-content:center;place-items:center;margin:auto;transition:opacity 1s ease-in-out,visibility 1s ease-in-out;display:grid;position:absolute;top:0;left:0;right:0;overflow:hidden}#page-accueil .storytelling__animation[hidden]{visibility:hidden;opacity:0;transition:opacity 1s ease-in-out,visibility 1s ease-in-out;display:grid!important}#page-accueil .storytelling__animation.no-js{visibility:hidden;opacity:0;transition:opacity 1s ease-in-out,visibility 1s ease-in-out}#page-accueil .storytelling__animation .animation-conteneur{width:120vw;block-size:var(--hauteur-animation);overflow:visible}#page-accueil .storytelling__animation .animation-texte{font-size:var(--taille-police);text-shadow:4px 4px 0 var(--couleur-blanc);text-transform:uppercase;letter-spacing:var(--espacement-inter-lettres-rapproche-s);font-weight:600;overflow:visible}#page-accueil .storytelling__image{inline-size:max-content;max-inline-size:100%;min-block-size:var(--hauteur-conteneur);max-block-size:var(--hauteur-conteneur);align-content:center;position:sticky;top:0}#page-accueil .storytelling__image[data-caché]{display:none!important}#page-accueil .storytelling__image picture{max-block-size:inherit}#page-accueil .storytelling__image img{max-block-size:inherit;object-fit:contain;background:0 0;margin:auto;scale:.95}@media (scripting:none){#page-accueil .storytelling__animation{visibility:hidden}}@media (width<=700px){#page-accueil{--conteneur-marges-internes-ligne:var(--espace-l)}}@media (width<=500px){#page-accueil{--conteneur-marges-internes-ligne:var(--espace-m)}}@supports ((-moz-appearance:none)){#page-accueil .storytelling__animation{--taille-police:calc(var(--espace-xl)*2.2)}}
|
||||
|
|
@ -1 +1 @@
|
|||
#page-boutique .actions{text-align:center;align-content:center;width:100%}#page-boutique .actions button{height:initial;padding:var(--espace-xl) 0;margin:auto}
|
||||
#page-boutique .actions{text-align:center;align-content:center;width:100%}#page-boutique .actions button{height:initial;padding:var(--espace-xl)0;margin:auto}
|
||||
|
|
@ -1 +1 @@
|
|||
.page-modele-simple{--page-hauteur-minimale:calc(100svh - var(--en-tete-hauteur) - var(--pied-de-page-hauteur) - var(--espace-xl) - 1px);--page-marges-bloc-debut:var(--en-tete-hauteur);margin-top:var(--page-marges-bloc-debut);margin-bottom:var(--espace-xl);border-bottom:1px solid var(--couleur-noir);flex-flow:column;display:flex}.page-modele-simple .contenu{width:min(50rem,100%);min-height:var(--page-hauteur-minimale);border:1px solid var(--couleur-noir);border-bottom:initial;flex-flow:column;place-items:center;margin:auto;font-style:italic;font-weight:400;display:flex}.page-modele-simple .contenu__en-tete{width:100%;padding:var(--espace-m) var(--espace-xl);color:var(--couleur-blanc);background:var(--couleur-noir)}.page-modele-simple .contenu__en-tete h2{text-transform:uppercase;width:fit-content;letter-spacing:var(--espacement-inter-lettres-etendu-l);margin:auto}.page-modele-simple .contenu__textuel{max-width:34rem;height:100%;padding:0 var(--espace-xl);text-wrap:pretty;flex-flow:column;flex:1;place-content:center;display:flex}.page-modele-simple .contenu__textuel p+p{margin-top:var(--espace-m)}.page-modele-simple#page-cgv .contenu{font-style:normal}.page-modele-simple#page-cgv .contenu header{font-style:italic}.page-modele-simple#page-cgv .contenu__textuel{max-width:initial;padding:0}.page-modele-simple#page-cgv .contenu__textuel__section{width:100%}.page-modele-simple#page-cgv .contenu__textuel__section:first-of-type header{border-top:initial}.page-modele-simple#page-cgv .contenu__textuel__section header{width:100%;margin-bottom:var(--espace-l);padding:var(--espace-m) var(--espace-xl);border-top:1px solid var(--couleur-noir);border-bottom:1px solid var(--couleur-noir)}.page-modele-simple#page-cgv .contenu__textuel__section header h3{text-transform:uppercase;width:fit-content;letter-spacing:var(--espacement-inter-lettres-etendu-l);margin:auto}.page-modele-simple#page-cgv .contenu__textuel__section ul{padding:0 var(--espace-xl);margin-bottom:1lh;list-style:inside square}.page-modele-simple#page-cgv .contenu__textuel__section p{padding:0 var(--espace-xl)}.page-modele-simple#page-cgv .contenu__textuel__section p:last-of-type{margin-bottom:var(--espace-xl)}@media (width<=50rem){.page-modele-simple .contenu{border-right:initial;border-left:initial}}
|
||||
.page-modele-simple{--page-hauteur-minimale:calc(100svh - var(--en-tete-hauteur) - var(--pied-de-page-hauteur) - var(--espace-xl) - 1px);--page-marges-bloc-debut:var(--en-tete-hauteur);margin-top:var(--page-marges-bloc-debut);margin-bottom:var(--espace-xl);border-bottom:1px solid var(--couleur-noir);flex-flow:column;display:flex}.page-modele-simple .contenu{width:min(50rem,100%);min-height:var(--page-hauteur-minimale);border:1px solid var(--couleur-noir);border-bottom:initial;flex-flow:column;place-items:center;margin:auto;font-style:italic;font-weight:400;display:flex}.page-modele-simple .contenu__en-tete{width:100%;padding:var(--espace-m)var(--espace-xl);color:var(--couleur-blanc);background:var(--couleur-noir)}.page-modele-simple .contenu__en-tete h2{text-transform:uppercase;width:fit-content;letter-spacing:var(--espacement-inter-lettres-etendu-l);margin:auto}.page-modele-simple .contenu__textuel{max-width:34rem;height:100%;padding:0 var(--espace-xl);text-wrap:pretty;flex-flow:column;flex:1;place-content:center;display:flex}.page-modele-simple .contenu__textuel p+p{margin-top:var(--espace-m)}.page-modele-simple#page-cgv .contenu{font-style:normal}.page-modele-simple#page-cgv .contenu header{font-style:italic}.page-modele-simple#page-cgv .contenu__textuel{max-width:initial;padding:0}.page-modele-simple#page-cgv .contenu__textuel__section{width:100%}.page-modele-simple#page-cgv .contenu__textuel__section:first-of-type header{border-top:initial}.page-modele-simple#page-cgv .contenu__textuel__section header{width:100%;margin-bottom:var(--espace-l);padding:var(--espace-m)var(--espace-xl);border-top:1px solid var(--couleur-noir);border-bottom:1px solid var(--couleur-noir)}.page-modele-simple#page-cgv .contenu__textuel__section header h3{text-transform:uppercase;width:fit-content;letter-spacing:var(--espacement-inter-lettres-etendu-l);margin:auto}.page-modele-simple#page-cgv .contenu__textuel__section ul{padding:0 var(--espace-xl);margin-bottom:1lh;list-style:inside square}.page-modele-simple#page-cgv .contenu__textuel__section p{padding:0 var(--espace-xl)}.page-modele-simple#page-cgv .contenu__textuel__section p:last-of-type{margin-bottom:var(--espace-xl)}@media (width<=50rem){.page-modele-simple .contenu{border-right:initial;border-left:initial}}
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -1 +1 @@
|
|||
#page-succes-commande{--page-hauteur-minimale:calc(100svh - var(--en-tete-hauteur) - var(--pied-de-page-hauteur) - var(--espace-xl) - 1px);--page-marges-bloc-debut:var(--en-tete-hauteur);margin-top:var(--page-marges-bloc-debut);margin-bottom:var(--espace-xl);border-bottom:1px solid var(--couleur-noir);flex-flow:column;display:flex}#page-succes-commande .contenu{width:min(50rem,100%);min-height:var(--page-hauteur-minimale);border:1px solid var(--couleur-noir);border-bottom:initial;flex-flow:column;place-items:center;margin:auto;font-style:italic;font-weight:500;display:flex}#page-succes-commande .contenu__en-tete{width:100%;padding:var(--espace-m) var(--espace-xl);color:var(--couleur-blanc);background:var(--couleur-noir)}#page-succes-commande .contenu__en-tete h2{text-transform:uppercase;width:fit-content;letter-spacing:var(--espacement-inter-lettres-etendu-l);margin:auto}#page-succes-commande .contenu__textuel{max-width:34rem;height:100%;padding:var(--espace-xl);flex-flow:column;flex:1;place-content:center;font-style:normal;display:flex}#page-succes-commande .contenu__textuel p+p{margin-top:var(--espace-m)}#page-succes-commande .contenu__rappel-commande{border-top:1px solid var(--couleur-noir);flex-flow:column;display:flex}#page-succes-commande .contenu__rappel-commande__produit{border-bottom:1px solid var(--couleur-noir);grid-template-rows:1fr;grid-template-columns:1fr 1fr;display:grid}#page-succes-commande .contenu__rappel-commande__produit:only-child,#page-succes-commande .contenu__rappel-commande__produit:last-of-type{border-bottom:initial}#page-succes-commande .contenu__rappel-commande__produit__illustratif{border-right:1px solid var(--couleur-noir)}#page-succes-commande .contenu__rappel-commande__produit__illustratif picture{overflow:hidden}#page-succes-commande .contenu__rappel-commande__produit__illustratif img{aspect-ratio:1;height:auto}#page-succes-commande .contenu__rappel-commande .detail-produit{width:100%;padding:0 var(--espace-xl);flex-flow:column;place-content:center;font-style:italic;display:flex}#page-succes-commande .contenu__rappel-commande .detail-produit__nom-prix{column-gap:var(--espace-xl);margin-bottom:var(--espace-xs);font-size:var(--espace-l);line-height:var(--hauteur-ligne-moitie);flex-flow:row;justify-content:space-between;display:flex}#page-succes-commande .contenu__rappel-commande .detail-produit__nom-prix span{min-width:4rem;font-weight:600;font-style:initial;text-align:right}#page-succes-commande .contenu__rappel-commande .detail-produit__description{margin-bottom:var(--espace-l);line-height:var(--hauteur-ligne-moitie);text-transform:lowercase}@media (width<=50rem){#page-succes-commande .contenu{border-right:initial;border-left:initial}}
|
||||
#page-succes-commande{--page-hauteur-minimale:calc(100svh - var(--en-tete-hauteur) - var(--pied-de-page-hauteur) - var(--espace-xl) - 1px);--page-marges-bloc-debut:var(--en-tete-hauteur);margin-top:var(--page-marges-bloc-debut);margin-bottom:var(--espace-xl);border-bottom:1px solid var(--couleur-noir);flex-flow:column;display:flex}#page-succes-commande .contenu{width:min(50rem,100%);min-height:var(--page-hauteur-minimale);border:1px solid var(--couleur-noir);border-bottom:initial;flex-flow:column;place-items:center;margin:auto;font-style:italic;font-weight:500;display:flex}#page-succes-commande .contenu__en-tete{width:100%;padding:var(--espace-m)var(--espace-xl);color:var(--couleur-blanc);background:var(--couleur-noir)}#page-succes-commande .contenu__en-tete h2{text-transform:uppercase;width:fit-content;letter-spacing:var(--espacement-inter-lettres-etendu-l);margin:auto}#page-succes-commande .contenu__textuel{max-width:34rem;height:100%;padding:var(--espace-xl);flex-flow:column;flex:1;place-content:center;font-style:normal;display:flex}#page-succes-commande .contenu__textuel p+p{margin-top:var(--espace-m)}#page-succes-commande .contenu__rappel-commande{border-top:1px solid var(--couleur-noir);flex-flow:column;display:flex}#page-succes-commande .contenu__rappel-commande__produit{border-bottom:1px solid var(--couleur-noir);grid-template-rows:1fr;grid-template-columns:1fr 1fr;display:grid}#page-succes-commande .contenu__rappel-commande__produit:only-child,#page-succes-commande .contenu__rappel-commande__produit:last-of-type{border-bottom:initial}#page-succes-commande .contenu__rappel-commande__produit__illustratif{border-right:1px solid var(--couleur-noir)}#page-succes-commande .contenu__rappel-commande__produit__illustratif picture{overflow:hidden}#page-succes-commande .contenu__rappel-commande__produit__illustratif img{aspect-ratio:1;height:auto}#page-succes-commande .contenu__rappel-commande .detail-produit{width:100%;padding:0 var(--espace-xl);flex-flow:column;place-content:center;font-style:italic;display:flex}#page-succes-commande .contenu__rappel-commande .detail-produit__nom-prix{column-gap:var(--espace-xl);margin-bottom:var(--espace-xs);font-size:var(--espace-l);line-height:var(--hauteur-ligne-moitie);flex-flow:row;justify-content:space-between;display:flex}#page-succes-commande .contenu__rappel-commande .detail-produit__nom-prix span{min-width:4rem;font-weight:600;font-style:initial;text-align:right}#page-succes-commande .contenu__rappel-commande .detail-produit__description{margin-bottom:var(--espace-l);line-height:var(--hauteur-ligne-moitie);text-transform:lowercase}@media (width<=50rem){#page-succes-commande .contenu{border-right:initial;border-left:initial}}
|
||||
|
|
@ -25,12 +25,12 @@ Timber::$dirname = ['views'];
|
|||
|
||||
// Charge les Scripts du thème (report d'erreurs)
|
||||
function load_scripts(): void {
|
||||
// wp_enqueue_script_module(
|
||||
// id: 'haiku-atelier-2024-gaffe',
|
||||
// deps: [],
|
||||
// src: get_template_directory_uri() . '/assets/js/gaffe.js',
|
||||
// version: filemtime(get_template_directory() . '/assets/js/gaffe.js'),
|
||||
// );
|
||||
wp_enqueue_script_module(
|
||||
id: 'haiku-atelier-2024-gaffe',
|
||||
deps: [],
|
||||
src: get_template_directory_uri() . '/assets/js/gaffe.js',
|
||||
version: filemtime(get_template_directory() . '/assets/js/gaffe.js'),
|
||||
);
|
||||
wp_enqueue_script_module(
|
||||
id: 'haiku-atelier-2024-bouton-panier',
|
||||
deps: [],
|
||||
|
|
|
|||
|
|
@ -15,23 +15,6 @@ use Symfony\Component\Uid\Uuid;
|
|||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
// TODO: Appliquer le bon calcul pour les montants vs. percentages
|
||||
function get_discount_amount(WC_Coupon $coupon) {
|
||||
if ($coupon->get_discount_type() === 'fixed_cart') {
|
||||
return $coupon->get_amount() * 100;
|
||||
} else {
|
||||
return $coupon->get_amount();
|
||||
}
|
||||
}
|
||||
|
||||
function get_discount_duration(WC_Coupon $coupon): string {
|
||||
if ($coupon->get_discount_type() === 'fixed_cart') {
|
||||
return 'once';
|
||||
} else {
|
||||
return 'forever';
|
||||
}
|
||||
}
|
||||
|
||||
// Récupère les informations nécessaires
|
||||
/** @var WC_Session_Handler $session_wc La Session WooCommerce contenant entre autre le Panier. */
|
||||
$session_wc = WC()->session;
|
||||
|
|
@ -118,18 +101,28 @@ if (empty($methode_livraison['nom'])) {
|
|||
// Sélectionne la clé API Stripe
|
||||
Stripe::setApiKey(Config::get('STRIPE_API_SECRET'));
|
||||
|
||||
// TODO: Appliquer le bon calcul pour les montants vs. percentages
|
||||
function get_discount_amount(WC_Coupon $coupon) {
|
||||
if ($coupon->get_discount_type() === 'amount_off') {
|
||||
return $coupon->get_amount() * 100;
|
||||
} else {
|
||||
return $coupon->get_amount() * 100;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Met à jour les Codes promos
|
||||
$coupons_stripe = collect(Coupon::all()->data);
|
||||
$coupons_wc = collect(WC()->cart->get_coupons())
|
||||
->map(static fn(WC_Coupon $coupon): array => [
|
||||
'currency' => 'EUR',
|
||||
'duration' => get_discount_duration($coupon),
|
||||
'duration' => 'forever',
|
||||
'fixed_cart' === $coupon->get_discount_type() ? 'amount_off' : 'percent_off' => get_discount_amount($coupon),
|
||||
'id' => $coupon->get_code(),
|
||||
'name' => $coupon->get_code(),
|
||||
])
|
||||
->each(static function (array $item) use ($coupons_stripe): void {
|
||||
// Si le code promo n'existe pas, le créer
|
||||
// Si le code promo n'existe, le créer
|
||||
if (!$coupons_stripe->contains('name', $item['name'])) {
|
||||
Coupon::create($item);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@
|
|||
* Le modèle de la Page d'un Produit.
|
||||
*/
|
||||
|
||||
use function Crell\fp\pipe;
|
||||
use HaikuAtelier\Data\Product;
|
||||
|
||||
use Timber\Timber;
|
||||
|
||||
require_once __DIR__ . '/src/inc/HTML.php';
|
||||
|
|
@ -16,16 +18,43 @@ $templates = ['produit.twig'];
|
|||
|
||||
$product = wc_get_product();
|
||||
|
||||
// Le Produit DOIT exister.
|
||||
if ($product === null || is_bool($product)) {
|
||||
throw new Exception("Le Produit n'existe pas.");
|
||||
}
|
||||
|
||||
// Assemble les données d'intérêt pour la page au sein d'une Classe.
|
||||
// $donnees_produit = recupere_informations_produit_page_produit($product);
|
||||
$donnees_produit = Product::new($product);
|
||||
|
||||
// Un tableau des informations d'affichage de chaque Variation du Produit
|
||||
$variations_produit = pipe(
|
||||
$product->get_children(),
|
||||
// Récupère les Variations
|
||||
static fn(/** @var list<int> */ $enfants): array => array_map(
|
||||
callback: wc_get_product(...),
|
||||
array: $enfants,
|
||||
),
|
||||
// Ne conserve que les Informations souhaitées.
|
||||
static fn(/** @var list<WC_Product> */ $variations): array => array_map(
|
||||
callback: static fn(WC_Product $variation): array => [
|
||||
'id' => $variation->get_id(),
|
||||
// Ne récupère que le titre de l'Attribut unique de la Variation.
|
||||
'titre' => match (true) {
|
||||
'' !== $variation->get_attribute('pa_side') => $variation->get_attribute('pa_side'),
|
||||
'' !== $variation->get_attribute('pa_stone') => $variation->get_attribute('pa_stone'),
|
||||
'' !== $variation->get_attribute('pa_size') => $variation->get_attribute('pa_size'),
|
||||
'' !== $variation->get_attribute('pa_giftcard-amount') => $variation->get_attribute(
|
||||
'pa_giftcard-amount',
|
||||
),
|
||||
default => '',
|
||||
},
|
||||
'prix' => $variation->get_price(),
|
||||
],
|
||||
array: $variations,
|
||||
),
|
||||
);
|
||||
|
||||
/** @var int $prix_maximal Le prix de la Variation la plus chère */
|
||||
$prix_maximal = collect($donnees_produit->variations)->max('price');
|
||||
$prix_maximal = collect($variations_produit)->max('prix');
|
||||
|
||||
$produits_meme_collection = array_map(
|
||||
array: recupere_produits_meme_collection($donnees_produit->collection)($donnees_produit->id),
|
||||
|
|
@ -33,8 +62,8 @@ $produits_meme_collection = array_map(
|
|||
);
|
||||
|
||||
$context['produit'] = $donnees_produit;
|
||||
$context['product_json'] = wp_json_encode($donnees_produit);
|
||||
$context['prix_maximal'] = $prix_maximal;
|
||||
$context['variations_produit'] = $variations_produit;
|
||||
$context['produits_meme_collection'] = $produits_meme_collection;
|
||||
|
||||
/**
|
||||
|
|
@ -57,8 +86,12 @@ function charge_scripts_page_produit(): void {
|
|||
|
||||
add_action('wp_enqueue_scripts', 'charge_scripts_page_produit');
|
||||
|
||||
$lal = wp_json_encode($context);
|
||||
echo "<script>console.debug({$lal});</script>";
|
||||
// $lal = wp_json_encode($context);
|
||||
// echo "<script>console.debug({$lal});</script>";
|
||||
|
||||
// $lol = wc_get_product()->get_children();
|
||||
// $lol = wp_json_encode($lol);
|
||||
// echo "<script>console.debug({$lol});</script>";
|
||||
|
||||
// Rendu
|
||||
Timber::render(
|
||||
|
|
|
|||
|
|
@ -14,10 +14,10 @@ use WP_Term;
|
|||
|
||||
final readonly class Product {
|
||||
/**
|
||||
* @param list<Attribute> $attributes
|
||||
* @param list<string> $left_column_photos
|
||||
* @param list<string> $right_column_photos
|
||||
* @param list<ProductVariation> $variations
|
||||
* @param list<Attribute> $attributes
|
||||
* @param list<string> $left_column_photos
|
||||
* @param list<string> $right_column_photos
|
||||
* @param list<int> $variation_ids
|
||||
*/
|
||||
private function __construct(
|
||||
public array $attributes,
|
||||
|
|
@ -33,7 +33,7 @@ final readonly class Product {
|
|||
public string $hover_photo,
|
||||
public string $slug,
|
||||
public int $stock,
|
||||
public array $variations,
|
||||
public array $variation_ids,
|
||||
public string $url,
|
||||
) {}
|
||||
|
||||
|
|
@ -78,9 +78,8 @@ final readonly class Product {
|
|||
$hover_photo = $right_column_photos[0] ?? genere_balise_img_multiformats('-1', true);
|
||||
$slug = $product->get_slug();
|
||||
$stock = $product->get_stock_quantity() ?? 1;
|
||||
$variations = $product->get_children()
|
||||
|> (static fn($ids) => Arr::map($ids, wc_get_product(...)))
|
||||
|> (static fn($products) => Arr::map($products, ProductVariation::new(...)));
|
||||
/** @var list<int> */
|
||||
$variation_ids = $product->get_children();
|
||||
$url = $product->get_permalink();
|
||||
|
||||
return new self(
|
||||
|
|
@ -97,7 +96,7 @@ final readonly class Product {
|
|||
hover_photo: $hover_photo,
|
||||
slug: $slug,
|
||||
stock: $stock,
|
||||
variations: $variations,
|
||||
variation_ids: $variation_ids,
|
||||
url: $url,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,35 +0,0 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace HaikuAtelier\Data;
|
||||
|
||||
use WC_Product;
|
||||
|
||||
final readonly class ProductVariation {
|
||||
/**
|
||||
* @param int $id L'ID de la Variation
|
||||
* @param string $price Le prix de la Variation
|
||||
* @param list<ProductVariationAttribute> $attributes Les attributs appliqués à la Variation
|
||||
*/
|
||||
private function __construct(
|
||||
public int $id,
|
||||
public string $price,
|
||||
public array $attributes,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Créé une nouvelle instance de `ProductVariation` à partir d'un `WC_Product`.
|
||||
*/
|
||||
public static function new(WC_Product $product): self {
|
||||
$id = $product->get_id();
|
||||
$price = $product->get_price();
|
||||
/** @var list<ProductVariationAttribute> */
|
||||
$attributes = array_map(
|
||||
/** @phpstan-ignore argument.type (Impossible à satisfaire) */
|
||||
static fn(string $key, string $value) => new ProductVariationAttribute($key, $value),
|
||||
array_keys($product->get_attributes()),
|
||||
array_values($product->get_attributes()),
|
||||
);
|
||||
|
||||
return new self($id, $price, $attributes);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace HaikuAtelier\Data;
|
||||
|
||||
final readonly class ProductVariationAttribute {
|
||||
/**
|
||||
* @param string $attribute Le slug de l'Attribut
|
||||
* @param string $value Le slug de la valeur de l'Attribut
|
||||
*/
|
||||
public function __construct(
|
||||
public string $attribute,
|
||||
public string $value,
|
||||
) {}
|
||||
}
|
||||
|
|
@ -197,9 +197,9 @@ function genere_prix_maximal_produit_variable_dans_reponse_rest(
|
|||
}
|
||||
|
||||
// Assigne le prix de la Variation la plus chère dans la Réponse
|
||||
$reponse->data['prix_maximal'] = collect($reponse->data['variations'])
|
||||
->map(wc_get_product(...))
|
||||
->map(static fn($p) => $p->get_price())->max();
|
||||
$reponse->data['prix_maximal'] = collect($reponse->data['variations'])->map(wc_get_product(...))->map(
|
||||
static fn($p) => $p->get_price(),
|
||||
)->max();
|
||||
|
||||
return $reponse;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,10 +49,10 @@ function genere_balise_img_multiformats(string $id, bool $lazy = false): string
|
|||
'format' => pathinfo((string) $chemin_format)['extension'],
|
||||
'taille' => filesize($chemin_format),
|
||||
'url' => pathinfo($url)['dirname']
|
||||
. '/'
|
||||
. pathinfo($url)['filename']
|
||||
. '.'
|
||||
. pathinfo((string) $chemin_format)['extension'],
|
||||
. '/'
|
||||
. pathinfo($url)['filename']
|
||||
. '.'
|
||||
. pathinfo((string) $chemin_format)['extension'],
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
@ -72,18 +72,18 @@ function genere_balise_img_multiformats(string $id, bool $lazy = false): string
|
|||
$loading = $lazy ? 'lazy' : 'eager';
|
||||
|
||||
return <<<EOD
|
||||
{$sources}
|
||||
{$sources}
|
||||
|
||||
<img
|
||||
alt="{$alt}"
|
||||
decoding="async"
|
||||
height="{$dimensions[0]}"
|
||||
loading="{$loading}"
|
||||
onload="this.style.opacity=1"
|
||||
src="{$url}"
|
||||
width="{$dimensions[1]}"
|
||||
/>
|
||||
EOD;
|
||||
<img
|
||||
alt="{$alt}"
|
||||
decoding="async"
|
||||
height="{$dimensions[0]}"
|
||||
loading="{$loading}"
|
||||
onload="this.style.opacity=1"
|
||||
src="{$url}"
|
||||
width="{$dimensions[1]}"
|
||||
/>
|
||||
EOD;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,11 +1,6 @@
|
|||
import type { InferOutput } from "valibot";
|
||||
|
||||
import type {
|
||||
WCV3ProductsArgsSchema,
|
||||
WCV3ProductSchema,
|
||||
WCV3ProductsSchema,
|
||||
} from "../../../schemas/api/v3/products.ts";
|
||||
import type { WCV3ProductsArgsSchema, WCV3ProductsSchema } from "../../../schemas/api/v3/products.ts";
|
||||
|
||||
export type WCV3Product = InferOutput<typeof WCV3ProductSchema>;
|
||||
export type WCV3Products = InferOutput<typeof WCV3ProductsSchema>;
|
||||
export type WCV3ProductsArgs = InferOutput<typeof WCV3ProductsArgsSchema>;
|
||||
|
|
|
|||
|
|
@ -67,7 +67,6 @@ const E = {
|
|||
CONTENUS_ACCORDEON: mustGetElesInDocument<HTMLDivElement>(DOM_CONTENUS_ACCORDEON),
|
||||
DOM_VARIATION: recupereElementDocumentEither<HTMLSelectElement>(DOM_DOM_QUANTITE),
|
||||
PRIX_PRODUIT: mustGetEleInDocument<HTMLParagraphElement>(DOM_PRIX_PRODUIT),
|
||||
PRODUCT_JSON: mustGetEleInDocument<HTMLScriptElement>("#product-json"),
|
||||
VARIATION_CHOICE_FORM: mustGetEleInDocument<HTMLFormElement>("#variation-choice"),
|
||||
};
|
||||
|
||||
|
|
@ -125,51 +124,26 @@ const gereAccordeonDetailsProduit = (): void => {
|
|||
E.BOUTON_AJOUT_PANIER.addEventListener("click", (event: MouseEvent): void => ajouteProduitAuPanier(event));
|
||||
};
|
||||
|
||||
const getAttributesFromDom = (): ReadonlyArray<WCStoreCartAddItemArgsItems> => {
|
||||
const getAttributeValuesFromDom = () => {
|
||||
const selectElements = epipe(
|
||||
document.querySelectorAll<HTMLSelectElement>(".selecteur-produit select"),
|
||||
Array.from<HTMLSelectElement>,
|
||||
);
|
||||
if (selectElements.length === 0) return [];
|
||||
|
||||
const attributes = selectElements.map((select: HTMLSelectElement) => {
|
||||
const attributeValues = selectElements.map(select => {
|
||||
return {
|
||||
attribute: select.id,
|
||||
attribute: select.id.replace("selecteur-attribut-", ""),
|
||||
value: select.value,
|
||||
} satisfies WCStoreCartAddItemArgsItems;
|
||||
});
|
||||
|
||||
return attributes;
|
||||
};
|
||||
|
||||
function areArraysEqual<T>(array1: Array<T>, array2: Array<T>): boolean {
|
||||
if (array1 !== array2) {
|
||||
const a1 = JSON.stringify(array1.toSorted());
|
||||
const a2 = JSON.stringify(array2.toSorted());
|
||||
return a1 === a2;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const updatePriceOnAttributeChange = (): void => {
|
||||
E.VARIATION_CHOICE_FORM.addEventListener("change", (): void => {
|
||||
if (!E.VARIATION_CHOICE_FORM.checkValidity()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const productVariations: Array<unknown> = epipe(E.PRODUCT_JSON.textContent, JSON.parse)?.variations;
|
||||
const chosenAttributes = getAttributesFromDom();
|
||||
|
||||
const chosenVariation = productVariations.find(v => areArraysEqual(v.attributes, chosenAttributes));
|
||||
const newPrice: string = chosenVariation.price;
|
||||
|
||||
E.PRIX_PRODUIT.textContent = `${newPrice}€`;
|
||||
});
|
||||
return attributeValues;
|
||||
};
|
||||
|
||||
const ajouteProduitAuPanier = (event: MouseEvent): void => {
|
||||
event.preventDefault();
|
||||
console.debug("getAttributeValuesFromDom", getAttributesFromDom());
|
||||
console.debug("getAttributeValuesFromDom", getAttributeValuesFromDom());
|
||||
|
||||
// Construis les arguments de la requête au backend
|
||||
const argsRequete: WCStoreCartAddItemArgs = {
|
||||
|
|
@ -179,7 +153,7 @@ const ajouteProduitAuPanier = (event: MouseEvent): void => {
|
|||
// .orDefault(ETATS_PAGE.idProduit),
|
||||
id: ETATS_PAGE.idProduit,
|
||||
quantity: 1,
|
||||
variation: getAttributesFromDom(),
|
||||
variation: getAttributeValuesFromDom(),
|
||||
};
|
||||
|
||||
// Réalise la Requête et traite sa Réponse
|
||||
|
|
@ -260,27 +234,19 @@ const ajouteProduitAuPanier = (event: MouseEvent): void => {
|
|||
};
|
||||
|
||||
const initAddToCartButtonActivationOnUserChoice = (): void => {
|
||||
const isInStock = E.BOUTON_AJOUT_PANIER.hasAttribute("data-in-stock");
|
||||
|
||||
// S'il n'y a pas de stock, ne rien faire.
|
||||
if (!isInStock) {
|
||||
return;
|
||||
}
|
||||
|
||||
// S'il n'y a pas de sélecteur de variation, activer le bouton.
|
||||
const selectElements: ReadonlyArray<HTMLSelectElement> = epipe(
|
||||
document.querySelectorAll<HTMLSelectElement>(".selecteur-produit select"),
|
||||
Array.from<HTMLSelectElement>,
|
||||
);
|
||||
// S'il n'y a pas de sélecteur de variation, activer le bouton.
|
||||
if (selectElements.length === 0) {
|
||||
E.BOUTON_AJOUT_PANIER.removeAttribute(ATTRIBUT_DESACTIVE);
|
||||
}
|
||||
|
||||
// (Dés)active le bouton d'ajout au panier en fonction de la validité du formulaire.
|
||||
E.VARIATION_CHOICE_FORM.addEventListener("change", (): void => {
|
||||
const isFormValid = E.VARIATION_CHOICE_FORM.checkValidity();
|
||||
const formValidity = E.VARIATION_CHOICE_FORM.checkValidity();
|
||||
|
||||
if (isFormValid) {
|
||||
if (formValidity) {
|
||||
E.BOUTON_AJOUT_PANIER.removeAttribute(ATTRIBUT_DESACTIVE);
|
||||
} else {
|
||||
E.BOUTON_AJOUT_PANIER.setAttribute(ATTRIBUT_DESACTIVE, "");
|
||||
|
|
@ -291,8 +257,4 @@ const initAddToCartButtonActivationOnUserChoice = (): void => {
|
|||
document.addEventListener("DOMContentLoaded", (): void => {
|
||||
gereAccordeonDetailsProduit();
|
||||
initAddToCartButtonActivationOnUserChoice();
|
||||
updatePriceOnAttributeChange();
|
||||
|
||||
// DEBUG
|
||||
console.debug(JSON.parse(document.querySelector("#product-json")?.textContent));
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,14 +1,12 @@
|
|||
{% extends 'base.twig' %}
|
||||
|
||||
{% block head %}
|
||||
{{ include('parts/en-tetes-backend.twig') }}
|
||||
|
||||
<script id="injection">
|
||||
<script>
|
||||
// Injection d'états pour les Scripts de la page.
|
||||
|
||||
const _etats = {
|
||||
authString: "{{ auth_string }}",
|
||||
nonce: "{{ nonce_wc }}",
|
||||
authString: "{{ auth_string }}",
|
||||
};
|
||||
</script>
|
||||
{% endblock head %}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{% extends 'base.twig' %}
|
||||
|
||||
{% block head %}
|
||||
<script id="injection">
|
||||
<script>
|
||||
// Injection d'états pour les Scripts de la page.
|
||||
|
||||
const _etats = {
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
<script
|
||||
id="injection-v2"
|
||||
type="application/json"
|
||||
>
|
||||
{ "authString": "{{ auth_string }}", "nonce": "{{ nonce_wc }}" }
|
||||
</script>
|
||||
|
|
@ -12,48 +12,48 @@
|
|||
<h3 class="selecteur-produit__nom">{{ produit.name }}</h3>
|
||||
|
||||
<div class="selecteur-produit__attribut-variation">
|
||||
{% if produit.attributes %}
|
||||
{% for attribut in produit.attributes %}
|
||||
<div class="test">
|
||||
{{ include('parts/pages/produit/selecteur-attributs-produit.twig') }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{#
|
||||
{% if variations_produit|length > 1 %}
|
||||
<label
|
||||
for="selecteur-variation"
|
||||
id="label-selecteur-variation"
|
||||
>
|
||||
Option:
|
||||
</label>
|
||||
|
||||
<div class="selecteur-produit__attribut-variation__selecteurs">
|
||||
<select
|
||||
aria-labelledby="label-selecteur-variation"
|
||||
id="selecteur-variation"
|
||||
name="variations"
|
||||
>
|
||||
<option
|
||||
disabled
|
||||
selected
|
||||
value=""
|
||||
>
|
||||
--
|
||||
</option>
|
||||
{% for variation in variations_produit %}
|
||||
<option
|
||||
data-prix="{{ variation.prix }}"
|
||||
value="{{ variation.id }}"
|
||||
>
|
||||
{{ variation.titre }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
{% if produit.attributes %}
|
||||
{% for attribut in produit.attributes %}
|
||||
<div class="test">
|
||||
{{ include('parts/pages/produit/selecteur-attributs-produit.twig') }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
#}
|
||||
|
||||
{% if variations_produit|length > 1 %}
|
||||
<label
|
||||
for="selecteur-variation"
|
||||
id="label-selecteur-variation"
|
||||
>
|
||||
Option:
|
||||
</label>
|
||||
|
||||
<div class="selecteur-produit__attribut-variation__selecteurs">
|
||||
<select
|
||||
aria-labelledby="label-selecteur-variation"
|
||||
id="selecteur-variation"
|
||||
name="variations"
|
||||
>
|
||||
<option
|
||||
disabled
|
||||
selected
|
||||
value=""
|
||||
>
|
||||
--
|
||||
</option>
|
||||
{% for variation in variations_produit %}
|
||||
<option
|
||||
data-prix="{{ variation.prix }}"
|
||||
value="{{ variation.id }}"
|
||||
>
|
||||
{{ variation.titre }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<p class="selecteur-produit__prix">{{ prix_maximal ?? produit.price }}€</p>
|
||||
|
|
@ -126,7 +126,6 @@
|
|||
<button
|
||||
class="bouton-case-pleine"
|
||||
disabled
|
||||
data-in-stock
|
||||
for="variation-choice"
|
||||
id="bouton-ajout-panier"
|
||||
type="submit"
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
<div class="selecteur-produit__attribut-variation__selecteurs">
|
||||
<label
|
||||
for="{{ attribut.slug }}"
|
||||
id="label-{{ attribut.slug }}"
|
||||
for="selecteur-attribut-{{ attribut.slug }}"
|
||||
id="label-selecteur-attribut-{{ attribut.slug }}"
|
||||
>
|
||||
{{ attribut.name }}:
|
||||
</label>
|
||||
|
||||
<select
|
||||
aria-labelledby="label-{{ atribut.slug }}"
|
||||
id="{{ attribut.slug }}"
|
||||
name="{{ attribut.slug }}"
|
||||
aria-labelledby="label-selecteur-attribut-{{ atribut.slug }}"
|
||||
id="selecteur-attribut-{{ attribut.slug }}"
|
||||
name="attribut-{{ attribut.slug }}"
|
||||
required
|
||||
>
|
||||
<option
|
||||
|
|
|
|||
|
|
@ -1,30 +1,20 @@
|
|||
{% extends 'base.twig' %}
|
||||
|
||||
{% block head %}
|
||||
<script id="injection">
|
||||
<script>
|
||||
// dprint-ignore-file
|
||||
// Injection d'états pour les Scripts de la page.
|
||||
// Injection d'états pour les Scripts de la page.
|
||||
|
||||
/**
|
||||
* @typedef {Object} Etats - États utiles pour les scripts de la page.
|
||||
* @property {number} idProduit - L'ID en base de données du Produit.
|
||||
* @property {string} nonce - Un nonce pour l'authentification de requêtes API.
|
||||
*/
|
||||
|
||||
/** @type {Etats} */
|
||||
const _etats = {
|
||||
idProduit: {{ produit.id }},
|
||||
nonce: "{{ nonce_wc }}",
|
||||
};
|
||||
</script>
|
||||
|
||||
<!-- markup-fmt-ignore -->
|
||||
<script
|
||||
id="product-json"
|
||||
type="application/json"
|
||||
>
|
||||
// dprint-ignore
|
||||
{{ product_json }}
|
||||
/**
|
||||
* @typedef {Object} Etats - États utiles pour les scripts de la page.
|
||||
* @property {number} idProduit - L'ID en base de données du Produit.
|
||||
* @property {string} nonce - Un nonce pour l'authentification de requêtes API.
|
||||
*/
|
||||
/** @type {Etats} */
|
||||
const _etats = {
|
||||
idProduit: {{ produit.id }},
|
||||
nonce: "{{ nonce_wc }}",
|
||||
};
|
||||
</script>
|
||||
{% endblock head %}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue