Skip to content

Commit

Permalink
feat: qr code check in
Browse files Browse the repository at this point in the history
  • Loading branch information
qin-guan committed Oct 23, 2023
1 parent 8f106fd commit e10e2ee
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 47 deletions.
34 changes: 29 additions & 5 deletions components/app/services/event/page.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { f7BlockTitle, f7Button, f7Card, f7Chip, f7Link, f7List, f7ListItem, f7NavRight, f7NavTitle, f7Navbar, f7Page, f7Searchbar, f7SkeletonBlock, f7Subnavbar, f7SwipeoutActions, f7SwipeoutButton } from 'framework7-vue'
import { f7, f7BlockTitle, f7Button, f7Card, f7Chip, f7Link, f7List, f7ListItem, f7NavRight, f7NavTitle, f7Navbar, f7Page, f7Searchbar, f7SkeletonBlock, f7Subnavbar, f7SwipeoutActions, f7SwipeoutButton } from 'framework7-vue'
import type { VirtualList } from 'framework7/types'
import type { UnwrapRef } from 'vue'
Expand Down Expand Up @@ -57,6 +57,27 @@ async function toggle(admissionKey: string) {
await set(dbRef(db, `${props.id}/${admissionKey}`), $dayjs().unix())
}
async function onScan(admissionKey: string) {
const attendee = attendees.value.find(attendee => attendee.admissionKey === admissionKey)
if (!attendee) // Attendee is in master list from API
return
if (checkedInUsers.value[admissionKey]) { // Attendee is already checked in
f7.toast.show({
text: `${attendee.name} already checked in`,
closeTimeout: 3000,
})
return
}
await set(dbRef(db, `${props.id}/${admissionKey}`), $dayjs().unix())
f7.toast.show({
text: `Checked in ${attendee.name}`,
closeTimeout: 10000,
})
}
// Virtualized list helpers
function searchAll(query: string, items: UnwrapRef<typeof attendees>) {
Expand Down Expand Up @@ -112,13 +133,13 @@ function renderExternal(_: unknown, data: VirtualList.VirtualListRenderData) {
</f7Card>

<f7List inset>
<f7Button tonal large @click="state.scannerOpened = !state.scannerOpened">
Open scanner
<f7Button tonal @click="state.scannerOpened = !state.scannerOpened">
{{ state.scannerOpened ? 'Close' : 'Open' }} scanner
</f7Button>
</f7List>

<div v-if="state.scannerOpened">
<AppServicesEventScanner />
<AppServicesEventScanner @scan="onScan" />
</div>

<f7BlockTitle>Attendees</f7BlockTitle>
Expand All @@ -137,7 +158,10 @@ function renderExternal(_: unknown, data: VirtualList.VirtualListRenderData) {
:style="`top: ${attendeeVirtualListData.topPosition}px`" :virtual-list-index="attendee.index"
>
<f7SwipeoutActions right>
<f7SwipeoutButton confirm-text="Are you sure?" :color="checkedInUsers[attendee.admissionKey] ? 'red' : 'green'" @click="toggle(attendee.admissionKey)">
<f7SwipeoutButton
confirm-text="Are you sure?"
:color="checkedInUsers[attendee.admissionKey] ? 'red' : 'green'" @click="toggle(attendee.admissionKey)"
>
{{ checkedInUsers[attendee.admissionKey] ? 'Check out' : 'Check in' }}
</f7SwipeoutButton>
</f7SwipeoutActions>
Expand Down
83 changes: 41 additions & 42 deletions components/app/services/event/scanner.vue
Original file line number Diff line number Diff line change
@@ -1,57 +1,56 @@
<script setup lang="ts">
import { Html5QrcodeScanner } from 'html5-qrcode'
import { useQuery } from '@tanstack/vue-query'
import { f7Button, f7List, f7ListInput, f7ListItem } from 'framework7-vue'

Check failure on line 3 in components/app/services/event/scanner.vue

View workflow job for this annotation

GitHub Actions / ci

'f7Button' is defined but never used

Check failure on line 3 in components/app/services/event/scanner.vue

View workflow job for this annotation

GitHub Actions / ci

'f7ListInput' is defined but never used
import { Html5Qrcode } from 'html5-qrcode'
const emit = defineEmits(['scan'])
const state = ref({
error: '',
selectedCameraId: '',
})
function onScanSuccess(decodedText, decodedResult) {
// Handle on success condition with the decoded text or result.
console.log(`Scan result: ${decodedText}`, decodedResult)
}
onMounted(() => {
const html5QrcodeScanner = new Html5QrcodeScanner(
'reader',
{ fps: 10, qrbox: 300 },
false,
)
const { data: cameras } = useQuery({
queryKey: ['cameras'],
queryFn: () => Html5Qrcode.getCameras(),
refetchOnWindowFocus: false,
})
html5QrcodeScanner.render(onScanSuccess, (err) => {
if (err)
state.value.error = err
})
watchEffect((onCleanup) => {
if (state.value.selectedCameraId.length === 0)
return
document.querySelectorAll('#html5-qrcode-button-camera-permission').forEach((elm) => {
console.log(elm)
elm.className += ' button'
const html5Qrcode = new Html5Qrcode('reader')
onCleanup(async () => {
await html5Qrcode.stop()
html5Qrcode.clear()
})
return async () => {
await html5QrcodeScanner.clear()
}
html5Qrcode.start(
state.value.selectedCameraId,
{
fps: 10,
qrbox: 250,
},
(decodedText) => {
emit('scan', decodedText)
},
() => {}, // Ignore all errors, unlikely to happen
)
})
</script>

<template>
<div class="flex flex-col items-center justify-center">
<div id="reader" class="w-[90%]" style="border: none;" />
<div v-if="state.error">
{{ state.error }}
</div>
<div>
<f7List inset>
<f7ListItem v-if="cameras" title="Camera" smart-select :smart-select-params="{ openIn: 'popover' }">
<select v-model="state.selectedCameraId" name="camera">
<option v-for="{ id, label } in cameras" :key="id" :value="id">
{{ label }}
</option>
</select>
</f7ListItem>
</f7List>

<div id="reader" />
</div>
</template>

<style scoped>
:deep(#html5-qrcode-button-camera-stop) {
display: none !important;
}
:deep(#reader div:first-child img:nth-child(2)) {
display: none !important; /* Hide info button */
}
#reader {
border: none !important;
}
</style>

0 comments on commit e10e2ee

Please # to comment.