🎉 Première version du projet
10
.babelrc
Normal file
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"presets": [
|
||||
"@babel/preset-env"
|
||||
],
|
||||
"plugins": [
|
||||
["@babel/plugin-transform-runtime", {
|
||||
"regenerator": true
|
||||
}]
|
||||
]
|
||||
}
|
18
.editorconfig
Normal file
|
@ -0,0 +1,18 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = false
|
||||
insert_final_newline = true
|
||||
end_of_line = lf
|
||||
|
||||
[*.html]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.{js,json,yml}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.js]
|
||||
quote_type = single
|
33
.eslintrc.js
Normal file
|
@ -0,0 +1,33 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
env: {
|
||||
node: true,
|
||||
browser: true,
|
||||
},
|
||||
extends: ["standard"],
|
||||
rules: {
|
||||
"no-console": process.env.NODE_ENV === "production" ? "error" : "off",
|
||||
"no-debugger": process.env.NODE_ENV === "production" ? "error" : "off",
|
||||
"comma-dangle": [2, "always-multiline"],
|
||||
"no-var": 2,
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: ["**/__tests__/*.js", "**/tests/unit/**/*.spec.js"],
|
||||
env: {
|
||||
jest: true
|
||||
}
|
||||
}
|
||||
],
|
||||
parserOptions: {
|
||||
parser: "babel-eslint"
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: ["**/__tests__/*.js", "**/tests/unit/**/*.spec.js"],
|
||||
env: {
|
||||
jest: true
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
213
.gitignore
vendored
Normal file
|
@ -0,0 +1,213 @@
|
|||
|
||||
# Created by https://www.toptal.com/developers/gitignore/api/node,code,linux,vim,windows,macos
|
||||
# Edit at https://www.toptal.com/developers/gitignore?templates=node,code,linux,vim,windows,macos
|
||||
|
||||
### Code ###
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
*.code-workspace
|
||||
|
||||
### Linux ###
|
||||
*~
|
||||
|
||||
# temporary files which can be created if a process still has a handle open of a deleted file
|
||||
.fuse_hidden*
|
||||
|
||||
# KDE directory preferences
|
||||
.directory
|
||||
|
||||
# Linux trash folder which might appear on any partition or disk
|
||||
.Trash-*
|
||||
|
||||
# .nfs files are created when an open file is removed but is still being accessed
|
||||
.nfs*
|
||||
|
||||
### macOS ###
|
||||
# General
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Icon must end with two \r
|
||||
Icon
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
### Node ###
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# TypeScript v1 declaration files
|
||||
typings/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
.env.test
|
||||
.env*.local
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
|
||||
### Vim ###
|
||||
# Swap
|
||||
[._]*.s[a-v][a-z]
|
||||
!*.svg # comment out if you don't need vector files
|
||||
[._]*.sw[a-p]
|
||||
[._]s[a-rt-v][a-z]
|
||||
[._]ss[a-gi-z]
|
||||
[._]sw[a-p]
|
||||
|
||||
# Session
|
||||
Session.vim
|
||||
Sessionx.vim
|
||||
|
||||
# Temporary
|
||||
.netrwhist
|
||||
# Auto-generated tag files
|
||||
tags
|
||||
# Persistent undo
|
||||
[._]*.un~
|
||||
|
||||
### Windows ###
|
||||
# Windows thumbnail cache files
|
||||
Thumbs.db
|
||||
Thumbs.db:encryptable
|
||||
ehthumbs.db
|
||||
ehthumbs_vista.db
|
||||
|
||||
# Dump file
|
||||
*.stackdump
|
||||
|
||||
# Folder config file
|
||||
[Dd]esktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Windows Installer files
|
||||
*.cab
|
||||
*.msi
|
||||
*.msix
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# Windows shortcuts
|
||||
*.lnk
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/node,code,linux,vim,windows,macos
|
4
.prettierrc.js
Normal file
|
@ -0,0 +1,4 @@
|
|||
module.exports = {
|
||||
singleQuote: true,
|
||||
semi: false
|
||||
};
|
20
LICENCE
Normal file
|
@ -0,0 +1,20 @@
|
|||
Copyright (c) 2020 Ministère de l'intérieur
|
||||
Copyright (c) 2020 Johann Pardanaud
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
44
README.md
Normal file
|
@ -0,0 +1,44 @@
|
|||
# Générateur de certificat de déplacement
|
||||
|
||||
## Développer
|
||||
|
||||
### Installer le projet
|
||||
|
||||
```console
|
||||
git clone https://github.com/LAB-MI/attestation-deplacement-derogatoire-q4-2020.git
|
||||
cd attestation-deplacement-derogatoire-q4-2020
|
||||
npm i
|
||||
npm start
|
||||
```
|
||||
|
||||
## Générer et tester le code de production
|
||||
|
||||
### Tester le code de production en local
|
||||
|
||||
#### Générer le code de production pour tester que le build fonctionne en entier
|
||||
|
||||
```console
|
||||
npm run build:dev
|
||||
```
|
||||
|
||||
#### Tester le code de production en local
|
||||
|
||||
```console
|
||||
npx serve dist
|
||||
```
|
||||
|
||||
Et visiter http://localhost:5000
|
||||
|
||||
Le code à déployer sera le contenu du dossier `dist`
|
||||
|
||||
## Crédits
|
||||
|
||||
Ce projet a été réalisé à partir d'un fork du dépôt [deplacement-covid-19](https://github.com/nesk/deplacement-covid-19) de lui-même réalisé à partir d'un fork du dépôt [covid-19-certificate](https://github.com/nesk/covid-19-certificate) de [Johann Pardanaud](https://github.com/nesk).
|
||||
|
||||
Les projets open source suivants ont été utilisés pour le développement de ce
|
||||
service :
|
||||
|
||||
- [PDF-LIB](https://pdf-lib.js.org/)
|
||||
- [qrcode](https://github.com/soldair/node-qrcode)
|
||||
- [Bootstrap](https://getbootstrap.com/)
|
||||
- [Font Awesome](https://fontawesome.com/license)
|
24761
package-lock.json
generated
Normal file
94
package.json
Normal file
|
@ -0,0 +1,94 @@
|
|||
{
|
||||
"name": "attestation-couvre-feu",
|
||||
"version": "1.0.3",
|
||||
"description": "Générateur d'attestation de déplacement dérogatoire'",
|
||||
"main": "certificate.js",
|
||||
"scripts": {
|
||||
"lint": "eslint src/**/*.js",
|
||||
"preformat": "prettier --write \"src/**/*.js\"",
|
||||
"format": "npm run lint -- --fix",
|
||||
"start": "cross-env VERSION=localversion parcel ./src/index.html",
|
||||
"start:grid": "cross-env VERSION=localversion parcel ./src/grid.html",
|
||||
"clean:dist": "rimraf dist",
|
||||
"prebuild": "run-p lint clean:dist",
|
||||
"build:simple": "cross-env-shell VERSION=$npm_package_version parcel build --public-url $PUBLIC_URL ./src/index.html ./src/robots.txt ./src/sitemap.xml",
|
||||
"build": "cross-env npm run build:simple",
|
||||
"postbuild": "cross-env-shell react-snap",
|
||||
"prebuild:ci": "run-p lint clean:dist",
|
||||
"build:ci": "cross-env npm run build:simple",
|
||||
"postbuild:ci": "cross-env-shell react-snap",
|
||||
"build:dev": "cross-env PUBLIC_URL='/' npm run build:simple",
|
||||
"preserve": "npm run build",
|
||||
"serve": "serve dist",
|
||||
"serve:dist": "serve dist"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/lab-mi/attestation-deplacement-derogatoire-q4-2020"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/lab-mi/attestation-deplacement-derogatoire-q4-2020/issues"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.12.3",
|
||||
"@babel/plugin-transform-runtime": "^7.12.1",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"copy-and-watch": "^0.1.5",
|
||||
"cross-env": "^7.0.2",
|
||||
"eslint": "^7.12.1",
|
||||
"eslint-config-standard": "^16.0.0",
|
||||
"eslint-plugin-import": "^2.22.1",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^4.2.1",
|
||||
"eslint-plugin-standard": "^4.0.2",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"parcel-bundler": "^1.12.4",
|
||||
"parcel-plugin-sw-cache": "^0.3.1",
|
||||
"postcss-current-selector": "0.0.3",
|
||||
"postcss-nested": "^4.2.3",
|
||||
"postcss-nested-ancestors": "^2.0.0",
|
||||
"postcss-preset-env": "^6.7.0",
|
||||
"prettier": "^2.1.2",
|
||||
"react-snap": "^1.23.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"serve": "^11.3.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.32",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.15.1",
|
||||
"bootstrap": "^4.5.3",
|
||||
"pdf-lib": "^1.11.2",
|
||||
"qrcode": "^1.4.4"
|
||||
},
|
||||
"browserslist": [
|
||||
"last 5 versions"
|
||||
],
|
||||
"reactSnap": {
|
||||
"source": "dist",
|
||||
"minifyHtml": {
|
||||
"collapseWhitespace": false,
|
||||
"removeComments": false
|
||||
},
|
||||
"puppeteerArgs": [
|
||||
"--no-sandbox",
|
||||
"--disable-setuid-sandbox"
|
||||
]
|
||||
},
|
||||
"cache": {
|
||||
"globPatterns": [
|
||||
"**/*.{html,js,css,jpg,png,pdf,svg,eot,ttf,woff,woff2}"
|
||||
],
|
||||
"disablePlugin": false,
|
||||
"inDev": true,
|
||||
"strategy": "default",
|
||||
"clearDist": false,
|
||||
"templatedURLs": {
|
||||
"./": [
|
||||
"index.html"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
11
postcss.config.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
module.exports = {
|
||||
plugins: {
|
||||
autoprefixer: {
|
||||
grid: true,
|
||||
},
|
||||
'postcss-preset-env': {},
|
||||
'postcss-nested-ancestors': {},
|
||||
'postcss-nested': {},
|
||||
'postcss-current-selector': {},
|
||||
},
|
||||
}
|
1
src/MIN_Interieur_RVB.svg
Normal file
After Width: | Height: | Size: 20 KiB |
1
src/MIN_Interieur_RVB_dark.svg
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
src/certificate.pdf
Normal file
195
src/confidentialite.html
Normal file
|
@ -0,0 +1,195 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
|
||||
<meta name="msapplication-TileColor" content="#603cba" />
|
||||
<meta name="msapplication-config" content="./favicons/browserconfig.xml" />
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="./favicons/apple-touch-icon.png" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="./favicons/favicon-32x32.png" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="./favicons/favicon-16x16.png" />
|
||||
<link rel="manifest" href="./favicons/site.webmanifest" />
|
||||
<link rel="mask-icon" href="./favicons/safari-pinned-tab.svg" color="#21bf73" />
|
||||
|
||||
<title>COVID-19 – Générateur d'attestation de déplacement</title>
|
||||
</head>
|
||||
|
||||
<body class="confidentialite">
|
||||
<div class="container">
|
||||
<header class="wrapper">
|
||||
<picture>
|
||||
<source srcset="/MIN_Interieur_RVB_dark.svg" media="(prefers-color-scheme: dark)">
|
||||
<img src="/MIN_Interieur_RVB.svg" alt="Ministère de l'intérieur. Liberté, égalité, fraternité." class="logo" role="presentation" aria-hidden="true">
|
||||
</picture>
|
||||
<div class="header-content">
|
||||
<h1 class="flex flex-wrap">
|
||||
<div class="covid-title">
|
||||
COVID-19
|
||||
</div>
|
||||
<div class="covid-subtitle">
|
||||
Générateur d'attestation de déplacement
|
||||
</div>
|
||||
</h1>
|
||||
</div>
|
||||
</header>
|
||||
<div class="wrapper">
|
||||
<h2 class="titre-2">Politique de confidentialité</h2>
|
||||
<h3 class="titre-3">Collecte des données personnelles</h3>
|
||||
<p>
|
||||
Les informations saisies dans ce générateur d’attestation de
|
||||
déplacement ne font l’objet d’aucune collecte par le ministère
|
||||
de l’Intérieur. Ces données personnelles<sup>1</sup> sont
|
||||
exclusivement stockées dans le terminal (ordinateur, tablette,
|
||||
smartphone) utilisé pour générer l’attestation. Le code source
|
||||
de ce générateur d'attestation est ouvert. Il peut être consulté
|
||||
sur <a href="https://github.com/LAB-MI/attestation-deplacement-derogatoire-q4-2020">
|
||||
https://github.com/LAB-MI/attestation-deplacement-derogatoire-q4-2020</a>.
|
||||
</p>
|
||||
<em>1 - Prénom, Nom, Date de naissance, Lieu de naissance, Adresse,
|
||||
Code postal, Ville.</em>
|
||||
<h3 class="titre-3">Traceurs et cookies</h3>
|
||||
<p>
|
||||
Lors de la consultation du générateur, des cookies sont déposés
|
||||
sur le terminal utilisé. Ces cookies sont utilisés pour
|
||||
sécuriser le service proposé.
|
||||
</p>
|
||||
|
||||
<h4 class="titre-4">Définition d’un cookie</h4>
|
||||
<p>
|
||||
Un "cookie" est une suite d'informations, généralement de petite
|
||||
taille et identifié par un nom, qui peut être transmis à votre
|
||||
navigateur par un site web sur lequel vous vous connectez. Votre
|
||||
navigateur web le conserve pendant une certaine durée, et le
|
||||
renvoie au serveur web chaque fois que vous vous y
|
||||
re-connecterez. Les cookies ont de multiples usages : ils
|
||||
peuvent servir à mémoriser votre identifiant client auprès d'un
|
||||
site marchand, le contenu courant de votre panier d'achat, un
|
||||
identifiant permettant de tracer votre navigation pour des
|
||||
finalités statistiques ou publicitaires, etc.
|
||||
</p>
|
||||
<h4 class="titre-4">Cookies utilisés</h4>
|
||||
<p>
|
||||
Différents cookies techniques sont utilisés sur ce générateur
|
||||
d’attestation de déplacement :
|
||||
</p>
|
||||
<table class="cookies">
|
||||
<caption>Liste des cookies utilisés sur ce site/caption>
|
||||
<thead>
|
||||
<tr class="header-row">
|
||||
<th scope="col">Nom du Cookie</th>
|
||||
<th scope="col">Finalité</th>
|
||||
<th scope="col">Durée de conservation</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td data-label="Nom du Cookie" class="name-col">incap_ses_*</td>
|
||||
<td data-label="Finalité" >
|
||||
Protection DDoS Incapsula et Pare-feu pour application
|
||||
web : cookie servant à relier les requêtes HTTP à une
|
||||
session. La réouverture du navigateur et l’accès au même
|
||||
site sont considérés comme des visites différentes. Pour
|
||||
maintenir les sessions existantes (cookie de session).
|
||||
</td>
|
||||
<td data-label="Durée de conservation" >Supprimé après la fermeture du navigateur.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td data-label="Nom du Cookie" class="name-col">nlbi_*</td>
|
||||
<td data-label="Finalité" >
|
||||
Protection DDoS Incapsula et Pare-feu pour application
|
||||
web : cookie équilibreur de charge qui garantit que les
|
||||
requêtes d’un client sont envoyées au même serveur
|
||||
d’origine.
|
||||
</td>
|
||||
<td data-label="Durée de conservation" >Supprimé après la fermeture du navigateur.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td data-label="Nom du Cookie" class="name-col">visid_incap_*</td>
|
||||
<td data-label="Finalité" >
|
||||
Protection DDoS Incapsula et Pare-feu pour application
|
||||
web : cookie servant à relier certaines sessions à un
|
||||
visiteur spécifique (visiteur représentant un ordinateur
|
||||
spécifique). Afin d’identifier les clients ayant déjà
|
||||
consulté Incapsula. Le seul cookie qui perdure pour une
|
||||
durée de 12 mois.
|
||||
</td>
|
||||
<td data-label="Durée de conservation" >12 mois.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td data-label="Nom du Cookie" class="name-col">__utm*</td>
|
||||
<td data-label="Finalité" >
|
||||
Cookie de classification Incapsula : pour voir comment
|
||||
le client réagit à et gère un cookie mal formé afin
|
||||
d’identifier la nature de ce client.
|
||||
</td>
|
||||
<td data-label="Durée de conservation" >900 secondes maximum.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p>
|
||||
Vous pouvez refuser ces cookies en configurant les paramètres de
|
||||
votre navigateur.
|
||||
</p>
|
||||
<h4 class="titre-4">
|
||||
Moyens d'opposition au dépôt des cookies via votre navigateur
|
||||
</h4>
|
||||
<p>
|
||||
<strong>Si vous utilisez Firefox : </strong>Cliquez sur le
|
||||
bouton de menu et sélectionnez « Options ». Ensuite,
|
||||
sélectionnez le panneau « Vie privée et sécurité ». Rendez-vous
|
||||
à la section « Protection renforcée contre le pistage ».
|
||||
Choisissez la protection personnalisé [bouton radio
|
||||
« Personnalisé »]. Décochez la case « cookies ». Cliquez sur le
|
||||
bouton « Actualisez tous les onglets » si vous souhaitez
|
||||
appliquer ces changements à la navigation en cours au sein de
|
||||
votre navigateur.
|
||||
</p>
|
||||
<p>
|
||||
<strong>Si vous utilisez Chrome : </strong>En haut à droite,
|
||||
cliquez sur « Plus > Paramètres ». En bas, cliquez sur «
|
||||
Paramètres avancés ». Dans la section "Confidentialité et
|
||||
sécurité", cliquez sur « Paramètres des sites». Cliquez sur «
|
||||
Cookies et données des sites». Désactivez l’option « Autoriser
|
||||
les sites à enregistrer/lire les données des cookies ».
|
||||
</p>
|
||||
<p>
|
||||
<strong>Si vous utilisez Internet Explorer : </strong>Sélectionnez le bouton « Outils », puis « Options
|
||||
Internet ».
|
||||
Sélectionnez l’onglet « Confidentialité » puis sous « Paramètres
|
||||
», sélectionnez « Avancé ». Cochez la case "Ignorer la gestion
|
||||
automatique des cookies", puis sélectionner "Refuser" dans la
|
||||
colonne "Cookies tierces parties".
|
||||
</p>
|
||||
<p>
|
||||
Pour plus d’informations sur le sujet, n’hésitez pas à visiter
|
||||
le
|
||||
<a href="https://www.cnil.fr/fr/cookies-et-autres-traceurs">site de la CNIL</a>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn-wrapper">
|
||||
<a href="./index.html" class="btn-generateur">
|
||||
Retour au générateur
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<footer class="main-footer">
|
||||
<div class="footer-links">
|
||||
<a class="footer-line footer-link">Confidentialité</a>
|
||||
<a href="https://www.interieur.gouv.fr/Infos-du-site/Mentions-legales" target="_blank" title="Mentions légales - nouvelle page"
|
||||
class="footer-line footer-link">Mentions légales</a>
|
||||
<a href="https://www.gouvernement.fr/info-coronavirus" target="_blank" title="Information du gouvernement sur le Covid-19 - nouvelle page" class="footer-line footer-link">Informations du
|
||||
gouvernement sur le Covid-19</a>
|
||||
<p class="footer-line">
|
||||
Plus d’infos au<a class="num-08" href="tel:0800130000">
|
||||
0 800 130 000</a>
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
<script src="./js/confidentialite.js"></script>
|
||||
</body>
|
||||
</html>
|
666
src/css/main.css
Normal file
|
@ -0,0 +1,666 @@
|
|||
@font-face {
|
||||
font-family: 'marianne-bold';
|
||||
src: url('../fonts/marianne-bold-webfont.woff2') format('woff2'),
|
||||
url('../fonts/marianne-bold-webfont.woff') format('woff');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'marianne-regular';
|
||||
src: url('../fonts/marianne-regular-webfont.woff2') format('woff2'),
|
||||
url('../fonts/marianne-regular-webfont.woff') format('woff');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
html {
|
||||
background: white;
|
||||
}
|
||||
|
||||
@supports (font: -apple-system-body) {
|
||||
html {
|
||||
font: -apple-system-body !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
html {
|
||||
background: #222;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 20px;
|
||||
background: white;
|
||||
padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
background: #222;
|
||||
}
|
||||
}
|
||||
|
||||
form, #alert-print {
|
||||
margin: 30px auto;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
color: black;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
h1 {
|
||||
color: white;
|
||||
background-color: white;
|
||||
background: #222;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Small devices (landscape phones, 576px and up) */
|
||||
@media (min-width: 576px) {
|
||||
h1 {
|
||||
font-size: 2.5em;
|
||||
}
|
||||
}
|
||||
|
||||
/* Medium devices (tablets, 768px and up) */
|
||||
@media (min-width: 768px) {
|
||||
h1 {
|
||||
font-size: 3em;
|
||||
}
|
||||
}
|
||||
|
||||
svg {
|
||||
height: 1em;
|
||||
}
|
||||
|
||||
h1.flex.flex-wrap {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
h1.flex.flex-wrap {
|
||||
border: 1px solid white;
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: large;
|
||||
color: black;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
h2 {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
footer {
|
||||
margin-top: 40px;
|
||||
background: white;
|
||||
}
|
||||
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
footer {
|
||||
background: #222;
|
||||
}
|
||||
}
|
||||
|
||||
canvas {
|
||||
border: 1px solid #ced4da;
|
||||
border-radius: .25rem;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #000191;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
a {
|
||||
color: #A3A3FF;
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
color: black;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
p {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
max-width: 800px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
#form-profile .form-radio .form-check {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
#form-profile .form-radio-label .form-check-label {
|
||||
font-weight: 400;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
#form-profile .form-radio-label .form-check-label {
|
||||
color: #ddd;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
label {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.form-control::placeholder {
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
input, select, textarea, .form-control, .form-control:focus {
|
||||
background: #333;
|
||||
color: white;
|
||||
border: 1px solid white;
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
background: #555;
|
||||
}
|
||||
}
|
||||
|
||||
#form-generate .form-radio .form-check {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
#alert-facebook {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
right: 20px;
|
||||
}
|
||||
|
||||
#alert-print {
|
||||
margin: 1rem auto 0;
|
||||
}
|
||||
|
||||
#date-selector-group {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#date-selector-group-sortie {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#date-selector {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
height: 100%;
|
||||
transform: translateX(-50%); /* center the input to avoid reset buttons */
|
||||
opacity: 0;
|
||||
z-index: 1;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#date-selector-sortie {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
height: 100%;
|
||||
transform: translateX(-50%); /* center the input to avoid reset buttons */
|
||||
opacity: 0;
|
||||
z-index: 1;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
input:invalid+span:after {
|
||||
content: '✖';
|
||||
padding-left: 0.3em;
|
||||
}
|
||||
|
||||
input:valid+span:after {
|
||||
content: '✓';
|
||||
padding-left: 0.3em;
|
||||
}
|
||||
|
||||
/* Chrome, Safari, Edge, Opera */
|
||||
input::-webkit-outer-spin-button,
|
||||
input::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Firefox */
|
||||
input[type=number] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
::placeholder {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.exemple {
|
||||
margin-top: 0.2em;
|
||||
font-style: italic;
|
||||
color: #999999;
|
||||
height: 1em;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.covid-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.3em;
|
||||
font-family: 'marianne-regular', Arial, Helvetica, sans-serif;
|
||||
font-size: 0.50em;
|
||||
color: #ffffff;
|
||||
background-color: #000191;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.covid-title {
|
||||
background: #2929ff;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 992px) {
|
||||
.covid-title {
|
||||
flex-grow: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.covid-subtitle {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
font-size: 0.40em;
|
||||
padding: 0.3em;
|
||||
background-color: #e1000f;
|
||||
text-transform:uppercase ;
|
||||
color: #ffffff;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.note-title {
|
||||
border-bottom: 3px solid #fff;
|
||||
}
|
||||
|
||||
.highlighted {
|
||||
font-weight: bold;
|
||||
border-bottom: 2px solid;
|
||||
}
|
||||
|
||||
.footnote-title {
|
||||
color: #fff;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.msg-info {
|
||||
text-align: left;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.msg-alert {
|
||||
text-align: left;
|
||||
color: red;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.text-desc {
|
||||
font-style :italic;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.covid-subtitle {
|
||||
background: #ad000c;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.msg-info {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-attestation {
|
||||
padding: 0.9em;
|
||||
font-size: 1.2em;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
background-color: #000191;
|
||||
border-radius: 0.5em;
|
||||
}
|
||||
|
||||
.btn-attestation:hover {
|
||||
background-color: #3031C1;
|
||||
}
|
||||
|
||||
.github {
|
||||
font-size: 0.7em;
|
||||
text-align: center;
|
||||
color: black;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.github {
|
||||
color: white
|
||||
}
|
||||
}
|
||||
|
||||
.github-link {
|
||||
color: #000191;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.github-link {
|
||||
color: #A3A3FF;
|
||||
}
|
||||
}
|
||||
|
||||
.github-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.label-mi {
|
||||
text-align: center;
|
||||
font-size: 1em;
|
||||
color: black;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.label-mi {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.validity {
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
width: 2em;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.validity {
|
||||
color: #DDD;
|
||||
}
|
||||
}
|
||||
|
||||
.main-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding-top: 3em;
|
||||
padding-bottom: 3em;
|
||||
background: #222;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.footer-links {
|
||||
margin: 0 auto;
|
||||
max-width: 500px;
|
||||
background: #222;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.main-footer {
|
||||
background: #444;
|
||||
}
|
||||
|
||||
.footer-links {
|
||||
background: #444;
|
||||
}
|
||||
}
|
||||
|
||||
.footer-line {
|
||||
display: block;
|
||||
margin: 0.75em;
|
||||
font-size: 0.9em;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.footer-link:hover {
|
||||
text-decoration: underline;
|
||||
color: #0a81ff;
|
||||
}
|
||||
|
||||
.footer-link:focus {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.num-08 {
|
||||
font-weight: bold;
|
||||
color: #00a94f;
|
||||
}
|
||||
|
||||
.num-08:hover {
|
||||
text-decoration: underline;
|
||||
color: #0A81FF;
|
||||
}
|
||||
|
||||
.stores-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.confidentialite {
|
||||
.cookies {
|
||||
border-collapse: collapse;
|
||||
|
||||
td,
|
||||
th {
|
||||
border: 1px solid #000191;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
td, th {
|
||||
border-color: #A3A3FF;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.header-row {
|
||||
font-weight: bold;
|
||||
color: #ffffff;
|
||||
background-color: #000191;
|
||||
text-transform: uppercase;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.name-col {
|
||||
font-weight: bold;
|
||||
padding: 8px 16px;
|
||||
color: #000191;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.name-col {
|
||||
color: #A3A3FF;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
.btn-generateur {
|
||||
padding: 0.8em;
|
||||
font-size: 1.2em;
|
||||
font-weight: bold;
|
||||
color: #ffffff;
|
||||
background-color: #000191;
|
||||
border-radius: 0.5em;
|
||||
text-decoration: none;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
}
|
||||
em {
|
||||
font-size: .8rem;
|
||||
}
|
||||
}
|
||||
|
||||
.titre-2 {
|
||||
text-align: left;
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
color: #000191;
|
||||
}
|
||||
.titre-3 {
|
||||
text-align: left;
|
||||
font-size: 1.25rem;
|
||||
font-weight: bold;
|
||||
color: #000000;
|
||||
}
|
||||
.titre-4 {
|
||||
text-align: left;
|
||||
font-size: 1rem;
|
||||
font-weight: bold;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.titre-3 {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.titre-4 {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.titre-2 {
|
||||
color: #A3A3FF;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 800px){
|
||||
table thead {
|
||||
display: none;
|
||||
}
|
||||
|
||||
table tr{
|
||||
display: block;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
table td {
|
||||
display: flex;
|
||||
text-align: left;
|
||||
min-height: 3rem;
|
||||
}
|
||||
|
||||
table td:before {
|
||||
content: attr(data-label);
|
||||
font-weight: bold;
|
||||
color: #000191;
|
||||
width: 100px;
|
||||
margin-right: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
#snackbar {
|
||||
min-width: 250px;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
border-radius: 3px;
|
||||
padding: 16px;
|
||||
position: fixed;
|
||||
z-index: 1;
|
||||
left: 50%;
|
||||
bottom: 30px;
|
||||
font-size: 17px;
|
||||
transform: translateX(-50%);
|
||||
box-shadow: 0 0 8px 1px #fff;
|
||||
opacity: 0;
|
||||
transition: all 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
#snackbar.show {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@media only screen and (min-width:600px) {
|
||||
.hide-on-desktop, * [aria-labelledby='hide-on-desktop'] {
|
||||
display: none;
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.center {
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
[id^="footnote"] {
|
||||
margin: 30px auto;
|
||||
max-width: 400px;
|
||||
font-size: 0.8em;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
[id^="footnote"] {
|
||||
color:white;
|
||||
background: #222;
|
||||
}
|
||||
}
|
||||
|
||||
#update-alert {
|
||||
position: fixed;
|
||||
bottom: 1em;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.alert-info {
|
||||
color: #030494;
|
||||
background-color: #caf8ff;
|
||||
border-color: #caf8ff;
|
||||
}
|
||||
|
||||
.btn-info {
|
||||
color: #fff;
|
||||
background-color: #030494;
|
||||
border-color: #030494;
|
||||
}
|
||||
|
||||
.fieldset {
|
||||
margin-left: -2em;
|
||||
margin-right: -2em;
|
||||
padding-left: 2em;
|
||||
padding-right: 2em;
|
||||
border: 3px solid transparent;
|
||||
}
|
||||
|
||||
.legend {
|
||||
padding-left: 0.5em;
|
||||
}
|
||||
|
||||
.fieldset-error {
|
||||
border: 3px solid red;
|
||||
}
|
BIN
src/favicons/android-chrome-192x192.png
Normal file
After Width: | Height: | Size: 9.7 KiB |
BIN
src/favicons/android-chrome-512x512.png
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
src/favicons/apple-touch-icon-120x120-precomposed.png
Normal file
After Width: | Height: | Size: 7.7 KiB |
BIN
src/favicons/apple-touch-icon-120x120.png
Normal file
After Width: | Height: | Size: 7.7 KiB |
BIN
src/favicons/apple-touch-icon-precomposed.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
src/favicons/apple-touch-icon.png
Normal file
After Width: | Height: | Size: 16 KiB |
9
src/favicons/browserconfig.xml
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<browserconfig>
|
||||
<msapplication>
|
||||
<tile>
|
||||
<square150x150logo src="mstile-150x150.png"/>
|
||||
<TileColor>#603cba</TileColor>
|
||||
</tile>
|
||||
</msapplication>
|
||||
</browserconfig>
|
BIN
src/favicons/favicon-16x16.png
Normal file
After Width: | Height: | Size: 613 B |
BIN
src/favicons/favicon-32x32.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
src/favicons/favicon.ico
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
src/favicons/mstile-150x150.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
38
src/favicons/safari-pinned-tab.svg
Normal file
|
@ -0,0 +1,38 @@
|
|||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="1024.000000pt" height="1024.000000pt" viewBox="0 0 1024.000000 1024.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
<metadata>
|
||||
Created by potrace 1.11, written by Peter Selinger 2001-2013
|
||||
</metadata>
|
||||
<g transform="translate(0.000000,1024.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000" stroke="none">
|
||||
<path d="M4858 10236 c-1 -2 -50 -6 -108 -10 -58 -3 -116 -8 -130 -11 -14 -2
|
||||
-52 -7 -85 -10 -105 -9 -381 -52 -395 -60 -3 -2 -26 -6 -50 -10 -25 -4 -126
|
||||
-27 -225 -52 -661 -165 -1268 -457 -1830 -879 -82 -62 -161 -123 -175 -136
|
||||
-14 -13 -55 -48 -90 -78 -243 -205 -529 -511 -739 -790 -39 -52 -75 -100 -80
|
||||
-106 -22 -27 -153 -228 -218 -334 -178 -293 -340 -637 -453 -965 -69 -202
|
||||
-140 -458 -174 -635 -2 -14 -9 -47 -15 -75 -13 -62 -20 -106 -55 -360 -37
|
||||
-258 -43 -912 -11 -1106 2 -13 6 -51 10 -84 3 -33 8 -76 10 -95 11 -75 27
|
||||
-176 32 -205 3 -16 7 -39 8 -50 7 -43 16 -89 20 -94 2 -4 6 -24 9 -46 6 -38
|
||||
50 -211 95 -375 93 -339 299 -812 504 -1155 105 -175 101 -169 107 -175 3 -3
|
||||
27 -39 54 -80 50 -77 254 -351 301 -405 14 -16 49 -57 78 -90 149 -172 466
|
||||
-486 549 -543 13 -9 32 -25 43 -35 92 -87 397 -307 599 -432 309 -190 707
|
||||
-377 1035 -485 206 -68 328 -103 400 -117 14 -2 28 -6 31 -8 5 -3 121 -28 231
|
||||
-50 56 -11 257 -45 299 -50 289 -35 363 -39 675 -39 245 -1 407 5 510 19 17 2
|
||||
59 7 95 10 36 4 74 9 85 11 11 1 40 6 65 9 81 11 257 44 377 71 65 14 124 27
|
||||
133 29 8 2 56 15 105 30 50 15 106 31 125 36 56 14 255 83 380 132 260 103
|
||||
475 207 735 357 160 92 510 340 675 478 218 182 544 511 671 677 13 17 26 32
|
||||
29 35 14 11 143 183 212 280 66 94 228 346 228 355 0 2 20 37 44 77 45 77 188
|
||||
360 220 438 11 25 27 63 37 85 125 284 284 828 325 1115 3 19 7 46 9 60 12 62
|
||||
23 148 31 225 3 33 7 71 10 85 25 158 25 830 -1 1020 -8 63 -16 129 -21 174
|
||||
-3 27 -6 52 -8 55 -2 3 -7 31 -11 61 -3 30 -8 62 -10 70 -2 8 -7 29 -9 45 -3
|
||||
17 -10 55 -16 85 -6 30 -14 66 -16 80 -44 226 -187 679 -284 895 -10 22 -32
|
||||
74 -50 115 -51 118 -200 404 -280 535 -221 366 -482 698 -781 995 -113 113
|
||||
-258 245 -332 303 -15 12 -39 32 -52 44 -36 32 -280 213 -350 260 -387 258
|
||||
-753 444 -1160 588 -242 86 -581 177 -780 209 -36 6 -82 14 -110 20 -11 2 -45
|
||||
7 -75 11 -30 4 -62 9 -71 10 -29 6 -116 15 -294 32 -50 4 -642 12 -647 9z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.3 KiB |
43
src/favicons/site.webmanifest
Normal file
|
@ -0,0 +1,43 @@
|
|||
{
|
||||
"name": "Générateur d'attestation de déplacement dérogatoire",
|
||||
"short_name": "Déplacement covid-19",
|
||||
"description": "L'application officielle du gouvernement pour la génération d'attestation de déplacement dérogatoire dématérialisée.",
|
||||
"categories": ["government", "health"],
|
||||
"lang": "fr-FR",
|
||||
"icons": [
|
||||
{
|
||||
"src": "android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "android-chrome-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "apple-touch-icon.png",
|
||||
"sizes": "180x180",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "apple-touch-icon-precomposed.png",
|
||||
"sizes": "180x180",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "apple-touch-icon-120x120.png",
|
||||
"sizes": "120x120",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "apple-touch-icon-120x120-precomposed.png",
|
||||
"sizes": "120x120",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"orientation": "portrait-primary",
|
||||
"theme_color": "#ced4da",
|
||||
"background_color": "#000191",
|
||||
"display": "minimal-ui"
|
||||
}
|
BIN
src/fonts/marianne-bold-webfont.woff
Normal file
BIN
src/fonts/marianne-bold-webfont.woff2
Normal file
BIN
src/fonts/marianne-regular-webfont.woff
Normal file
BIN
src/fonts/marianne-regular-webfont.woff2
Normal file
143
src/form-data.json
Normal file
|
@ -0,0 +1,143 @@
|
|||
[
|
||||
[
|
||||
{
|
||||
"key": "firstname",
|
||||
"type": "text",
|
||||
"contentType": "firstname",
|
||||
"label": "Prénom",
|
||||
"autocomplete": "given-name",
|
||||
"placeholder": "Camille"
|
||||
},
|
||||
{
|
||||
"key": "lastname",
|
||||
"type": "text",
|
||||
"contentType": "lastname",
|
||||
"label": "Nom",
|
||||
"autocomplete": "family-name",
|
||||
"placeholder": "Dupont"
|
||||
},
|
||||
{
|
||||
"key": "birthday",
|
||||
"type": "text",
|
||||
"contentType": "birthday",
|
||||
"label": "Date de naissance",
|
||||
"autocomplete": "birthday",
|
||||
"pattern": "^([0][1-9]|[1-2][0-9]|30|31)\/([0][1-9]|10|11|12)\/(19[0-9][0-9]|20[0-1][0-9]|2020)",
|
||||
"maxlength": 10,
|
||||
"placeholder": "01/01/1970"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"key": "placeofbirth",
|
||||
"type": "text",
|
||||
"contentType": "cityofbirth",
|
||||
"label": "Lieu de naissance",
|
||||
"autocomplete": "off",
|
||||
"placeholder": "Paris"
|
||||
},
|
||||
{
|
||||
"key": "address",
|
||||
"type": "text",
|
||||
"contentType": "address",
|
||||
"label": "Adresse",
|
||||
"autocomplete": "adress-line1",
|
||||
"placeholder": "999 avenue de France"
|
||||
},
|
||||
{
|
||||
"key": "city",
|
||||
"type": "text",
|
||||
"contentType": "city",
|
||||
"label": "Ville",
|
||||
"autocomplete": "address-level2",
|
||||
"placeholder": "Paris"
|
||||
},
|
||||
{
|
||||
"key": "zipcode",
|
||||
"type": "number",
|
||||
"contentType": "zipcode",
|
||||
"label": "Code Postal",
|
||||
"autocomplete": "postal-code",
|
||||
"placeholder": "75001",
|
||||
"inputmode": "numeric",
|
||||
"pattern": "[0-9]{5}",
|
||||
"min": 1000,
|
||||
"max": 99999,
|
||||
"minlength":4,
|
||||
"maxlength":5
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"key": "creationDate",
|
||||
"type": "date",
|
||||
"contentType": "creationDate",
|
||||
"label": "Date de création",
|
||||
"isHidden": true
|
||||
},
|
||||
{
|
||||
"key": "creationHour",
|
||||
"type": "time",
|
||||
"contentType": "creationHour",
|
||||
"label": "Heure de création",
|
||||
"isHidden": true
|
||||
},
|
||||
{
|
||||
"key": "datesortie",
|
||||
"type": "date",
|
||||
"contentType": "datesortie",
|
||||
"label": "Date de sortie",
|
||||
"pattern": "^([0][1-9]|[1-2][0-9]|30|31)\/([0][1-9]|10|11|12)\/(19[0-9][0-9]|20[0-1][0-9]|2020)",
|
||||
"autocomplete": ""
|
||||
},
|
||||
{
|
||||
"key": "heuresortie",
|
||||
"type": "time",
|
||||
"contentType": "heuresortie",
|
||||
"label": "Heure de sortie",
|
||||
"autocomplete": ""
|
||||
},
|
||||
{
|
||||
"key": "reason",
|
||||
"type": "list",
|
||||
"items": [
|
||||
{
|
||||
"code": "travail",
|
||||
"label": "Déplacements entre le domicile et le lieu d'exercice de l'activité professionnelle ou les déplacements professionnels ne pouvant être différés <a class=\"footnote\" id=\"footnote2\" href=\"#footnote2\">[2]</a> ;"
|
||||
},
|
||||
{
|
||||
"code": "achats",
|
||||
"label": "Déplacements pour effectuer des achats de fournitures nécessaires à l'activité professionnelle, des achats de première nécessité <a class=\"footnote\" id=\"footnote3\" href=\"#footnote3\">[3]</a> dans des établissements dont les activités demeurent autorisées (liste sur gouvernement.fr) et les livraisons à domicile ;"
|
||||
},
|
||||
{
|
||||
"code": "sante",
|
||||
"label": "Consultations et soins ne pouvant être assurés à distance et ne pouvant être différés et l’achat de médicaments ;"
|
||||
},
|
||||
{
|
||||
"code": "famille",
|
||||
"label": "Déplacements pour motif familial impérieux, pour l'assistance aux personnes vulnérables et précaires ou la garde d'enfants ;"
|
||||
},
|
||||
{
|
||||
"code": "handicap",
|
||||
"label": "Déplacements des personnes en situation de handicap et de leur accompagnant ;"
|
||||
},
|
||||
{
|
||||
"code": "sport_animaux",
|
||||
"label": "Déplacements brefs, dans la limite d'une heure quotidienne et dans un rayon maximal d'un kilomètre autour du domicile, liés soit à l'activité physique individuelle des personnes, à l'exclusion de toute pratique sportive collective et de toute proximité avec d'autres personnes, soit à la promenade avec les seules personnes regroupées dans un même domicile, soit aux besoins des animaux de compagnie ."
|
||||
},
|
||||
{
|
||||
"code": "convocation",
|
||||
"label": " Convocation judiciaire ou administrative et rendez-vous dans un service public ;"
|
||||
},
|
||||
{
|
||||
"code": "missions",
|
||||
"label": " Participation à des missions d'intérêt général sur demande de l'autorité administrative ;"
|
||||
},
|
||||
{
|
||||
"code": "enfants",
|
||||
"label": "Déplacement pour chercher les enfants à l’école et à l’occasion de leurs activités périscolaires ;"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
14
src/form.html
Normal file
|
@ -0,0 +1,14 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Essai génération du formulaire</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrapper">
|
||||
<form id="form-profile" accept-charset="UTF-8"></form>
|
||||
</div>
|
||||
<script src="./js/form.js"></script>
|
||||
</body>
|
||||
</html>
|
12
src/grid.html
Normal file
|
@ -0,0 +1,12 @@
|
|||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" /> </head>
|
||||
|
||||
<body>
|
||||
<label for="file">Sélectionner le pdf</label>
|
||||
<input type="file" id="file" name="file">
|
||||
|
||||
<script src="./js/pdf-grid-helper.js"></script>
|
||||
</body>
|
||||
</html>
|
116
src/index.html
Normal file
|
@ -0,0 +1,116 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="msapplication-TileColor" content="#603cba">
|
||||
<meta name="msapplication-config" content="./favicons/browserconfig.xml">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
<meta name="title" content="Générateur d'attestation de déplacement dérogatoire - COVID-19">
|
||||
<meta name="description" content="Ce service officiel génère une version numérique de la déclaration de déplacement covid-19 à présenter aux forces de sécurité lors d’un contrôle.">
|
||||
<meta name="keywords" content="covid19, covid-19, attestation, déclaration, déplacement, officielle, gouvernement">
|
||||
<meta name="robots" content="index, follow">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<meta name="language" content="French">
|
||||
<meta property="og:title" content="Générateur d'attestation de déplacement dérogatoire - COVID-19" />
|
||||
<meta property="og:locale" content="fr_FR" />
|
||||
<meta property="og:description" content="Ce service officiel génère une version numérique de la déclaration de déplacement covid-19 à présenter aux forces de sécurité lors d’un contrôle." />
|
||||
<link rel="canonical" href="https://media.interieur.gouv.fr/attestation-couvre-feu-covid-19/" />
|
||||
<meta property="og:url" content="https://media.interieur.gouv.fr/attestation-couvre-feu-covid-19/" />
|
||||
<meta property="og:site_name" content="Générateur d'attestation de déplacement dérogatoire - COVID-19" />
|
||||
<script type='application/ld+json'>{"@context":"http://www.schema.org","@type":"GovernmentOrganization","name":"Générateur d'attestation de déplacement dérogatoire - COVID-19","description":"Ce service officiel génère une version numérique de la déclaration de déplacement covid-19 à présenter aux forces de sécurité lors d’un contrôle.","address":{"@type":"PostalAddress","addressCountry":"France"}}</script>
|
||||
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="./favicons/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="./favicons/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="./favicons/favicon-16x16.png">
|
||||
<link rel="manifest" href="./favicons/site.webmanifest">
|
||||
<link rel="mask-icon" href="./favicons/safari-pinned-tab.svg" color="#21bf73">
|
||||
|
||||
<title>Attestation de déplacement dérogatoire</title>
|
||||
</head>
|
||||
<body>
|
||||
<header role="banner" class="wrapper">
|
||||
<picture>
|
||||
<source srcset="/MIN_Interieur_RVB_dark.svg" media="(prefers-color-scheme: dark)">
|
||||
<img src="/MIN_Interieur_RVB.svg" alt="Ministère de l'intérieur. Liberté, égalité, fraternité."
|
||||
class="logo" role="presentation" aria-hidden="true">
|
||||
</picture>
|
||||
|
||||
<div>
|
||||
<h1 class="flex flex-wrap">
|
||||
<span class="covid-title">
|
||||
COVID-19
|
||||
</span>
|
||||
<span class="covid-subtitle">
|
||||
Attestation de déplacement dérogatoire
|
||||
</span>
|
||||
</h1>
|
||||
<p class="text-desc">
|
||||
En application des mesures générales nécessaires pour faire face à l’épidémie de covid-19
|
||||
dans le cadre de l’état d’urgence sanitaire
|
||||
</p>
|
||||
|
||||
</div>
|
||||
</header>
|
||||
<main role="main">
|
||||
<p
|
||||
class="alert alert-danger d-none"
|
||||
role="alert"
|
||||
id="alert-facebook"
|
||||
></p>
|
||||
|
||||
<div class="wrapper">
|
||||
<form id="form-profile" accept-charset="UTF-8"></form>
|
||||
<p class="text-center mt-5">
|
||||
<button type="button" id="generate-btn" class="btn btn-primary btn-attestation"><span ><i class="fa fa-file-pdf inline-block mr-1"></i> Générer mon attestation</span></button>
|
||||
</p>
|
||||
|
||||
<div class="bg-primary d-none" id="snackbar">
|
||||
L'attestation est téléchargée sur votre appareil.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="">
|
||||
<p id="footnotes">
|
||||
<span id="footnote1">
|
||||
[1] Les personnes souhaitant bénéficier de l'une de ces exceptions doivent se munir s'il y a lieu, lors de leurs déplacements hors de leur domicile, d'un document leur permettant de justifier que le déplacement considéré entre dans le champ de l'une de ces exceptions.
|
||||
</span><br>
|
||||
<span id="footnote2">
|
||||
[2] A utiliser par les travailleurs non salariés, lorsqu'ils ne peuvent disposer d'un justificatif de déplacement établi par leur employeur.
|
||||
</span><br>
|
||||
<span id="footnote3">
|
||||
[3] Y compris les acquisitions à titre gratuit (distribution de denrées alimentaires...) et les déplacements liés à la perception de prestations sociales et au retrait d'espèces.
|
||||
</span><br>
|
||||
</p>
|
||||
<p class="github">
|
||||
Le code source de ce service est consultable sur <a href="https://github.com/LAB-MI/attestation-deplacement-derogatoire-q4-2020" class="github-link">GitHub</a>.
|
||||
</p>
|
||||
<p class="label-mi">
|
||||
Ministère de l'Intérieur - DNUM - SDIT
|
||||
</p>
|
||||
<picture class="center">
|
||||
<source srcset="/logo_dnum_dark.svg" media="(prefers-color-scheme: dark)">
|
||||
<img class="center" src="/logo_dnum.svg" alt="logo dnum">
|
||||
</picture>
|
||||
</div>
|
||||
</main>
|
||||
<footer role="contentinfo" class="main-footer">
|
||||
<div class="footer-links">
|
||||
<a href="./confidentialite.html" title="Confidentialité - nouvelle page" target="_blank" class="footer-line footer-link">Confidentialité</a>
|
||||
<a href="https://www.interieur.gouv.fr/Infos-du-site/Mentions-legales" title="Mentions légales - nouvelle page" target="_blank" class="footer-line footer-link">Mentions légales</a>
|
||||
<a href="https://www.gouvernement.fr/info-coronavirus" title="Information du gouvernement sur le Covid-19 - nouvelle page" target="_blank" class="footer-line footer-link">Informations du gouvernement sur le Covid-19</a>
|
||||
<div class="footer-line" >Plus d’infos au <a class="num-08" href="tel:0800130000" title="Numéro vert - appel gratuit depuis un poste fixe en France">0 800 130 000</a></div>
|
||||
<p class="footer-line" id="version"></p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<div class="alert alert-info d-none" id="update-alert">
|
||||
Une nouvelle version est disponible. Cliquer sur le bouton pour l'obtenir.
|
||||
<p class="text-right">
|
||||
<button id="reload-btn" class="btn btn-info">Mettre à jour</button>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<script src="./js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
35
src/js/check-updates.js
Normal file
|
@ -0,0 +1,35 @@
|
|||
import { $ } from './dom-utils'
|
||||
|
||||
// Ce fichier est généré au build par le plugin parcel-plugin-sw-cache
|
||||
const swName = '../sw.js'
|
||||
window.isUpdateAvailable = new Promise(function (resolve, reject) {
|
||||
// lazy way of disabling service workers while developing
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker
|
||||
.register(swName)
|
||||
.then((registration) => {
|
||||
registration.onupdatefound = () => {
|
||||
const installingWorker = registration.installing
|
||||
installingWorker.onstatechange = () => {
|
||||
switch (installingWorker.state) {
|
||||
case 'installed':
|
||||
if (navigator.serviceWorker.controller) {
|
||||
// new update available
|
||||
resolve(true)
|
||||
} else {
|
||||
// no update available
|
||||
resolve(false)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((err) => console.error('[SW ERROR]', err)) // eslint-disable-line no-console
|
||||
}
|
||||
})
|
||||
|
||||
window.isUpdateAvailable.then((isAvailable) => {
|
||||
$('#reload-btn').addEventListener('click', () => window.location.reload())
|
||||
$('#update-alert').classList.remove('d-none')
|
||||
})
|
2
src/js/confidentialite.js
Normal file
|
@ -0,0 +1,2 @@
|
|||
import 'bootstrap/dist/css/bootstrap.min.css'
|
||||
import '../css/main.css'
|
43
src/js/dom-utils.js
Normal file
|
@ -0,0 +1,43 @@
|
|||
export const $ = (...args) => document.querySelector(...args)
|
||||
export const $$ = (...args) => [...document.querySelectorAll(...args)]
|
||||
|
||||
const plainAttributes = [
|
||||
'for',
|
||||
'inputmode',
|
||||
'minlength',
|
||||
'maxlength',
|
||||
'min',
|
||||
'max',
|
||||
'pattern',
|
||||
]
|
||||
|
||||
export const createElement = (tag, attrs) => {
|
||||
const el = document.createElement(tag)
|
||||
plainAttributes.forEach(plainAttr => {
|
||||
if (attrs && plainAttr in attrs && attrs[plainAttr]) {
|
||||
el.setAttribute(plainAttr, attrs[plainAttr])
|
||||
}
|
||||
if (attrs) {
|
||||
delete attrs[plainAttr]
|
||||
}
|
||||
})
|
||||
Object.assign(el, attrs)
|
||||
return el
|
||||
}
|
||||
|
||||
export const appendTo = el => domNodes => {
|
||||
if (domNodes[Symbol.iterator]) {
|
||||
el.append(...domNodes)
|
||||
return
|
||||
}
|
||||
el.append(domNodes)
|
||||
}
|
||||
|
||||
export function downloadBlob (blob, fileName) {
|
||||
const link = createElement('a')
|
||||
const url = URL.createObjectURL(blob)
|
||||
link.href = url
|
||||
link.download = fileName
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
}
|
15
src/js/facebook-util.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { $ } from './dom-utils'
|
||||
|
||||
export function warnFacebookBrowserUserIfNecessary () {
|
||||
if (isFacebookBrowser()) {
|
||||
const alertFacebookElt = $('#alert-facebook')
|
||||
alertFacebookElt.value =
|
||||
"ATTENTION !! Vous utilisez actuellement le navigateur Facebook, ce générateur ne fonctionne pas correctement au sein de ce navigateur ! Merci d'ouvrir Chrome sur Android ou bien Safari sur iOS."
|
||||
alertFacebookElt.classList.remove('d-none')
|
||||
}
|
||||
}
|
||||
// see: https://stackoverflow.com/a/32348687/1513045
|
||||
function isFacebookBrowser () {
|
||||
const ua = navigator.userAgent || navigator.vendor || window.opera
|
||||
return ua.includes('FBAN') || ua.includes('FBAV')
|
||||
}
|
161
src/js/form-util.js
Normal file
|
@ -0,0 +1,161 @@
|
|||
import { $, $$, downloadBlob } from './dom-utils'
|
||||
import { addSlash, getFormattedDate } from './util'
|
||||
import pdfBase from '../certificate.pdf'
|
||||
import { generatePdf } from './pdf-util'
|
||||
|
||||
const conditions = {
|
||||
'#field-firstname': {
|
||||
length: 1,
|
||||
},
|
||||
'#field-lastname': {
|
||||
length: 1,
|
||||
},
|
||||
'#field-birthday': {
|
||||
pattern: /^([0][1-9]|[1-2][0-9]|30|31)\/([0][1-9]|10|11|12)\/(19[0-9][0-9]|20[0-1][0-9]|2020)/g,
|
||||
},
|
||||
'#field-placeofbirth': {
|
||||
length: 1,
|
||||
},
|
||||
'#field-address': {
|
||||
length: 1,
|
||||
},
|
||||
'#field-city': {
|
||||
length: 1,
|
||||
},
|
||||
'#field-zipcode': {
|
||||
pattern: /\d{5}/g,
|
||||
},
|
||||
'#field-datesortie': {
|
||||
pattern: /\d{4}-\d{2}-\d{2}/g,
|
||||
},
|
||||
'#field-heuresortie': {
|
||||
pattern: /\d{2}:\d{2}/g,
|
||||
},
|
||||
}
|
||||
|
||||
function validateAriaFields () {
|
||||
return Object.keys(conditions)
|
||||
.map((field) => {
|
||||
const fieldData = conditions[field]
|
||||
const pattern = fieldData.pattern
|
||||
const length = fieldData.length
|
||||
const isInvalidPattern = pattern && !$(field).value.match(pattern)
|
||||
const isInvalidLength = length && !$(field).value.length
|
||||
|
||||
const isInvalid = !!(isInvalidPattern || isInvalidLength)
|
||||
|
||||
$(field).setAttribute('aria-invalid', isInvalid)
|
||||
if (isInvalid) {
|
||||
$(field).focus()
|
||||
}
|
||||
return isInvalid
|
||||
})
|
||||
.includes(true)
|
||||
}
|
||||
|
||||
export function setReleaseDateTime (releaseDateInput) {
|
||||
const loadedDate = new Date()
|
||||
releaseDateInput.value = getFormattedDate(loadedDate)
|
||||
}
|
||||
|
||||
export function getProfile (formInputs) {
|
||||
const fields = {}
|
||||
for (const field of formInputs) {
|
||||
let value = field.value
|
||||
if (field.id === 'field-datesortie') {
|
||||
const dateSortie = field.value.split('-')
|
||||
value = `${dateSortie[2]}/${dateSortie[1]}/${dateSortie[0]}`
|
||||
}
|
||||
fields[field.id.substring('field-'.length)] = value
|
||||
}
|
||||
return fields
|
||||
}
|
||||
|
||||
export function getReasons (reasonInputs) {
|
||||
const reasons = reasonInputs
|
||||
.filter(input => input.checked)
|
||||
.map(input => input.value).join(', ')
|
||||
return reasons
|
||||
}
|
||||
|
||||
export function prepareInputs (formInputs, reasonInputs, reasonFieldset, reasonAlert, snackbar) {
|
||||
formInputs.forEach((input) => {
|
||||
const exempleElt = input.parentNode.parentNode.querySelector('.exemple')
|
||||
const validitySpan = input.parentNode.parentNode.querySelector('.validity')
|
||||
if (input.placeholder && exempleElt) {
|
||||
input.addEventListener('input', (event) => {
|
||||
if (input.value) {
|
||||
exempleElt.innerHTML = 'ex. : ' + input.placeholder
|
||||
validitySpan.removeAttribute('hidden')
|
||||
} else {
|
||||
exempleElt.innerHTML = ''
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
$('#field-birthday').addEventListener('keyup', function (event) {
|
||||
event.preventDefault()
|
||||
const input = event.target
|
||||
const key = event.keyCode || event.charCode
|
||||
if (key !== 8 && key !== 46) {
|
||||
input.value = addSlash(input.value)
|
||||
}
|
||||
})
|
||||
|
||||
reasonInputs.forEach(radioInput => {
|
||||
radioInput.addEventListener('change', function (event) {
|
||||
const isInError = reasonInputs.every(input => !input.checked)
|
||||
reasonFieldset.classList.toggle('fieldset-error', isInError)
|
||||
reasonAlert.classList.toggle('hidden', !isInError)
|
||||
})
|
||||
})
|
||||
|
||||
$('#generate-btn').addEventListener('click', async (event) => {
|
||||
event.preventDefault()
|
||||
|
||||
const reasons = getReasons(reasonInputs)
|
||||
if (!reasons) {
|
||||
reasonFieldset.classList.add('fieldset-error')
|
||||
reasonAlert.classList.remove('hidden')
|
||||
reasonFieldset.scrollIntoView && reasonFieldset.scrollIntoView()
|
||||
return
|
||||
}
|
||||
|
||||
const invalid = validateAriaFields()
|
||||
if (invalid) {
|
||||
return
|
||||
}
|
||||
|
||||
console.log(getProfile(formInputs), reasons)
|
||||
|
||||
const pdfBlob = await generatePdf(getProfile(formInputs), reasons, pdfBase)
|
||||
|
||||
const creationInstant = new Date()
|
||||
const creationDate = creationInstant.toLocaleDateString('fr-CA')
|
||||
const creationHour = creationInstant
|
||||
.toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' })
|
||||
.replace(':', '-')
|
||||
|
||||
downloadBlob(pdfBlob, `attestation-${creationDate}_${creationHour}.pdf`)
|
||||
|
||||
snackbar.classList.remove('d-none')
|
||||
setTimeout(() => snackbar.classList.add('show'), 100)
|
||||
|
||||
setTimeout(function () {
|
||||
snackbar.classList.remove('show')
|
||||
setTimeout(() => snackbar.classList.add('d-none'), 500)
|
||||
}, 6000)
|
||||
})
|
||||
}
|
||||
|
||||
export function prepareForm () {
|
||||
const formInputs = $$('#form-profile input')
|
||||
const snackbar = $('#snackbar')
|
||||
const reasonInputs = [...$$('input[name="field-reason"]')]
|
||||
const reasonFieldset = $('#reason-fieldset')
|
||||
const reasonAlert = reasonFieldset.querySelector('.msg-alert')
|
||||
const releaseDateInput = $('#field-datesortie')
|
||||
setReleaseDateTime(releaseDateInput)
|
||||
prepareInputs(formInputs, reasonInputs, reasonFieldset, reasonAlert, snackbar)
|
||||
}
|
159
src/js/form.js
Normal file
|
@ -0,0 +1,159 @@
|
|||
import 'bootstrap/dist/css/bootstrap.min.css'
|
||||
|
||||
import '../css/main.css'
|
||||
|
||||
import formData from '../form-data.json'
|
||||
|
||||
import { $, appendTo, createElement } from './dom-utils'
|
||||
|
||||
const createTitle = () => {
|
||||
const h2 = createElement('h2', { className: 'titre-2', innerHTML: 'Remplissez en ligne votre déclaration numérique : ' })
|
||||
const p = createElement('p', { className: 'msg-info', innerHTML: 'Tous les champs sont obligatoires.' })
|
||||
return [h2, p]
|
||||
}
|
||||
// createElement('div', { className: 'form-group' })
|
||||
|
||||
const createFormGroup = ({
|
||||
autocomplete = false,
|
||||
autofocus = false,
|
||||
inputmode,
|
||||
label,
|
||||
max,
|
||||
min,
|
||||
maxlength,
|
||||
minlength,
|
||||
name,
|
||||
pattern,
|
||||
placeholder = '',
|
||||
type = 'text',
|
||||
}) => {
|
||||
const formGroup = createElement('div', { className: 'form-group' })
|
||||
const labelAttrs = {
|
||||
for: `field-${name}`,
|
||||
id: `field-${name}-label`,
|
||||
innerHTML: label,
|
||||
}
|
||||
const labelEl = createElement('label', labelAttrs)
|
||||
|
||||
const inputGroup = createElement('div', { className: 'input-group align-items-center' })
|
||||
const inputAttrs = {
|
||||
autocomplete,
|
||||
autofocus,
|
||||
className: 'form-control',
|
||||
id: `field-${name}`,
|
||||
inputmode,
|
||||
min,
|
||||
max,
|
||||
minlength,
|
||||
maxlength,
|
||||
name,
|
||||
pattern,
|
||||
placeholder,
|
||||
required: true,
|
||||
type,
|
||||
}
|
||||
|
||||
const input = createElement('input', inputAttrs)
|
||||
|
||||
const validityAttrs = {
|
||||
className: 'validity',
|
||||
}
|
||||
const validity = createElement('span', validityAttrs)
|
||||
|
||||
const appendToFormGroup = appendTo(formGroup)
|
||||
appendToFormGroup(labelEl)
|
||||
appendToFormGroup(inputGroup)
|
||||
|
||||
const appendToInputGroup = appendTo(inputGroup)
|
||||
appendToInputGroup(input)
|
||||
appendToInputGroup(validity)
|
||||
|
||||
return formGroup
|
||||
}
|
||||
|
||||
const createReasonField = (reasonData) => {
|
||||
const formReasonAttrs = { className: 'form-checkbox align-items-center' }
|
||||
const formReason = createElement('div', formReasonAttrs)
|
||||
const appendToReason = appendTo(formReason)
|
||||
|
||||
const id = `checkbox-${reasonData.code}`
|
||||
const inputReasonAttrs = {
|
||||
className: 'form-check-input',
|
||||
type: 'checkbox',
|
||||
id,
|
||||
name: 'field-reason',
|
||||
value: reasonData.code,
|
||||
}
|
||||
const inputReason = createElement('input', inputReasonAttrs)
|
||||
|
||||
const labelAttrs = { innerHTML: reasonData.label, className: 'form-checkbox-label', for: id }
|
||||
const label = createElement('label', labelAttrs)
|
||||
|
||||
appendToReason([inputReason, label])
|
||||
return formReason
|
||||
}
|
||||
|
||||
const createReasonFieldset = (reasonsData) => {
|
||||
const fieldsetAttrs = {
|
||||
id: 'reason-fieldset',
|
||||
className: 'fieldset',
|
||||
}
|
||||
|
||||
const fieldset = createElement('fieldset', fieldsetAttrs)
|
||||
const appendToFieldset = appendTo(fieldset)
|
||||
|
||||
const legendAttrs = {
|
||||
className: 'legend titre 3 ',
|
||||
innerHTML: 'Choisissez un motif de déplacement',
|
||||
}
|
||||
const legend = createElement('legend', legendAttrs)
|
||||
|
||||
const textAlertAttrs = { className: 'msg-alert hidden', innerHTML: 'Veuillez choisir un motif' }
|
||||
const textAlert = createElement('p', textAlertAttrs)
|
||||
|
||||
const textSubscribeReasonAttrs = {
|
||||
innerHTML: `certifie que mon déplacement est lié au motif suivant (cocher la case) autorisé en application des
|
||||
mesures générales nécessaires pour faire face à l'épidémie de Covid19 dans le cadre de l'état
|
||||
d'urgence sanitaire <a class="footnote" id="footnote1" href="#footnote1">[1]</a> :`,
|
||||
}
|
||||
|
||||
const textSubscribeReason = createElement('p', textSubscribeReasonAttrs)
|
||||
|
||||
const reasonsFields = reasonsData.items.map(createReasonField)
|
||||
|
||||
appendToFieldset([legend, textAlert, textSubscribeReason, ...reasonsFields])
|
||||
// Créer un form-checkbox par motif
|
||||
return fieldset
|
||||
}
|
||||
|
||||
export function createForm () {
|
||||
const form = $('#form-profile')
|
||||
// Évite de recréer le formulaire s'il est déjà créé par react-snap (ou un autre outil de prerender)
|
||||
if (form.innerHTML !== '') {
|
||||
return
|
||||
}
|
||||
|
||||
const appendToForm = appendTo(form)
|
||||
|
||||
const formFirstPart = formData
|
||||
.flat(1)
|
||||
.filter(field => field.key !== 'reason')
|
||||
.filter(field => !field.isHidden)
|
||||
.map((field,
|
||||
index) => {
|
||||
const formGroup = createFormGroup({
|
||||
autofocus: index === 0,
|
||||
...field,
|
||||
name: field.key,
|
||||
})
|
||||
|
||||
return formGroup
|
||||
})
|
||||
|
||||
const reasonsData = formData
|
||||
.flat(1)
|
||||
.find(field => field.key === 'reason')
|
||||
|
||||
const reasonFieldset = createReasonFieldset(reasonsData)
|
||||
appendToForm([...createTitle(), ...formFirstPart, reasonFieldset])
|
||||
}
|
6
src/js/icons.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
import { library, dom } from '@fortawesome/fontawesome-svg-core'
|
||||
import { faFilePdf } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
library.add(faFilePdf)
|
||||
|
||||
dom.watch()
|
15
src/js/main.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
import 'bootstrap/dist/css/bootstrap.min.css'
|
||||
|
||||
import '../css/main.css'
|
||||
|
||||
import './icons'
|
||||
import './check-updates'
|
||||
import { prepareForm } from './form-util'
|
||||
import { warnFacebookBrowserUserIfNecessary } from './facebook-util'
|
||||
import { addVersion } from './util'
|
||||
import { createForm } from './form'
|
||||
|
||||
warnFacebookBrowserUserIfNecessary()
|
||||
createForm()
|
||||
prepareForm()
|
||||
addVersion(process.env.VERSION)
|
67
src/js/pdf-grid-helper.js
Normal file
|
@ -0,0 +1,67 @@
|
|||
import { PDFDocument, StandardFonts, rgb } from 'pdf-lib'
|
||||
|
||||
export async function generatePdf (e) {
|
||||
e.stopPropagation()
|
||||
|
||||
const pdfBase = document.getElementById('file').files[0]
|
||||
const buffer = await pdfBase.arrayBuffer()
|
||||
|
||||
const pdfDoc = await PDFDocument.load(buffer)
|
||||
|
||||
const pages = pdfDoc.getPages()
|
||||
|
||||
const page1 = pages[0]
|
||||
|
||||
const font = await pdfDoc.embedFont(StandardFonts.Helvetica)
|
||||
const drawText = (text, x, y, size = 11) => {
|
||||
page1.drawText(text, { x, y, size, font })
|
||||
}
|
||||
|
||||
let x
|
||||
let y
|
||||
for (x = 25; x < 1000; x += 25) {
|
||||
for (y = 25; y < 1000; y += 25) {
|
||||
drawText('.', {
|
||||
x: x,
|
||||
y: y,
|
||||
size: 11,
|
||||
font: font,
|
||||
color: rgb(0.95, 0.1, 0.1),
|
||||
})
|
||||
drawText(`${x}`, {
|
||||
x: x + 3,
|
||||
y: y,
|
||||
size: 7,
|
||||
font: font,
|
||||
color: rgb(0, 0, 0),
|
||||
})
|
||||
drawText(`${y}`, {
|
||||
x: x + 3,
|
||||
y: y - 6,
|
||||
size: 7,
|
||||
font: font,
|
||||
color: rgb(0, 0, 0),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pdfDoc.addPage()
|
||||
|
||||
const pdfBytes = await pdfDoc.save()
|
||||
|
||||
// Trigger the browser to download the PDF document
|
||||
|
||||
const pdfAsBlob = new Blob([pdfBytes], { type: 'application/pdf' })
|
||||
downloadBlob(pdfAsBlob, 'grid.pdf', 'application/pdf')
|
||||
}
|
||||
|
||||
function downloadBlob (blob, fileName) {
|
||||
const link = document.createElement('a')
|
||||
const url = URL.createObjectURL(blob)
|
||||
link.href = url
|
||||
link.download = fileName
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
}
|
||||
|
||||
document.querySelector('#file').addEventListener('change', generatePdf)
|
140
src/js/pdf-util.js
Normal file
|
@ -0,0 +1,140 @@
|
|||
import { generateQR } from './util'
|
||||
import { PDFDocument, StandardFonts } from 'pdf-lib'
|
||||
|
||||
const ys = {
|
||||
travail: 578,
|
||||
achats: 533,
|
||||
sante: 477,
|
||||
famille: 435,
|
||||
handicap: 396,
|
||||
sport_animaux: 358,
|
||||
convocation: 295,
|
||||
missions: 255,
|
||||
enfants: 211,
|
||||
}
|
||||
|
||||
export async function generatePdf (profile, reasons, pdfBase) {
|
||||
const creationInstant = new Date()
|
||||
const creationDate = creationInstant.toLocaleDateString('fr-FR')
|
||||
const creationHour = creationInstant
|
||||
.toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' })
|
||||
.replace(':', 'h')
|
||||
|
||||
const {
|
||||
lastname,
|
||||
firstname,
|
||||
birthday,
|
||||
placeofbirth,
|
||||
address,
|
||||
zipcode,
|
||||
city,
|
||||
datesortie,
|
||||
heuresortie,
|
||||
} = profile
|
||||
|
||||
const data = [
|
||||
`Cree le: ${creationDate} a ${creationHour}`,
|
||||
`Nom: ${lastname}`,
|
||||
`Prenom: ${firstname}`,
|
||||
`Naissance: ${birthday} a ${placeofbirth}`,
|
||||
`Adresse: ${address} ${zipcode} ${city}`,
|
||||
`Sortie: ${datesortie} a ${heuresortie}`,
|
||||
`Motifs: ${reasons}`,
|
||||
].join(';\n ')
|
||||
|
||||
const existingPdfBytes = await fetch(pdfBase).then((res) => res.arrayBuffer())
|
||||
|
||||
const pdfDoc = await PDFDocument.load(existingPdfBytes)
|
||||
|
||||
// set pdf metadata
|
||||
pdfDoc.setTitle('COVID-19 - Déclaration de déplacement')
|
||||
pdfDoc.setSubject('Attestation de déplacement dérogatoire')
|
||||
pdfDoc.setKeywords([
|
||||
'covid19',
|
||||
'covid-19',
|
||||
'attestation',
|
||||
'déclaration',
|
||||
'déplacement',
|
||||
'officielle',
|
||||
'gouvernement',
|
||||
])
|
||||
pdfDoc.setProducer('DNUM/SDIT')
|
||||
pdfDoc.setCreator('')
|
||||
pdfDoc.setAuthor("Ministère de l'intérieur")
|
||||
|
||||
const page1 = pdfDoc.getPages()[0]
|
||||
|
||||
const font = await pdfDoc.embedFont(StandardFonts.Helvetica)
|
||||
const drawText = (text, x, y, size = 11) => {
|
||||
page1.drawText(text, { x, y, size, font })
|
||||
}
|
||||
|
||||
drawText(`${firstname} ${lastname}`, 119, 696)
|
||||
drawText(birthday, 119, 674)
|
||||
drawText(placeofbirth, 297, 674)
|
||||
drawText(`${address} ${zipcode} ${city}`, 133, 652)
|
||||
|
||||
reasons
|
||||
.split(', ')
|
||||
.forEach(reason => {
|
||||
drawText('x', 84, ys[reason], 18)
|
||||
})
|
||||
|
||||
let locationSize = getIdealFontSize(font, profile.city, 83, 7, 11)
|
||||
|
||||
if (!locationSize) {
|
||||
alert(
|
||||
'Le nom de la ville risque de ne pas être affiché correctement en raison de sa longueur. ' +
|
||||
'Essayez d\'utiliser des abréviations ("Saint" en "St." par exemple) quand cela est possible.',
|
||||
)
|
||||
locationSize = 7
|
||||
}
|
||||
|
||||
drawText(profile.city, 105, 177, locationSize)
|
||||
drawText(`${profile.datesortie}`, 91, 153, 11)
|
||||
drawText(`${profile.heuresortie}`, 264, 153, 11)
|
||||
|
||||
// const shortCreationDate = `${creationDate.split('/')[0]}/${
|
||||
// creationDate.split('/')[1]
|
||||
// }`
|
||||
// drawText(shortCreationDate, 314, 189, locationSize)
|
||||
|
||||
// // Date création
|
||||
// drawText('Date de création:', 479, 130, 6)
|
||||
// drawText(`${creationDate} à ${creationHour}`, 470, 124, 6)
|
||||
|
||||
const generatedQR = await generateQR(data)
|
||||
|
||||
const qrImage = await pdfDoc.embedPng(generatedQR)
|
||||
|
||||
page1.drawImage(qrImage, {
|
||||
x: page1.getWidth() - 156,
|
||||
y: 100,
|
||||
width: 92,
|
||||
height: 92,
|
||||
})
|
||||
|
||||
pdfDoc.addPage()
|
||||
const page2 = pdfDoc.getPages()[1]
|
||||
page2.drawImage(qrImage, {
|
||||
x: 50,
|
||||
y: page2.getHeight() - 350,
|
||||
width: 300,
|
||||
height: 300,
|
||||
})
|
||||
|
||||
const pdfBytes = await pdfDoc.save()
|
||||
|
||||
return new Blob([pdfBytes], { type: 'application/pdf' })
|
||||
}
|
||||
|
||||
function getIdealFontSize (font, text, maxWidth, minSize, defaultSize) {
|
||||
let currentSize = defaultSize
|
||||
let textWidth = font.widthOfTextAtSize(text, defaultSize)
|
||||
|
||||
while (textWidth > maxWidth && currentSize > minSize) {
|
||||
textWidth = font.widthOfTextAtSize(text, --currentSize)
|
||||
}
|
||||
|
||||
return textWidth > maxWidth ? null : currentSize
|
||||
}
|
35
src/js/util.js
Normal file
|
@ -0,0 +1,35 @@
|
|||
import QRCode from 'qrcode'
|
||||
|
||||
export function generateQR (text) {
|
||||
const opts = {
|
||||
errorCorrectionLevel: 'M',
|
||||
type: 'image/png',
|
||||
quality: 0.92,
|
||||
margin: 1,
|
||||
}
|
||||
return QRCode.toDataURL(text, opts)
|
||||
}
|
||||
|
||||
export function pad2Zero (str) {
|
||||
return String(str).padStart(2, '0')
|
||||
}
|
||||
|
||||
export function getFormattedDate (date) {
|
||||
const year = date.getFullYear()
|
||||
const month = pad2Zero(date.getMonth() + 1) // Les mois commencent à 0
|
||||
const day = pad2Zero(date.getDate())
|
||||
return `${year}-${month}-${day}`
|
||||
}
|
||||
|
||||
export function addSlash (str) {
|
||||
return str
|
||||
.replace(/^(\d{2})$/g, '$1/')
|
||||
.replace(/^(\d{2})\/(\d{2})$/g, '$1/$2/')
|
||||
.replace(/\/\//g, '/')
|
||||
}
|
||||
|
||||
export function addVersion (version) {
|
||||
document.getElementById(
|
||||
'version',
|
||||
).innerHTML = `${new Date().getFullYear()} - ${version}`
|
||||
}
|
1
src/logo_dnum.svg
Normal file
After Width: | Height: | Size: 30 KiB |
1
src/logo_dnum_dark.svg
Normal file
After Width: | Height: | Size: 23 KiB |
1
src/robots.txt
Normal file
|
@ -0,0 +1 @@
|
|||
Sitemap: https://media.interieur.gouv.fr/attestation-couvre-feu-covid-19/sitemap.xml
|
27
src/sitemap.xml
Normal file
|
@ -0,0 +1,27 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset
|
||||
xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9
|
||||
http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
|
||||
<!-- created with Free Online Sitemap Generator www.xml-sitemaps.com -->
|
||||
|
||||
|
||||
<url>
|
||||
<loc>https://media.interieur.gouv.fr/attestation-couvre-feu-covid-19/</loc>
|
||||
<lastmod>2020-04-06T04:22:03+00:00</lastmod>
|
||||
<priority>1.00</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://media.interieur.gouv.fr/attestation-couvre-feu-covid-19/confidentialite.html</loc>
|
||||
<lastmod>2020-04-06T04:22:03+00:00</lastmod>
|
||||
<priority>0.80</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://media.interieur.gouv.fr/attestation-couvre-feu-covid-19/index.html</loc>
|
||||
<lastmod>2020-04-06T04:22:03+00:00</lastmod>
|
||||
<priority>0.64</priority>
|
||||
</url>
|
||||
|
||||
|
||||
</urlset>
|