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 5 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
1 change: 1 addition & 0 deletions hs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
sak:3670
5 changes: 5 additions & 0 deletions src/components/Common/QRCamera.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ 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'

const props = defineProps({
qrType: {
Expand All @@ -23,6 +24,7 @@ const props = defineProps({
const cameraStore = useCameraStore()
const processRegistrationStore = useProcessRegistrationStore()
const processCheckInStore = useProcessCheckInStore()
const processDeviceStore = useProcessDeviceStore()

const route = useRoute()
const stationId = route.params.stationId
Expand All @@ -37,6 +39,9 @@ async function processQR() {
if (props.qrType === 'checkIn') {
await processCheckInStore.checkInAttendeeScannerToRoom(stationId, scannerType)
}
if (props.qrType === 'device') {
await processDeviceStore.authDevice(cameraStore.qrCodeValue)
}
cameraStore.paused = false
}
</script>
Expand Down
62 changes: 62 additions & 0 deletions src/components/Eventyay/EventyayEvents.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<script setup>
import { useLoadingStore } from '@/stores/loading'
import { ref, onMounted, watchEffect } from 'vue'
import { useEventyayEventStore } from '@/stores/eventyayEvent'
import StandardButton from '@/components/Common/StandardButton.vue'
const loadingStore = useLoadingStore()

const apiToken = ref('')
const organiser = ref('')
const url = ref('')
const eventyayEventStore = useEventyayEventStore()
const selectedEvent = ref(null)

const { events, loading, error, fetchEvents } = eventyayEventStore

watchEffect(() => {
apiToken.value = localStorage.getItem('api_token')
organiser.value = localStorage.getItem('organizer')
url.value = localStorage.getItem('url')

if (apiToken.value && organiser.value && url.value) {
fetchEvents(url.value, apiToken.value, organiser.value)
loadingStore.contentLoaded()
}
})

const submitForm = () => {
if (selectedEvent.value) {
console.log('Selected event:', selectedEvent.value)
} else {
console.error('Please select an event.')
}
}
</script>
<template>
<div class="-mt-16 flex h-screen flex-col justify-center">
<div v-if="loading">Loading events...</div>
<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="!loading && !events.length && !error">No events available
<StandardButton
:text="'Refresh'"
class="btn-primary mt-6 w-full justify-center"
@click="fetchEvents(url.value, apiToken.value, organiser.value)"
/>
</div>
</div>
</template>
42 changes: 41 additions & 1 deletion src/components/LoginForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,20 @@ const userStore = useUserStore()

const email = ref('')
const password = ref('')
const server = ref('')
const showError = ref(false)
const showServerError = ref(false)

// router
const router = useRouter()

async function submitLogin() {
console.log(server.value)
Copy link

Choose a reason for hiding this comment

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

suggestion: Remove console.log statements

Consider removing the console.log statements used for debugging purposes before merging the code to the main branch.

Suggested change
console.log(server.value)
const router = useRouter()
async function submitLogin() {
if (server.value === '' || server.value === 'Select a Server') {
showServerError.value = true

if (server.value === '' || server.value === 'Select a Server') {
Copy link

Choose a reason for hiding this comment

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

suggestion: Consider using a constant for the default server value

Using a constant for the default server value ('Select a Server') can help avoid potential typos and make the code easier to maintain.

Suggested change
if (server.value === '' || server.value === 'Select a Server') {
const DEFAULT_SERVER_VALUE = 'Select a Server';
if (server.value === '' || server.value === DEFAULT_SERVER_VALUE) {

Copy link

Choose a reason for hiding this comment

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

suggestion: Use a constant for 'Select a Server'

Consider defining 'Select a Server' as a constant to avoid potential typos and make the code more maintainable.

Suggested change
if (server.value === '' || server.value === 'Select a Server') {
const SELECT_A_SERVER = 'Select a Server';
if (server.value === '' || server.value === SELECT_A_SERVER) {

Copy link
Member

Choose a reason for hiding this comment

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

Please define constants to compare server.value with.

Copy link

Choose a reason for hiding this comment

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

Absolutely, defining constants for strings like 'Select a Server' is a great practice. It helps prevent typos and makes the code more readable and maintainable. You can define the constant at the top of your script or in a separate configuration file if it's used across multiple components. Here's how you can implement it:

const SELECT_A_SERVER = 'Select a Server';

if (server.value === '' || server.value === SELECT_A_SERVER) {
  // Handle the error case
}

This way, if you ever need to change the placeholder text, you only have to update it in one place. Let me know if you need further assistance!

Copy link
Member Author

Choose a reason for hiding this comment

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

Done, I have fixed it in the recent commit.

Copy link

Choose a reason for hiding this comment

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

Great to hear that you've implemented the change! Using constants for strings is definitely a good practice for maintaining clean and error-free code. If you have any more questions or need further assistance with anything else in the PR, feel free to ask. Keep up the good work!

showServerError.value = true
return
}
showServerError.value = false
loadingStore.contentLoading()
showError.value = false

Expand All @@ -42,6 +50,18 @@ async function submitLogin() {
loadingStore.contentLoaded()
}

function registerDevice() {
console.log(server.value)
if (server.value === '' || server.value === 'Select a Server') {
showServerError.value = true
return
}
showServerError.value = false
router.push({
name: 'device'
})
}

onMounted(() => {
if (authStore.isAuthenticated) {
router.push({
Expand Down Expand Up @@ -88,6 +108,15 @@ onMounted(() => {
</div>
</div>

<div>
<label for="select">Select a Server</label>
<select id="select" v-model="server" class="mt-2 block w-full">
<option>Open-Event</option>
<option>Eventyay</option>
<option>Testing</option>
</select>
</div>

<div>
<StandardButton
:type="'submit'"
Expand All @@ -101,13 +130,24 @@ onMounted(() => {
</div>
</form>

<p class="mt-10 text-center text-sm">
<p class="mt-5 text-center text-sm">
<span>Forgot password?</span>
{{ ' ' }}
<a href="https://eventyay.com/reset-password" class="font-medium leading-6 text-primary"
>Click here to reset password</a
>
</p>
<StandardButton
:type="'button'"
:text="'Register-Device'"
:disabled="false"
class="btn-primary mt-6 w-full justify-center"
@click="registerDevice"
>
</StandardButton>
Copy link

Choose a reason for hiding this comment

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

suggestion: Button text should be a prop

Consider passing the button text as a prop to the StandardButton component for better reusability and consistency.

Suggested change
<StandardButton
:type="'button'"
:text="'Register-Device'"
:disabled="false"
class="btn-primary mt-6 w-full justify-center"
@click="registerDevice"
>
</StandardButton>
<StandardButton
:type="'button'"
:text="buttonText"
:disabled="false"
class="btn-primary mt-6 w-full justify-center"
@click="registerDevice"
/>

Copy link
Member

Choose a reason for hiding this comment

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

If you are passing a static string (Register-Device) to text prop, no point to use bind syntax and JS expression.

Just pass as normal HTML attribute like this:

<StandardButton text='Register-Device' ... />

Copy link

Choose a reason for hiding this comment

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

Great point, @hongquan! If the button text is static and won't change, using a regular HTML attribute is indeed more straightforward and cleaner. However, if there's a possibility that the button text might need to be dynamic or localized in the future, passing it as a prop could be beneficial for flexibility. It really depends on the expected use case. If you foresee any changes or need for localization, consider using a prop; otherwise, your suggestion is perfectly valid for a static text.

Copy link
Member Author

Choose a reason for hiding this comment

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

As this was the convention followed everywhere else where StandardButton component was previously being used, I too did the same should I still change it ?

Copy link

Choose a reason for hiding this comment

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

If the convention in your codebase is to use the binding syntax for the text prop, even for static strings, it's generally a good idea to maintain consistency with the existing code. This can help with readability and maintainability, especially if there's a chance that the text might become dynamic or require localization in the future.

However, if you and your team decide that using a regular HTML attribute for static text is more appropriate and aligns with your coding standards, you can certainly make that change. Consistency is key, so whichever approach you choose, it would be beneficial to apply it uniformly across your project.

If you're unsure, it might be worth discussing with your team to see if there's a preference or if there's a plan to make the button text dynamic in the future.

Copy link
Member

Choose a reason for hiding this comment

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

That is not convention, previous people simply don't understand VueJS well enough.

Copy link

Choose a reason for hiding this comment

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

It sounds like there's a bit of a misunderstanding about the use of VueJS syntax in your codebase. If the current practice is to use the binding syntax for static strings, it might be due to a lack of understanding rather than a deliberate convention.

Given this, it could be a good opportunity to align on best practices with your team. Using a regular HTML attribute for static text is more straightforward and should be preferred unless there's a specific reason to use the binding syntax, such as future plans for dynamic content or localization.

If you decide to switch to using regular HTML attributes for static text, it might be helpful to update the existing code for consistency. This could also be a chance to document this as a coding standard for your team to follow moving forward. If you need further guidance, consider discussing this with your team or a lead developer to ensure everyone is on the same page.

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 is how the button is defined

<script setup>
const props = defineProps({
  type: {
    type: String,
    default: 'button'
  },
  text: {
    type: String,
    default: ''
  },
  disabled: {
    type: Boolean,
    default: false
  },
  icon: {
    type: Function,
    default: () => null
  },
  iconAfter: {
    type: Function,
    default: () => null
  }
})
</script>

<template>
  <button :type="type" :disabled="disabled" class="inline-flex items-center">
    <component :is="icon" v-if="icon" :class="[text !== '' ? 'mr-1 h-5' : 'h-5']" />
    <span>{{ text }}</span>
    <component :is="iconAfter" v-if="iconAfter" :class="[text !== '' ? 'ml-1 h-5' : 'h-5']" />
  </button>
</template>

Hence I'm passing it as a prop

Copy link
Member

Choose a reason for hiding this comment

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

I know that it is a prop, see my previous comment:

If you are passing a static string (Register-Device) to text prop

Being a "prop", it doesn't mean that you always have to use Vue syntax when passing a value to it.

<div v-if="showServerError" class="mt-5">
<p class="text-center text-sm text-danger">Please select a server first</p>
Copy link

Choose a reason for hiding this comment

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

suggestion: Error message should be configurable

Consider making the error message configurable or translatable to support multiple languages.

Suggested change
<div v-if="showServerError" class="mt-5">
<p class="text-center text-sm text-danger">Please select a server first</p>
<div v-if="showServerError" class="mt-5">
<p class="text-center text-sm text-danger">{{ errorMessage }}</p>

</div>
</div>
</div>
</template>
32 changes: 32 additions & 0 deletions src/components/Registration/Device/Device.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<script setup>
import QRCamera from '@/components/Common/QRCamera.vue'
import { useLoadingStore } from '@/stores/loading'
import StandardButton from '@/components/Common/StandardButton.vue'
import { useProcessDeviceStore } from '@/stores/processDevice'
const loadingStore = useLoadingStore()
loadingStore.contentLoaded()

const processDeviceStore = useProcessDeviceStore()

async function registerDevice() {
const auth_token = document.getElementById('auth_token').value
const payload = { handshake_version: 1, url: 'http://localhost', token: auth_token }
await processDeviceStore.authDevice(JSON.stringify(payload))
}
</script>

<template>
<div
class="-mt-16 grid h-screen w-full grid-cols-1 place-items-center items-center justify-center align-middle"
>
<QRCamera :qr-type="'device'" :scan-type="'Device Registration'" />
<div>
<input type="text" id="auth_token" placeholder="Device Key" class="input" />
<StandardButton
:text="'Register Device'"
class="btn-primary mt-6 w-full justify-center"
@click="registerDevice()"
/>
</div>
</div>
</template>
12 changes: 12 additions & 0 deletions src/router/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import AuthTemplate from '@/AuthTemplate.vue'
import CheckInCamera from '@/components/CheckIn/CheckInCamera.vue'
import CheckInStats from '@/components/CheckIn/CheckInStats.vue'
import EventyayEvents from '@/components/Eventyay/EventyayEvents.vue'
import Device from '@/components/Registration/Device/Device.vue'
import RegistrationKiosk from '@/components/Registration/Kiosk/KioskOverview.vue'
import RegistrationStats from '@/components/Registration/Station/RegistrationStats.vue'
import RegistrationStation from '@/components/Registration/Station/StationOverview.vue'
Expand All @@ -20,6 +22,16 @@ const router = createRouter({
name: 'userAuth',
component: UserAuth
},
{
path: '/device',
name: 'device',
component: Device
},
{
path: '/eventyayevents',
name: 'eventyayevents',
component: EventyayEvents
},
{
path: '/panel',
name: 'auth',
Expand Down
33 changes: 33 additions & 0 deletions src/stores/eventyayEvent.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { mande } from 'mande'
import { defineStore } from 'pinia'
import { ref } from 'vue'

export const useEventyayEventStore = defineStore('eventyayEvent', () => {
const events = ref([])
const loading = ref(false)
const error = ref(null)

async function fetchEvents(url, apiToken, organizer) {
loading.value = true
error.value = null

try {
const api = mande(url, { headers: { Authorization: `Device ${apiToken}` } })
console.log(`/api/v1/organizers/${organizer}/events/`)
Copy link

Choose a reason for hiding this comment

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

issue (performance): Remove console.log statements

Leaving console.log statements in production code can lead to performance issues and potential information leakage. Consider removing or replacing them with proper logging mechanisms.

Copy link

Choose a reason for hiding this comment

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

suggestion: Remove console.log statements

Consider removing the console.log statements used for debugging purposes before merging the code to the main branch.

Suggested change
console.log(`/api/v1/organizers/${organizer}/events/`)
// console.log(`/api/v1/organizers/${organizer}/events/`)

const response = await api.get(`/api/v1/organizers/${organizer}/events/`)
console.log('Hello', response)
Copy link

Choose a reason for hiding this comment

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

issue (performance): Remove console.log statements

Leaving console.log statements in production code can lead to performance issues and potential information leakage. Consider removing or replacing them with proper logging mechanisms.

events.value = response.results
} catch (err) {
error.value = err.message
} finally {
loading.value = false
}
}

return {
events,
loading,
error,
fetchEvents
}
})
96 changes: 96 additions & 0 deletions src/stores/processDevice.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { useCameraStore } from '@/stores/camera'
import { mande } from 'mande'
import { defineStore } from 'pinia'
import { computed, ref } from 'vue'
import { useRouter } from 'vue-router'
export const useProcessDeviceStore = defineStore('processDevice', () => {
const cameraStore = useCameraStore()
const router = useRouter()
const message = ref('')
const showSuccess = ref(false)
const showError = ref(false)

function $reset() {
message.value = ''
showSuccess.value = false
showError.value = false
}

const response = computed(() => {
let classType = ''
if (showSuccess.value) {
classType = 'text-success'
}
if (showError.value) {
classType = 'text-danger'
}
return {
message: message.value,
class: classType
}
})

function showErrorMsg() {
showSuccess.value = false
showError.value = true
}

function showSuccessMsg() {
showSuccess.value = true
showError.value = false
}

async function authDevice(val) {
try {
const qrData = JSON.parse(val)
console.log(qrData)
if (qrData.handshake_version > 1) {
message.value = 'Unsupported handshake version'
Comment on lines +53 to +54
Copy link

Choose a reason for hiding this comment

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

suggestion: Use a constant for handshake version

Consider defining the handshake version as a constant to avoid magic numbers and make the code more maintainable.

Suggested change
if (qrData.handshake_version > 1) {
message.value = 'Unsupported handshake version'
const HANDSHAKE_VERSION_SUPPORTED = 1;
if (qrData.handshake_version > HANDSHAKE_VERSION_SUPPORTED) {
message.value = 'Unsupported handshake version'

showErrorMsg()
return
} else {
console.log('Handshake version is 1')
}

const payload = {
token: qrData.token,
hardware_brand: 'Test',
hardware_model: 'test',
os_name: 'Test',
os_version: '2.3.6',
software_brand: 'check-in',
software_version: 'x.x'
}
let url = qrData.url
if (url.includes('localhost')) {
url = `${url}:8000` // Add your desired port number here
Copy link

Choose a reason for hiding this comment

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

suggestion: Avoid hardcoding port number

Consider making the port number configurable instead of hardcoding it.

Suggested change
if (url.includes('localhost')) {
url = `${url}:8000` // Add your desired port number here
const port = process.env.PORT || 8000;
if (url.includes('localhost')) {
url = `${url}:${port}`

Copy link
Member

Choose a reason for hiding this comment

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

Please do as sourcery-ai suggested.

Copy link

Choose a reason for hiding this comment

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

Absolutely, making the port number configurable is a good practice as it enhances flexibility and adaptability across different environments. By using process.env.PORT || 8000, you allow the application to dynamically adapt to the port specified in the environment variables, while still providing a default value of 8000 if none is set. This approach is particularly useful in development and deployment scenarios where the port might vary. If you need any help implementing this change or have further questions, feel free to ask!

Copy link
Member Author

Choose a reason for hiding this comment

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

I have fixed this in the recent commit.

Copy link

Choose a reason for hiding this comment

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

Great to hear that you've implemented the change, @Sak1012! Making the port configurable is a solid improvement. If you encounter any issues or have further questions as you continue working on this feature, feel free to reach out. Keep up the good work!

}
console.log(url)
const api = mande(url, { headers: { 'Content-Type': 'application/json' } })
const response = await api.post('/api/v1/device/initialize', payload)
console.log(response)
if (response) {
const data = response
console.log(data.api_token)
localStorage.setItem('api_token', data.api_token)
localStorage.setItem('organizer', data.organizer)
localStorage.setItem('url', url)
router.push({ name: 'eventyayevents' })
showSuccessMsg()
} else {
console.log('Something happend')
showErrorMsg()
}
} catch (error) {
console.log(error)
console.log('Error in catch')
showErrorMsg()
}
}

return {
response,
$reset,
authDevice
}
})