This commit is contained in:
LAB-MI 2020-04-06 18:27:49 +02:00
commit 40251245d2
No known key found for this signature in database
GPG Key ID: C18B2EFCB212E65B
31 changed files with 13417 additions and 0 deletions

10
.babelrc Normal file
View File

@ -0,0 +1,10 @@
{
"presets": [
"@babel/preset-env"
],
"plugins": [
["@babel/plugin-transform-runtime", {
"regenerator": true
}]
]
}

14
.editorconfig Normal file
View File

@ -0,0 +1,14 @@
root = true
[*]
charset = utf-8
trim_trailing_whitespace = false
insert_final_newline = true
[*.html]
indent_style = space
indent_size = 4
[*.{js,json,yml}]
indent_style = space
indent_size = 2

32
.eslintrc.js Normal file
View File

@ -0,0 +1,32 @@
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"],
},
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
}
}
]
};

187
.gitignore vendored Normal file
View File

@ -0,0 +1,187 @@
# Created by https://www.gitignore.io/api/node,code,linux,macos,windows
# Edit at https://www.gitignore.io/?templates=node,code,linux,macos,windows
### Code ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
### 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
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
# next.js build output
.next
# nuxt.js build output
.nuxt
# rollup.js default build output
dist/
# Uncomment the public line if your project uses Gatsby
# https://nextjs.org/blog/next-9-1#public-directory-support
# https://create-react-app.dev/docs/using-the-public-folder/#docsNav
# public
# Storybook build outputs
.out
.storybook-out
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# Temporary folders
tmp/
temp/
### 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.gitignore.io/api/node,code,linux,macos,windows

19
LICENCE Normal file
View File

@ -0,0 +1,19 @@
Copyright (c) Ministère de l'intérieur
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.

35
MIN_Interieur_RVB.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 16 KiB

31
README.md Normal file
View File

@ -0,0 +1,31 @@
# Générateur de certificat de déplacement
## Développer
### Installer le projet
```console
$ git clone https://github.com/lab-mi/deplacement-codiv-19.git
$ cd deplacement-codiv-19
$ npm i
$ npm start
```
### Générer le code de production
```console
$ npm run build
```
Le code à déployer sera dans le dossier `dist`
## Crédits
Ce projet a été 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)

257
certificate.js Normal file
View File

