qrcode: move validity code to a new element (#86853)

This commit is contained in:
Corentin Sechet 2024-02-22 15:57:43 +01:00 committed by Corentin Sechet
parent 99662694e6
commit 8e7056f4de
4 changed files with 211 additions and 51 deletions

View File

@ -7,6 +7,7 @@ $gray-light: #CECECE;
qrcode-reader {
position: absolute;
inset: 0;
box-sizing: border-box;
display: grid;
}
@ -88,6 +89,12 @@ qrcode-reader {
&.closed {
display: none;
}
&.invalid-qrcode {
qrcode-reader-validity {
display: none;
}
}
}
&--popup-errors {
@ -109,18 +116,6 @@ qrcode-reader {
margin: 10px;
}
&--validity {
font-weight: bold;
display: grid;
grid-template-columns: auto 1fr;
gap: 5px;
margin-bottom: 5px;
}
&--validity-label {
align-self: end;
}
&--data-items, &--metadata-items {
display: grid;
grid-template-columns: auto 1fr;
@ -173,3 +168,78 @@ qrcode-reader {
@keyframes spinner-keyframes {
100% { transform:rotate(360deg); }
}
.qrcode-reader-section {
--color: #{$green};
--background-color: #{$green-light};
display: grid;
gap: 3px;
&--header {
padding: 0 5px;
border-top-right-radius: 5px;
border-top-left-radius: 5px;
display: grid;
grid-template-columns: auto 1fr;
align-items: center;
gap: 5px;
background: var(--background-color);
font-size: 1.1rem;
color: var(--color);
fill: var(--color);
}
&--ok-icon, &--error-icon {
grid-area: 1 / 1 / 2 / 2;
}
&--error-icon {
display: none;
}
&--title {
font-size: 1.2rem;
grid-area: 1 / 2 / 2 / 3;
}
&--errors {
display: none;
color: $red;
margin: 0 15px;
}
&--content {
margin: 0 15px;
}
&.error {
--color: #{$red};
--background-color: #{$red-light};
.qrcode-reader-section--errors {
display: block;
}
.qrcode-reader-section--ok-icon {
display: none;
}
.qrcode-reader-section--error-icon {
display: block;
}
}
}
qrcode-reader-validity {
.qrcode-reader-section--content {
display: grid;
grid-template-columns: auto 1fr;
gap: 0 5px;
}
.qrcode-reader-validity--label {
align-self: end;
font-weight: bold;
}
}

View File

@ -46,20 +46,12 @@ const readerTemplate = template(`
</div>
<div class="qrcode-reader--popup closed">
<div class="qrcode-reader--popup-errors"></div>
<qrcode-reader-validity></qrcode-reader-validity>
<div class="qrcode-reader--popup-content"></div>
<button class="qrcode-reader--close-popup-button">${translate('close')}</button>
</div>
`)
const validityTemplate = template(`
<div class="qrcode-reader--validity">
<div class="qrcode-reader--validity-label">${translate('from')} :</div>
<div>{validityStart}</div>
<div>${translate('to')} :</div>
<div>{validityEnd}</div>
</div>
`)
const dataTemplate = template(`
<div class="qrcode-reader--data-items"></div>
<span class="qrcode-reader--metadata-items"></span>
@ -153,6 +145,7 @@ class QRCodeReader extends window.HTMLElement {
#popupContent
#errors
#currentCertificate
#validity
constructor () {
super()
@ -167,6 +160,7 @@ class QRCodeReader extends window.HTMLElement {
this.#popup = this.querySelector('.qrcode-reader--popup')
this.#popupContent = this.querySelector('.qrcode-reader--popup-content')
this.#errors = this.querySelector('.qrcode-reader--popup-errors')
this.#validity = this.querySelector('qrcode-reader-validity')
const closePopupButton = this.querySelector('.qrcode-reader--close-popup-button')
closePopupButton.addEventListener('click', () => {
@ -215,18 +209,22 @@ class QRCodeReader extends window.HTMLElement {
async #showResult (qrCodeContent) {
this.#popup.classList.remove('error')
this.#popup.classList.remove('closed')
this.#popup.classList.remove('invalid-qrcode')
this.#errors.innerHTML = ''
let signed
try {
signed = decodeBase45(qrCodeContent)
} catch (error) {
this.#showError(translate('invalid_qrcode'))
this.#popup.classList.add('invalid-qrcode')
return
}
const opened = window.nacl.sign.open(signed, this.#verifyKey)
if (opened == null) {
this.#showError(translate('invalid_signature'))
this.#popup.classList.add('invalid-qrcode')
return
}
@ -245,36 +243,13 @@ class QRCodeReader extends window.HTMLElement {
delete data.uuid
const validityStart = data.validity_start && new Date(parseFloat(data.validity_start) * 1000)
const validityStart = data.validity_start
delete data.validity_start
const validityEnd = data.validity_end && new Date(parseFloat(data.validity_end) * 1000)
const validityEnd = data.validity_end
delete data.validity_end
const now = new Date()
if (validityStart && now.getTime() < validityStart.getTime()) {
this.#errors.innerText = translate('not_yet_valid')
this.#popup.classList.add('error')
} else if (validityEnd && now.getTime() > validityEnd.getTime()) {
this.#errors.innerText = translate('expired')
this.#popup.classList.add('error')
}
const validityElement = validityTemplate.cloneNode(true)
if (validityStart) {
validityElement.innerHTML = validityElement.innerHTML.replace('{validityStart}', validityStart.toLocaleString())
} else {
validityElement.innerHTML = validityElement.innerHTML.replace('{validityStart}', translate('always'))
}
if (validityStart) {
validityElement.innerHTML = validityElement.innerHTML.replace('{validityEnd}', validityEnd.toLocaleString())
} else {
validityElement.innerHTML = validityElement.innerHTML.replace('{validityEnd}', translate('never'))
}
this.#popupContent.append(validityElement.content)
this.#validity.refresh(validityStart, validityEnd)
const dataElement = dataTemplate.cloneNode(true)
@ -294,6 +269,10 @@ class QRCodeReader extends window.HTMLElement {
this.#showMetadata(metadataItems, certificateUUID),
this.#showTally(tallyStatus, certificateUUID)
])
if(this.querySelector('.qrcode-reader-section.error')) {
this.#popup.classList.add('error')
}
}
async #showMetadata(itemsElement, certificateUUID) {
@ -422,3 +401,113 @@ class QRCodeReader extends window.HTMLElement {
}
window.customElements.define('qrcode-reader', QRCodeReader)
const sectionTemplate = template(`
<div class="qrcode-reader-section">
<div class="qrcode-reader-section--header">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="16"
height="16"
class="qrcode-reader-section--ok-icon">
>
<path d="M12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22ZM12 20C16.4183 20 20 16.4183 20 12C20 7.58172 16.4183 4 12 4C7.58172 4 4 7.58172 4 12C4 16.4183 7.58172 20 12 20ZM11.0026 16L6.75999 11.7574L8.17421 10.3431L11.0026 13.1716L16.6595 7.51472L18.0737 8.92893L11.0026 16Z"></path>
</svg>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
class="qrcode-reader-section--error-icon">
>
<path d="M12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22ZM12 20C16.4183 20 20 16.4183 20 12C20 7.58172 16.4183 4 12 4C7.58172 4 4 7.58172 4 12C4 16.4183 7.58172 20 12 20ZM11 15H13V17H11V15ZM11 7H13V13H11V7Z"></path>
</svg>
<div class="qrcode-reader-section--title"></div>
</div>
<div class="qrcode-reader-section--errors">
</div>
<div class="qrcode-reader-section--content">
</div>
</div>
`)
class QRCodeReaderSection extends HTMLElement {
#title
#content
#errors
#wrapper
connectedCallback() {
this.appendChild(sectionTemplate.content.cloneNode(true))
this.#wrapper = this.querySelector('.qrcode-reader-section')
this.#title = this.querySelector('.qrcode-reader-section--title')
this.#content = this.querySelector('.qrcode-reader-section--content')
this.#errors = this.querySelector('.qrcode-reader-section--errors')
}
setTitle(title) {
this.#title.innerText = title
}
setContent(element) {
this.#content.innerHTML = ''
this.#content.appendChild(element)
}
showError(message) {
this.#wrapper.classList.add('error')
this.#errors.innerHTML = message
}
reset() {
this.#wrapper.classList.remove('error')
this.#content.innerHTML = ''
}
}
const validityTemplate = template(`
<div class="qrcode-reader-validity--label">${translate('from')} :</div>
<div>{validityStart}</div>
<div class="qrcode-reader-validity--label">${translate('to')} :</div>
<div>{validityEnd}</div>
`)
class QRCodeReaderValidity extends QRCodeReaderSection {
connectedCallback() {
super.connectedCallback()
this.setTitle(translate('validity_section_title'))
}
refresh(validityStart, validityEnd) {
this.reset()
validityStart = validityStart && new Date(parseFloat(validityStart) * 1000)
validityEnd = validityEnd && new Date(parseFloat(validityEnd) * 1000)
const now = new Date()
if (validityStart && now.getTime() < validityStart.getTime()) {
this.showError(translate('not_yet_valid'))
} else if (validityEnd && now.getTime() > validityEnd.getTime()) {
this.showError(translate('expired'))
}
const validityElement = validityTemplate.cloneNode(true)
if (validityStart) {
validityElement.innerHTML = validityElement.innerHTML.replace('{validityStart}', validityStart.toLocaleString())
} else {
validityElement.innerHTML = validityElement.innerHTML.replace('{validityStart}', translate('always'))
}
if (validityStart) {
validityElement.innerHTML = validityElement.innerHTML.replace('{validityEnd}', validityEnd.toLocaleString())
} else {
validityElement.innerHTML = validityElement.innerHTML.replace('{validityEnd}', translate('never'))
}
this.setContent(validityElement.content)
}
}
window.customElements.define('qrcode-reader-validity', QRCodeReaderValidity)

View File

@ -26,7 +26,8 @@
"tally_status_ok": "{% trans 'QR Code tallied.' %}",
"metadata_network_error": "{% trans 'A network error occured while tallying the QR code.' %}",
"to": "{% trans 'To' %}",
"valid": "{% trans 'Valid QR code' %}"
"valid": "{% trans 'Valid QR code' %}",
"validity_section_title": "{% trans 'Validity' %}",
}
</script>
<link rel="stylesheet" href="{% static 'qrcode/css/style.css' %}">

View File

@ -106,7 +106,7 @@ qrcodeReaderTest('qrcode reader shows valid qrcode informations', async ({mock})
expect(popup.classList.contains('closed')).toBe(false)
expect(popup.classList.contains('error')).toBe(false)
const validity = popup.querySelector('.qrcode-reader--validity')
const validity = popup.querySelector('qrcode-reader-validity .qrcode-reader-section--content')
expect(validity.innerText).toMatch(/from :\s*10\/31\/2023, 11:00:00 PM\s*to :\s*12\/1\/2023, 10:59:59 PM/)
@ -137,7 +137,7 @@ qrcodeReaderTest('qrcode reader shows error on not yet valid or expired qrcodes'
await scan(okCodeData)
const popup = reader.querySelector('.qrcode-reader--popup')
const errors = popup.querySelector('.qrcode-reader--popup-errors')
const errors = popup.querySelector('qrcode-reader-validity .qrcode-reader-section--errors')
const closeButton = reader.querySelector('.qrcode-reader--close-popup-button')
expect(popup.classList.contains('closed')).toBe(false)
@ -227,7 +227,7 @@ qrcodeReaderTest('qrcode reader accepts certificate without validity dates', asy
await scan(certificateWithoutValidity)
const popup = reader.querySelector('.qrcode-reader--popup')
const validity = popup.querySelector('.qrcode-reader--validity')
const validity = popup.querySelector('qrcode-reader-validity .qrcode-reader-section--content')
expect(popup.classList.contains('closed')).toBe(false)
expect(popup.classList.contains('error')).toBe(false)