3 Commits

Author SHA1 Message Date
ab8ea038c0 ci: publish frontend test and coverage reports
Some checks failed
Tests / Backend - Tests No test results found
THE-TIP-TOP-MULTIBRANCH/pipeline/head There was a failure building this commit
2026-08-24 18:25:58 +00:00
79e10946b8 merge: integrate Google Analytics
All checks were successful
Tests / Backend - Tests skipped: 7, passed: 27
THE-TIP-TOP-MULTIBRANCH/pipeline/head This commit looks good
2026-08-23 10:35:40 +00:00
79f463d280 feat: integrate consent-based Google Analytics 2026-08-23 10:35:33 +00:00
9 changed files with 344 additions and 58 deletions

22
Jenkinsfile vendored
View File

@@ -49,6 +49,7 @@ pipeline {
env.DEPLOY_NETWORK = 'tiptop-dev' env.DEPLOY_NETWORK = 'tiptop-dev'
env.FRONTEND_API_URL = "https://api-dev.dsp5-archi-o24a-4-5-g4.fr" env.FRONTEND_API_URL = "https://api-dev.dsp5-archi-o24a-4-5-g4.fr"
env.FRONTEND_GA_ID = ""
} else if (env.BRANCH_NAME.startsWith('release-')) { } else if (env.BRANCH_NAME.startsWith('release-')) {
env.DEPLOY_ENABLED = 'true' env.DEPLOY_ENABLED = 'true'
@@ -59,6 +60,7 @@ pipeline {
env.DEPLOY_NETWORK = 'tiptop-preprod' env.DEPLOY_NETWORK = 'tiptop-preprod'
env.FRONTEND_API_URL = "https://api-preprod.dsp5-archi-o24a-4-5-g4.fr" env.FRONTEND_API_URL = "https://api-preprod.dsp5-archi-o24a-4-5-g4.fr"
env.FRONTEND_GA_ID = ""
} else if (env.BRANCH_NAME == 'main') { } else if (env.BRANCH_NAME == 'main') {
env.DEPLOY_ENABLED = 'true' env.DEPLOY_ENABLED = 'true'
@@ -69,6 +71,7 @@ pipeline {
env.DEPLOY_NETWORK = 'tiptop-prod' env.DEPLOY_NETWORK = 'tiptop-prod'
env.FRONTEND_API_URL = "https://api.dsp5-archi-o24a-4-5-g4.fr" env.FRONTEND_API_URL = "https://api.dsp5-archi-o24a-4-5-g4.fr"
env.FRONTEND_GA_ID = "G-4FTNTPJDRJ"
} else { } else {
env.DEPLOY_ENABLED = 'false' env.DEPLOY_ENABLED = 'false'
@@ -78,6 +81,7 @@ pipeline {
env.COMPOSE_PROJECT = '' env.COMPOSE_PROJECT = ''
env.DEPLOY_NETWORK = '' env.DEPLOY_NETWORK = ''
env.FRONTEND_API_URL = '' env.FRONTEND_API_URL = ''
env.FRONTEND_GA_ID = ""
} }
echo "Branche : ${env.BRANCH_NAME}" echo "Branche : ${env.BRANCH_NAME}"
@@ -185,7 +189,7 @@ pipeline {
} }
} }
stage('Frontend - Lint et Build') { stage('Frontend - Lint, Tests et Build') {
steps { steps {
sh ''' sh '''
docker run --rm \ docker run --rm \
@@ -209,10 +213,25 @@ pipeline {
sh -c " sh -c "
npm ci --prefer-offline --no-audit --no-fund && npm ci --prefer-offline --no-audit --no-fund &&
npm run lint && npm run lint &&
npm run test:coverage &&
npm run build npm run build
" "
''' '''
} }
post {
always {
junit(
testResults: 'frontend/test-results/*.xml',
allowEmptyResults: true
)
archiveArtifacts(
artifacts: 'frontend/coverage/**',
fingerprint: true,
allowEmptyArchive: true
)
}
}
} }
stage('Construction des images Docker') { stage('Construction des images Docker') {
@@ -230,6 +249,7 @@ pipeline {
docker build \ docker build \
--build-arg VITE_API_URL="${FRONTEND_API_URL}" \ --build-arg VITE_API_URL="${FRONTEND_API_URL}" \
--build-arg VITE_GA_ID="${FRONTEND_GA_ID}" \
-t "${FRONTEND_IMAGE}:${IMAGE_TAG}" \ -t "${FRONTEND_IMAGE}:${IMAGE_TAG}" \
frontend frontend
''' '''

4
frontend/.gitignore vendored
View File

@@ -22,3 +22,7 @@ dist-ssr
*.njsproj *.njsproj
*.sln *.sln
*.sw? *.sw?
# Tests / coverage
coverage/
test-results/

View File

@@ -8,7 +8,10 @@ RUN npm ci
COPY . . COPY . .
ARG VITE_API_URL ARG VITE_API_URL
ARG VITE_GA_ID
ENV VITE_API_URL=${VITE_API_URL} ENV VITE_API_URL=${VITE_API_URL}
ENV VITE_GA_ID=${VITE_GA_ID}
RUN npm run build RUN npm run build

View File

@@ -21,6 +21,7 @@
"@types/react": "^19.2.14", "@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3", "@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^6.0.1", "@vitejs/plugin-react": "^6.0.1",
"@vitest/coverage-v8": "^4.1.11",
"eslint": "^10.3.0", "eslint": "^10.3.0",
"eslint-plugin-react-hooks": "^7.1.1", "eslint-plugin-react-hooks": "^7.1.1",
"eslint-plugin-react-refresh": "^0.5.2", "eslint-plugin-react-refresh": "^0.5.2",
@@ -339,6 +340,16 @@
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/@bcoe/v8-coverage": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz",
"integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/@bramus/specificity": { "node_modules/@bramus/specificity": {
"version": "2.4.2", "version": "2.4.2",
"resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz",
@@ -1657,17 +1668,48 @@
} }
} }
}, },
"node_modules/@vitest/coverage-v8": {
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.11.tgz",
"integrity": "sha512-8MVGEFnJIcdGjcbfKmeq8z0pZHH0JlVtoVZH9Q/qwUp6wyFnEJUBMrw9DCaj+ra3vShGmhavjalMIhPNxZAUcw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@bcoe/v8-coverage": "^1.0.2",
"@vitest/utils": "4.1.11",
"ast-v8-to-istanbul": "^1.0.0",
"istanbul-lib-coverage": "^3.2.2",
"istanbul-lib-report": "^3.0.1",
"istanbul-reports": "^3.2.0",
"magicast": "^0.5.2",
"obug": "^2.1.1",
"std-env": "^4.0.0-rc.1",
"tinyrainbow": "^3.1.0"
},
"funding": {
"url": "https://opencollective.com/vitest"
},
"peerDependencies": {
"@vitest/browser": "4.1.11",
"vitest": "4.1.11"
},
"peerDependenciesMeta": {
"@vitest/browser": {
"optional": true
}
}
},
"node_modules/@vitest/expect": { "node_modules/@vitest/expect": {
"version": "4.1.10", "version": "4.1.11",
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.10.tgz", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.11.tgz",
"integrity": "sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==", "integrity": "sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@standard-schema/spec": "^1.1.0", "@standard-schema/spec": "^1.1.0",
"@types/chai": "^5.2.2", "@types/chai": "^5.2.2",
"@vitest/spy": "4.1.10", "@vitest/spy": "4.1.11",
"@vitest/utils": "4.1.10", "@vitest/utils": "4.1.11",
"chai": "^6.2.2", "chai": "^6.2.2",
"tinyrainbow": "^3.1.0" "tinyrainbow": "^3.1.0"
}, },
@@ -1676,13 +1718,13 @@
} }
}, },
"node_modules/@vitest/mocker": { "node_modules/@vitest/mocker": {
"version": "4.1.10", "version": "4.1.11",
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.10.tgz", "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.11.tgz",
"integrity": "sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==", "integrity": "sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vitest/spy": "4.1.10", "@vitest/spy": "4.1.11",
"estree-walker": "^3.0.3", "estree-walker": "^3.0.3",
"magic-string": "^0.30.21" "magic-string": "^0.30.21"
}, },
@@ -1703,9 +1745,9 @@
} }
}, },
"node_modules/@vitest/pretty-format": { "node_modules/@vitest/pretty-format": {
"version": "4.1.10", "version": "4.1.11",
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.10.tgz", "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.11.tgz",
"integrity": "sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==", "integrity": "sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -1716,13 +1758,13 @@
} }
}, },
"node_modules/@vitest/runner": { "node_modules/@vitest/runner": {
"version": "4.1.10", "version": "4.1.11",
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.10.tgz", "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.11.tgz",
"integrity": "sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==", "integrity": "sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vitest/utils": "4.1.10", "@vitest/utils": "4.1.11",
"pathe": "^2.0.3" "pathe": "^2.0.3"
}, },
"funding": { "funding": {
@@ -1730,14 +1772,14 @@
} }
}, },
"node_modules/@vitest/snapshot": { "node_modules/@vitest/snapshot": {
"version": "4.1.10", "version": "4.1.11",
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.10.tgz", "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.11.tgz",
"integrity": "sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==", "integrity": "sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vitest/pretty-format": "4.1.10", "@vitest/pretty-format": "4.1.11",
"@vitest/utils": "4.1.10", "@vitest/utils": "4.1.11",
"magic-string": "^0.30.21", "magic-string": "^0.30.21",
"pathe": "^2.0.3" "pathe": "^2.0.3"
}, },
@@ -1746,9 +1788,9 @@
} }
}, },
"node_modules/@vitest/spy": { "node_modules/@vitest/spy": {
"version": "4.1.10", "version": "4.1.11",
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.10.tgz", "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.11.tgz",
"integrity": "sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==", "integrity": "sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"funding": { "funding": {
@@ -1756,13 +1798,13 @@
} }
}, },
"node_modules/@vitest/utils": { "node_modules/@vitest/utils": {
"version": "4.1.10", "version": "4.1.11",
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.10.tgz", "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.11.tgz",
"integrity": "sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==", "integrity": "sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vitest/pretty-format": "4.1.10", "@vitest/pretty-format": "4.1.11",
"convert-source-map": "^2.0.0", "convert-source-map": "^2.0.0",
"tinyrainbow": "^3.1.0" "tinyrainbow": "^3.1.0"
}, },
@@ -1855,6 +1897,25 @@
"node": ">=12" "node": ">=12"
} }
}, },
"node_modules/ast-v8-to-istanbul": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.5.tgz",
"integrity": "sha512-UPAgKJFSEGMWSDr3LX4tqnAb4f7KGT8O40Tyx8wbYmmZ/yn58lNCm8h3svs3eXgiGd5AXxz8NDOvXWvicq+rJA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/trace-mapping": "^0.3.31",
"estree-walker": "^3.0.3",
"js-tokens": "^10.0.0"
}
},
"node_modules/ast-v8-to-istanbul/node_modules/js-tokens": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz",
"integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==",
"dev": true,
"license": "MIT"
},
"node_modules/balanced-match": { "node_modules/balanced-match": {
"version": "4.0.4", "version": "4.0.4",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
@@ -2517,6 +2578,16 @@
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/hermes-estree": { "node_modules/hermes-estree": {
"version": "0.25.1", "version": "0.25.1",
"resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz",
@@ -2547,6 +2618,13 @@
"node": "^20.19.0 || ^22.12.0 || >=24.0.0" "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
} }
}, },
"node_modules/html-escaper": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
"integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
"dev": true,
"license": "MIT"
},
"node_modules/ignore": { "node_modules/ignore": {
"version": "5.3.2", "version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
@@ -2614,6 +2692,45 @@
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/istanbul-lib-coverage": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
"integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=8"
}
},
"node_modules/istanbul-lib-report": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz",
"integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"istanbul-lib-coverage": "^3.0.0",
"make-dir": "^4.0.0",
"supports-color": "^7.1.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/istanbul-reports": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz",
"integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"html-escaper": "^2.0.0",
"istanbul-lib-report": "^3.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/jiti": { "node_modules/jiti": {
"version": "2.7.0", "version": "2.7.0",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz",
@@ -3061,6 +3178,47 @@
"@jridgewell/sourcemap-codec": "^1.5.5" "@jridgewell/sourcemap-codec": "^1.5.5"
} }
}, },
"node_modules/magicast": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.4.tgz",
"integrity": "sha512-llBEhWm1SacoRwgHUoQJYtwp4PBLF4faQi5TCpIGyGs9n4y5+juI0tDgyKIfpqxckRHaHzouUEph3THklWh03w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.29.7",
"@babel/types": "^7.29.7",
"source-map-js": "^1.2.1"
}
},
"node_modules/make-dir": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
"integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==",
"dev": true,
"license": "MIT",
"dependencies": {
"semver": "^7.5.3"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/make-dir/node_modules/semver": {
"version": "7.8.5",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
"integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
"dev": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/mdn-data": { "node_modules/mdn-data": {
"version": "2.27.1", "version": "2.27.1",
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz",
@@ -3553,6 +3711,19 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"license": "MIT",
"dependencies": {
"has-flag": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/symbol-tree": { "node_modules/symbol-tree": {
"version": "3.2.4", "version": "3.2.4",
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
@@ -3822,19 +3993,19 @@
} }
}, },
"node_modules/vitest": { "node_modules/vitest": {
"version": "4.1.10", "version": "4.1.11",
"resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.10.tgz", "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.11.tgz",
"integrity": "sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==", "integrity": "sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vitest/expect": "4.1.10", "@vitest/expect": "4.1.11",
"@vitest/mocker": "4.1.10", "@vitest/mocker": "4.1.11",
"@vitest/pretty-format": "4.1.10", "@vitest/pretty-format": "4.1.11",
"@vitest/runner": "4.1.10", "@vitest/runner": "4.1.11",
"@vitest/snapshot": "4.1.10", "@vitest/snapshot": "4.1.11",
"@vitest/spy": "4.1.10", "@vitest/spy": "4.1.11",
"@vitest/utils": "4.1.10", "@vitest/utils": "4.1.11",
"es-module-lexer": "^2.0.0", "es-module-lexer": "^2.0.0",
"expect-type": "^1.3.0", "expect-type": "^1.3.0",
"magic-string": "^0.30.21", "magic-string": "^0.30.21",
@@ -3862,12 +4033,12 @@
"@edge-runtime/vm": "*", "@edge-runtime/vm": "*",
"@opentelemetry/api": "^1.9.0", "@opentelemetry/api": "^1.9.0",
"@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
"@vitest/browser-playwright": "4.1.10", "@vitest/browser-playwright": "4.1.11",
"@vitest/browser-preview": "4.1.10", "@vitest/browser-preview": "4.1.11",
"@vitest/browser-webdriverio": "4.1.10", "@vitest/browser-webdriverio": "4.1.11",
"@vitest/coverage-istanbul": "4.1.10", "@vitest/coverage-istanbul": "4.1.11",
"@vitest/coverage-v8": "4.1.10", "@vitest/coverage-v8": "4.1.11",
"@vitest/ui": "4.1.10", "@vitest/ui": "4.1.11",
"happy-dom": "*", "happy-dom": "*",
"jsdom": "*", "jsdom": "*",
"vite": "^6.0.0 || ^7.0.0 || ^8.0.0" "vite": "^6.0.0 || ^7.0.0 || ^8.0.0"

View File

@@ -9,7 +9,8 @@
"lint": "eslint .", "lint": "eslint .",
"preview": "vite preview", "preview": "vite preview",
"test": "vitest run", "test": "vitest run",
"test:watch": "vitest" "test:watch": "vitest",
"test:coverage": "vitest run --coverage"
}, },
"dependencies": { "dependencies": {
"react": "^19.2.6", "react": "^19.2.6",
@@ -25,6 +26,7 @@
"@types/react": "^19.2.14", "@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3", "@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^6.0.1", "@vitejs/plugin-react": "^6.0.1",
"@vitest/coverage-v8": "^4.1.11",
"eslint": "^10.3.0", "eslint": "^10.3.0",
"eslint-plugin-react-hooks": "^7.1.1", "eslint-plugin-react-hooks": "^7.1.1",
"eslint-plugin-react-refresh": "^0.5.2", "eslint-plugin-react-refresh": "^0.5.2",

View File

@@ -1,40 +1,78 @@
import { useState } from 'react' import { useEffect, useState } from 'react'
import { Link } from 'react-router-dom' import { Link, useLocation } from 'react-router-dom'
import { initAnalytics, trackPageView } from '../services/analytics'
export default function CookieBanner() { export default function CookieBanner() {
const [visible, setVisible] = useState(() => !localStorage.getItem('cookieConsent')) const location = useLocation()
if (!visible) return null const [consent, setConsent] = useState(() =>
localStorage.getItem('cookieConsent')
)
const visible = consent === null
// Si le consentement avait déjà été donné lors d'une précédente visite,
// Analytics est initialisé au chargement de l'application.
useEffect(() => {
if (consent !== 'accepted') return
initAnalytics()
trackPageView(location.pathname + location.search)
}, [consent, location.pathname, location.search])
const handleChoice = (choice) => { const handleChoice = (choice) => {
localStorage.setItem('cookieConsent', choice) localStorage.setItem('cookieConsent', choice)
setVisible(false) setConsent(choice)
if (choice === 'accepted') {
initAnalytics()
trackPageView(location.pathname + location.search)
} }
}
if (!visible) return null
return ( return (
<div className="fixed bottom-0 left-0 right-0 z-50 px-4 py-4 bg-white border-t border-gray-200 shadow-lg"> <div className="fixed bottom-0 left-0 right-0 z-50 px-4 py-4 bg-white border-t border-gray-200 shadow-lg">
<div className="max-w-5xl mx-auto flex flex-col sm:flex-row items-start sm:items-center gap-4"> <div className="max-w-5xl mx-auto flex flex-col sm:flex-row items-start sm:items-center gap-4">
<div className="flex-1"> <div className="flex-1">
<p className="text-sm font-semibold text-gray-800 mb-1">Ce site utilise des cookies</p> <p className="text-sm font-semibold text-gray-800 mb-1">
Gestion des cookies
</p>
<p className="text-xs text-gray-500 leading-relaxed"> <p className="text-xs text-gray-500 leading-relaxed">
Nous utilisons des cookies essentiels au fonctionnement du site. En continuant votre navigation, vous acceptez leur utilisation conformément à notre{' '} Les cookies strictement nécessaires au fonctionnement du site sont
<Link to="/confidentialite" className="text-[#006B6B] hover:underline">politique de confidentialité</Link>. utilisés automatiquement. Avec votre accord, nous utilisons également
Google Analytics afin de mesurer l'audience et améliorer le site. Vous
pouvez accepter ou refuser ces cookies statistiques. Consultez notre{' '}
<Link
to="/confidentialite"
className="text-[#006B6B] hover:underline"
>
politique de confidentialité
</Link>.
</p> </p>
</div> </div>
<div className="flex gap-2 flex-shrink-0 w-full sm:w-auto"> <div className="flex gap-2 flex-shrink-0 w-full sm:w-auto">
<button <button
type="button"
onClick={() => handleChoice('refused')} onClick={() => handleChoice('refused')}
className="flex-1 sm:flex-none border border-gray-300 text-gray-600 hover:bg-gray-50 text-sm font-medium px-4 py-2.5 rounded-lg transition-colors" className="flex-1 sm:flex-none border border-gray-300 text-gray-600 hover:bg-gray-50 text-sm font-medium px-4 py-2.5 rounded-lg transition-colors"
> >
Refuser Refuser
</button> </button>
<button <button
type="button"
onClick={() => handleChoice('accepted')} onClick={() => handleChoice('accepted')}
className="flex-1 sm:flex-none bg-[#006B6B] hover:bg-[#005959] text-white text-sm font-medium px-4 py-2.5 rounded-lg transition-colors" className="flex-1 sm:flex-none bg-[#006B6B] hover:bg-[#005959] text-white text-sm font-medium px-4 py-2.5 rounded-lg transition-colors"
> >
Accepter Accepter
</button> </button>
</div> </div>
</div> </div>
</div> </div>
) )

View File

@@ -0,0 +1,33 @@
const GA_ID = import.meta.env.VITE_GA_ID
export function initAnalytics() {
if (!GA_ID || window.gtag) return
const script = document.createElement('script')
script.async = true
script.src = `https://www.googletagmanager.com/gtag/js?id=${GA_ID}`
document.head.appendChild(script)
window.dataLayer = window.dataLayer || []
window.gtag = function gtag() {
window.dataLayer.push(arguments)
}
window.gtag('js', new Date())
// Les page_view sont envoyées explicitement lors des changements de route React.
window.gtag('config', GA_ID, {
send_page_view: false,
})
}
export function trackPageView(path) {
if (!GA_ID || !window.gtag) return
window.gtag('event', 'page_view', {
page_path: path,
page_location: window.location.href,
page_title: document.title,
})
}

View File

@@ -8,7 +8,7 @@ describe('CookieBanner', () => {
it("s'affiche quand aucun choix n'a été fait", () => { it("s'affiche quand aucun choix n'a été fait", () => {
renderWithProviders(<CookieBanner />) renderWithProviders(<CookieBanner />)
expect(screen.getByText(/ce site utilise des cookies/i)).toBeInTheDocument() expect(screen.getByText(/gestion des cookies/i)).toBeInTheDocument()
}) })
it('enregistre le consentement et disparaît quand on accepte', async () => { it('enregistre le consentement et disparaît quand on accepte', async () => {

View File

@@ -4,14 +4,29 @@ import tailwindcss from '@tailwindcss/vite'
export default defineConfig({ export default defineConfig({
plugins: [react(), tailwindcss()], plugins: [react(), tailwindcss()],
server: { server: {
proxy: { proxy: {
'/api': 'http://localhost:5000', '/api': 'http://localhost:5000',
}, },
}, },
test: { test: {
environment: 'jsdom', environment: 'jsdom',
globals: true, globals: true,
setupFiles: './src/setupTests.js', setupFiles: './src/setupTests.js',
// Jenkins pourra lire directement ce fichier.
reporters: ['default', 'junit'],
outputFile: {
junit: './test-results/junit.xml',
},
// Rapports utilisés ensuite par Jenkins et SonarQube.
coverage: {
provider: 'v8',
reporter: ['text', 'html', 'lcov'],
reportsDirectory: './coverage',
},
}, },
}) })