Compare commits

...

2 Commits

Author SHA1 Message Date
LAB-MI 6a5f269560 Automated Release v1.0.1
Please find changes in CHANGELOG.md file
2020-04-16 12:57:25 +00:00
LAB-MI 86b15a4d6e Automated Release v1.0.0
Please find changes in CHANGELOG.md file
2020-04-16 08:15:53 +00:00
25 changed files with 1267 additions and 283 deletions

View File

@ -9,6 +9,7 @@ module.exports = {
"no-console": process.env.NODE_ENV === "production" ? "error" : "off", "no-console": process.env.NODE_ENV === "production" ? "error" : "off",
"no-debugger": process.env.NODE_ENV === "production" ? "error" : "off", "no-debugger": process.env.NODE_ENV === "production" ? "error" : "off",
"comma-dangle": [2, "always-multiline"], "comma-dangle": [2, "always-multiline"],
"no-var": 2,
}, },
overrides: [ overrides: [
{ {

View File

@ -1,45 +1,5 @@
# Changelog # Changelog
## [1.0.13] - 2020-04-06 ## [1.0.0] - 2020-04-15
### Added ### Added
- first public release - first public release
## [1.1.0] - 2020-04-08
### Added
- (#21, #23, #25, #26, #27, #28, #29) :wheelchair: improve A11Y support
- adds github links in footer
- (#19) clear localstorage
- prevent sending personal data
- 💚 Publish html as workflow asset and code in public repository
- Add sitemap and tell robots.txt to scan the sitemap
- Add Changelog
### Changes
- (#18) 📝 Update header for reasons section
- (#3) ✏ Replace codiv by covid in package.json, README
- changes start target
- (#1) 🐛 Autocomplete birthday field only on keyup, not when deleting
- (#1) 🎨 Improve regex control pattern for birhtday
- :recycle: Move check update code to dedicated file
- :recycle: Move dom utils in dedicated file
- :sparkles: Notify user about newer version
- :sparkles: Add cache with service workers
- :recycle: Cleanup directory structure
- :heavy_plus_sign: Avoid using env specific syntax in npm scripts
- changes PWA display
## [1.1.1] - 2020-04-09
### Fixed
- :wrench: Fix parcel-plugin-sw-cache configuration
### Added
- :pencil: add CONTRIBUTORS
### Changes
- :pencil: change LICENCE ( #16 )
## [1.1.2] - 2020-04-10
### Added
- ♿ Increase contrast ratio hover link color (#29)
- ♿ Add landmarks roles to header, main and footer sections (#43)
- ♿ Add title to open stores link and add more explicit text (#44, #45)
- ♿ Expose aria-invalid field assistance technologies (#24)
- ✨ Add timestamp to generated pdf (#9)
- 💄 Don't use autocompletion for release date and time (#31)
- 💄 Add underline on hover links
### Fixes
- Fix Typo site.webmanifest (#52)

View File

@ -1,20 +1,13 @@
Ce générateur d'attestation de déplacement dérogatoire a été mis en place dans le cadre du confinement lié à la pandémie du virus COVID-19 de 2020. Ce générateur d'attestation de déplacement international dérogatoire a été mis en place dans le cadre du confinement lié à la pandémie du virus COVID-19 de 2020.
Ce service repose sur l'utilisation initale du projet covid-19-certificate <https://github.com/nesk/covid-19-certificate> de Johann Pardanaud <https://github.com/nesk>. Ce service repose sur l'utilisation initale du projet covid-19-certificate <https://github.com/nesk/covid-19-certificate> de Johann Pardanaud <https://github.com/nesk> et de l'attestation de déplacement nationale dérogatoire <https://github.com/LAB-MI/deplacement-covid-19> du ministère de l'intérieur.
Il a été enrichi par l'incubateur du ministère de l'intérieur : le LAB-MI <https://beta.interieur.gouv.fr>. Il a été enrichi par l'incubateur du ministère de l'intérieur : le LAB-MI <https://beta.interieur.gouv.fr>.
La liste qui suit mentionne les différents participants ayant contribué à rendre ce service utile à la fois à la population et aux forces de l'ordre : La liste qui suit mentionne les différents participants ayant contribué à rendre ce service utile à la fois à la population et aux forces de l'ordre :
Johann Pardanaud (https://github.com/nesk)
La liste qui suit mentionne les différents participants ayant contribué à rendre ce service utile à la fois à la population et aux forces de l'ordre :
Philippe Bron (https://github.com/PhilippeBron) Philippe Bron (https://github.com/PhilippeBron)
Cristian (https://github.com/cristianpb) Cristian (https://github.com/cristianpb)
Stanislas Ormières (https://github.com/laruiss) Victor Journé (https://github.com/victorjourne)
Caroline Robillard (https://github.com/Carolinedanslesnuages) Philippe (https://github.com/pli01)
Joel Pagniez (https://github.com/JoelPagniez)
Sophie GUERLAIS
Philippe (https://github.com/pli01)
Matthieu Bacconnier (https://github.com/Neamar)
Hugo Cartigny (https://github.com/BlueskyFR)
Sébastien Touzé (https://github.com/SebastienTouze)
John Livingston (https://github.com/JohnXLivingston)
David Libeau (https://github.com/DavidLibeau)
Arnaud Delafosse (https://github.com/ArnaudDelafosse)

View File

@ -1,4 +1,4 @@
# Générateur de certificat de déplacement # Générateur: ATTESTATION DE DÉPLACEMENT INTERNATIONAL DÉROGATOIRE VERS LA FRANCE MÉTROPOLITAINE
## Développer ## Développer

59
doc-tools/conception.md Normal file
View File

@ -0,0 +1,59 @@
# Attestation de déplacement international dérogatoire vers la France métropolitaine
## Fichiers pdf des attestations papier :
- version française : 07-04-20-Attestation-etranger-metropole-FR.pdf
- version anglaise : Attestation_deplacement_International_EN-1.pdf
## Analyse de la structure du document
### Champs ajoutés :
- nationalité
- typologie de nationalités : pays tiers ; eu ou assimilés ; française
### Champs supprimés :
- lieu de naissance
- date de sortie
- heure de sortie
### Remarques :
- Les motifs sont identiques pour les types tiers et eu.
- Pas de motif pour les français.
- Un seul motif possible défini en fonction du type de nationalité
## Structure des champs du QR Code
- **Cree le:** creationDate a creationHour;\n
- **Nom:** `firstname`;\n
- **Prenom:** `lastname`;\n
- **Naissance:** `birthday` (`nationality`);\n
- **Adresse:** `address` `zipcode` `town` `country`;\n
- **Sortie:** N/A;\n
- **Motifs:** `national`-`reason` (`reaseon` à vide pour les français)
### Correspondances formulaire/QR Code
Afin de pouvoir être sotckées dans le QR Code, à chaque choix du formulaire est associé un alias.
#### types de nationalités
| libellé formulaire | alias |
|:-----|:-----|
| Ressortissants de pays tiers | tiers |
| Ressortissants de lUnion européenne et assimilés | eu |
| Ressortissants de nationalité française | fr |
#### types de motifs
| libellé formulaire | alias |
|:-----|:-----|
| Personnes ayant leur résidence principale en France | residence |
| Personnes transitant par la France pour rejoindre leur résidence | transit |
| Professionnels de santé aux fins de lutter contre le Covid-19 | prof._sante |
| Transporteurs de marchandises | marchandises |
| Equipages et personnels exploitant des vols | equipage |
| Personnels des missions diplomatiques et consulaires | diplomatique |
| Travailleurs frontaliers | frontalier |

52
doc-tools/grid.html Normal file
View File

@ -0,0 +1,52 @@
<html>
<head>
<meta charset="utf-8" />
<script src="https://unpkg.com/pdf-lib@1.4.1"></script>
<script src="https://unpkg.com/downloadjs@1.4.7"></script>
</head>
<body>
<label for="file">Sélectionner le pdf</label>
<input type="file" id="file" name="file" onchange="generatePdf()">
</body>
<script>
const { PDFDocument, StandardFonts, rgb } = PDFLib
async function generatePdf () {
var pdfBase = document.getElementById("file").files[0];
var buffer = await pdfBase.arrayBuffer();
const pdfDoc = await PDFDocument.load(buffer)
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 })
}
var x;
var y;
for (x = 25; x < 1000; x += 25) {
for (y = 25; y < 1000; y += 25) {
page1.drawText('.', { x: x, y: y, size: 11, font: font, color: rgb(0.95, 0.1, 0.1) })
page1.drawText(`${x}`, { x: x+3, y: y, size: 7, font: font, color: rgb(0, 0, 0) })
page1.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
download(pdfBytes, "grid.pdf", "application/pdf");
}
</script>
</html>

2
package-lock.json generated
View File

@ -1,5 +1,5 @@
{ {
"name": "deplacement-covid-19", "name": "deplacement-vers-france-covid-19",
"version": "0.0.1", "version": "0.0.1",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,

View File

@ -1,15 +1,16 @@
{ {
"name": "deplacement-covid-19", "name": "deplacement-vers-france-covid-19",
"version": "0.0.1", "version": "0.0.1",
"description": "Générateur d'attestation de déplacement dérogatoire'", "description": "Générateur d'attestation de déplacement international dérogatoire vers la France",
"main": "certificate.js", "main": "certificate.js",
"scripts": { "scripts": {
"lint": "eslint ./*.js", "lint": "eslint src/*.js",
"start": "cross-env VERSION=${VERSION:-localversion} parcel --public-url ${PUBLIC_URL:-/deplacement-covid-19} ./src/index.html", "format": "npm run lint -- --fix",
"start": "cross-env VERSION=${VERSION:-localversion} parcel --public-url ${PUBLIC_URL:-/deplacement-vers-france-covid-19} ./src/index.html",
"clean:dist": "rimraf dist", "clean:dist": "rimraf dist",
"prebuild": "run-s lint clean:dist", "prebuild": "run-s lint clean:dist",
"build": "parcel build --public-url ${PUBLIC_URL:-/deplacement-covid-19} ./src/index.html ./src/robots.txt ./src/sitemap.xml", "build": "parcel build --public-url ${PUBLIC_URL:-/deplacement-vers-france-covid-19} ./src/index.html ./src/index-en.html ./src/robots.txt ./src/sitemap.xml",
"postbuild": "PUBLIC_URL=${PUBLIC_URL:-/deplacement-covid-19} react-snap", "postbuild": "PUBLIC_URL=${PUBLIC_URL:-/deplacement-vers-france-covid-19} react-snap",
"preserve": "npm run build", "preserve": "npm run build",
"serve": "serve dist", "serve": "serve dist",
"serve:dist": "serve dist" "serve:dist": "serve dist"
@ -19,7 +20,7 @@
"url": "git+https://github.com/lab-mi/deplacement-covid-19" "url": "git+https://github.com/lab-mi/deplacement-covid-19"
}, },
"keywords": [], "keywords": [],
"author": "", "author": "Ministère de l'Intérieur",
"license": "MIT", "license": "MIT",
"bugs": { "bugs": {
"url": "https://github.com/lab-mi/deplacement-covid-19/issues" "url": "https://github.com/lab-mi/deplacement-covid-19/issues"
@ -55,7 +56,8 @@
"qrcode": "^1.4.4" "qrcode": "^1.4.4"
}, },
"browserslist": [ "browserslist": [
"last 5 versions" "last 5 versions",
"ios_saf >= 7"
], ],
"reactSnap": { "reactSnap": {
"source": "dist", "source": "dist",
@ -73,6 +75,9 @@
"strategy": "default", "strategy": "default",
"clearDist": false, "clearDist": false,
"templatedURLs": { "templatedURLs": {
"./": ["index.html"] "./": [
} } "index.html"
]
}
}
} }

Binary file not shown.

Binary file not shown.

13
src/Flag_of_France.svg Normal file
View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Calque_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 900 600" style="enable-background:new 0 0 900 600;" xml:space="preserve">
<style type="text/css">
.st0{fill:#ED2939;}
.st1{fill:#FFFFFF;}
.st2{fill:#002395;}
</style>
<rect y="0" class="st0" width="900" height="600"/>
<rect y="0" class="st1" width="600" height="600"/>
<rect y="0" class="st2" width="300" height="600"/>
</svg>

After

Width:  |  Height:  |  Size: 606 B

328
src/certificate-en.js Normal file
View File

@ -0,0 +1,328 @@
import 'bootstrap/dist/css/bootstrap.min.css'
import './main.css'
import { PDFDocument, StandardFonts } from 'pdf-lib'
import QRCode from 'qrcode'
import { library, dom } from '@fortawesome/fontawesome-svg-core'
import { faEye, faFilePdf } from '@fortawesome/free-solid-svg-icons'
import './check-updates'
import { $, $$ } from './dom-utils'
import pdfBase from './Attestation_deplacement_International_EN-1.pdf'
library.add(faEye, faFilePdf)
dom.watch()
$('#radio-language-fr').addEventListener('click', async event => {
window.location.href = `${process.env.PUBLIC_URL === '/' ? '' : process.env.PUBLIC_URL}/index.html`
})
const generateQR = async text => {
try {
const opts = {
errorCorrectionLevel: 'M',
type: 'image/png',
quality: 0.92,
margin: 1,
}
return await QRCode.toDataURL(text, opts)
} catch (err) {
console.error(err)
}
}
function saveProfile () {
for (const field of $$('#form-profile input')) {
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, typeNationality, reasons) {
const creationDate = new Date().toLocaleDateString('fr-FR')
const creationHour = new Date().toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' }).replace(':', 'h')
const { lastname, firstname, birthday, nationality, address, zipcode, town, country } = profile
const data = [
`Cree le: ${creationDate} a ${creationHour}`,
`Nom: ${lastname}`,
`Prenom: ${firstname}`,
`Naissance: ${birthday} (${nationality})`,
`Adresse: ${address} ${zipcode} ${town} ${country}`,
'Sortie: N/A',
`Motifs: ${typeNationality}-${reasons}`,
].join(';\n ')
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}`, 125, 590)
drawText(birthday, 125, 567)
drawText(nationality, 125, 545)
drawText(`${address} ${zipcode}`, 127, 527)
drawText(`${town}, ${country}`, 127, 505)
if (typeNationality === 'tiers') {
if (reasons.includes('residence')) {
drawText('x', 49, 449, 19)
}
if (reasons.includes('transit')) {
drawText('x', 49, 412, 19)
}
if (reasons.includes('prof._sante')) {
drawText('x', 49, 385, 19)
}
if (reasons.includes('marchandises')) {
drawText('x', 49, 370, 19)
}
if (reasons.includes('equipage')) {
drawText('x', 49, 354, 19)
}
if (reasons.includes('diplomatique')) {
drawText('x', 49, 339, 19)
}
if (reasons.includes('frontalier')) {
drawText('x', 49, 313, 19)
}
}
if (typeNationality === 'eu') {
if (reasons.includes('resident')) {
drawText('x', 49, 276, 19)
}
if (reasons.includes('transit')) {
drawText('x', 49, 263, 19)
}
if (reasons.includes('prof._sante')) {
drawText('x', 49, 250, 19)
}
if (reasons.includes('marchandises')) {
drawText('x', 49, 238, 19)
}
if (reasons.includes('equipage')) {
drawText('x', 49, 225, 19)
}
if (reasons.includes('diplomatique')) {
drawText('x', 49, 212, 19)
}
if (reasons.includes('frontalier')) {
drawText('x', 49, 189, 19)
}
}
if (typeNationality === 'fr') {
drawText('x', 49, 162, 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
}
// Fait à :
drawText(profile.town, 395, 142, locationSize)
// Le
drawText(`${new Date().toLocaleDateString('fr-FR', { month: 'numeric', day: 'numeric' })}`, 488, 142)
const generatedQR = await generateQR(data)
const qrImage = await pdfDoc.embedPng(generatedQR)
page1.drawImage(qrImage, {
x: 450,
y: 572,
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')
const 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
}
function getAndSaveTypeNationality () {
const typeNationality = $$('input[name="field-type-nationality"]:checked')
.map(x => x.value)
.join('-')
localStorage.setItem('typeNationality', typeNationality)
return typeNationality
}
// 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').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.'
$('#alert-facebook').classList.remove('d-none')
}
function addSlash () {
$('#field-birthday').value = $('#field-birthday').value.replace(/^(\d{2})$/g, '$1/')
$('#field-birthday').value = $('#field-birthday').value.replace(/^(\d{2})\/(\d{2})$/g, '$1/$2/')
$('#field-birthday').value = $('#field-birthday').value.replace(/\/\//g, '/')
}
$('#field-birthday').onkeyup = function () {
const key = event.keyCode || event.charCode
if (key === 8 || key === 46) {
return false
} else {
addSlash()
return false
}
}
const snackbar = $('#snackbar')
$('#generate-btn').addEventListener('click', async event => {
event.preventDefault()
saveProfile()
const typeNationality = getAndSaveTypeNationality()
let reasons
if (typeNationality === 'fr') {
reasons = 'N/A'
} else {
reasons = getAndSaveReasons()
}
const pdfBlob = await generatePdf(getProfile(), typeNationality, reasons)
localStorage.clear()
const creationDate = new Date().toLocaleDateString('fr-CA')
const creationHour = new Date().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)
})
$$('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 = ''
}
})
}
})
const conditions = {
'#field-firstname': {
condition: 'length',
},
'#field-lastname': {
condition: 'length',
},
'#field-birthday': {
condition: 'pattern',
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-nationality': {
condition: 'length',
},
'#field-address': {
condition: 'length',
},
'#field-town': {
condition: 'length',
},
'#field-zipcode': {
condition: 'lenght',
},
'#field-country': {
condition: 'length',
},
}
Object.keys(conditions).forEach(field => {
$(field).addEventListener('input', () => {
if (conditions[field].condition === 'pattern') {
const pattern = conditions[field].pattern
if ($(field).value.match(pattern)) {
$(field).setAttribute('aria-invalid', 'false')
} else {
$(field).setAttribute('aria-invalid', 'true')
}
}
if (conditions[field].condition === 'length') {
if ($(field).value.length > 0) {
$(field).setAttribute('aria-invalid', 'false')
} else {
$(field).setAttribute('aria-invalid', 'true')
}
}
})
})
function addVersion () {
document.getElementById('version').innerHTML = `${new Date().getFullYear()} - ${process.env.VERSION}`
}
addVersion()

View File

@ -9,17 +9,19 @@ import { faEye, faFilePdf } from '@fortawesome/free-solid-svg-icons'
import './check-updates' import './check-updates'
import { $, $$ } from './dom-utils' import { $, $$ } from './dom-utils'
import pdfBase from './certificate.pdf' import pdfBase from './07-04-20-Attestation-etranger-metropole-FR.pdf'
library.add(faEye, faFilePdf) library.add(faEye, faFilePdf)
dom.watch() dom.watch()
var year, month, day $('#radio-language-en').addEventListener('click', async event => {
window.location.href = `${process.env.PUBLIC_URL === '/' ? '' : process.env.PUBLIC_URL}/index-en.html`
})
const generateQR = async text => { const generateQR = async text => {
try { try {
var opts = { const opts = {
errorCorrectionLevel: 'M', errorCorrectionLevel: 'M',
type: 'image/png', type: 'image/png',
quality: 0.92, quality: 0.92,
@ -31,39 +33,9 @@ const generateQR = async text => {
} }
} }
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 () { function saveProfile () {
for (const field of $$('#form-profile input')) { for (const field of $$('#form-profile input')) {
if (field.id === 'field-datesortie') { localStorage.setItem(field.id.substring('field-'.length), field.value)
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)
}
} }
} }
@ -87,23 +59,21 @@ function idealFontSize (font, text, maxWidth, minSize, defaultSize) {
return (textWidth > maxWidth) ? null : currentSize return (textWidth > maxWidth) ? null : currentSize
} }
async function generatePdf (profile, reasons) { async function generatePdf (profile, typeNationality, reasons) {
const creationDate = new Date().toLocaleDateString('fr-FR') const creationDate = new Date().toLocaleDateString('fr-FR')
const creationHour = new Date().toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' }).replace(':', 'h') const creationHour = new Date().toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' }).replace(':', 'h')
const { lastname, firstname, birthday, lieunaissance, address, zipcode, town, datesortie, heuresortie } = profile const { lastname, firstname, birthday, nationality, address, zipcode, town, country } = profile
const releaseHours = String(heuresortie).substring(0, 2)
const releaseMinutes = String(heuresortie).substring(3, 5)
const data = [ const data = [
`Cree le: ${creationDate} a ${creationHour}`, `Cree le: ${creationDate} a ${creationHour}`,
`Nom: ${lastname}`, `Nom: ${lastname}`,
`Prenom: ${firstname}`, `Prenom: ${firstname}`,
`Naissance: ${birthday} a ${lieunaissance}`, `Naissance: ${birthday} (${nationality})`,
`Adresse: ${address} ${zipcode} ${town}`, `Adresse: ${address} ${zipcode} ${town} ${country}`,
`Sortie: ${datesortie} a ${releaseHours}h${releaseMinutes}`, 'Sortie: N/A',
`Motifs: ${reasons}`, `Motifs: ${typeNationality}-${reasons}`,
].join('; ') ].join(';\n ')
const existingPdfBytes = await fetch(pdfBase).then(res => res.arrayBuffer()) const existingPdfBytes = await fetch(pdfBase).then(res => res.arrayBuffer())
@ -115,31 +85,61 @@ async function generatePdf (profile, reasons) {
page1.drawText(text, { x, y, size, font }) page1.drawText(text, { x, y, size, font })
} }
drawText(`${firstname} ${lastname}`, 123, 686) drawText(`${firstname} ${lastname}`, 125, 590)
drawText(birthday, 123, 661) drawText(birthday, 125, 567)
drawText(lieunaissance, 92, 638) drawText(nationality, 125, 545)
drawText(`${address} ${zipcode} ${town}`, 134, 613) drawText(`${address} ${zipcode}`, 127, 527)
drawText(`${town}, ${country}`, 127, 505)
if (reasons.includes('travail')) { if (typeNationality === 'tiers') {
drawText('x', 76, 527, 19) if (reasons.includes('residence')) {
drawText('x', 49, 449, 19)
}
if (reasons.includes('transit')) {
drawText('x', 49, 412, 19)
}
if (reasons.includes('prof._sante')) {
drawText('x', 49, 385, 19)
}
if (reasons.includes('marchandises')) {
drawText('x', 49, 370, 19)
}
if (reasons.includes('equipage')) {
drawText('x', 49, 354, 19)
}
if (reasons.includes('diplomatique')) {
drawText('x', 49, 328, 19)
}
if (reasons.includes('frontalier')) {
drawText('x', 49, 302, 19)
}
} }
if (reasons.includes('courses')) {
drawText('x', 76, 478, 19) if (typeNationality === 'eu') {
if (reasons.includes('resident')) {
drawText('x', 49, 265, 19)
}
if (reasons.includes('transit')) {
drawText('x', 49, 249, 19)
}
if (reasons.includes('prof._sante')) {
drawText('x', 49, 234, 19)
}
if (reasons.includes('marchandises')) {
drawText('x', 49, 218, 19)
}
if (reasons.includes('equipage')) {
drawText('x', 49, 202, 19)
}
if (reasons.includes('diplomatique')) {
drawText('x', 49, 177, 19)
}
if (reasons.includes('frontalier')) {
drawText('x', 49, 150, 19)
}
} }
if (reasons.includes('sante')) { if (typeNationality === 'fr') {
drawText('x', 76, 436, 19) drawText('x', 49, 127, 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) let locationSize = idealFontSize(font, profile.town, 83, 7, 11)
@ -149,26 +149,18 @@ async function generatePdf (profile, reasons) {
locationSize = 7 locationSize = 7
} }
drawText(profile.town, 111, 226, locationSize) // Fait à :
drawText(profile.town, 388, 107, locationSize)
if (reasons !== '') { // Le
// Date sortie drawText(`${new Date().toLocaleDateString('fr-FR', { month: 'numeric', day: 'numeric' })}`, 488, 107)
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 generatedQR = await generateQR(data)
const qrImage = await pdfDoc.embedPng(generatedQR) const qrImage = await pdfDoc.embedPng(generatedQR)
page1.drawImage(qrImage, { page1.drawImage(qrImage, {
x: page1.getWidth() - 170, x: 450,
y: 155, y: 572,
width: 100, width: 100,
height: 100, height: 100,
}) })
@ -189,7 +181,7 @@ async function generatePdf (profile, reasons) {
function downloadBlob (blob, fileName) { function downloadBlob (blob, fileName) {
const link = document.createElement('a') const link = document.createElement('a')
var url = URL.createObjectURL(blob) const url = URL.createObjectURL(blob)
link.href = url link.href = url
link.download = fileName link.download = fileName
document.body.appendChild(link) document.body.appendChild(link)
@ -204,6 +196,15 @@ function getAndSaveReasons () {
return values return values
} }
function getAndSaveTypeNationality () {
const typeNationality = $$('input[name="field-type-nationality"]:checked')
.map(x => x.value)
.join('-')
localStorage.setItem('typeNationality', typeNationality)
return typeNationality
}
// see: https://stackoverflow.com/a/32348687/1513045 // see: https://stackoverflow.com/a/32348687/1513045
function isFacebookBrowser () { function isFacebookBrowser () {
const ua = navigator.userAgent || navigator.vendor || window.opera const ua = navigator.userAgent || navigator.vendor || window.opera
@ -237,12 +238,19 @@ $('#generate-btn').addEventListener('click', async event => {
event.preventDefault() event.preventDefault()
saveProfile() saveProfile()
const reasons = getAndSaveReasons() const typeNationality = getAndSaveTypeNationality()
const pdfBlob = await generatePdf(getProfile(), reasons) let reasons
if (typeNationality === 'fr') {
reasons = 'N/A'
} else {
reasons = getAndSaveReasons()
}
const pdfBlob = await generatePdf(getProfile(), typeNationality, reasons)
localStorage.clear() localStorage.clear()
const creationDate = new Date().toLocaleDateString('fr-CA') const creationDate = new Date().toLocaleDateString('fr-CA')
const creationHour = new Date().toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' }).replace(':', '-') const creationHour = new Date().toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' }).replace(':', '-')
downloadBlob(pdfBlob, `attestation-${creationDate}_${creationHour}.pdf`) downloadBlob(pdfBlob, `attestation-${creationDate}_${creationHour}.pdf`)
snackbar.classList.remove('d-none') snackbar.classList.remove('d-none')
setTimeout(() => snackbar.classList.add('show'), 100) setTimeout(() => snackbar.classList.add('show'), 100)
@ -275,9 +283,9 @@ const conditions = {
}, },
'#field-birthday': { '#field-birthday': {
condition: 'pattern', condition: 'pattern',
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 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-lieunaissance': { '#field-nationality': {
condition: 'length', condition: 'length',
}, },
'#field-address': { '#field-address': {
@ -287,34 +295,28 @@ const conditions = {
condition: 'length', condition: 'length',
}, },
'#field-zipcode': { '#field-zipcode': {
condition: 'pattern', condition: 'lenght',
pattern: /\d{5}/g
}, },
'#field-datesortie': { '#field-country': {
condition: 'pattern', condition: 'length',
pattern: /\d{4}-\d{2}-\d{2}/g
}, },
'#field-heuresortie': {
condition: 'pattern',
pattern: /\d{2}:\d{2}/g
}
} }
Object.keys(conditions).forEach(field => { Object.keys(conditions).forEach(field => {
$(field).addEventListener('input', () => { $(field).addEventListener('input', () => {
if (conditions[field].condition == 'pattern') { if (conditions[field].condition === 'pattern') {
const pattern = conditions[field].pattern; const pattern = conditions[field].pattern
if ($(field).value.match(pattern)) { if ($(field).value.match(pattern)) {
$(field).setAttribute('aria-invalid', "false"); $(field).setAttribute('aria-invalid', 'false')
} else { } else {
$(field).setAttribute('aria-invalid', "true"); $(field).setAttribute('aria-invalid', 'true')
} }
} }
if (conditions[field].condition == 'length') { if (conditions[field].condition === 'length') {
if ($(field).value.length > 0) { if ($(field).value.length > 0) {
$(field).setAttribute('aria-invalid', "false"); $(field).setAttribute('aria-invalid', 'false')
} else { } else {
$(field).setAttribute('aria-invalid', "true"); $(field).setAttribute('aria-invalid', 'true')
} }
} }
}) })

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -14,26 +14,6 @@
"src": "android-chrome-512x512.png", "src": "android-chrome-512x512.png",
"sizes": "512x512", "sizes": "512x512",
"type": "image/png" "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", "orientation": "portrait-primary",

70
src/flag EN.svg Normal file
View File

@ -0,0 +1,70 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="flag_EN" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 207 133.5" style="enable-background:new 0 0 207 133.5;" xml:space="preserve">
<style type="text/css">
.st0{fill-rule:evenodd;clip-rule:evenodd;fill:#EDEDED;}
.st1{fill-rule:evenodd;clip-rule:evenodd;fill:#CE0100;}
.st2{fill-rule:evenodd;clip-rule:evenodd;fill:#1E359B;}
.st3{fill:#EDEDED;}
.st4{fill:#CC0B1B;}
.st5{fill:#2C4DB5;}
</style>
<g>
<g>
<rect x="9.7" y="38.3" class="st0" width="138.6" height="92.4"/>
<polygon class="st1" points="148.3,76.8 148.3,92.3 85.8,92.3 85.8,130.7 72.2,130.7 72.2,92.3 9.7,92.3 9.7,76.8 72.2,76.8
72.2,38.3 85.8,38.3 85.8,76.8 "/>
<path class="st2" d="M22.5,38.3h46.2v30.5L22.5,38.3z M89.3,38.3h46.2L89.3,68.8V38.3z M148.3,47.8l-37.6,24.8h37.6V47.8z
M110.7,96.5h37.6v24.8L110.7,96.5z M135.6,130.7l-46.2-30.5v30.5H135.6z M9.7,96.5h37.6L9.7,121.3V96.5z M68.7,100.3l-46.2,30.5
h46.2V100.3z M47.3,72.6L9.7,47.8v24.8H47.3z"/>
<g>
<polygon class="st1" points="52.3,72.6 9.7,44.5 9.7,38.8 61,72.6 "/>
<polygon class="st1" points="68.7,97.2 17.8,130.7 9.7,130.7 9.7,130.4 61.1,96.5 68.7,96.5 "/>
<polygon class="st1" points="148.3,39 97.4,72.6 89.3,72.6 89.3,72.1 140.7,38.3 148.3,38.3 "/>
<polygon class="st1" points="148.3,130 97.4,96.5 106.1,96.5 148.3,124.3 "/>
</g>
</g>
</g>
<g>
<rect x="64" y="4.4" class="st3" width="138.6" height="92.4"/>
<path class="st4" d="M64,89.7h138.6v7.1H64V89.7z M64,4.4h138.6v7.1H64V4.4z M64,18.6h138.6v7.1H64V18.6z M64,32.9h138.6V40H64
V32.9z M64,47.1h138.6v7.1H64V47.1z M64,61.3h138.6v7.1H64V61.3z M64,75.5h138.6v7.1H64V75.5z"/>
<rect x="64" y="4.4" class="st5" width="70.2" height="49.8"/>
<path class="st3" d="M128.9,18.4l-0.6-2l-0.6,2h-2.1l1.7,1.2l-0.6,2l1.7-1.2l1.7,1.2l-0.6-2l1.7-1.2H128.9z M71.1,9.7l1.7-1.2h-2.1
l-0.6-2l-0.6,2h-2.1L69,9.7l-0.6,2l1.7-1.2l1.7,1.2L71.1,9.7z M82.7,9.7l1.7-1.2h-2.1l-0.6-2l-0.6,2H79l1.7,1.2l-0.6,2l1.7-1.2
l1.7,1.2L82.7,9.7z M94.3,9.7L96,8.5H94l-0.6-2l-0.6,2h-2.1l1.7,1.2l-0.6,2l1.7-1.2l1.7,1.2L94.3,9.7z M106,9.7l1.7-1.2h-2.1
l-0.6-2l-0.6,2h-2.1l1.7,1.2l-0.6,2l1.7-1.2l1.7,1.2L106,9.7z M77.2,44.4l1.7-1.2h-2.1l-0.6-2l-0.6,2h-2.1l1.7,1.2l-0.6,2l1.7-1.2
l1.7,1.2L77.2,44.4z M77,34.7l1.7-1.2h-2.1l-0.6-2l-0.6,2h-2.1l1.7,1.2l-0.6,2l1.7-1.2l1.7,1.2L77,34.7z M77,25l1.7-1.2h-2.1
l-0.6-2l-0.6,2h-2.1L75,25l-0.6,2l1.7-1.2l1.7,1.2L77,25z M117.6,9.7l1.7-1.2h-2.1l-0.6-2l-0.6,2h-2.1l1.7,1.2l-0.6,2l1.7-1.2
l1.7,1.2L117.6,9.7z M129.3,9.7l1.7-1.2h-2.1l-0.6-2l-0.6,2h-2.1l1.7,1.2l-0.6,2l1.7-1.2l1.7,1.2L129.3,9.7z M88.7,25l1.7-1.2h-2.1
l-0.6-2l-0.6,2h-2.1l1.7,1.2L86,27l1.7-1.2l1.7,1.2L88.7,25z M88.6,34.7l1.7-1.2h-2.1l-0.6-2l-0.6,2h-2.1l1.7,1.2l-0.6,2l1.7-1.2
l1.7,1.2L88.6,34.7z M88.8,44.4l1.7-1.2h-2.1l-0.6-2l-0.6,2h-2.1l1.7,1.2l-0.6,2l1.7-1.2l1.7,1.2L88.8,44.4z M100.5,44.4l1.7-1.2
h-2.1l-0.6-2l-0.6,2h-2.1l1.7,1.2l-0.6,2l1.7-1.2l1.7,1.2L100.5,44.4z M100.3,34.7l1.7-1.2h-2.1l-0.6-2l-0.6,2h-2.1l1.7,1.2l-0.6,2
l1.7-1.2l1.7,1.2L100.3,34.7z M100.3,25l1.7-1.2h-2.1l-0.6-2l-0.6,2h-2.1l1.7,1.2l-0.6,2l1.7-1.2l1.7,1.2L100.3,25z M76.9,14.7
l1.7-1.2h-2.1l-0.6-2l-0.6,2h-2.1l1.7,1.2l-0.6,2l1.7-1.2l1.7,1.2L76.9,14.7z M88.5,14.7l1.7-1.2h-2.1l-0.6-2l-0.6,2h-2.1l1.7,1.2
l-0.6,2l1.7-1.2l1.7,1.2L88.5,14.7z M112,25l1.7-1.2h-2.1l-0.6-2l-0.6,2h-2.1l1.7,1.2l-0.6,2l1.7-1.2l1.7,1.2L112,25z M111.9,34.7
l1.7-1.2h-2.1l-0.6-2l-0.6,2h-2.1l1.7,1.2l-0.6,2l1.7-1.2l1.7,1.2L111.9,34.7z M112.1,44.4l1.7-1.2h-2.1l-0.6-2l-0.6,2h-2.1
l1.7,1.2l-0.6,2l1.7-1.2l1.7,1.2L112.1,44.4z M123.8,44.4l1.7-1.2h-2.1l-0.6-2l-0.6,2H120l1.7,1.2l-0.6,2l1.7-1.2l1.7,1.2
L123.8,44.4z M123.5,34.7l1.7-1.2h-2.1l-0.6-2l-0.6,2h-2.1l1.7,1.2l-0.6,2l1.7-1.2l1.7,1.2L123.5,34.7z M123.6,25l1.7-1.2h-2.1
l-0.6-2l-0.6,2h-2.1l1.7,1.2l-0.6,2l1.7-1.2l1.7,1.2L123.6,25z M100.2,14.7l1.7-1.2h-2.1l-0.6-2l-0.6,2h-2.1l1.7,1.2l-0.6,2
l1.7-1.2l1.7,1.2L100.2,14.7z M111.8,14.7l1.7-1.2h-2.1l-0.6-2l-0.6,2h-2.1l1.7,1.2l-0.6,2l1.7-1.2l1.7,1.2L111.8,14.7z
M123.5,14.7l1.7-1.2h-2.1l-0.6-2l-0.6,2h-2.1l1.7,1.2l-0.6,2l1.7-1.2l1.7,1.2L123.5,14.7z M71.4,49.4l1.7-1.2H71l-0.6-2l-0.6,2
h-2.1l1.7,1.2l-0.6,2l1.7-1.2l1.7,1.2L71.4,49.4z M71.1,39.7l1.7-1.2h-2.1l-0.6-2l-0.6,2h-2.1l1.7,1.2l-0.6,2l1.7-1.2l1.7,1.2
L71.1,39.7z M71.2,30l1.7-1.2h-2.1l-0.6-2l-0.6,2h-2.1l1.7,1.2l-0.6,2l1.7-1.2l1.7,1.2L71.2,30z M82.8,30l1.7-1.2h-2.1l-0.6-2
l-0.6,2h-2.1l1.7,1.2l-0.6,2l1.7-1.2l1.7,1.2L82.8,30z M82.8,39.7l1.7-1.2h-2.1l-0.6-2l-0.6,2H79l1.7,1.2l-0.6,2l1.7-1.2l1.7,1.2
L82.8,39.7z M83,49.4l1.7-1.2h-2.1l-0.6-2l-0.6,2h-2.1l1.7,1.2l-0.6,2l1.7-1.2l1.7,1.2L83,49.4z M94.7,49.4l1.7-1.2h-2.1l-0.6-2
l-0.6,2h-2.1l1.7,1.2l-0.6,2l1.7-1.2l1.7,1.2L94.7,49.4z M94.4,39.7l1.7-1.2H94l-0.6-2l-0.6,2h-2.1l1.7,1.2l-0.6,2l1.7-1.2l1.7,1.2
L94.4,39.7z M94.5,30l1.7-1.2h-2.1l-0.6-2l-0.6,2h-2.1l1.7,1.2l-0.6,2l1.7-1.2l1.7,1.2L94.5,30z M71.1,19.7l1.7-1.2h-2.1l-0.6-2
l-0.6,2h-2.1l1.7,1.2l-0.6,2l1.7-1.2l1.7,1.2L71.1,19.7z M82.7,19.7l1.7-1.2h-2.1l-0.6-2l-0.6,2H79l1.7,1.2l-0.6,2l1.7-1.2l1.7,1.2
L82.7,19.7z M106.1,30l1.7-1.2h-2.1l-0.6-2l-0.6,2h-2.1l1.7,1.2l-0.6,2l1.7-1.2l1.7,1.2L106.1,30z M106.1,39.7l1.7-1.2h-2.1l-0.6-2
l-0.6,2h-2.1l1.7,1.2l-0.6,2l1.7-1.2l1.7,1.2L106.1,39.7z M106.3,49.4l1.7-1.2h-2.1l-0.6-2l-0.6,2h-2.1l1.7,1.2l-0.6,2l1.7-1.2
l1.7,1.2L106.3,49.4z M117.9,49.4l1.7-1.2h-2.1l-0.6-2l-0.6,2h-2.1l1.7,1.2l-0.6,2l1.7-1.2l1.7,1.2L117.9,49.4z M129.6,49.4
l1.7-1.2h-2.1l-0.6-2l-0.6,2h-2.1l1.7,1.2l-0.6,2l1.7-1.2l1.7,1.2L129.6,49.4z M117.7,39.7l1.7-1.2h-2.1l-0.6-2l-0.6,2H114l1.7,1.2
l-0.6,2l1.7-1.2l1.7,1.2L117.7,39.7z M129.4,39.7l1.7-1.2H129l-0.6-2l-0.6,2h-2.1l1.7,1.2l-0.6,2l1.7-1.2l1.7,1.2L129.4,39.7z
M117.8,30l1.7-1.2h-2.1l-0.6-2l-0.6,2H114l1.7,1.2l-0.6,2l1.7-1.2l1.7,1.2L117.8,30z M129.4,30l1.7-1.2H129l-0.6-2l-0.6,2h-2.1
l1.7,1.2l-0.6,2l1.7-1.2l1.7,1.2L129.4,30z M94.3,19.7l1.7-1.2H94l-0.6-2l-0.6,2h-2.1l1.7,1.2l-0.6,2l1.7-1.2l1.7,1.2L94.3,19.7z
M106,19.7l1.7-1.2h-2.1l-0.6-2l-0.6,2h-2.1l1.7,1.2l-0.6,2l1.7-1.2l1.7,1.2L106,19.7z M117.6,19.7l1.7-1.2h-2.1l-0.6-2l-0.6,2
h-2.1l1.7,1.2l-0.6,2l1.7-1.2l1.7,1.2L117.6,19.7z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.1 KiB

378
src/index-en.html Normal file
View File

@ -0,0 +1,378 @@
<!DOCTYPE html>
<html lang="en">
<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="international travel certificate to mainland France - 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, certificate, travel, international, official, government">
<meta name="robots" content="index, follow">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="language" content="English">
<meta property="og:title" content="Generator of international travel certificate to mainland France - 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-vers-france-covid-19/" />
<meta property="og:url" content="https://media.interieur.gouv.fr/deplacement-vers-france-covid-19/" />
<meta property="og:site_name" content="Generator of international travel certificate to mainland France - COVID-19" />
<script type='application/ld+json'>{"@context":"http://www.schema.org","@type":"GovernmentOrganization","name":"Générateur d'attestation de déplacement international dérogatoire vers la France métropolitaine - 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 Generator of international travel certificate to mainland France</title>
</head>
<body>
<header role="banner" class="wrapper">
<ul class="flex-justify">
<img class="logo" src="/MIN_Interieur_RVB.svg" alt="Ministère de l'intérieur. Liberté, égalité, fraternité.">
<fieldset class="form-language">
<legend class="legend-language">Select your language</legend>
<div class="form-check">
<input class="form-check-input" type="radio" name="field-language" id="radio-language-en" value="en" checked>
<label class="form-check-label" for="radio-language-en">ENGLISH</label>
<img class="flags" src="flag EN.svg">
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="field-language" id="radio-language-fr" value="fr">
<label class="form-check-label" for="radio-language-fr">FRANCAIS</label>
<img class="flags" src="Flag_of_France.svg">
</div>
</fieldset>
</ul>
<div>
<h1 class="flex flex-wrap">
<span class="covid-title">
COVID-19
</span>
<span class="covid-subtitle">
Generator of international travel certificate to mainland France
</span>
</h1>
<p class="text-alert">
Filled in data is exclusively stored on your mobile phone or computer.
No information is collected by the Ministry of the Interior.
The generated pdf certificate has a QR Code.
This visual barcode makes it possible to read the information on your certificate at the time it was entered.
It is readable with all generic QR code readers.
</p>
<p>
This certificate must be presented to transportation companies, before boarding, by passengers travelling to
mainland France. It must also be presented to border control authorities, for any type of border:
<ul>
<li>European external borders of France (air, maritime, land including railway connections).</li>
<li>European internal borders of France.</li>
</ul>
</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">
<h2 class="titre-2">Fill online your digital certificate&nbsp;:</h2>
<p class="text-alert">All fields are mandatory.</p>
<div class="form-group">
<label for="field-firstname" id="field-firstname-label">First name</label>
<div class="input-group align-items-center">
<input
type="text"
class="form-control"
id="field-firstname"
name="firstname"
autocomplete="given-name"
placeholder="Jean"
required
aria-invalid="true"
>
<span class="validity" aria-hidden="true"></span>
</div>
<p class="exemple"></p>
</div>
<div class="form-group">
<label for="field-lastname" id="field-lastname-label">Surname</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-invalid="true"
required
>
<span class="validity" aria-hidden="true"></span>
</div>
<p class="exemple"></p>
</div>
<div class="form-group">
<label for="field-birthday" id="field-birthday-label">Date of birth (dd/mm/yyyy)</label>
<div class="input-group align-items-center">
<input
type="text"
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)"
inputmode="numeric"
class="form-control"
id="field-birthday"
name="birthday"
aria-invalid="true"
autocomplete="bday"
placeholder="01/01/1970"
maxlength="10"
required
>
<span class="validity" aria-hidden="true"></span>
</div>
<p class="exemple"></p>
</div>
<div class="form-group">
<label for="field-nationality" id="field-nationality-label">Nationality</label>
<div class="input-group align-items-center">
<input
type="text"
class="form-control"
id="field-nationality"
name="nationality"
autocomplete="country-name"
aria-invalid="true"
placeholder="Belgian"
required
>
<span class="validity" aria-hidden="true"></span>
</div>
<p class="exemple"></p>
</div>
<div class="form-group">
<label for="field-address" id="field-address-label">Address</label>
<div class="input-group align-items-center">
<input
type="text"
class="form-control"
id="field-address"
name="address"
aria-invalid="true"
autocomplete="address-line1"
placeholder="999 avenue de Belgique"
required
>
<span class="validity" aria-hidden="true"></span>
</div>
<p class="exemple"></p>
</div>
<div class="form-group">
<label for="field-town" id="field-town-label">City</label>
<div class="input-group align-items-center">
<input
type="text"
class="form-control"
id="field-town"
name="town"
autocomplete="address-level1"
aria-invalid="true"
placeholder="Brussels"
required
>
<span class="validity" aria-hidden="true"></span>
</div>
<p class="exemple"></p>
</div>
<div class="form-group">
<label for="field-zipcode" id="field-zipcode-label" >Zip code</label>
<div class="input-group align-items-center">
<input
type="text"
class="form-control"
id="field-zipcode"
name="zipcode"
aria-invalid="true"
autocomplete="postal-code"
placeholder="1000"
required
>
<span class="validity" aria-hidden="true"></span>
</div>
<p class="exemple"></p>
</div>
<div class="form-group">
<label for="field-country" id="field-country-label">Country</label>
<div class="input-group align-items-center">
<input
type="text"
class="form-control"
id="field-country"
name="country"
autocomplete="country-name"
aria-invalid="true"
placeholder="Belgium"
required
>
<span class="validity" aria-hidden="true"></span>
</div>
<p class="exemple"></p>
</div>
<fieldset class="control">
<legend class="titre-3">You are : </legend>
<div class="form-check">
<input class="form-check-input" type="radio" name="field-type-nationality" id="radio-tiers" value="tiers">
<label class="form-nationality-label" for="radio-tiers">Third country nationals.</label>
<fieldset class='conditional'>
<legend class="titre-4">Choose the reason :</legend>
<div class="form-check">
<input class="form-check-input" type="radio" name="field-reason" id="radio-residence-tiers" value="residence">
<label class="form-check-label" for="radio-residence-tiers">Individuals having their primary residence in France or in the European Union or assimilated countries<a href="#footnote1">[1]</a>, who are holders of a French or European residence permit or valid long-stay visa, accompanied by their spouse and
children.</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="field-reason" id="radio-transit-tiers" value="transit">
<label class="form-check-label" for="radio-transit-tiers">Individuals in transit to reach their country of origin who are holders of a travel document to their country of origin and remaining in the international area with no intention to enter the national territory.</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="field-reason" id="radio-prof._sante-tiers" value="prof._sante">
<label class="form-check-label" for="radio-prof._sante-tiers">Healthcare workers supporting the fight against Covid-19.</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="field-reason" id="radio-marchandises-tiers" value="marchandises">
<label class="form-check-label" for="radio-marchandises-tiers">Goods carriers, including seamen.</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="field-reason" id="radio-equipage-tiers" value="equipage">
<label class="form-check-label" for="radio-equipage-tiers">Flight and cargo crews, or travelling as a passenger to their departure base.</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="field-reason" id="radio-diplomatique-tiers" value="diplomatique">
<label class="form-check-label" for="radio-diplomatique-tiers">Diplomatic mission staff, or international organisations staff working in headquarters or offices located in France, who are holders of a special residence permit or a type D promae visa.</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="field-reason" id="radio-frontalier-tiers" value="frontalier">
<label class="form-check-label" for="radio-frontalier-tiers">Cross-border workers at internal land borders.</label>
</div>
</fieldset>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="field-type-nationality" id="radio-eu" value="eu">
<label class="form-nationality-label" for="radio-eu">European Union or assimilated countries nationals<a href="#footnote2">[2]</a>.</label>
<fieldset class='conditional'>
<legend class="titre-4">Choose the reason :</legend>
<div class="form-check">
<input class="form-check-input" type="radio" name="field-reason" id="radio-residence-eu" value="residence">
<label class="form-check-label" for="radio-residence-eu">Individuals having their primary residence in France, accompanied by their spouse and children.</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="field-reason" id="radio-transit-eu" value="transit">
<label class="form-check-label" for="radio-transit-eu">Individuals transiting through France to reach their residence, accompanied by their spouse and children.</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="field-reason" id="radio-prof._sante-eu" value="prof._sante">
<label class="form-check-label" for="radio-prof._sante-eu">Healthcare workers supporting the fight against Covid-19.</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="field-reason" id="radio-marchandises-eu" value="marchandises">
<label class="form-check-label" for="radio-marchandises-eu">Goods carriers, including seamen.</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="field-reason" id="radio-equipage-eu" value="equipage">
<label class="form-check-label" for="radio-equipage-eu">Flight and cargo crews, or travelling as a passenger to their departure base.</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="field-reason" id="radio-diplomatique-eu" value="diplomatique">
<label class="form-check-label" for="radio-diplomatique-eu">Diplomatic mission staff, or international organisations staff working in headquarters or offices located in France,
who are hold ers of a special residence permit or a type D promae visa.</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="field-reason" id="radio-frontalier" value="frontalier">
<label class="form-check-label" for="radio-frontalier">Cross-border workers at internal land borders.</label>
</div>
</fieldset>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="field-type-nationality" id="radio-fr" value="fr">
<label class="form-nationality-label" for="radio-fr">French nationals, accompanied by their spouse and children.</label>
</div>
</fieldset>
<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> Generate my certificate</span></button>
</p>
<div class="bg-primary d-none" id="snackbar">
The certificate is downloaded on your terminal.
</div>
</form>
</div>
<div class="">
<p id="footnotes">
<span id="footnote1">[1] United Kingdom, Iceland, Liechtenstein, Norway, Andorra, Monaco, Switzerland, San Marino, Holy See.</span><br>
<span id="footnote2">[2] European Union, United Kingdom, Iceland, Liechtenstein, Norway, Andorra, Monaco, Switzerland, San Marino and Holy See nationals (2004/38/CE directive).</span>
</p>
<p class="github">
The source code of this service is available for consultation on <a href="https://github.com/LAB-MI/deplacement-covid-19" class="github-link">GitHub</a>.
</p>
<p class="label-mi">
Ministry of the Interior - DNUM - SDIT
</p>
<img class="center" src="/logo_dnum.svg" alt="logo dnum">
</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">Privacy policy</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">Legal notices</a>
<a href="https://www.gouvernement.fr/info-coronavirus" title="Informations du gouvernement sur le Covid-19 - nouvelle page" target="_blank" class="footer-line footer-link">Government information on COVID-19</a>
<div class="footer-line" >For more information, call <a class="num-08" href="tel:0800130000">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">
A new version is available. Click on the button to get it.
<p class="text-right">
<button id="reload-btn" class="btn btn-info">Update</button>
</p>
</div>
<script src="./certificate-en.js"></script>
</body>
</html>

View File

@ -6,19 +6,19 @@
<meta name="msapplication-TileColor" content="#603cba"> <meta name="msapplication-TileColor" content="#603cba">
<meta name="msapplication-config" content="./favicons/browserconfig.xml"> <meta name="msapplication-config" content="./favicons/browserconfig.xml">
<meta name="theme-color" content="#ffffff"> <meta name="theme-color" content="#ffffff">
<meta name="title" content="Générateur d'attestation de déplacement dérogatoire - COVID-19"> <meta name="title" content="Générateur d'attestation de déplacement international dérogatoire vers la France métropolitaine - 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="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="keywords" content="covid19, covid-19, attestation, déplacement, international, officielle, gouvernement">
<meta name="robots" content="index, follow"> <meta name="robots" content="index, follow">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="language" content="French"> <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:title" content="Générateur d'attestation de déplacement international dérogatoire vers la France métropolitaine - COVID-19" />
<meta property="og:locale" content="fr_FR" /> <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." /> <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/" /> <link rel="canonical" href="https://media.interieur.gouv.fr/deplacement-vers-france-covid-19/" />
<meta property="og:url" content="https://media.interieur.gouv.fr/deplacement-covid-19/" /> <meta property="og:url" content="https://media.interieur.gouv.fr/deplacement-vers-france-covid-19/" />
<meta property="og:site_name" content="Générateur d'attestation de déplacement dérogatoire - COVID-19" /> <meta property="og:site_name" content="Générateur d'attestation de déplacement international dérogatoire vers la France métropolitaine - 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> <script type='application/ld+json'>{"@context":"http://www.schema.org","@type":"GovernmentOrganization","name":"Générateur d'attestation de déplacement international dérogatoire vers la France métropolitaine - 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="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="32x32" href="./favicons/favicon-32x32.png">
@ -26,25 +26,50 @@
<link rel="manifest" href="./favicons/site.webmanifest"> <link rel="manifest" href="./favicons/site.webmanifest">
<link rel="mask-icon" href="./favicons/safari-pinned-tab.svg" color="#21bf73"> <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> <title>COVID-19 Générateur d'attestation de déplacement international dérogatoire vers la France métropolitaine</title>
</head> </head>
<body> <body>
<header role="banner" class="wrapper"> <header role="banner" class="wrapper">
<img class="logo" src="/MIN_Interieur_RVB.svg" alt="Ministère de l'intérieur. Liberté, égalité, fraternité."> <ul class="flex-justify">
<img class="logo" src="/MIN_Interieur_RVB.svg" alt="Ministère de l'intérieur. Liberté, égalité, fraternité.">
<fieldset class="form-language">
<legend class="legend-language">Choisissez votre langue</legend>
<div class="form-check">
<input class="form-check-input" type="radio" name="field-language" id="radio-language-en" value="en">
<label class="form-check-label" for="radio-language-en">ENGLISH</label>
<img class="flags" src="flag EN.svg">
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="field-language" id="radio-language-fr" value="fr" checked>
<label class="form-check-label" for="radio-language-fr">FRANCAIS</label>
<img class="flags" src="Flag_of_France.svg">
</div>
</fieldset>
</ul>
<div> <div>
<h1 class="flex flex-wrap"> <h1 class="flex flex-wrap">
<span class="covid-title"> <span class="covid-title">
COVID-19 COVID-19
</span> </span>
<span class="covid-subtitle"> <span class="covid-subtitle">
Générateur d'attestation de&nbsp;déplacement&nbsp;dérogatoire Générateur d'attestation de déplacement&nbsp;international&nbsp;dérogatoire vers la France&nbsp;métropolitaine
</span> </span>
</h1> </h1>
<p class="text-alert"> <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. 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. 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. Il peut être déchiffré à l'aide de tout type de lecteur de QR code générique.
</p> </p>
<p>
Cette attestation est à présenter aux compagnies de transport, avant lutilisation du titre de transport, par les
passagers qui souhaitent voyager à destination de la France métropolitaine. Elle sera aussi présentée aux autorités en
charge du contrôle frontières, pour tout type de frontière :
<ul>
<li>Aux frontières extérieures de la France (liaisons aériennes, maritimes, terrestres, dont les liaisons ferroviaires).</li>
<li>Aux frontières intérieures de la France.</li>
</ul>
</p>
</div> </div>
</header> </header>
<main role="main"> <main role="main">
@ -55,7 +80,7 @@
<form id="form-profile" accept-charset="UTF-8"> <form id="form-profile" accept-charset="UTF-8">
<h2 class="titre-2">Remplissez en ligne votre attestation numérique :</h2> <h2 class="titre-2">Remplissez en ligne votre attestation numérique :</h2>
<p class="text-alert">Tous les champs sont obligatoires.</p> <p class="text-alert">Tous les champs sont obligatoires.</p>
<div class="form-group"> <div class="form-group">
<label for="field-firstname" id="field-firstname-label">Prénom</label> <label for="field-firstname" id="field-firstname-label">Prénom</label>
<div class="input-group align-items-center"> <div class="input-group align-items-center">
@ -67,14 +92,13 @@
autocomplete="given-name" autocomplete="given-name"
placeholder="Jean" placeholder="Jean"
required required
autofocus
aria-invalid="true" aria-invalid="true"
> >
<span class="validity" aria-hidden="true"></span> <span class="validity" aria-hidden="true"></span>
</div> </div>
<p class="exemple"></p> <p class="exemple"></p>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="field-lastname" id="field-lastname-label">Nom</label> <label for="field-lastname" id="field-lastname-label">Nom</label>
<div class="input-group align-items-center"> <div class="input-group align-items-center">
@ -87,13 +111,12 @@
placeholder="Dupont" placeholder="Dupont"
aria-invalid="true" aria-invalid="true"
required required
autofocus
> >
<span class="validity" aria-hidden="true"></span> <span class="validity" aria-hidden="true"></span>
</div> </div>
<p class="exemple"></p> <p class="exemple"></p>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="field-birthday" id="field-birthday-label">Date de naissance (au format jj/mm/aaaa)</label> <label for="field-birthday" id="field-birthday-label">Date de naissance (au format jj/mm/aaaa)</label>
<div class="input-group align-items-center"> <div class="input-group align-items-center">
@ -114,24 +137,25 @@
</div> </div>
<p class="exemple"></p> <p class="exemple"></p>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="field-lieunaissance" id="field-lieunaissance-label">Lieu de naissance</label> <label for="field-nationality" id="field-nationality-label">Nationalité</label>
<div class="input-group align-items-center"> <div class="input-group align-items-center">
<input <input
type="text" type="text"
class="form-control" class="form-control"
id="field-lieunaissance" id="field-nationality"
name="lieunaissance" name="nationality"
autocomplete="country-name"
aria-invalid="true" aria-invalid="true"
placeholder="Lyon" placeholder="Belge"
required required
> >
<span class="validity" aria-hidden="true"></span> <span class="validity" aria-hidden="true"></span>
</div> </div>
<p class="exemple"></p> <p class="exemple"></p>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="field-address" id="field-address-label">Adresse</label> <label for="field-address" id="field-address-label">Adresse</label>
<div class="input-group align-items-center"> <div class="input-group align-items-center">
@ -141,15 +165,15 @@
id="field-address" id="field-address"
name="address" name="address"
aria-invalid="true" aria-invalid="true"
autocomplete="address-line1" autocomplete="address-line1"
placeholder="999 avenue de france" placeholder="999 avenue de Belgique"
required required
> >
<span class="validity" aria-hidden="true"></span> <span class="validity" aria-hidden="true"></span>
</div> </div>
<p class="exemple"></p> <p class="exemple"></p>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="field-town" id="field-town-label">Ville</label> <label for="field-town" id="field-town-label">Ville</label>
<div class="input-group align-items-center"> <div class="input-group align-items-center">
@ -160,91 +184,151 @@
name="town" name="town"
autocomplete="address-level1" autocomplete="address-level1"
aria-invalid="true" aria-invalid="true"
placeholder="Paris" placeholder="Bruxelles"
required required
> >
<span class="validity" aria-hidden="true"></span> <span class="validity" aria-hidden="true"></span>
</div> </div>
<p class="exemple"></p> <p class="exemple"></p>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="field-zipcode" id="field-zipcode-label" >Code Postal</label> <label for="field-zipcode" id="field-zipcode-label" >Code Postal</label>
<div class="input-group align-items-center"> <div class="input-group align-items-center">
<input <input
type="text" type="text"
inputmode="numeric"
pattern="[0-9]{5}"
min="00000"
max="99999"
class="form-control" class="form-control"
id="field-zipcode" id="field-zipcode"
name="zipcode" name="zipcode"
aria-invalid="true" aria-invalid="true"
autocomplete="postal-code" autocomplete="postal-code"
minlength="4" placeholder="1000"
maxlength="5"
placeholder="75001"
required required
> >
<span class="validity" aria-hidden="true"></span> <span class="validity" aria-hidden="true"></span>
</div> </div>
<p class="exemple"></p> <p class="exemple"></p>
</div> </div>
<fieldset> <div class="form-group">
<legend class="title-3">Choisissez le ou les motif(s) de sortie</legend> <label for="field-country" id="field-country-label">Pays</label>
<div class="form-check"> <div class="input-group align-items-center">
<input class="form-check-input" type="checkbox" name="field-reason" id="checkbox-travail" value="travail"> <input
<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> type="text"
class="form-control"
id="field-country"
name="country"
autocomplete="country-name"
aria-invalid="true"
placeholder="Belgique"
required
>
<span class="validity" aria-hidden="true"></span>
</div> </div>
<p class="exemple"></p>
</div>
<fieldset class="control">
<legend class="titre-3">Vous êtes : </legend>
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" name="field-reason" id="checkbox-courses" value="courses"> <input class="form-check-input" type="radio" name="field-type-nationality" id="radio-tiers" value="tiers">
<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" class="stores-link" title="Liste des commerces et établissements qui restent ouverts - nouvelle page" target="_blank">liste des commerces et établissements qui restent ouverts</a>).</label> <label class="form-nationality-label" for="radio-tiers">Ressortissant de pays tiers.</label>
<fieldset class='conditional'>
<legend class="titre-4">Choisissez le motif :</legend>
<div class="form-check">
<input class="form-check-input" type="radio" name="field-reason" id="radio-residence-tiers" value="residence">
<label class="form-check-label" for="radio-residence-tiers">Personnes ayant leur résidence principale en France ou dans lUnion européenne et pays assimilés<a href="#footnote1">[1]</a>, titulaires dun titre de séjour ou dun visa de long séjour français ou européen en cours de validité, ainsi que leurs conjoints et leurs enfants.</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="field-reason" id="radio-transit-tiers" value="transit">
<label class="form-check-label" for="radio-transit-tiers">Personnes en transit pour rejoindre leur pays dorigine, présentant le titre de voyage vers leur pays dorigine et
restant en zone internationale sans entrer sur le territoire national.</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="field-reason" id="radio-prof._sante-tiers" value="prof._sante">
<label class="form-check-label" for="radio-prof._sante-tiers">Professionnels de santé aux fins de lutter contre le Covid-19.</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="field-reason" id="radio-marchandises-tiers" value="marchandises">
<label class="form-check-label" for="radio-marchandises-tiers">Transporteurs de marchandises, dont les marins.</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="field-reason" id="radio-equipage-tiers" value="equipage">
<label class="form-check-label" for="radio-equipage-tiers">Équipages et personnels exploitant des vols passagers et cargo, ou voyageant comme passagers pour se positionner sur leur base de départ.</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="field-reason" id="radio-diplomatique-tiers" value="diplomatique">
<label class="form-check-label" for="radio-diplomatique-tiers">Personnels des missions diplomatiques et consulaires, ainsi que des organisations internationales ayant leur siège
ou un bureau en France, titulaires dun titre de séjour spécial ou dun visa D promae.</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="field-reason" id="radio-frontalier-tiers" value="frontalier">
<label class="form-check-label" for="radio-frontalier-tiers">Travailleurs frontaliers aux frontières intérieures terrestres.</label>
</div>
</fieldset>
</div> </div>
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" name="field-reason" id="checkbox-sante" value="sante"> <input class="form-check-input" type="radio" name="field-type-nationality" id="radio-eu" value="eu">
<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> <label class="form-nationality-label" for="radio-eu">Ressortissant de lUnion européenne et assimilés<a href="#footnote2">[2]</a>.</label>
<fieldset class='conditional'>
<legend class="titre-4">Choisissez le motif</legend>
<div class="form-check">
<input class="form-check-input" type="radio" name="field-reason" id="radio-residence-eu" value="residence">
<label class="form-check-label" for="radio-residence-eu">Personnes ayant leur résidence principale en France, ainsi que leurs conjoints et leurs enfants.</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="field-reason" id="radio-transit-eu" value="transit">
<label class="form-check-label" for="radio-transit-eu">Personnes transitant par la France pour rejoindre leur résidence, ainsi que leurs conjoints et leurs enfants.</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="field-reason" id="radio-prof._sante-eu" value="prof._sante">
<label class="form-check-label" for="radio-prof._sante-eu">Professionnels de santé aux fins de lutter contre le Covid-19.</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="field-reason" id="radio-marchandises-eu" value="marchandises">
<label class="form-check-label" for="radio-marchandises-eu">Transporteurs de marchandises, dont les marins.</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="field-reason" id="radio-equipage-eu" value="equipage">
<label class="form-check-label" for="radio-equipage-eu">Équipages et personnels exploitant des vols passagers et cargo, ou voyageant comme passagers pour se positionner sur leur base de départ.</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="field-reason" id="radio-diplomatique-eu" value="diplomatique">
<label class="form-check-label" for="radio-diplomatique-eu">Personnels des missions diplomatiques et consulaires, ainsi que des organisations internationales ayant leur siège
ou un bureau en France, titulaires dun titre de séjour spécial ou dun visa D promae.</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="field-reason" id="radio-frontalier" value="frontalier">
<label class="form-check-label" for="radio-frontalier">Travailleurs frontaliers aux frontières intérieures terrestres.</label>
</div>
</fieldset>
</div> </div>
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" name="field-reason" id="checkbox-famille" value="famille"> <input class="form-check-input" type="radio" name="field-type-nationality" id="radio-fr" value="fr">
<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> <label class="form-nationality-label" for="radio-fr">Ressortissant de nationalité française, ainsi que votre conjoint et enfants.</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>
</fieldset> </fieldset>
<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" autocomplete="off" placeholder="JJ/MM/YYYY" aria-invalid="true" required>
<span class="validity" aria-hidden="true"></span>
</div>
</div>
<div class="form-group">
<label for="field-heuresortie">Heure de sortie</label>
<div class="input-group align-items-center">
<input type="time" class="form-control" id="field-heuresortie" name="heure" autocomplete="off" aria-invalid="true" required>
<span class="validity" aria-hidden="true"></span>
</div>
</div>
<p class="text-center mt-5"> <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> <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> </p>
@ -253,14 +337,21 @@
L'attestation est téléchargée sur votre appareil. L'attestation est téléchargée sur votre appareil.
</div> </div>
</form> </form>
</div> </div>
<div class=""> <div class="">
<p id="footnotes">
<span id="footnote1">[1] Royaume-Uni, Islande, Liechtenstein, Norvège, Andorre, Monaco, Suisse, Saint-Marin, Saint Siège.</span><br>
<span id="footnote2">[2] Ressortissants de lUnion européenne et ressortissants britanniques, ainsi que les ressortissants islandais, liechtensteinois, norvégiens, andorrans, monégasques, suisses, saint-marinais, citoyens du Saint Siège (directive 2004/38/CE).</span>
</p>
<p class="github"> <p class="github">
Le code source de ce service est consultable sur <a href="https://github.com/LAB-MI/deplacement-covid-19" class="github-link">Github</a>. Le code source de ce service est consultable sur <a href="https://github.com/LAB-MI/deplacement-covid-19" class="github-link">Github</a>.
</p> </p>
<p class="label-mi"> <p class="label-mi">
Ministère de l'Intérieur - DNUM - SDIT Ministère de l'Intérieur - DNUM - SDIT
</p> </p>
<img class="center" src="/logo_dnum.svg" alt="logo dnum"> <img class="center" src="/logo_dnum.svg" alt="logo dnum">
</div> </div>
</main> </main>
@ -268,11 +359,11 @@
<div class="footer-links"> <div class="footer-links">
<a href="./confidentialite.html" title="Confidentialité - nouvelle page" target="_blank" class="footer-line footer-link">Confidentialité</a> <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.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> <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 dinfos au <a class="num-08" href="tel:0800130000">0 800 130 000</a></div> <div class="footer-line" >Plus dinfos au <a class="num-08" href="tel:0800130000">0 800 130 000</a></div>
<p class="footer-line" id="version"></p> <p class="footer-line" id="version"></p>
</div> </div>
</footer> </footer>
<div class="alert alert-info d-none" id="update-alert"> <div class="alert alert-info d-none" id="update-alert">
Une nouvelle version est disponible. Cliquer sur le bouton pour l'obtenir. Une nouvelle version est disponible. Cliquer sur le bouton pour l'obtenir.

View File

@ -29,14 +29,14 @@ h1 {
} }
/* Small devices (landscape phones, 576px and up) */ /* Small devices (landscape phones, 576px and up) */
@media (min-width: 576px) { @media (min-width: 576px) {
h1 { h1 {
font-size: 2.5em; font-size: 2.5em;
} }
} }
/* Medium devices (tablets, 768px and up) */ /* Medium devices (tablets, 768px and up) */
@media (min-width: 768px) { @media (min-width: 768px) {
h1 { h1 {
font-size: 3em; font-size: 3em;
} }
@ -46,6 +46,14 @@ svg {
height: 1em; height: 1em;
} }
ul.flex-justify {
display: -webkit-flex; /* Safari */
-webkit-align-items: center; /* Safari 7.0+ */
justify-content:space-between;
padding: 0;
margin: 0;
}
h1.flex.flex-wrap { h1.flex.flex-wrap {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
@ -83,6 +91,11 @@ a {
transform: translateY(-2px); transform: translateY(-2px);
} }
#form-profile .form-nationality-label {
font-weight: 800;
transform: translateY(-2px);
}
#form-generate .form-check { #form-generate .form-check {
margin: 10px; margin: 10px;
} }
@ -155,6 +168,27 @@ input:valid+span:after {
width: 50%; width: 50%;
} }
fieldset .form-language {
display: flex;
align-items: right;
}
.legend-language {
text-align: left;
font-size: 1rem;
color: #000000;
}
.form-language .form-check-label {
font-size: 1rem;
font-weight: bold;
color: #000000;
}
.flags {
width: 20px;
height: 15px;
}
.covid-title { .covid-title {
display: flex; display: flex;
@ -185,11 +219,10 @@ input:valid+span:after {
.text-alert{ .text-alert{
text-align: left; text-align: left;
font-style: italic;
color: #000000; color: #000000;
} }
.btn-attestation { .btn-attestation {
padding: 0.9em; padding: 0.9em;
font-size: 1.2em; font-size: 1.2em;
@ -308,13 +341,13 @@ input:valid+span:after {
text-decoration: none; text-decoration: none;
color: #ffffff; color: #ffffff;
} }
} }
em { em {
font-size: .8rem; font-size: .8rem;
} }
} }
.titre-2 { .titre-2 {
text-align: left; text-align: left;
font-size: 1.5rem; font-size: 1.5rem;
@ -401,3 +434,22 @@ input:valid+span:after {
left: 50%; left: 50%;
transform: translateX(-50%); transform: translateX(-50%);
} }
.control:not(:checked) ~ .conditional,
#radio-fr:not(:checked) ~ .conditional,
#radio-eu:not(:checked) ~ .conditional,
#radio-tiers:not(:checked) ~ .conditional {
clip: rect(0 0 0 0);
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
}
[id^="footnote"] {
margin: 30px auto;
max-width: 400px;
font-size: 0.8em;
color: #000000;
}

View File

@ -1 +1 @@
Sitemap: https://media.interieur.gouv.fr/deplacement-covid-19/sitemap.xml Sitemap: https://media.interieur.gouv.fr/deplacement-vers-france-covid-19/sitemap.xml

View File

@ -8,20 +8,20 @@
<url> <url>
<loc>https://media.interieur.gouv.fr/deplacement-covid-19/</loc> <loc>https://media.interieur.gouv.fr/deplacement-vers-france-covid-19/</loc>
<lastmod>2020-04-06T04:22:03+00:00</lastmod> <lastmod>2020-04-06T04:22:03+00:00</lastmod>
<priority>1.00</priority> <priority>1.00</priority>
</url> </url>
<url> <url>
<loc>https://media.interieur.gouv.fr/deplacement-covid-19/confidentialite.html</loc> <loc>https://media.interieur.gouv.fr/deplacement-vers-france-covid-19/confidentialite.html</loc>
<lastmod>2020-04-06T04:22:03+00:00</lastmod> <lastmod>2020-04-06T04:22:03+00:00</lastmod>
<priority>0.80</priority> <priority>0.80</priority>
</url> </url>
<url> <url>
<loc>https://media.interieur.gouv.fr/deplacement-covid-19/index.html</loc> <loc>https://media.interieur.gouv.fr/deplacement-vers-france-covid-19/index.html</loc>
<lastmod>2020-04-06T04:22:03+00:00</lastmod> <lastmod>2020-04-06T04:22:03+00:00</lastmod>
<priority>0.64</priority> <priority>0.64</priority>
</url> </url>
</urlset> </urlset>