Skip to content

Add functionality to use check-in app for QR code scanning with eventyay #115

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 23 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 18 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .env
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
VITE_TEST_API_URL=https://test-api.eventyay.com/v1
VITE_PROD_API_URL=https://api.eventyay.com/v1
VITE_TEST_API_URL=https://app-test-eventyay.com/v1
VITE_PROD_API_URL=https://app.eventyay.com/v1
VITE_LOCAL_PORT=8000
1,400 changes: 1,201 additions & 199 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"dayjs": "^1.11.9",
"mande": "^2.0.6",
"pinia": "^2.1.3",
"pinia-plugin-persistedstate": "^4.1.2",
"vue": "^3.3.4",
"vue-qrcode-reader": "^5.3.4",
"vue-router": "^4.2.2"
Expand Down
18 changes: 17 additions & 1 deletion src/components/Common/QRCamera.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import QRCamera from '@/components/Utilities/QRCamera.vue'
import { useCameraStore } from '@/stores/camera'
import { useProcessRegistrationStore } from '@/stores/processRegistration'
import { useProcessCheckInStore } from '@/stores/processCheckIn'
import { useProcessDeviceStore } from '@/stores/processDevice'
import { useProcessEventyayCheckInStore } from '@/stores/processEventyayCheckIn'
import { useLeadScanStore } from '@/stores/leadscan'
import { storeToRefs } from 'pinia'