@ -0,0 +1,257 @@
import 'bootstrap/dist/css/bootstrap.min.css'
import { PDFDocument, StandardFonts } from 'pdf-lib'
import QRCode from 'qrcode'
import pdfBase from './certificate.pdf'
import './main.css'
const $ = (...args) => document.querySelector(...args)
const $$ = (...args) => [...document.querySelectorAll(...args)]
var year, month, day
const generateQR = async text => {
try {
var opts = {
errorCorrectionLevel: 'M',
type: 'image/png',
quality: 0.92,
margin: 1,
}
return await QRCode.toDataURL(text, opts)
} catch (err) {
console.error(err)
}
}
function pad (str) {
return String(str).padStart(2, '0')
}
function setDateNow (date) {
year = date.getFullYear()
month = pad(date.getMonth() + 1) // Les mois commencent à 0
day = pad(date.getDate())
}
document.addEventListener('DOMContentLoaded', setReleaseDateTime)
function setReleaseDateTime () {
const loadedDate = new Date()
setDateNow(loadedDate)
const releaseDateInput = document.querySelector('#field-datesortie')
releaseDateInput.value = `${year}-${month}-${day}`
const hour = pad(loadedDate.getHours())
const minute = pad(loadedDate.getMinutes())
const releaseTimeInput = document.querySelector('#field-heuresortie')
releaseTimeInput.value = `${hour}:${minute}`
}
function saveProfile () {
for (const field of $$('#form-profile input')) {
if (field.id === 'field-datesortie') {
var dateSortie = field.value.split('-')
localStorage.setItem(field.id.substring('field-'.length), `${dateSortie[2]}/${dateSortie[1]}/${dateSortie[0]}`)
} else {
localStorage.setItem(field.id.substring('field-'.length), field.value)
}
}
}
function getProfile () {
const fields = {}
for (let i = 0; i < localStorage.length; i++) {
const name = localStorage.key(i)
fields[name] = localStorage.getItem(name)
}
return fields
}
function idealFontSize (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
}
async function generatePdf (profile, reasons) {
const generatedDate = new Date()
setDateNow(generatedDate)
const creationDate = `${day}/${month}/${year}`
const hour = pad(generatedDate.getHours())
const minute = pad(generatedDate.getMinutes())
const creationHour = `${hour}h${minute}`
const { lastname, firstname, birthday, lieunaissance, address, zipcode, town, datesortie, heuresortie } = profile
const releaseHours = String(heuresortie).substring(0, 2)
const releaseMinutes = String(heuresortie).substring(3, 5)
const data = [
`Cree le: ${creationDate} a ${creationHour}`,
`Nom: ${lastname}`,
`Prenom: ${firstname}`,
`Naissance: ${birthday} a ${lieunaissance}`,
`Adresse: ${address} ${zipcode} ${town}`,
`Sortie: ${datesortie} a ${releaseHours}h${releaseMinutes}`,
`Motifs: ${reasons}`,
].join('; ')
const existingPdfBytes = await fetch(pdfBase).then(res => res.arrayBuffer())
const pdfDoc = await PDFDocument.load(existingPdfBytes)
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}`, 123, 686)
drawText(birthday, 123, 661)
drawText(lieunaissance, 92, 638)
drawText(`${address} ${zipcode} ${town}`, 134, 613)
if (reasons.includes('travail')) {
drawText('x', 76, 527, 19)
}
if (reasons.includes('courses')) {
drawText('x', 76, 478, 19)
}
if (reasons.includes('sante')) {
drawText('x', 76, 436, 19)
}
if (reasons.includes('famille')) {
drawText('x', 76, 400, 19)
}
if (reasons.includes('sport')) {
drawText('x', 76, 345, 19)
}
if (reasons.includes('judiciaire')) {
drawText('x', 76, 298, 19)
}
if (reasons.includes('missions')) {
drawText('x', 76, 260, 19)
}
let locationSize = idealFontSize(font, profile.town, 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.town, 111, 226, locationSize)
if (reasons !== '') {
// Date sortie
drawText(`${profile.datesortie}`, 92, 200)
drawText(releaseHours, 200, 201)
drawText(releaseMinutes, 220, 201)
}
// Date création
drawText('Date de création:', 464, 150, 7)
drawText(`${creationDate} à ${creationHour}`, 455, 144, 7)
const generatedQR = await generateQR(data)
const qrImage = await pdfDoc.embedPng(generatedQR)
page1.drawImage(qrImage, {
x: page1.getWidth() - 170,
y: 155,
width: 100,
height: 100,
})
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 downloadBlob (blob, fileName) {
const link = document.createElement('a')
var url = URL.createObjectURL(blob)
link.href = url
link.download = fileName
document.body.appendChild(link)
link.click()
}
function getAndSaveReasons () {
const values = $$('input[name="field-reason"]:checked')
.map(x => x.value)
.join('-')
localStorage.setItem('reasons', values)
return values
}
// see: https://stackoverflow.com/a/32348687/1513045
function isFacebookBrowser () {
const ua = navigator.userAgent || navigator.vendor || window.opera
return ua.includes('FBAN') || ua.includes('FBAV')
}
if (isFacebookBrowser()) {
$('#alert-facebook').classList.remove('d-none')
}
function addSlash () {
this.value = this.value.replace(/^(\d{2})$/g, '$1/')
this.value = this.value.replace(/^(\d{2})\/(\d{2})$/g, '$1/$2/')
}
$('#field-birthday').addEventListener('keyup', addSlash)
const snackbar = $('#snackbar')
$('#form-profile').addEventListener('submit', async event => {
event.preventDefault()
saveProfile()
const reasons = getAndSaveReasons()
const pdfBlob = await generatePdf(getProfile(), reasons)
downloadBlob(pdfBlob, 'attestation.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)
})
$$('input').forEach(input => {
const exempleElt = input.parentNode.parentNode.querySelector('.exemple')
if (input.placeholder && exempleElt) {
input.addEventListener('input', (event) => {
if (input.value) {
exempleElt.innerHTML = 'ex.&nbsp;: ' + input.placeholder
} else {
exempleElt.innerHTML = ''
}
})
}
})
function addVersion () {
document.getElementById('version').innerHTML = `${new Date().getFullYear()} - ${process.env.VERSION}`
}
addVersion()

BIN
certificate.pdf Normal file

Binary file not shown.

188
confidentialite.html Normal file
View File

@ -0,0 +1,188 @@
<!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">
<img class="logo" src="/MIN_Interieur_RVB.svg" alt="logo_ministère_interieur" />
<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&nbsp;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 dattestation de
déplacement ne font lobjet daucune collecte par le ministère
de lIntérieur. Ces données personnelles<sup>1</sup> sont
exclusivement stockées dans le terminal (ordinateur, tablette,
smartphone) utilisé pour générer lattestation.
</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 dun 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
dattestation de déplacement :
</p>
<table class="cookies">
<thead>
<tr class="header-row">
<th>Nom du Cookie</th>
<th>Finalité</th>
<th>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 laccè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 dun client sont envoyées au même serveur
dorigine.
</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 didentifier 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
didentifier 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 loption « 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 longlet « 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 dinformations sur le sujet, nhé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-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-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-link">Informations du
gouvernement sur le Covid-19</a>
<p class="footer-link">
Plus dinfos au<a class="num-08" href="tel:0800130000">
0 800 130 000</a>
</p>
</div>
</footer>
<script src="./confidentialite.js"></script>
</body>
</html>

2
confidentialite.js Normal file
View File

@ -0,0 +1,2 @@
import 'bootstrap/dist/css/bootstrap.min.css'
import './main.css'

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View 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
favicons/favicon-16x16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 613 B

BIN
favicons/favicon-32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
favicons/mstile-150x150.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View 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

19
favicons/site.webmanifest Normal file
View File

@ -0,0 +1,19 @@
{
"name": "",
"short_name": "",
"icons": [
{
"src": "android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

273
index.html Normal file
View File

@ -0,0 +1,273 @@
<!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 lattestation déplacement covid-19 à présenter aux forces de sécurité lors dun contrôle.">
<meta name="keywords" content="covid19, covid-19, attestation, 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 lattestation déplacement covid-19 à présenter aux forces de sécurité lors dun contrôle." />
<link rel="canonical" href="https://media.interieur.gouv.fr/deplacement-covid-19/" />
<meta property="og:url" content="https://media.interieur.gouv.fr/deplacement-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 lattestation déplacement covid-19 à présenter aux forces de sécurité lors dun 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>COVID-19 Générateur d'attestation de déplacement dérogatoire</title>
</head>
<body>
<header class="wrapper">
<img class="logo" src="/MIN_Interieur_RVB.svg" alt="">
<div>
<h1 class="flex flex-wrap">
<span class="covid-title">
COVID-19
</span>
<span class="covid-subtitle">
Générateur d'attestation de&nbsp;déplacement&nbsp;dérogatoire
</span>
</h1>
<p class="text-alert">
Les données saisies sont stockées exclusivement sur votre téléphone ou votre ordinateur. Aucune information n'est collectée par le Ministère de l'Intérieur.
L'attestation pdf générée contient un QR Code. Ce code-barres graphique permet de lire les informations portées dans votre attestation au moment de leur saisie.
Il peut être déchiffré à l'aide de tout type de lecteur de QR code générique.
</p>
</div>
</header>
<div class="wrapper">
<form id="form-profile">
<h2 class="titre-2">Remplissez en ligne votre attestation numérique :</h2>
<div class="form-group">
<label for="field-firstname" id="field-firstname-label">Prénom</label>
<div class="input-group align-items-center">
<input
type="text"
class="form-control"
id="field-firstname"
name="firstname"
autocomplete="given-name"
placeholder="Jean"
aria-labelledby="field-firstname-label"
required
autofocus
>
<span class="validity"></span>
</div>
<p class="exemple"></p>
</div>
<div class="form-group">
<label for="field-lastname" id="field-lastname-label">Nom</label>
<div class="input-group align-items-center">
<input
type="text"
class="form-control"
id="field-lastname"
name="lastname"
autocomplete="family-name"
placeholder="Dupont"
aria-labelledby="field-lastname-label"
required
autofocus
>
<span class="validity"></span>
</div>
<p class="exemple"></p>
</div>
<div class="form-group">
<label for="field-birthday" id="field-birthday-label">Date de naissance (au format jj/mm/aaaa)</label>
<div class="input-group align-items-center">
<input
type="text"
inputmode="numeric"
class="form-control"
id="field-birthday"
name="birthday"
autocomplete="bday"
placeholder="01/01/1970"
maxlength="10"
aria-labelledby="field-birthday-label"
required
>
<span class="validity"></span>
</div>
<p class="exemple"></p>
</div>
<div class="form-group">
<label for="field-lieunaissance" id="field-lieunaissance-label">Lieu de naissance</label>
<div class="input-group align-items-center">
<input
type="text"
class="form-control"
id="field-lieunaissance"
name="lieunaissance"
aria-labelledby="field-lieunaissance-label"
placeholder="Lyon"
required
>
<span class="validity"></span>
</div>
<p class="exemple"></p>
</div>
<div class="form-group">
<label for="field-address" id="field-address-label">Adresse</label>
<div class="input-group align-items-center">
<input
type="text"
class="form-control"
id="field-address"
name="address"
autocomplete="address-line1"
aria-labelledby="field-address-label"
placeholder="999 avenue de france"
required
>
<span class="validity"></span>
</div>
<p class="exemple"></p>
</div>
<div class="form-group">
<label for="field-town" id="field-town-label">Ville</label>
<div class="input-group align-items-center">
<input
type="text"
class="form-control"
id="field-town"
name="town"
autocomplete="address-level1"
aria-labelledby="field-town-label"
placeholder="Paris"
required
>
<span class="validity"></span>
</div>
<p class="exemple"></p>
</div>
<div class="form-group">
<label for="field-zipcode" id="field-zipcode-label" >Code Postal</label>
<div class="input-group align-items-center">
<input
type="number"
min="00000"
max="99999"
class="form-control"
id="field-zipcode"
name="zipcode"
autocomplete="zipcode"
minlength="4"
maxlength="5"
aria-labelledby="field-zipcode-label"
placeholder="75001"
required
>
<span class="validity"></span>
</div>
<p class="exemple"></p>
</div>
<h3>Choisissez un motif de sortie</h3>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="field-reason" id="checkbox-travail" value="travail">
<label class="form-check-label" for="checkbox-travail">Déplacements entre le domicile et le lieu dexercice de lactivité professionnelle, lorsqu'ils sont indispensables à l'exercice dactivités ne pouvant être organisées sous forme de télétravail ou déplacements professionnels ne pouvant être différés.</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="field-reason" id="checkbox-courses" value="courses">
<label class="form-check-label" for="checkbox-courses">Déplacements pour effectuer des achats de fournitures nécessaires à lactivité professionnelle et des achats de première nécessité dans des établissements dont les activités demeurent autorisées <a href="https://www.service-public.fr/particuliers/actualites/A13921" target="_blank">(liste sur gouvernement.fr)</a>.</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="field-reason" id="checkbox-sante" value="sante">
<label class="form-check-label" for="checkbox-sante">Consultations et soins ne pouvant être assurés à distance et ne pouvant être différés ; consultations et soins des patients atteints d'une affection de longue durée.</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="field-reason" id="checkbox-famille" value="famille">
<label class="form-check-label" for="checkbox-famille">Déplacements pour motif familial impérieux, pour lassistance aux personnes vulnérables ou la garde denfants.</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="field-reason" id="checkbox-sport" value="sport">
<label class="form-check-label" for="checkbox-sport">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.</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="field-reason" id="checkbox-judiciaire" value="judiciaire">
<label class="form-check-label" for="checkbox-judiciaire">Convocation judiciaire ou administrative.</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="field-reason" id="checkbox-missions" value="missions">
<label class="form-check-label" for="checkbox-missions">Participation à des missions dintérêt général sur demande de lautorité administrative.</label>
</div>
<div class="form-group">
<label for="field-datesortie">Date de sortie</label>
<div class="input-group align-items-center">
<input type="date" class="form-control" id="field-datesortie" name="datesortie" placeholder="JJ/MM/YYYY" required>
<span class="validity"></span>
</div>
</div>
<div class="form-group">
<label for="field-heure">Heure de sortie</label>
<div class="input-group align-items-center">
<input type="time" class="form-control" id="field-heuresortie" name="heure" required>
<span class="validity"></span>
</div>
</div>
<p class="text-center mt-5">
<button type="submit" class="btn btn-primary btn-attestation"> <span class="btn-text">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>
</form>
</div>
<div class="">
<p class="label-mi">
Ministère de l'Intérieur - DNUM - SDIT
</p>
<img class="center" src="/logo_dnum.svg" alt="logo dnum">
</div>
<footer class="main-footer">
<div class="footer-links">
<a href="./confidentialite.html" title="Confidentialité - nouvelle page" target="_blank" class="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-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-link">Informations du gouvernement sur le Covid-19</a>
<div class="footer-link" >Plus dinfos au<a class="num-08" href="tel:0800130000"> 0 800 130 000</a></div>
<p class="footer-link" id="version"></p>
</div>
</footer>
<div class="alert alert-danger d-none" role="alert" id="alert-facebook">
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.
</div>
<script src="./certificate.js"></script>
</body>
</html>

1697
logo_dnum.svg Normal file

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 49 KiB

367
main.css Normal file
View File

@ -0,0 +1,367 @@
@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;
}
body {
margin: 20px;
}
form, #alert-print {
margin: 30px auto;
max-width: 400px;
}
h1 {
font-size: 2em;
}
/* 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;
}
h2 {
font-size: large;
}
footer {
margin-top: 40px;
}
canvas {
border: 1px solid #ced4da;
border-radius: .25rem;
}
.wrapper {
max-width: 800px;
margin-left: auto;
margin-right: auto;
}
#form-profile .form-check {
margin-bottom: 1rem;
}
#form-profile .form-check-label {
font-weight: 400;
transform: translateY(-2px);
}
#form-generate .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;
}
::placeholder {
font-style: italic;
}
.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 (min-width: 992px) {
.covid-title {
flex-grow: 0;
}
}
.covid-subtitle {
display: flex;
align-items: center;
font-size: 0.40em;
padding: 0.3em;
background-color: #e1000f;
text-transform:uppercase ;
color: #ffffff;
flex-grow: 1;
}
.text-alert{
text-align: left;
color: #000000;
}
.btn-attestation {
padding: 0.8em;
font-size: 1.2em;
font-weight: bold;
color: #ffffff;
background-color: #000191;
border-radius: 0.5em;
}
.github {
font-size: 0.7em;
text-align: center;
color: #000000;
}
.github-link {
color: #000191;
}
.label-mi {
text-align: center;
font-size: 1em;
color: #000000
}
.main-footer {
display: flex;
justify-content: center;
padding-top: 3em;
padding-bottom: 3em;
background-color:#000000;
color: white;
}
.footer-links {
margin: 0 auto;
max-width: 500px;
}
.footer-link {
display: block;
margin: 0.75em;
font-size: 0.9em;
color: #ffffff;
}
.footer-link:hover {
text-decoration: none;
color: #ffffff;
}
.footer-link:focus {
color: #ffffff;
}
.num-08 {
font-weight: bold;
color: #00a94f;
}
.confidentialite {
.cookies {
border-collapse: collapse;
td,
th {
border: 1px solid #000191;
padding: 8px;
}
.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;
}
}
.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 (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;
}
.center {
display: block;
margin-left: auto;
margin-right: auto;
}

10171
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

56
package.json Normal file
View File

@ -0,0 +1,56 @@
{
"name": "deplacement-covid-19",
"version": "0.0.1",
"description": "Générateur d'attestation de déplacement dérogatoire'",
"main": "certificate.js",
"scripts": {
"lint": "eslint ./*.js",
"start": "VERSION=${VERSION:-localversion} parcel index.html robots.txt",
"prebuild": "npm run lint && rimraf dist",
"build": "PUBLIC_URL=${PUBLIC_URL:-/deplacement-covid-19} ; parcel build --public-url ${PUBLIC_URL} index.html robots.txt",
"postbuild": "PUBLIC_URL=${PUBLIC_URL:-/deplacement-covid-19} react-snap"
},
"repository": {
"type": "git",
"url": "git+https://github.com/lab-mi/deplacement-codiv-19"
},
"keywords": [],
"author": "",
"license": "MIT",
"bugs": {
"url": "https://github.com/lab-mi/deplacement-covid-19/issues"
},
"devDependencies": {
"@babel/core": "^7.9.0",
"@babel/plugin-transform-runtime": "^7.9.0",
"babel-eslint": "^10.1.0",
"eslint": "^6.8.0",
"eslint-config-standard": "^14.1.1",
"eslint-plugin-import": "^2.20.1",
"eslint-plugin-node": "^11.0.0",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.1",
"parcel-bundler": "^1.12.4",
"postcss-current-selector": "0.0.3",
"postcss-nested": "^4.2.1",
"postcss-nested-ancestors": "^2.0.0",
"postcss-preset-env": "^6.7.0",
"react-snap": "^1.23.0",
"rimraf": "^3.0.2"
},
"dependencies": {
"bootstrap": "^4.4.1",
"pdf-lib": "^1.4.1",
"qrcode": "^1.4.4"
},
"browserslist": [
"last 5 versions"
],
"reactSnap": {
"source": "dist",
"minifyHtml": {
"collapseWhitespace": false,
"removeComments": false
}
}
}

11
postcss.config.js Normal file
View File

@ -0,0 +1,11 @@
module.exports = {
plugins: {
autoprefixer: {
grid: true,
},
'postcss-preset-env': {},
'postcss-nested-ancestors': {},
'postcss-nested': {},
'postcss-current-selector': {},
},
}

1
robots.txt Normal file
View File

@ -0,0 +1 @@
User-agent: *