const props = defineProps({
qrType: {
Expand All @@ -23,6 +27,9 @@ const props = defineProps({
const cameraStore = useCameraStore()
const processRegistrationStore = useProcessRegistrationStore()
const processCheckInStore = useProcessCheckInStore()
const processDeviceStore = useProcessDeviceStore()
const processEventyayCheckIn = useProcessEventyayCheckInStore()
const processLeadScan = useLeadScanStore()

const route = useRoute()
const stationId = route.params.stationId
Expand All @@ -37,12 +44,21 @@ async function processQR() {
if (props.qrType === 'checkIn') {
await processCheckInStore.checkInAttendeeScannerToRoom(stationId, scannerType)
}
if (props.qrType === 'device') {
await processDeviceStore.authDevice()
}
if (props.qrType === 'eventyaycheckin') {
await processEventyayCheckIn.checkIn()
}
if (props.qrType === 'eventyaylead') {
await processLeadScan.scanLead()
}
cameraStore.paused = false
}
</script>
<template>
<!-- padding to counter camera in mobile view -->
<div class="pt-24 text-center">
<div class="pt-2 text-center">
<h2 class="mb-3">
Scan QR<span v-if="scanType" class="capitalize"> - {{ scanType }}</span>
</h2>
Expand Down
89 changes: 89 additions & 0 deletions src/components/Common/TagInput.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<script setup>
import { onMounted } from 'vue'
import { useTagStore } from '@/stores/tags'
import { storeToRefs } from 'pinia'

const props = defineProps({
modelValue: {
type: Array,
required: true
}
})

const emit = defineEmits(['update:modelValue'])

const tagStore = useTagStore()
const { availableTags, currentTags, inputValue } = storeToRefs(tagStore)

onMounted(() => {
tagStore.fetchTags()
})
console.log(availableTags)
function handleInput(e) {
tagStore.handleCommaInput(e.target.value)
emit('update:modelValue', currentTags.value)
}

function handleKeydown(e) {
if (e.key === 'Enter') {
e.preventDefault()
if (inputValue.value) {
tagStore.addTag(inputValue.value)
inputValue.value = ''
emit('update:modelValue', currentTags.value)
}
} else if (e.key === 'Backspace' && !inputValue.value && currentTags.value.length > 0) {
tagStore.removeTag(currentTags.value.length - 1)
emit('update:modelValue', currentTags.value)
}
}

function addExistingTag(tag) {
tagStore.addTag(tag)
emit('update:modelValue', currentTags.value)
}

function removeTag(index) {
tagStore.removeTag(index)
emit('update:modelValue', currentTags.value)
}
</script>

<template>
<div class="w-full">
<div class="mb-2 flex flex-wrap gap-2">
<!-- Current tags -->
<div
v-for="(tag, index) in currentTags"
:key="index"
class="items-center rounded-full bg-primary px-2 py-1 text-sm text-white hover:bg-danger"
@click="removeTag(index)"
>
{{ tag }}
</div>
</div>

<!-- Available tags -->
<div class="mb-2 flex flex-wrap gap-2">
<button
v-for="tag in availableTags.filter((t) => !currentTags.includes(t))"
:key="tag"
@click="addExistingTag(tag)"
class="rounded-full border px-2 py-1 text-sm text-black hover:bg-secondary hover:text-white"
>
+ {{ tag }}
</button>
</div>

<!-- Tag input -->
<input
v-model="inputValue"
type="text"
class="w-full rounded border p-2"
placeholder="Add tags (comma-separated)"
@input="handleInput"
@keydown="handleKeydown"
@focus="tagStore.fetchTags()"
/>
</div>
</template>
139 changes: 139 additions & 0 deletions src/components/Eventyay/EventyayEventCheckIn.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
<script setup>
import StandardButton from '@/components/Common/StandardButton.vue'
import QRCamera from '@/components/Common/QRCamera.vue'
import { useLoadingStore } from '@/stores/loading'
import { useProcessEventyayCheckInStore } from '@/stores/processEventyayCheckIn'
import { useEventyayApi } from '@/stores/eventyayapi'
import { storeToRefs } from 'pinia'
import { watch, ref, onUnmounted } from 'vue'

const loadingStore = useLoadingStore()
loadingStore.contentLoaded()

const processEventyayCheckInStore = useProcessEventyayCheckInStore()
const { message, showSuccess, showError, badgeUrl, isGeneratingBadge } = storeToRefs(
processEventyayCheckInStore
)
const processApi = useEventyayApi()
const { apitoken, url, organizer, eventSlug } = processApi
const countdown = ref(5)
const timerInstance = ref(null)
const timeoutInstance = ref(null)
const notes = ref('')

function startCountdown() {
countdown.value = 5
timerInstance.value = setInterval(() => {
countdown.value--
if (countdown.value <= 0) {
clearInterval(timerInstance.value)
processEventyayCheckInStore.$reset()
}
}, 1000)
}

function stopTimer() {
if (timerInstance.value) {
clearInterval(timerInstance.value)
}
if (timeoutInstance.value) {
clearTimeout(timeoutInstance.value)
}
}

function handleNotesInput() {
stopTimer()
countdown.value = '...'
}

function handleSave() {
console.log('Saving notes:', notes.value)
processEventyayCheckInStore.$reset()
stopTimer()
}

function handleCancel() {
processEventyayCheckInStore.$reset()
stopTimer()
}

function handlePrintBadge() {
if (badgeUrl.value) {
console.log(`${url}${badgeUrl.value}`)
const printWindow = window.open(`${url}${badgeUrl.value}`)
if (printWindow) {
printWindow.onload = function () {
printWindow.print()
}
}
}
}

async function handlePrint() {
stopTimer()
console.log('Printing badge...')
console.log('Badge URL:', badgeUrl.value)
if (badgeUrl.value) {
await processEventyayCheckInStore.printBadge(badgeUrl.value)
}
handlePrintBadge()
console.log('Badge printed')
startCountdown()
}

watch([showSuccess, showError], ([newSuccess, newError], [oldSuccess, oldError]) => {
if ((!oldSuccess && newSuccess) || (!oldError && newError)) {
showPopup()
}
})

function showPopup() {
notes.value = ''
startCountdown()
timeoutInstance.value = setTimeout(() => {
processEventyayCheckInStore.$reset()
}, 5000)
}

// Cleanup timers when component is destroyed
onUnmounted(() => {
stopTimer()
})
</script>

<template>
<div class="flex h-screen w-full flex-col items-center justify-center">
<QRCamera qr-type="eventyaycheckin" scan-type="Check-In" />
<!-- Attendee Info Popup Modal -->
<div
v-if="(showSuccess || showError) && message?.attendee"
class="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50"
>
<div class="relative w-96 rounded bg-white p-5 shadow-lg">
<!-- Countdown display -->
<div
class="bg-gray-200 text-gray-600 absolute right-2 top-2 flex h-8 w-8 items-center justify-center rounded-full font-medium"
>
{{ countdown }}
</div>

<h2 :class="showError ? 'text-red-600 mb-2 text-xl' : 'text-green-600 mb-2 text-xl'">
{{ message.message }}
</h2>
<div>
<p><b>Name:</b> {{ message.attendee }}</p>
<div class="mt-4 flex flex-col space-y-3">
<StandardButton
v-if="badgeUrl && showSuccess"
type="button"
:text="isGeneratingBadge ? 'Generating Badge...' : 'Print Badge'"
:disabled="isGeneratingBadge"
@click="handlePrint"
class="btn-primary w-full justify-center"
/>
</div>
</div>
</div>
</div>
</div>
</template>
65 changes: 65 additions & 0 deletions src/components/Eventyay/EventyayEvents.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<script setup>
import { useLoadingStore } from '@/stores/loading'
import { useEventyayApi } from '@/stores/eventyayapi'
import { useEventyayEventStore } from '@/stores/eventyayEvent'

import { ref, onMounted, watchEffect } from 'vue'
import StandardButton from '@/components/Common/StandardButton.vue'
import { useRouter } from 'vue-router'

const loadingStore = useLoadingStore()
const router = useRouter()

const selectedEvent = ref(null)
const eventyayEventStore = useEventyayEventStore()
const { events, error } = eventyayEventStore
const processApi = useEventyayApi()
const { apitoken, url, organizer, selectedRole } = processApi

loadingStore.contentLoaded()
eventyayEventStore.fetchEvents(url, apitoken, organizer)

const submitForm = () => {
if (selectedEvent.value) {
const selectedEventData = events.find((event) => event.slug === selectedEvent.value)
if (selectedEventData) {
console.log('Selected Event:', selectedEventData)
console.log('Selected Role:', selectedRole)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will be removed

processApi.setEventSlug(selectedEventData.slug)
if (selectedRole === 'Exhibitor') router.push({ name: 'eventyayleedlogin' })
if (selectedRole === 'CheckIn' || selectedRole === 'Badge Station')
router.push({ name: 'eventyaycheckin' })
}
} else {
console.error('Please select an event.')
}
}
</script>
<template>
<div class="-mt-16 flex h-screen flex-col justify-center">
<div v-if="error" class="text-danger">{{ error }}</div>
<form v-if="events.length" @submit.prevent="submitForm">
<div v-for="event in events" :key="event.slug" class="mb-2">
<label>
<input type="radio" :value="event.slug" v-model="selectedEvent" />
{{ event.name.en }}
</label>
</div>
<div>
<StandardButton
type="submit"
text="Select Event"
class="btn-primary mt-6 w-full justify-center"
/>
</div>
</form>
<div v-if="!events.length && !error">
No events available
<StandardButton
text="Refresh"
class="btn-primary mt-6 w-1/2 justify-center"
@click="fetchEvents(url.value, apiToken.value, organiser.value)"
/>
</div>
</div>
</template>
Loading