Merge branch 'dev'
Some checks failed
Build and Deploy / Deploy (push) Failing after 30s

This commit is contained in:
Xeovalyte 2023-05-10 12:16:55 +02:00
commit c9e6cf37e9
28 changed files with 3001 additions and 2622 deletions

View File

@ -1,12 +1,12 @@
<template> <template>
<div v-if="!userLoaded" class="bg-neutral-100 dark:bg-neutral-900 text-primary h-screen flex justify-center items-center"> <div v-if="!userStore.userLoaded" class="bg-neutral-100 dark:bg-neutral-900 text-primary h-screen flex justify-center items-center">
<div> <div>
<Icon size="4em" name="ion:load-c" class="animate-spin" /> <Icon size="4em" name="ion:load-c" class="animate-spin" />
<h2 class="mt-2 font-bold">Loading...</h2> <h2 class="mt-2 font-bold">Loading...</h2>
</div> </div>
</div> </div>
<div v-else class=""> <div v-else class="">
<div v-if="isAuthenticated" class="bg-neutral-100 dark:bg-neutral-900 text-primary h-screen flex flex-col"> <div v-if="userStore.isAuthenticated" class="bg-neutral-100 dark:bg-neutral-900 text-primary h-screen flex flex-col">
<LayoutTopbar /> <LayoutTopbar />
<div class="overflow-y-auto pt-3"> <div class="overflow-y-auto pt-3">
<NuxtPage /> <NuxtPage />
@ -20,35 +20,12 @@
</template> </template>
<script setup> <script setup>
import { doc, getFirestore, serverTimestamp, writeBatch, collection, getDoc } from "firebase/firestore";
import { getAuth, onAuthStateChanged, signInWithEmailAndPassword } from "firebase/auth";
import { getMessaging, getToken, onMessage } from "firebase/messaging";
import { PushNotifications } from '@capacitor/push-notifications';
import { Device } from '@capacitor/device'; import { Device } from '@capacitor/device';
import { useToast } from 'vue-toastification'
const db = getFirestore() const userStore = useUserStore()
const route = useRoute()
const toast = useToast()
const isAuthenticated = ref(false) onMounted(async () => {
const user = ref('frikandel') userStore.init()
const registrationToken = ref('')
const auth = ref(null)
const userLoaded = ref(false)
const userData = ref(null)
const userPersons = ref([])
const userAllPersons = ref([])
const calEvents = ref([])
const news = ref(null)
const contestTimes = ref(null)
const competitors = ref([])
const users = ref([])
const messaging = ref(null)
onMounted(() => {
auth.value = getAuth()
Device.getInfo().then(info => { Device.getInfo().then(info => {
if (info.platform === 'ios') document.getElementsByClassName('top-right')[0].classList.add('toastios') if (info.platform === 'ios') document.getElementsByClassName('top-right')[0].classList.add('toastios')
@ -57,176 +34,12 @@ onMounted(() => {
if (process.client) { if (process.client) {
if ('serviceWorker' in navigator && window.isSecureContext) { if ('serviceWorker' in navigator && window.isSecureContext) {
Device.getInfo().then(info => { Device.getInfo().then(info => {
if (info.platform === 'web') registerSW() if (info.platform === 'web') registerServiceWorker()
else document.getElementsByClassName('top-right')[0].classList.add('toastios') else document.getElementsByClassName('top-right')[0].classList.add('toastios')
}); });
} }
} }
onAuthStateChanged(auth.value, async (usr) => {
if (usr) {
user.value = usr
let docRef = doc(db, "users", user.value.uid);
let docSnap = await getDoc(docRef);
if (docSnap.exists()) {
const data = docSnap.data()
userData.value = data
getPersons(userData.value.relatiecodes)
} else {
setTimeout(() => window.location.reload(true), 1000)
}
if (!userData.value.sendNews && route.path === '/news/newmessage') navigateTo('/')
if (!userData.value.admin && route.path.startsWith('/settings/admin')) navigateTo('/')
isAuthenticated.value = true
logDeviceInfo()
} else {
isAuthenticated.value = false
user.value = null
userData.value = null
userPersons.value = []
}
userLoaded.value = true
})
}) })
const getPersons = async (persons) => {
userPersons.value = [];
for (let i = 0; i < persons.length; i++) {
const docRef = doc(db, "ledenlijst", persons[i]);
const docSnap = await getDoc(docRef);
if (docSnap.exists()) {
userPersons.value.push(docSnap.data())
}
}
}
const setupNotifications = () => {
console.log('Initializing HomePage');
// Request permission to use push notifications
// iOS will prompt user and return if they granted permission or not
// Android will just grant without prompting
PushNotifications.requestPermissions().then(result => {
if (result.receive === 'granted') {
// Register with Apple / Google to receive push via APNS/FCM
PushNotifications.register()
} else {
toast.error('Error tijdens het registrenen van push notificaties')
}
});
// On success, we should be able to receive notifications
PushNotifications.addListener('registration',
async (token) => {
// alert('Push registration success, token: ' + token.value);
registrationToken.value = token
const { error } = await useFetch('/api/subscribetotopic', {
method: 'post',
body: { topic: 'all', registrationToken: token.value }
})
if (error.value) {
console.log(error.value)
return toast.error('Error tijdens het krijgen van relateicodes')
}
console.log('Subscribed to topic!')
}
);
// Some issue with our setup and push will not work
PushNotifications.addListener('registrationError',
(error) => {
toast.error('Error tijdens het registreren van push notificaties')
console.log(error)
}
);
// Show us the notification payload if the app is open on our device
PushNotifications.addListener('pushNotificationReceived',
(notification) => {
toast.info(`${notification.title}`, {
onClick: () => navigateTo('/news')
})
}
);
// Method called when tapping on a notification
PushNotifications.addListener('pushNotificationActionPerformed',
(notification) => {
navigateTo('/news')
}
);
}
const registerFCM = () => {
getToken(messaging.value, { vapidKey: 'BI7l3nyGV6wJcFh7wrwmQ42W7RSXl46bmhXZJmDd4P-0K_JFP0ClTqjO-rr5H5DXBbmVR4kXwxFpUlo_d6cUy4Q' }).then(async (currentToken) => {
if (currentToken) {
console.log(currentToken)
const { error} = await useFetch('/api/subscribetotopic', {
method: 'post',
body: { topic: 'all', registrationToken: currentToken }
})
if (error.value) {
console.log(error.value)
return toast.error('Error tijdens het registreren van push notifications')
}
registrationToken.value = currentToken
console.log('Subscribed to topic!')
} else {
// Show permission request UI
console.log('No registration token available. Request permission to generate one.');
// ...
}
}).catch((err) => {
console.log('An error occurred while retrieving token. ', err);
// ...
});
}
const registerSW = () => {
messaging.value = getMessaging()
navigator.serviceWorker.register('/sw.js').catch(e => alert(e));
onMessage(messaging.value, (payload) => {
console.log('Message received. ', payload);
toast.info(`${payload.notification.title}`, {
onClick: () => navigateTo('/news')
})
});
}
const logDeviceInfo = async () => {
const info = await Device.getInfo();
console.log(info);
if (info.platform !== 'web') setupNotifications()
else registerFCM()
};
const ledenlijst = ref([])
provide('firebase', { db, ledenlijst, isAuthenticated, user, userData, userPersons, auth, users, userAllPersons, getPersons, calEvents, news, registrationToken, contestTimes, competitors, registrationToken })
</script> </script>
<style> <style>

View File

@ -43,17 +43,17 @@
<script setup> <script setup>
import { useToast } from 'vue-toastification' import { useToast } from 'vue-toastification'
import { createUserWithEmailAndPassword, signInWithEmailAndPassword, sendPasswordResetEmail } from "firebase/auth"; import { createUserWithEmailAndPassword, signInWithEmailAndPassword, sendPasswordResetEmail } from "firebase/auth";
import { doc, setDoc } from "firebase/firestore"; import { doc, setDoc, getFirestore } from "firebase/firestore";
const { auth, db, userAllPersons } = inject('firebase')
const toast = useToast() const toast = useToast()
const userStore = useUserStore()
const showPassword = ref(false) const showPassword = ref(false)
const creatingAccount = ref(false) const creatingAccount = ref(false)
const disableButtons = ref(false) const disableButtons = ref(false)
const db = getFirestore()
const form = ref({ const form = ref({
email: '', email: '',
password: '', password: '',
@ -63,8 +63,7 @@ const form = ref({
const submitLoginForm = () => { const submitLoginForm = () => {
disableButtons.value = true disableButtons.value = true
signInWithEmailAndPassword(userStore.auth, form.value.email, form.value.password)
signInWithEmailAndPassword(auth.value, form.value.email, form.value.password)
.then(() => disableButtons.value = false) .then(() => disableButtons.value = false)
.catch(async (error) => { .catch(async (error) => {
@ -101,9 +100,9 @@ const submitCreateForm = () => {
disableButtons.value = true disableButtons.value = true
createUserWithEmailAndPassword(auth.value, form.value.email, form.value.newPassword) createUserWithEmailAndPassword(userStore.auth, form.value.email, form.value.newPassword)
.then(async (userCredential) => { .then(async (userCredential) => {
const idToken = await auth.value.currentUser.getIdToken(true) const idToken = await userStore.auth.currentUser.getIdToken(true)
const { error, data } = await useFetch('/api/getrelatiecodes', { const { error, data } = await useFetch('/api/getrelatiecodes', {
method: 'post', method: 'post',
body: { email: form.value.email, token: idToken } body: { email: form.value.email, token: idToken }
@ -132,7 +131,7 @@ const submitCreateForm = () => {
} }
}) })
userAllPersons.value = data.value.persons userStore.userAllPersons = data.value.persons
if (data.value.relatiecodes.length > 1) { if (data.value.relatiecodes.length > 1) {
return navigateTo('/settings/config/managerelatiecodes') return navigateTo('/settings/config/managerelatiecodes')
@ -150,7 +149,7 @@ const submitCreateForm = () => {
} }
const forgotPassword = () => { const forgotPassword = () => {
sendPasswordResetEmail(auth.value, form.value.email) sendPasswordResetEmail(userStore.auth, form.value.email)
.then(() => { .then(() => {
toast.info('Wachtwoord vergeten email verstuurd!') toast.info('Wachtwoord vergeten email verstuurd!')
}) })

View File

@ -17,7 +17,7 @@
<Icon size="1.8em" name="ion:settings-sharp" /> <Icon size="1.8em" name="ion:settings-sharp" />
<span>Settings</span> <span>Settings</span>
</NuxtLink> </NuxtLink>
<NuxtLink v-if="userPersons[0] && userPersons.filter(a => a.wedstrijdteam).length > 0" to="/wedstrijd" class="flex flex-col items-center hover:cursor-pointer drop-shadow" :class="route.path.startsWith('/wedstrijd') ? 'text-primary' : ''"> <NuxtLink v-if="userStore.userPersons[0] && userStore.userPersons.filter(a => a.wedstrijdteam).length > 0" to="/wedstrijd" class="flex flex-col items-center hover:cursor-pointer drop-shadow" :class="route.path.startsWith('/wedstrijd') ? 'text-primary' : ''">
<Icon size="1.8em" name="ion:podium-outline" /> <Icon size="1.8em" name="ion:podium-outline" />
<span>Wedstrijd</span> <span>Wedstrijd</span>
</NuxtLink> </NuxtLink>
@ -29,7 +29,7 @@
import { Device } from '@capacitor/device'; import { Device } from '@capacitor/device';
const route = useRoute() const route = useRoute()
const { userData, userPersons } = inject('firebase') const userStore = useUserStore()
const platform = ref(null) const platform = ref(null)

View File

@ -0,0 +1,123 @@
import { getMessaging, getToken, onMessage } from 'firebase/messaging'
import { PushNotifications } from '@capacitor/push-notifications';
import { Device } from '@capacitor/device';
export const setupIosNotifications = () => {
const userStore = useUserStore()
const toast = useToast()
// Request permission to use push notifications
// iOS will prompt user and return if they granted permission or not
// Android will just grant without prompting
PushNotifications.requestPermissions().then(result => {
if (result.receive === 'granted') {
// Register with Apple / Google to receive push via APNS/FCM
PushNotifications.register()
} else {
toast.error('Error tijdens het registrenen van push notificaties')
}
});
// On success, we should be able to receive notifications
PushNotifications.addListener('registration',
async (token) => {
// alert('Push registration success, token: ' + token.value);
userStore.registrationToken = token
const { error } = await useFetch('/api/subscribetotopic', {
method: 'post',
body: { topic: 'all', registrationToken: token.value }
})
if (error.value) {
console.log(error.value)
return toast.error('Error tijdens het krijgen van relateicodes')
}
console.log('Subscribed to topic!')
}
);
// Some issue with our setup and push will not work
PushNotifications.addListener('registrationError',
(error) => {
toast.error('Error tijdens het registreren van push notificaties')
console.log(error)
}
);
// Show us the notification payload if the app is open on our device
PushNotifications.addListener('pushNotificationReceived',
(notification) => {
toast.info(`${notification.title}`, {
onClick: () => navigateTo('/news')
})
}
);
// Method called when tapping on a notification
PushNotifications.addListener('pushNotificationActionPerformed',
(notification) => {
navigateTo('/news')
}
);
}
export const setupWebNotifications = () => {
const messaging = getMessaging()
const userStore = useUserStore()
const toast = useToast()
getToken(messaging, { vapidKey: 'BI7l3nyGV6wJcFh7wrwmQ42W7RSXl46bmhXZJmDd4P-0K_JFP0ClTqjO-rr5H5DXBbmVR4kXwxFpUlo_d6cUy4Q' }).then(async (currentToken) => {
if (currentToken) {
console.log(currentToken)
const { error} = await useFetch('/api/subscribetotopic', {
method: 'post',
body: { topic: 'all', registrationToken: currentToken }
})
if (error.value) {
console.log(error.value)
return toast.error('Error tijdens het registreren van push notifications')
}
userStore.registrationToken = currentToken
console.log('Subscribed to topic!')
} else {
// Show permission request UI
console.log('No registration token available. Request permission to generate one.');
// ...
}
}).catch((err) => {
console.log('An error occurred while retrieving token. ', err);
// ...
});
}
export const setupNotifications = async () => {
const info = await Device.getInfo();
console.log(info);
if (info.platform !== 'web') setupIosNotifications()
else setupWebNotifications()
}
export const registerServiceWorker = () => {
const messaging = getMessaging()
const toast = useToast()
navigator.serviceWorker.register('/sw.js').catch(e => alert(e));
onMessage(messaging, (payload) => {
console.log('Message received. ', payload);
toast.info(`${payload.notification.title}`, {
onClick: () => navigateTo('/news')
})
});
}

View File

@ -0,0 +1,6 @@
import { useToast } from 'vue-toastification'
export default () => {
const toast = useToast()
return toast
}

View File

@ -1,34 +1,38 @@
// https://v3.nuxtjs.org/api/configuration/nuxt.config // https://v3.nuxtjs.org/api/configuration/nuxt.config
export default defineNuxtConfig({ export default defineNuxtConfig({
ssr: false, ssr: false,
modules: [ imports: {
'@nuxtjs/tailwindcss', dirs: ['stores']
'nuxt-icon', },
'@vueuse/nuxt', modules: [
'@nuxtjs/robots', '@nuxtjs/tailwindcss',
'@nuxtjs/plausible', 'nuxt-icon',
], '@vueuse/nuxt',
plausible: { '@nuxtjs/robots',
domain: 'wrbapp.xeovalyte.com', '@nuxtjs/plausible',
apiHost: 'https://plausible.xeovalyte.dev', [ '@pinia/nuxt', { autoImports: [ 'defineStore' ]} ],
}, ],
build: { plausible: {
transpile: ['vue-toastification'], domain: 'wrbapp.xeovalyte.com',
}, apiHost: 'https://plausible.xeovalyte.dev',
app: { },
head: { build: {
title: 'WRB App', transpile: ['vue-toastification'],
charset: 'utf-8', },
viewport: 'width=device-width initial-scale=1 viewport-fit=cover', app: {
meta: [ head: {
{ name: 'theme-color', content: '#eb6330' }, title: 'WRB App',
{ name: 'description', content: 'De officiele app voor de Waddinxveense Reddingsbrigade'} charset: 'utf-8',
], viewport: 'width=device-width initial-scale=1 viewport-fit=cover',
link: [ meta: [
{ rel: 'manifest', href: '/manifest.json' }, { name: 'theme-color', content: '#eb6330' },
{ rel: 'icon', href: '/favicon.ico', type: 'image/x-icon' } { name: 'description', content: 'De officiele app voor de Waddinxveense Reddingsbrigade'}
] ],
} link: [
{ rel: 'manifest', href: '/manifest.json' },
{ rel: 'icon', href: '/favicon.ico', type: 'image/x-icon' }
]
}
}, },
runtimeConfig: { runtimeConfig: {
privateKeyId: '', privateKeyId: '',

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,8 @@
{ {
"private": true, "private": true,
"overrides": {
"vue": "latest"
},
"scripts": { "scripts": {
"build": "nuxt build", "build": "nuxt build",
"dev": "nuxt dev", "dev": "nuxt dev",
@ -8,27 +11,29 @@
"postinstall": "nuxt prepare" "postinstall": "nuxt prepare"
}, },
"devDependencies": { "devDependencies": {
"@capacitor/cli": "^4.6.3", "@capacitor/cli": "^4.7.1",
"@nuxtjs/plausible": "^0.2.0", "@nuxtjs/plausible": "^0.2.0",
"@nuxtjs/tailwindcss": "^6.3.1", "@nuxtjs/tailwindcss": "^6.6.0",
"@tailwindcss/forms": "^0.5.3", "@tailwindcss/forms": "^0.5.3",
"@vueuse/core": "^9.12.0", "@vueuse/core": "^9.13.0",
"@vueuse/nuxt": "^9.12.0", "@vueuse/nuxt": "^9.13.0",
"nuxt": "^3.2.0", "nuxt": "^3.3.1",
"nuxt-icon": "^0.2.11" "nuxt-icon": "^0.3.3"
}, },
"dependencies": { "dependencies": {
"@capacitor/core": "^4.6.3", "@capacitor/core": "^4.7.1",
"@capacitor/device": "^4.1.0", "@capacitor/device": "^4.1.0",
"@capacitor/ios": "^4.6.3", "@capacitor/ios": "^4.7.1",
"@capacitor/push-notifications": "^4.1.2", "@capacitor/push-notifications": "^4.1.2",
"@formkit/nuxt": "^1.0.0-beta.11-c95e605", "@formkit/nuxt": "^1.0.0-beta.11-c95e605",
"@nuxtjs/robots": "^3.0.0", "@nuxtjs/robots": "^3.0.0",
"@vueuse/components": "^9.12.0", "@pinia/nuxt": "^0.4.7",
"@vueuse/firebase": "^9.12.0", "@vueuse/components": "^9.13.0",
"@vueuse/shared": "^9.12.0", "@vueuse/firebase": "^9.13.0",
"firebase": "^9.17.1", "@vueuse/shared": "^9.13.0",
"firebase": "^9.18.0",
"firebase-admin": "^11.5.0", "firebase-admin": "^11.5.0",
"pinia": "^2.0.33",
"vue-toastification": "^2.0.0-rc.5" "vue-toastification": "^2.0.0-rc.5"
} }
} }

View File

@ -1,96 +1,55 @@
<template> <template>
<div class="flex flex-col gap-5 mx-auto p-2 w-full max-w-md"> <div class="flex flex-col gap-5 mx-auto p-2 w-full max-w-md">
<div v-if="calEvents[0]" class="flex flex-col gap-3"> <div v-if="calendarStore.events[0]" class="flex flex-col gap-3">
<div v-for="(event, index) in calEvents" :key="index"> <div v-for="(event, index) in calendarStore.events" :key="index">
<div class="item container flex flex-col"> <div class="item container flex flex-col">
<h2 class="">{{ longEventDate(event.date) }}</h2> <h2 class="">{{ longEventDate(event.date) }}</h2>
<p class="whitespace-pre overflow-x-auto font-bold text-xl">{{ event.description}}</p> <p class="whitespace-pre overflow-x-auto font-bold text-xl">{{ event.description}}</p>
</div>
</div>
</div>
<div class="w-full flex flex-col justify-center items-center" v-else>
<Icon size="2em" name="ion:load-c" class="animate-spin" />
<h2 class="mt-2 font-bold">Loading...</h2>
</div> </div>
</div>
</div> </div>
<div class="w-full flex flex-col justify-center items-center" v-else>
<Icon size="2em" name="ion:load-c" class="animate-spin" />
<h2 class="mt-2 font-bold">Loading...</h2>
</div>
</div>
</template> </template>
<script setup> <script setup>
definePageMeta({ definePageMeta({
title: 'Agenda' title: 'Agenda'
}) })
const { userPersons, calEvents } = inject('firebase') const userStore = useUserStore()
const calendarStore = useCalendarStore()
onMounted(() => { onMounted(() => {
if (!calEvents[0]) getCalendarEvents([...new Set(userPersons.value.map(a => a.groups.join()).join().split(','))]) getEvents()
}) })
const getEvents = () => {
if (userStore.userPersons[0]) {
const groups = [...new Set(userStore.userPersons.map(a => a.groups.join()).join().split(','))]
calendarStore.getEvents(groups)
} else {
setTimeout(() => { getEvents() }, 50)
}
}
const longEventDate = (eventDate) => { const longEventDate = (eventDate) => {
const date = new Date(eventDate) const date = new Date(eventDate)
return date.toLocaleString('nl-NL', { return date.toLocaleString('nl-NL', {
weekday: 'short', weekday: 'short',
day: 'numeric', day: 'numeric',
year: 'numeric', year: 'numeric',
month: 'long', month: 'long',
hour: 'numeric', hour: 'numeric',
minute: 'numeric' minute: 'numeric'
} }
)} )}
const getCalendarEvents = async (group) => {
if (!group[0]) return setTimeout(() => { getCalendarEvents([...new Set(userPersons.value.map(a => a.groups.join()).join().split(','))])}, 50)
const events = []
let fridayEvents = []
let saturdayEvents = []
let matchEvents = []
if (group.includes('Zaterdag')){
let data = await fetch('https://www.googleapis.com/calendar/v3/calendars/c_astg0d0auheip5o4269v1qvv3g@group.calendar.google.com/events?key=AIzaSyBLmxWNEnbkiW_0c2UrsHMbxXv73dX-KYw')
if (!data.ok){
throw Error('No data available')
}
const jsonEvents = await data.json()
saturdayEvents = jsonEvents.items
} if (group.includes('Vrijdag')){
let data = await fetch('https://www.googleapis.com/calendar/v3/calendars/c_u3895n01jt8qnusm7f6pmqjb0k%40group.calendar.google.com/events?key=AIzaSyBLmxWNEnbkiW_0c2UrsHMbxXv73dX-KYw')
if (!data.ok){
throw Error('No data available')
}
const jsonEvents = await data.json()
fridayEvents = jsonEvents.items
} if (group.includes('Wedstrijd')){
let data = await fetch('https://www.googleapis.com/calendar/v3/calendars/c_c2296iboq07n24galuobeesovs@group.calendar.google.com/events?key=AIzaSyBLmxWNEnbkiW_0c2UrsHMbxXv73dX-KYw')
if (!data.ok){
throw Error('No data available')
}
const jsonEvents = await data.json()
matchEvents = jsonEvents.items
}
const allEvents = [...fridayEvents, ...saturdayEvents, ...matchEvents]
const now = new Date()
now.setHours(0, 0, 0, 0)
const sortedEvents = allEvents.sort((a, b) => new Date(a.start.dateTime) - new Date(b.start.dateTime))
const filteredEvents = sortedEvents.filter((event) => new Date(event.start.dateTime) > now )
filteredEvents.forEach(element => {
events.push({
description: element.description,
date: new Date(element.start.dateTime)
})
});
calEvents.value = events
}
</script> </script>

View File

@ -1,7 +1,7 @@
<template> <template>
<div class="flex flex-col justify-center items-center px-2 overflow-hidden"> <div class="flex flex-col justify-center items-center px-2 overflow-hidden">
<h1 class="font-bold text-3xl text-center mt-6 mb-3">Reddingsbrigade Waddinxveen</h1> <h1 class="font-bold text-3xl text-center mt-6 mb-3">Reddingsbrigade Waddinxveen</h1>
<h2 class="text-xl text-center mb-12">{{ userPersons.map(a => a.fullName).join(', ')}}</h2> <h2 class="text-xl text-center mb-12">{{ userStore.userPersons.map(a => a.fullName).join(', ')}}</h2>
<div class="container w-full max-w-md"> <div class="container w-full max-w-md">
<NuxtLink to="/news" class="rounded-t item-hover py-2 flex items-center"> <NuxtLink to="/news" class="rounded-t item-hover py-2 flex items-center">
<span>Nieuws</span> <span>Nieuws</span>
@ -24,8 +24,7 @@
<script setup> <script setup>
definePageMeta({ definePageMeta({
title: 'Home', title: 'Home',
}) })
const userStore = useUserStore()
const { userPersons } = inject('firebase')
</script> </script>

View File

@ -1,92 +1,51 @@
<template> <template>
<div class="flex flex-col gap-5 mx-auto p-2 w-full max-w-md"> <div class="flex flex-col gap-5 mx-auto p-2 w-full max-w-md">
<div v-if="loadedNews && news" class="flex flex-col gap-3"> <div v-if="newsStore.loaded && newsStore.news" class="flex flex-col gap-3">
<NuxtLink to="/news/newmessage" v-if="userData.sendNews" class="item-hover border-dashed border-2 container text-center font-bold text-xl border-neutral-500 mb-3"> <NuxtLink to="/news/newmessage" v-if="userStore.userData.sendNews" class="item-hover border-dashed border-2 container text-center font-bold text-xl border-neutral-500 mb-3">
Nieuw Bericht Nieuw Bericht
</NuxtLink> </NuxtLink>
<div v-if="news[0]" v-for="(item, index) in news" :key="index"> <div v-if="newsStore.news[0]" v-for="(item, index) in newsStore.news" :key="index">
<div class="item container flex flex-col relative"> <div class="item container flex flex-col relative">
<h3 class="text-sm">{{ longEventDate(item.date.toDate()) }}</h3> <h3 class="text-sm">{{ longEventDate(item.date.toDate()) }}</h3>
<h2 class="text-2xl font-bold">{{ item.title }}</h2> <h2 class="text-2xl font-bold">{{ item.title }}</h2>
<p>{{ item.description }}</p> <p>{{ item.description }}</p>
<Icon v-if="userData.sendNews" @click="deleteItem(item, index)" size="1.5em" name="ion:trash-sharp" class="absolute top-3 right-3 hover:cursor-pointer text-red-500" /> <Icon v-if="userStore.userData.sendNews" @click="newsStore.deleteNews(item, index)" size="1.5em" name="ion:trash-sharp" class="absolute top-3 right-3 hover:cursor-pointer text-red-500" />
</div>
</div>
<h2 v-else class="font-bold text-center text-xl mt-3">
Er is geen nieuws
</h2>
</div>
<div class="w-full flex flex-col justify-center items-center" v-else>
<Icon size="2em" name="ion:load-c" class="animate-spin" />
<h2 class="mt-2 font-bold">Loading...</h2>
</div> </div>
</div>
<h2 v-else class="font-bold text-center text-xl mt-3">
Er is geen nieuws
</h2>
</div> </div>
<div class="w-full flex flex-col justify-center items-center" v-else>
<Icon size="2em" name="ion:load-c" class="animate-spin" />
<h2 class="mt-2 font-bold">Loading...</h2>
</div>
</div>
</template> </template>
<script setup> <script setup>
import { getDocs, collection, deleteDoc, doc } from 'firebase/firestore'
import { useToast } from 'vue-toastification'
definePageMeta({ definePageMeta({
title: 'Nieuws' title: 'Nieuws'
}) })
const { news, userData, db } = inject('firebase') const userStore = useUserStore()
const newsStore = useNewsStore()
const toast = useToast()
const loadedNews = ref(false)
onMounted(() => { onMounted(() => {
if (!news.value) getNews() newsStore.getNews()
else loadedNews.value = true
}) })
const longEventDate = (eventDate) => { const longEventDate = (eventDate) => {
const date = new Date(eventDate) const date = new Date(eventDate)
return date.toLocaleString('nl-NL', { return date.toLocaleString('nl-NL', {
weekday: 'short', weekday: 'short',
day: 'numeric', day: 'numeric',
year: 'numeric', year: 'numeric',
month: 'long', month: 'long',
hour: 'numeric', hour: 'numeric',
minute: 'numeric' minute: 'numeric'
} }
)} )}
const getNews = async () => { </script>
news.value = []
try {
const querySnapshot = await getDocs(collection(db, "news"));
querySnapshot.forEach((doc) => {
let data = doc.data()
data.id = doc.id
news.value.push(data)
});
news.value.sort((a, b) => b.date.seconds - a.date.seconds)
loadedNews.value = true
} catch (e) {
console.log(e)
}
}
const deleteItem = async (item, index) => {
if (!item.id) return toast.error('Refresh eerst voordat je dit bericht kan verwijderen')
try {
news.value.splice(index, 1)
await deleteDoc(doc(db, "news", item.id));
toast.success('Bericht verwijderd')
} catch (e) {
console.log(e)
toast.error('Error tijdens bericht verwijderen')
}
}
</script>

View File

@ -22,66 +22,35 @@
</template> </template>
<script setup> <script setup>
import { addDoc, collection, serverTimestamp, Timestamp } from 'firebase/firestore'
import { useToast } from 'vue-toastification'
definePageMeta({ definePageMeta({
title: 'Nieuw Bericht', title: 'Nieuw Bericht',
key: 'back' key: 'back'
}) })
const { news, db, auth } = inject('firebase')
const router = useRouter() const router = useRouter()
const toast = useToast() const toast = useToast()
const newsStore = useNewsStore()
const disableButtons = ref(false) const disableButtons = ref(false)
const sendNews = async () => {
try {
disableButtons.value = true
await newsStore.send(form.value)
disableButtons.value = false
} catch (e) {
console.log(e)
disableButtons.value = false
toast.error('Error tijdens versturen bericht')
}
}
const form = ref({ const form = ref({
title: '', title: '',
description: '', description: '',
topic: '' topic: ''
}) })
const sendNews = async () => {
disableButtons.value = true
try {
const idToken = await auth.value.currentUser.getIdToken(true)
console.log(idToken)
const { error } = await useFetch('/api/sendmessage', {
method: 'post',
body: { title: form.value.title, body: form.value.description, token: idToken, topic: form.value.topic }
})
if (error.value) {
console.log(error.value)
return toast.error('Error tijdens het versturen van het bericht')
}
await addDoc(collection(db, "news"), {
title: form.value.title,
description: form.value.description,
date: serverTimestamp()
});
if (news.value) {
news.value.unshift({
title: form.value.title,
description: form.value.description,
date: Timestamp.now()
})
}
toast.success('Bericht is verstuurd')
navigateTo('/news')
} catch (e) {
console.log(e)
toast.error('Error tijdens het berict sturen')
}
disableButtons.value = false
}
</script> </script>

View File

@ -42,7 +42,7 @@
</template> </template>
<script setup> <script setup>
import { doc, getDocs, collection, writeBatch, updateDoc, setDoc } from "firebase/firestore"; import { doc, getDocs, collection, writeBatch, updateDoc, setDoc, getFirestore } from "firebase/firestore";
import { useToast } from 'vue-toastification' import { useToast } from 'vue-toastification'
definePageMeta({ definePageMeta({
@ -50,11 +50,11 @@ definePageMeta({
key: 'back' key: 'back'
}) })
const { db, ledenlijst, users } = inject('firebase')
const toast = useToast() const toast = useToast()
const usersStore = useUsersStore()
const modelData = ref(null) const modelData = ref(null)
const db = getFirestore()
const file = ref(null) const file = ref(null)
const disableButtons = ref(false) const disableButtons = ref(false)
@ -63,18 +63,7 @@ const newLedenlijst = ref([])
const showModel = ref(false) const showModel = ref(false)
onMounted(async () => { onMounted(async () => {
if (!ledenlijst.value.length) { usersStore.getLedenlijst()
try {
const querySnapshot = await getDocs(collection(db, "ledenlijst"));
querySnapshot.forEach((doc) => {
ledenlijst.value.push(doc.data())
});
} catch (e) {
console.log(e)
}
ledenlijst.value.sort((a, b) => a.fullName.localeCompare(b.fullName))
}
}) })
const handleModel = (lid) => { const handleModel = (lid) => {
@ -88,7 +77,7 @@ const handleModel = (lid) => {
const submitModelForm = async () => { const submitModelForm = async () => {
disableButtons.value = true disableButtons.value = true
ledenlijst.value.filter(a => a.relatiecode === modelData.value.relatiecode)[0].wedstrijdteam = modelData.value.wedstrijdteam usersStore.ledenlijst.filter(a => a.relatiecode === modelData.value.relatiecode)[0].wedstrijdteam = modelData.value.wedstrijdteam
if (modelData.value.wedstrijdteam === modelData.value.oldWedstrijdteam) { if (modelData.value.wedstrijdteam === modelData.value.oldWedstrijdteam) {
disableButtons.value = false disableButtons.value = false
@ -123,7 +112,7 @@ const submitModelForm = async () => {
} }
const filteredLedenlijst = computed(() => { const filteredLedenlijst = computed(() => {
return ledenlijst.value.filter(lid => lid.fullName.toLowerCase().includes(searchTerm.value.toLowerCase())) return usersStore.ledenlijst.filter(lid => lid.fullName.toLowerCase().includes(searchTerm.value.toLowerCase()))
}) })
const handleFileChanged = (event) => { const handleFileChanged = (event) => {
@ -224,7 +213,7 @@ const uploadLedenlijst = async () => {
console.log(e) console.log(e)
} }
ledenlijst.value = newLedenlijst.value usersStore.ledenlijst = newLedenlijst.value
updateUsers() updateUsers()
} }

View File

@ -44,7 +44,7 @@
</template> </template>
<script setup> <script setup>
import { getDocs, collection, doc, updateDoc } from 'firebase/firestore' import { getDocs, collection, doc, updateDoc, getFirestore } from 'firebase/firestore'
import { useToast } from 'vue-toastification' import { useToast } from 'vue-toastification'
definePageMeta({ definePageMeta({
@ -52,8 +52,9 @@ definePageMeta({
key: 'back' key: 'back'
}) })
const { users, db } = inject('firebase')
const toast = useToast() const toast = useToast()
const db = getFirestore()
const usersStore = useUsersStore()
const searchTerm = ref('') const searchTerm = ref('')
const disableButtons = ref(false) const disableButtons = ref(false)
@ -62,16 +63,7 @@ const showModel = ref(false)
const modelData = ref({}) const modelData = ref({})
onMounted(async () => { onMounted(async () => {
if (!users.value.length) { usersStore.getUsers()
try {
const querySnapshot = await getDocs(collection(db, "users"));
querySnapshot.forEach((doc) => {
users.value.push(doc.data())
});
} catch (e) {
console.log(e)
}
}
}) })
const handleModel = (user) => { const handleModel = (user) => {
@ -84,7 +76,7 @@ const handleModel = (user) => {
} }
const filteredUsers = computed(() => { const filteredUsers = computed(() => {
return users.value.filter(user => user.email.toLowerCase().includes(searchTerm.value.toLowerCase())) return usersStore.users.filter(user => user.email.toLowerCase().includes(searchTerm.value.toLowerCase()))
}) })
const submitModelForm = async () => { const submitModelForm = async () => {

View File

@ -1,26 +1,26 @@
<template> <template>
<div class="flex flex-col gap-5 mx-auto p-2 w-full max-w-md"> <div class="flex flex-col gap-5 mx-auto p-2 w-full max-w-md">
<form @submit.prevent="savePassword" class="flex flex-col"> <form @submit.prevent="savePassword" class="flex flex-col">
<label class="font-bold">Oud Wachtwoord</label> <label class="font-bold">Oud Wachtwoord</label>
<input v-model="form.oldPassword" required="true" class="input mb-5" :type="showPassword ? 'text' : 'password'"> <input v-model="form.oldPassword" required="true" class="input mb-5" :type="showPassword ? 'text' : 'password'">
<label class="font-bold">Nieuw Wachtwoord</label> <label class="font-bold">Nieuw Wachtwoord</label>
<input v-model="form.newPassword" required="true" class="input mb-5" :type="showPassword ? 'text' : 'password'"> <input v-model="form.newPassword" required="true" class="input mb-5" :type="showPassword ? 'text' : 'password'">
<label class="font-bold">Herhaal Nieuw Wachtwoord</label> <label class="font-bold">Herhaal Nieuw Wachtwoord</label>
<input v-model="form.confirmNewPassword" required="true" class="input" :type="showPassword ? 'text' : 'password'"> <input v-model="form.confirmNewPassword" required="true" class="input" :type="showPassword ? 'text' : 'password'">
<div class="mb-5 mt-1 flex items-center text-default"> <div class="mb-5 mt-1 flex items-center text-default">
<input v-model="showPassword" type="checkbox" class="mr-1 checkbox "> <input v-model="showPassword" type="checkbox" class="mr-1 checkbox ">
<span>Toon Wachtwoord</span> <span>Toon Wachtwoord</span>
</div> </div>
<div class="w-full flex flex-wrap justify-between"> <div class="w-full flex flex-wrap justify-between">
<input :disabled="disableButtons" type="submit" value="Wijzig Wachtwoord" class="btn w-full sm:w-52 mb-1"> <input :disabled="disableButtons" type="submit" value="Wijzig Wachtwoord" class="btn w-full sm:w-52 mb-1">
<button @click="router.back()" class="hover:underline font-bold w-full sm:w-max sm:ml-auto">Annuleer</button> <button @click="router.back()" class="hover:underline font-bold w-full sm:w-max sm:ml-auto">Annuleer</button>
</div> </div>
</form> </form>
</div> </div>
</template> </template>
<script setup> <script setup>
@ -28,60 +28,58 @@ import { reauthenticateWithCredential, EmailAuthProvider, updatePassword } from
import { useToast } from 'vue-toastification' import { useToast } from 'vue-toastification'
definePageMeta({ definePageMeta({
title: 'Wachtwoord Wijzigen', title: 'Wachtwoord Wijzigen',
key: 'back' key: 'back'
}) })
const { user, auth } = inject('firebase')
const toast = useToast() const toast = useToast()
const router = useRouter() const router = useRouter()
const userStore = useUserStore()
const form = ref({ const form = ref({
oldPassword: '', oldPassword: '',
newPassword: '', newPassword: '',
confirmNewPassword: '', confirmNewPassword: '',
}) })
onMounted(() => { onMounted(() => {
form.value = { form.value = {
oldPassword: '', oldPassword: '',
newPassword: '', newPassword: '',
confirmNewPassword: '', confirmNewPassword: '',
} }
}) })
const savePassword = () => { const savePassword = () => {
if (form.value.newPassword !== form.value.confirmNewPassword) return alert ('Niewe wachtwoorden zijn niet hetzelfde') if (form.value.newPassword !== form.value.confirmNewPassword) return alert ('Niewe wachtwoorden zijn niet hetzelfde')
disableButtons.value = true disableButtons.value = true
const credential = EmailAuthProvider.credential( const credential = EmailAuthProvider.credential(
user.value.email, userStore.user.email,
form.value.oldPassword form.value.oldPassword
) )
reauthenticateWithCredential(auth.value.currentUser, credential).then(() => { reauthenticateWithCredential(userStore.auth.currentUser, credential).then(() => {
updatePassword(auth.value.currentUser, form.value.newPassword).then(() => { updatePassword(userStore.auth.currentUser, form.value.newPassword).then(() => {
toast.success('Wachtwoord is veranderd') toast.success('Wachtwoord is veranderd')
navigateTo('/settings') navigateTo('/settings')
disableButtons.value = false disableButtons.value = false
}).catch((error) => {
toast.error('Error tijdens het wachtwoord veranderen')
console.log(error)
disableButtons.value = false
});
}).catch((error) => { }).catch((error) => {
disableButtons.value = false toast.error('Error tijdens het wachtwoord veranderen')
console.log(error)
if (error.code === 'auth/wrong-password') return toast.error('Oude wachtwoord is onjuist') disableButtons.value = false
toast.error('Error tijdens het wachtwoord veranderen')
console.log(error)
}); });
}).catch((error) => {
disableButtons.value = false
if (error.code === 'auth/wrong-password') return toast.error('Oude wachtwoord is onjuist')
toast.error('Error tijdens het wachtwoord veranderen')
console.log(error)
});
} }
const showPassword = ref(false) const showPassword = ref(false)

View File

@ -1,52 +1,43 @@
<template> <template>
<div class="flex flex-col gap-5 mx-auto p-2 w-full max-w-md"> <div class="flex flex-col gap-5 mx-auto p-2 w-full max-w-md">
<div v-if="userAllPersons.length !== 0 && userPersons.length !== 0" class="flex flex-col gap-3"> <div v-if="userStore.userAllPersons.length !== 0 && userStore.userPersons.length !== 0" class="flex flex-col gap-3">
<div v-for="person in userAllPersons" :key="person.relatiecode"> <div v-for="person in userStore.userAllPersons" :key="person.relatiecode">
<div @click="updateCheckbox(person)" class="item container flex flex-wrap" :class="person.relatiecode === userPersons[0].relatiecode ? 'bg-neutral-200 dark:bg-neutral-850 text-neutral-400 dark:text-neutral-500 hover:cursor-not-allowed' : 'hover:cursor-pointer'"> <div @click="updateCheckbox(person)" class="item container flex flex-wrap" :class="person.relatiecode === userStore.userPersons[0].relatiecode ? 'bg-neutral-200 dark:bg-neutral-850 text-neutral-400 dark:text-neutral-500 hover:cursor-not-allowed' : 'hover:cursor-pointer'">
<input v-model="person.checked" :disabled="person.relatiecode === userPersons[0].relatiecode" class="checkbox my-auto mr-3 disabled:bg-neutral-300 disabled:hover:text-neutral-300 dark:disabled:bg-neutral-600 dark:disabled:hover:text-neutral-600 disabled:hover:cursor-not-allowed" type="checkbox"> <input v-model="person.checked" :disabled="person.relatiecode === userStore.userPersons[0].relatiecode" class="checkbox my-auto mr-3 disabled:bg-neutral-300 disabled:hover:text-neutral-300 dark:disabled:bg-neutral-600 dark:disabled:hover:text-neutral-600 disabled:hover:cursor-not-allowed" type="checkbox">
<span><b>{{ person.fullName }}</b></span> <span><b>{{ person.fullName }}</b></span>
</div>
</div>
<div class="w-full flex flex-wrap">
<button :disabled="buttonsDisabled" @click="save" class="btn w-full sm:w-40 mb-1">Opslaan</button>
<span @click="router.back()" class="hover:underline font-bold w-full text-center sm:w-max sm:ml-auto hover:cursor-pointer">Annuleer</span>
</div>
</div>
<div class="w-full flex flex-col justify-center items-center" v-else>
<Icon size="2em" name="ion:load-c" class="animate-spin" />
<h2 class="mt-2 font-bold">Loading...</h2>
</div> </div>
</div>
<div class="w-full flex flex-wrap">
<button :disabled="buttonsDisabled" @click="save" class="btn w-full sm:w-40 mb-1">Opslaan</button>
<span @click="router.back()" class="hover:underline font-bold w-full text-center sm:w-max sm:ml-auto hover:cursor-pointer">Annuleer</span>
</div>
</div> </div>
<div class="w-full flex flex-col justify-center items-center" v-else>
<Icon size="2em" name="ion:load-c" class="animate-spin" />
<h2 class="mt-2 font-bold">Loading...</h2>
</div>
</div>
</template> </template>
<script setup> <script setup>
import { updateDoc, doc } from 'firebase/firestore' import { updateDoc, doc, getFirestore } from 'firebase/firestore'
import { useToast } from 'vue-toastification' import { useToast } from 'vue-toastification'
const router = useRouter() const router = useRouter()
definePageMeta({ definePageMeta({
title: 'Beheer Personen', title: 'Beheer Personen',
key: 'back' key: 'back'
}) })
const { user, userAllPersons, userPersons, db, getPersons, auth } = inject('firebase')
const toast = useToast() const toast = useToast()
const userStore = useUserStore()
const db = getFirestore()
const buttonsDisabled = ref(false) const buttonsDisabled = ref(false)
onMounted(() => { onMounted(() => {
if (userAllPersons.value.length === 0) { userStore.getAllPersons()
getAllPersons()
} else {
userAllPersons.value.forEach(person => {
if (userPersons.value.map(a => a.relatiecode).includes(person.relatiecode)) {
person.checked = true
} else {
person.checked = false
}
})
}
}) })
const save = async () => { const save = async () => {
@ -54,52 +45,25 @@ const save = async () => {
const newRelatiecodes = [] const newRelatiecodes = []
userAllPersons.value.forEach(person => { userStore.userAllPersons.forEach(person => {
if (person.checked) { if (person.checked) {
newRelatiecodes.push(person.relatiecode) newRelatiecodes.push(person.relatiecode)
} }
}) })
await updateDoc(doc(db, "users", user.value.uid), { await updateDoc(doc(db, "users", userStore.user.uid), {
relatiecodes: newRelatiecodes relatiecodes: newRelatiecodes
}) })
getPersons(newRelatiecodes) userStore.getPersons(newRelatiecodes)
buttonsDisabled.value = false buttonsDisabled.value = false
navigateTo('/settings') navigateTo('/settings')
} }
const updateCheckbox = (person) => { const updateCheckbox = (person) => {
if (person.relatiecode === userPersons.value[0].relatiecode) return; if (person.relatiecode === userStore.userPersons[0].relatiecode) return;
person.checked = !person.checked person.checked = !person.checked
} }
const getAllPersons = async () => {
if (userPersons.value.length === 0) return setTimeout(() => getAllPersons(), 50)
const idToken = await auth.value.currentUser.getIdToken(true)
const { data: response, error } = await useFetch('/api/getrelatiecodes', {
method: 'post',
body: { email: user.value.email, token: idToken }
})
if (error.value) {
console.log(error.value)
return toast.error('Error tijdens het krijgen van relateicodes')
}
response.value.persons.forEach(person => {
if (userPersons.value.map(a => a.relatiecode).includes(person.relatiecode)) {
person.checked = true
} else {
person.checked = false
}
})
userAllPersons.value = response.value.persons
}
</script> </script>

View File

@ -4,19 +4,19 @@
<h1 class="text-xl ml-2 font-bold">Info</h1> <h1 class="text-xl ml-2 font-bold">Info</h1>
<div class="container"> <div class="container">
<div class="item"> <div class="item">
Email: <b>{{ user.email }}</b> Email: <b>{{ userStore.user.email }}</b>
</div> </div>
<div class="divider" /> <div class="divider" />
<div class="item"> <div class="item">
Personen: <b>{{ userPersons.map(a => a.fullName).join(', ')}}</b> Personen: <b>{{ userStore.userPersons.map(a => a.fullName).join(', ')}}</b>
</div> </div>
<div class="divider" /> <div class="divider" />
<div class="item"> <div class="item">
Groepen: <b>{{ groups.join(', ') }}</b> Groepen: <b>{{ groups.join(', ') }}</b>
</div> </div>
<div v-if="userPersons.map(a => a.diploma).filter(n => n !== '').join('')" class="divider" /> <div v-if="userStore.userPersons.map(a => a.diploma).filter(n => n !== '').join('')" class="divider" />
<div v-if="userPersons.map(a => a.diploma).filter(n => n !== '').join('')" class="item"> <div v-if="userStore.userPersons.map(a => a.diploma).filter(n => n !== '').join('')" class="item">
Diploma: <b>{{ userPersons.map(a => a.diploma).filter(n => n !== '').join(', ')}}</b> Diploma: <b>{{ userStore.userPersons.map(a => a.diploma).filter(n => n !== '').join(', ')}}</b>
</div> </div>
<div class="divider" /> <div class="divider" />
<NuxtLink to="/settings/moreinfo" class="item-hover py-2 rounded-t flex items-center"> <NuxtLink to="/settings/moreinfo" class="item-hover py-2 rounded-t flex items-center">
@ -43,7 +43,7 @@
</div> </div>
</div> </div>
</div> </div>
<div v-if="userData.admin"> <div v-if="userStore.userData.admin">
<h1 class="text-xl ml-2 font-bold">Admin</h1> <h1 class="text-xl ml-2 font-bold">Admin</h1>
<div class="container"> <div class="container">
<NuxtLink to="/settings/admin/users" class="rounded-t item-hover py-2 flex items-center"> <NuxtLink to="/settings/admin/users" class="rounded-t item-hover py-2 flex items-center">
@ -70,14 +70,14 @@ definePageMeta({
title: 'Settings' title: 'Settings'
}) })
const { auth, userData, userPersons, user } = inject('firebase') const userStore = useUserStore()
const groups = computed(() => { const groups = computed(() => {
return [...new Set(userPersons.value.map(a => a.groups.join()).join().split(','))] return [...new Set(userStore.userPersons.map(a => a.groups.join()).join().split(','))]
}) })
const logout = () => { const logout = () => {
signOut(auth.value) signOut(userStore.auth)
.catch((error) => { .catch((error) => {
console.log(error) console.log(error)
}) })

View File

@ -15,11 +15,11 @@
<h1 class="text-xl ml-2 font-bold"></h1> <h1 class="text-xl ml-2 font-bold"></h1>
<div class="container"> <div class="container">
<div class="item break-words "> <div class="item break-words ">
Registration Token: <b>{{ registrationToken }}</b> Registration Token: <b>{{ userStore.registrationToken }}</b>
</div> </div>
<div class="divider" /> <div class="divider" />
<div class="item break-words "> <div class="item break-words ">
User ID: <b>{{ userData.id }}</b> User ID: <b>{{ userStore.userData.id }}</b>
</div> </div>
</div> </div>
</div> </div>
@ -28,9 +28,9 @@
<script setup> <script setup>
definePageMeta({ definePageMeta({
title: 'Meer Informatie', title: 'Meer Informatie',
key: 'back' key: 'back'
}) })
const { registrationToken, userData } = inject('firebase') const userStore = useUserStore()
</script> </script>

View File

@ -1,23 +0,0 @@
<template>
<div class="flex flex-col gap-5 mx-auto p-2 w-full max-w-md text-default text-sm">
<h2 class="text-xl font-bold">
Privacy
</h2>
<p>
Gegevens binnen deze app worden gebruikt voor de interne organisatie. Via Google analytics wordt bijgehouden welke schermen het meest worden gebruikt. Daarnaast maken wij gebruik van Firebase voor het anoniem verzamelen van gegevens omtrent crashes, bugs en het gebruik van de app.
</p>
<h2 class="text-xl font-bold">
AVG
</h2>
<p>
Sinds 25 mei 2018 is de Algemene verordening gegevensbescherming (AVG) van toepassing waardoor elke vereniging helder moet maken wat zij doen om de privacy van persoonsgegevens te waarborgen. U kunt <a href="https://www.reddingsbrigadewaddinxveen.nl/Doc/201809%20-%20Privacyverklaring%20WRB.pdf" class="underline">hier</a> onze privacy verklaring vinden.
</p>
</div>
</template>
<script setup>
definePageMeta({
title: 'Privacybeleid',
key: 'back'
})
</script>

View File

@ -5,7 +5,7 @@
<label class="font-bold text-default">Deelnemer</label> <label class="font-bold text-default">Deelnemer</label>
<select :disabled="modelData.edit" required v-model="modelData.relatiecode" class="input dark:bg-neutral-700 bg-neutral-300 mb-5"> <select :disabled="modelData.edit" required v-model="modelData.relatiecode" class="input dark:bg-neutral-700 bg-neutral-300 mb-5">
<option v-if="!modelData.edit" v-for="user in competitors.filter(x => !contest.events[modelData.event].competitors.map(y => y.relatiecode).includes(x.relatiecode))" :value="user.relatiecode">{{ user.name}} ({{ user.relatiecode }})</option> <option v-if="!modelData.edit" v-for="user in contestStore.competitors.filter(x => !contest.events[modelData.event].competitors.map(y => y.relatiecode).includes(x.relatiecode))" :value="user.relatiecode">{{ user.name}} ({{ user.relatiecode }})</option>
<option v-else v-for="user in competitors" :value="user.relatiecode">{{ user.name}} ({{ user.relatiecode }})</option> <option v-else v-for="user in competitors" :value="user.relatiecode">{{ user.name}} ({{ user.relatiecode }})</option>
</select> </select>
@ -45,7 +45,7 @@
<label class="font-bold">Onderdelen</label> <label class="font-bold">Onderdelen</label>
<div class="flex flex-col gap-y-3"> <div class="flex flex-col gap-y-3">
<div v-if="competitors" v-for="event in contest.events" class="container p-2"> <div v-if="contestStore.competitors[0]" v-for="event in contest.events" class="container p-2">
<div @click="event.open = !event.open" class="flex hover:cursor-pointer"> <div @click="event.open = !event.open" class="flex hover:cursor-pointer">
<h2 class="font-bold">{{ event.name }}</h2> <h2 class="font-bold">{{ event.name }}</h2>
<Icon size="1.2em" name="ion:arrow-down-b" class="ml-auto my-auto mr-2 transition-all" :class="{'rotate-180' : event.open }" /> <Icon size="1.2em" name="ion:arrow-down-b" class="ml-auto my-auto mr-2 transition-all" :class="{'rotate-180' : event.open }" />
@ -61,11 +61,11 @@
</thead> </thead>
<tbody> <tbody>
<tr @click="handleModel(competitor, event.id, true, index)" v-for="(competitor, index) in event.competitors" class="even:dark:bg-neutral-700 even:bg-neutral-300 hover:cursor-pointer"> <tr @click="handleModel(competitor, event.id, true, index)" v-for="(competitor, index) in event.competitors" class="even:dark:bg-neutral-700 even:bg-neutral-300 hover:cursor-pointer">
<td class="py-1 pl-1">{{ competitors.find(x => x.relatiecode === competitor.relatiecode ).name }}</td> <td class="py-1 pl-1">{{ contestStore.competitors.find(x => x.relatiecode === competitor.relatiecode ).name }}</td>
<td>{{ competitor.time.minutes.toString().padStart(2, '0') }}:{{ competitor.time.seconds.toString().padStart(2, '0') }}:{{ competitor.time.milliseconds.toString().padStart(2, '0') }}</td> <td>{{ competitor.time.minutes.toString().padStart(2, '0') }}:{{ competitor.time.seconds.toString().padStart(2, '0') }}:{{ competitor.time.milliseconds.toString().padStart(2, '0') }}</td>
<td>{{ competitor.dsq }}</td> <td>{{ competitor.dsq }}</td>
</tr> </tr>
<tr v-if="competitors.filter(x => !event.competitors.map(y => y.relatiecode).includes(x.relatiecode)).length > 0" class="even:dark:bg-neutral-700 even:bg-neutral-300"> <tr v-if="contestStore.competitors.filter(x => !event.competitors.map(y => y.relatiecode).includes(x.relatiecode)).length > 0" class="even:dark:bg-neutral-700 even:bg-neutral-300">
<td @click="handleModel(null, event.id)" class="hover:cursor-pointer py-1 pl-1">+ Deelnemer toevoegen</td> <td @click="handleModel(null, event.id)" class="hover:cursor-pointer py-1 pl-1">+ Deelnemer toevoegen</td>
<td></td> <td></td>
<td></td> <td></td>
@ -81,7 +81,7 @@
</template> </template>
<script setup> <script setup>
import { getDocs, collection, writeBatch, doc } from "firebase/firestore" import { getDocs, collection, writeBatch, doc, getFirestore } from "firebase/firestore"
import { useToast } from 'vue-toastification' import { useToast } from 'vue-toastification'
definePageMeta({ definePageMeta({
@ -91,7 +91,9 @@ definePageMeta({
const toast = useToast() const toast = useToast()
const { db, competitors } = inject('firebase') const contestStore = useContestStore()
const userStore = useUserStore()
const db = getFirestore()
const showModel = ref(false) const showModel = ref(false)
const disableButtons = ref(false) const disableButtons = ref(false)
@ -151,14 +153,6 @@ const contest = ref({
} }
}) })
const getCompetitors = async () => {
if (competitors.value[0]) return
const querySnapshot = await getDocs(collection(db, "competitors"))
querySnapshot.forEach((doc) => {
competitors.value.push(doc.data())
})
}
const handleModel = (competitor, event, edit, index) => { const handleModel = (competitor, event, edit, index) => {
if(!competitor) competitor = { if(!competitor) competitor = {
relatiecode: '', relatiecode: '',
@ -187,7 +181,7 @@ const submitModelForm = () => {
delete modelData.value.index delete modelData.value.index
delete modelData.value.edit delete modelData.value.edit
if (!edit) contest.value.events[modelData.value.event].competitors.push(modelData.value) if (!edit) contest.value.events[modelData.value.event].competitors.push(modelData.value)
showModel.value = false showModel.value = false
@ -206,7 +200,11 @@ const submitContestForm = async () => {
const docRef = doc(collection(db, 'timings')) const docRef = doc(collection(db, 'timings'))
batch.set(docRef, { batch.set(docRef, {
relatiecode: competitor.relatiecode, relatiecode: competitor.relatiecode,
<<<<<<< HEAD
contest: { location: contest.value.location, date: contest.value.date, type: contest.value.type }, contest: { location: contest.value.location, date: contest.value.date, type: contest.value.type },
=======
contest: { location: contest.value.location, date: new Date(contest.value.date), type: contest.value.type },
>>>>>>> dev
event: event.id, event: event.id,
time: { minutes: competitor.time.minutes.toString().padStart(2, '0'), seconds: competitor.time.seconds.toString().padStart(2, '0'), milliseconds: competitor.time.milliseconds.toString().padStart(2, '0'), combined: combinedTime }, time: { minutes: competitor.time.minutes.toString().padStart(2, '0'), seconds: competitor.time.seconds.toString().padStart(2, '0'), milliseconds: competitor.time.milliseconds.toString().padStart(2, '0'), combined: combinedTime },
dsq: competitor.dsq, dsq: competitor.dsq,
@ -223,7 +221,7 @@ const submitContestForm = async () => {
} }
onMounted(() => { onMounted(() => {
getCompetitors() contestStore.getCompetitors()
}) })
</script> </script>

View File

@ -4,44 +4,44 @@
<h1 class="font-bold text-center text-lg mb-5">Tijd {{ modelData.eventName }}</h1> <h1 class="font-bold text-center text-lg mb-5">Tijd {{ modelData.eventName }}</h1>
<label class="font-bold">Locatie</label> <label class="font-bold">Locatie</label>
<input :disabled="!userData.wedstrijdAdmin" v-model="modelData.contest.location" type="text" class="input dark:bg-neutral-700 bg-neutral-300 mb-5" /> <input :disabled="!userStore.userData.wedstrijdAdmin" v-model="modelData.contest.location" type="text" class="input dark:bg-neutral-700 bg-neutral-300 mb-5" />
<label class="font-bold">Type zwembad</label> <label class="font-bold">Type zwembad</label>
<select :disabled="!userData.wedstrijdAdmin" v-model="modelData.contest.type" required="true" class="input dark:bg-neutral-700 bg-neutral-300 mb-5"> <select :disabled="!userStore.userData.wedstrijdAdmin" v-model="modelData.contest.type" required="true" class="input dark:bg-neutral-700 bg-neutral-300 mb-5">
<option value="50m">50 Meter</option> <option value="50m">50 Meter</option>
<option value="25m">25 Meter</option> <option value="25m">25 Meter</option>
</select> </select>
<label class="font-bold">Datum</label> <label class="font-bold">Datum</label>
<input :disabled="!userData.wedstrijdAdmin" :value="dateToYYYYMMDD(modelData.contest.date)" @input="modelData.contest.date = $event.target.valueAsDate" required="true" class="input dark:bg-neutral-700 bg-neutral-300 w-min hover:cursor-pointer pr-0 mb-5 " type="date"> <input :disabled="!userStore.userData.wedstrijdAdmin" :value="dateToYYYYMMDD(modelData.contest.date)" @input="modelData.contest.date = $event.target.valueAsDate" required="true" class="input dark:bg-neutral-700 bg-neutral-300 w-min hover:cursor-pointer pr-0 mb-5 " type="date">
<label class="font-bold">Tijd</label> <label class="font-bold">Tijd</label>
<div class="mb-1"> <div class="mb-1">
<input :disabled="!userData.wedstrijdAdmin" v-model="modelData.time.minutes" type="number" step="1" min="0" max="99" placeholder="mm" class="input dark:bg-neutral-700 bg-neutral-300 w-10 text-center p-1" /> <input :disabled="!userStore.userData.wedstrijdAdmin" v-model="modelData.time.minutes" type="number" step="1" min="0" max="99" placeholder="mm" class="input dark:bg-neutral-700 bg-neutral-300 w-10 text-center p-1" />
<span class="text-default text-xl font-bold mx-1">:</span> <span class="text-default text-xl font-bold mx-1">:</span>
<input :disabled="!userData.wedstrijdAdmin" v-model="modelData.time.seconds" type="number" step="1" min="0" max="99" placeholder="ss" class="input dark:bg-neutral-700 bg-neutral-300 w-10 text-center p-1" /> <input :disabled="!userStore.userData.wedstrijdAdmin" v-model="modelData.time.seconds" type="number" step="1" min="0" max="99" placeholder="ss" class="input dark:bg-neutral-700 bg-neutral-300 w-10 text-center p-1" />
<span class="text-default text-xl font-bold mx-1">:</span> <span class="text-default text-xl font-bold mx-1">:</span>
<input :disabled="!userData.wedstrijdAdmin" v-model="modelData.time.milliseconds" type="number" step="1" min="0" max="99" placeholder="ms" class="input dark:bg-neutral-700 bg-neutral-300 w-10 text-center p-1" /> <input :disabled="!userStore.userData.wedstrijdAdmin" v-model="modelData.time.milliseconds" type="number" step="1" min="0" max="99" placeholder="ms" class="input dark:bg-neutral-700 bg-neutral-300 w-10 text-center p-1" />
</div> </div>
<div class="flex items-center mb-5"> <div class="flex items-center mb-5">
<input :disabled="!userData.wedstrijdAdmin" type="checkbox" v-model="modelData.dsq" class="mr-1 checkbox"> <input :disabled="!userStore.userData.wedstrijdAdmin" type="checkbox" v-model="modelData.dsq" class="mr-1 checkbox">
<span class="text-default">Diskwalificatie</span> <span class="text-default">Diskwalificatie</span>
</div> </div>
<label class="font-bold">Info </label> <label class="font-bold">Info </label>
<input :disabled="!userData.wedstrijdAdmin" v-model="modelData.info" type="text" placeholder="Bijv. Een diskwalificatie" class="input dark:bg-neutral-700 bg-neutral-300 mb-10" /> <input :disabled="!userStore.userData.wedstrijdAdmin" v-model="modelData.info" type="text" placeholder="Bijv. Een diskwalificatie" class="input dark:bg-neutral-700 bg-neutral-300 mb-10" />
<input v-if="userData.wedstrijdAdmin" :disabled="disableButtons" type="submit" class="btn" value="Bewerken" /> <input v-if="userStore.userData.wedstrijdAdmin" :disabled="disableButtons" type="submit" class="btn" value="Bewerken" />
</form> </form>
</div> </div>
<div v-if="timings[0]" class="flex flex-col justify-center items-center gap-y-3 px-2 overflow-hidden"> <div v-if="contestStore.timings[0]" class="flex flex-col justify-center items-center gap-y-3 px-2 overflow-hidden">
<div class="flex gap-x-5"> <div class="flex gap-x-5">
<div class="relative"> <div class="relative">
<button @click.stop="showDeelnemersDropdown = !showDeelnemersDropdown" class="btn">Deelnemers <Icon size="1.2em" name="ion:arrow-down-b" /></button> <button @click.stop="showDeelnemersDropdown = !showDeelnemersDropdown" class="btn">Deelnemers <Icon size="1.2em" name="ion:arrow-down-b" /></button>
<div v-if="showDeelnemersDropdown" v-on-click-outside.bubble="handleDeelnemersDropdown" class="w-48 mt-2 container absolute rounded-lg shadow p-3"> <div v-if="showDeelnemersDropdown" v-on-click-outside.bubble="handleDeelnemersDropdown" class="w-48 mt-2 container absolute rounded-lg shadow p-3">
<ul class="space-y-2 text-default"> <ul class="space-y-2 text-default">
<li v-for="competitor in competitors" @click="competitor.checked = !competitor.checked" class="flex gap-x-1 items-center hover:cursor-pointer"> <li v-for="competitor in contestStore.competitors" @click="competitor.checked = !competitor.checked" class="flex gap-x-1 items-center hover:cursor-pointer">
<input v-model="competitor.checked" type="checkbox" class="checkbox"> <input v-model="competitor.checked" type="checkbox" class="checkbox">
<label class="hover:cursor-pointer">{{ competitor.name }}</label> <label class="hover:cursor-pointer">{{ competitor.name }}</label>
</li> </li>
@ -63,14 +63,14 @@
<div v-for="event in events" class="container w-full max-w-3xl py-2 px-4"> <div v-for="event in events" class="container w-full max-w-3xl py-2 px-4">
<div @click="event.open = !event.open" class="flex hover:cursor-pointer"> <div @click="event.open = !event.open" class="flex hover:cursor-pointer">
<h2 class="font-bold mr-auto">{{ event.name }}</h2> <h2 class="font-bold mr-auto">{{ event.name }}</h2>
<span v-if="filteredTimings.filter(a => a.event === event.id).length > 0" class=""> <span v-if="contestStore.filteredTimings.filter(a => a.event === event.id).length > 0" class="">
<span class="hidden md:inline-block mr-1"> <span class="hidden md:inline-block mr-1">
{{ filteredTimings.filter(a => a.event === event.id)[0].contest.date.toLocaleDateString('nl-NL') }} | {{ contestStore.filteredTimings.filter(a => a.event === event.id)[0].contest.date.toLocaleDateString('nl-NL') }} |
{{ filteredTimings.filter(a => a.event === event.id)[0].contest.location }} | {{ contestStore.filteredTimings.filter(a => a.event === event.id)[0].contest.location }} |
{{ filteredTimings.filter(a => a.event === event.id)[0].contest.type }} | {{ contestStore.filteredTimings.filter(a => a.event === event.id)[0].contest.type }} |
</span> </span>
<span> <span>
{{ filteredTimings.filter(a => a.event === event.id)[0].time.minutes }}:{{ filteredTimings.filter(a => a.event === event.id)[0].time.seconds }}:{{ filteredTimings.filter(a => a.event === event.id)[0].time.milliseconds }}</span> {{ contestStore.filteredTimings.filter(a => a.event === event.id)[0].time.minutes }}:{{ contestStore.filteredTimings.filter(a => a.event === event.id)[0].time.seconds }}:{{ contestStore.filteredTimings.filter(a => a.event === event.id)[0].time.milliseconds }}</span>
</span> </span>
<span v-else class="">Geen tijd</span> <span v-else class="">Geen tijd</span>
<Icon size="1.2em" name="ion:arrow-down-b" class="my-auto ml-2 transition-all" :class="{'rotate-180' : event.open }" /> <Icon size="1.2em" name="ion:arrow-down-b" class="my-auto ml-2 transition-all" :class="{'rotate-180' : event.open }" />
@ -83,10 +83,10 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr @click="handleModel(time, event, index)" v-for="(time, index) in filteredTimings.filter(a => a.event === event.id)" class="even:dark:bg-neutral-700 even:bg-neutral-300 hover:cursor-pointer"> <tr @click="handleModel(time, event, index)" v-for="(time, index) in contestStore.filteredTimings.filter(a => a.event === event.id)" class="even:dark:bg-neutral-700 even:bg-neutral-300 hover:cursor-pointer">
<td v-if="properties.time.enabled" class="pl-1" :class="time.dsq ? 'line-through' : ''">{{ time.time.minutes }}:{{ time.time.seconds }}:{{ time.time.milliseconds}}</td> <td v-if="properties.time.enabled" class="pl-1" :class="time.dsq ? 'line-through' : ''">{{ time.time.minutes }}:{{ time.time.seconds }}:{{ time.time.milliseconds}}</td>
<td v-if="properties.date.enabled">{{ time.contest.date.toLocaleDateString('nl-NL') }}</td> <td v-if="properties.date.enabled">{{ time.contest.date.toLocaleDateString('nl-NL') }}</td>
<td v-if="properties.name.enabled" class="overflow-hidden whitespace-nowrap truncate">{{ competitors.filter(a => a.relatiecode === time.relatiecode)[0].name.split(', ')[1] + ' ' + competitors.filter(a => a.relatiecode === time.relatiecode)[0].name.split(', ')[0] }}</td> <td v-if="properties.name.enabled" class="overflow-hidden whitespace-nowrap truncate">{{ contestStore.competitors.filter(a => a.relatiecode === time.relatiecode)[0].name.split(', ')[1] + ' ' + contestStore.competitors.filter(a => a.relatiecode === time.relatiecode)[0].name.split(', ')[0] }}</td>
<td v-if="properties.type.enabled">{{ time.contest.type }}</td> <td v-if="properties.type.enabled">{{ time.contest.type }}</td>
<td v-if="properties.location.enabled">{{ time.contest.location }}</td> <td v-if="properties.location.enabled">{{ time.contest.location }}</td>
</tr> </tr>
@ -107,10 +107,10 @@ definePageMeta({
key: 'back' key: 'back'
}) })
const { db, userData, competitors } = inject('firebase')
const toast = useToast() const toast = useToast()
const userStore = useUserStore()
const contestStore = useContestStore()
const timings = ref([])
const showModel = ref(false) const showModel = ref(false)
const disableButtons = ref(false) const disableButtons = ref(false)
const showDeelnemersDropdown = ref(false) const showDeelnemersDropdown = ref(false)
@ -124,16 +124,6 @@ const handlePropertiesDropdown = () => {
showPropertiesDropdown.value = false showPropertiesDropdown.value = false
} }
const filteredTimings = computed(() => {
timings.value = timings.value.sort((a, b) => a.time.combined.localeCompare(b.time.combined))
timings.value.forEach((time, index) => {
if (time.dsq === true) {
timings.value.push(timings.value.splice(index, 1)[0])
}
})
return timings.value.filter(a => competitors.value.filter(b => b.checked === true).map(x => x.relatiecode).includes(a.relatiecode))
})
const modelData = ref({ const modelData = ref({
time: { time: {
minutes: null, minutes: null,
@ -216,35 +206,11 @@ const events = ref({
}, },
}) })
const getCompetitors = async () => {
if (competitors.value[0]) return
const querySnapshot = await getDocs(collection(db, "competitors"))
querySnapshot.forEach((doc) => {
const data = doc.data()
data.checked = true
competitors.value.push(data)
})
}
onMounted(async () => { onMounted(async () => {
const citiesRef = collection(db, "timings"); await contestStore.getCompetitors()
await contestStore.getTimings()
const q = query(citiesRef); contestStore.selectCompetitors('all')
const querySnapshot = await getDocs(q);
querySnapshot.forEach((doc) => {
// doc.data() is never undefined for query doc snapshots
const data = doc.data()
data.id = doc.id
data.contest.date = data.contest.date.toDate()
timings.value.push(data)
});
timings.value = timings.value.sort((a, b) => a.time.combined.localeCompare(b.time.combined))
getCompetitors()
}) })
const dateToYYYYMMDD = (d) => { const dateToYYYYMMDD = (d) => {

View File

@ -10,9 +10,9 @@
<span>Brigade Tijden</span> <span>Brigade Tijden</span>
<Icon class="ml-auto" size="2em" name="ion:arrow-forward"/> <Icon class="ml-auto" size="2em" name="ion:arrow-forward"/>
</NuxtLink> </NuxtLink>
<div v-if="userData.wedstrijdAdmin" class="divider" /> <div v-if="userStore.userData.wedstrijdAdmin" class="divider" />
<NuxtLink v-if="userData.wedstrijdAdmin" to="/wedstrijd/addcontest" class="rounded-b item-hover py-2 flex items-center"> <NuxtLink v-if="userStore.userData.wedstrijdAdmin" to="/wedstrijd/addcontest" class="rounded-b item-hover py-2 flex items-center">
<span>Wedstrijd Toevoegen</span> <span>Tijden Toevoegen</span>
<Icon class="ml-auto" size="2em" name="ion:arrow-forward"/> <Icon class="ml-auto" size="2em" name="ion:arrow-forward"/>
</NuxtLink> </NuxtLink>
</div> </div>
@ -24,5 +24,5 @@ definePageMeta({
title: 'Wedstrijd' title: 'Wedstrijd'
}) })
const { userData } = inject('firebase') const userStore = useUserStore()
</script> </script>

View File

@ -4,42 +4,42 @@
<h1 class="font-bold text-center text-lg mb-5">Tijd {{ modelData.eventName }}</h1> <h1 class="font-bold text-center text-lg mb-5">Tijd {{ modelData.eventName }}</h1>
<label class="font-bold">Locatie</label> <label class="font-bold">Locatie</label>
<input :disabled="!userData.wedstrijdAdmin" v-model="modelData.contest.location" type="text" class="input dark:bg-neutral-700 bg-neutral-300 mb-5" /> <input :disabled="!userStore.userData.wedstrijdAdmin" v-model="modelData.contest.location" type="text" class="input dark:bg-neutral-700 bg-neutral-300 mb-5" />
<label class="font-bold">Datum</label> <label class="font-bold">Datum</label>
<input :disabled="!userData.wedstrijdAdmin" :value="dateToYYYYMMDD(modelData.contest.date)" @input="modelData.contest.date = $event.target.valueAsDate" required="true" class="input dark:bg-neutral-700 bg-neutral-300 w-min hover:cursor-pointer pr-0 mb-5 " type="date"> <input :disabled="!userStore.userData.wedstrijdAdmin" :value="dateToYYYYMMDD(modelData.contest.date)" @input="modelData.contest.date = $event.target.valueAsDate" required="true" class="input dark:bg-neutral-700 bg-neutral-300 w-min hover:cursor-pointer pr-0 mb-5 " type="date">
<label class="font-bold">Type zwembad</label> <label class="font-bold">Type zwembad</label>
<select :disabled="!userData.wedstrijdAdmin" v-model="modelData.contest.type" required="true" class="input dark:bg-neutral-700 bg-neutral-300 mb-5"> <select :disabled="!userStore.userData.wedstrijdAdmin" v-model="modelData.contest.type" required="true" class="input dark:bg-neutral-700 bg-neutral-300 mb-5">
<option value="50m">50 Meter</option> <option value="50m">50 Meter</option>
<option value="25m">25 Meter</option> <option value="25m">25 Meter</option>
</select> </select>
<label class="font-bold">Tijd</label> <label class="font-bold">Tijd</label>
<div class="mb-1"> <div class="mb-1">
<input :disabled="!userData.wedstrijdAdmin" v-model="modelData.time.minutes" type="number" step="1" min="0" max="99" placeholder="mm" class="input dark:bg-neutral-700 bg-neutral-300 w-10 text-center p-1" /> <input :disabled="!userStore.userData.wedstrijdAdmin" v-model="modelData.time.minutes" type="number" step="1" min="0" max="99" placeholder="mm" class="input dark:bg-neutral-700 bg-neutral-300 w-10 text-center p-1" />
<span class="text-default text-xl font-bold mx-1">:</span> <span class="text-default text-xl font-bold mx-1">:</span>
<input :disabled="!userData.wedstrijdAdmin" v-model="modelData.time.seconds" type="number" step="1" min="0" max="99" placeholder="ss" class="input dark:bg-neutral-700 bg-neutral-300 w-10 text-center p-1" /> <input :disabled="!userStore.userData.wedstrijdAdmin" v-model="modelData.time.seconds" type="number" step="1" min="0" max="99" placeholder="ss" class="input dark:bg-neutral-700 bg-neutral-300 w-10 text-center p-1" />
<span class="text-default text-xl font-bold mx-1">:</span> <span class="text-default text-xl font-bold mx-1">:</span>
<input :disabled="!userData.wedstrijdAdmin" v-model="modelData.time.milliseconds" type="number" step="1" min="0" max="99" placeholder="ms" class="input dark:bg-neutral-700 bg-neutral-300 w-10 text-center p-1" /> <input :disabled="!userStore.userData.wedstrijdAdmin" v-model="modelData.time.milliseconds" type="number" step="1" min="0" max="99" placeholder="ms" class="input dark:bg-neutral-700 bg-neutral-300 w-10 text-center p-1" />
</div> </div>
<div class="flex items-center mb-5"> <div class="flex items-center mb-5">
<input :disabled="!userData.wedstrijdAdmin" type="checkbox" v-model="modelData.dsq" class="mr-1 checkbox"> <input :disabled="!userStore.userData.wedstrijdAdmin" type="checkbox" v-model="modelData.dsq" class="mr-1 checkbox">
<span class="text-default">Diskwalificatie</span> <span class="text-default">Diskwalificatie</span>
</div> </div>
<label class="font-bold">Info </label> <label class="font-bold">Info </label>
<input :disabled="!userData.wedstrijdAdmin" v-model="modelData.info" type="text" placeholder="Bijv. Een diskwalificatie" class="input dark:bg-neutral-700 bg-neutral-300 mb-10" /> <input :disabled="!userStore.userData.wedstrijdAdmin" v-model="modelData.info" type="text" placeholder="Bijv. Een diskwalificatie" class="input dark:bg-neutral-700 bg-neutral-300 mb-10" />
<input v-if="userData.wedstrijdAdmin" :disabled="disableButtons" type="submit" class="btn" value="Bewerken" /> <input v-if="userStore.userData.wedstrijdAdmin" :disabled="disableButtons" type="submit" class="btn" value="Bewerken" />
</form> </form>
</div> </div>
<div v-if="timings[0]" class="flex flex-col justify-center items-center gap-y-3 px-2 overflow-hidden"> <div v-if="contestStore.filteredTimings[0]" class="flex flex-col justify-center items-center gap-y-3 px-2 overflow-hidden">
<div v-for="event in events" class="container w-full max-w-md py-2 px-4"> <div v-for="event in events" class="container w-full max-w-md py-2 px-4">
<div @click="event.open = !event.open" class="flex hover:cursor-pointer"> <div @click="event.open = !event.open" class="flex hover:cursor-pointer">
<h2 class="font-bold">{{ event.name }}</h2> <h2 class="font-bold">{{ event.name }}</h2>
<span v-if="timings.filter(a => a.event === event.id).length > 0" class="ml-auto">{{ filteredTimings.filter(a => a.event === event.id)[0].time.minutes }}:{{ filteredTimings.filter(a => a.event === event.id)[0].time.seconds }}:{{ filteredTimings.filter(a => a.event === event.id)[0].time.milliseconds }}</span> <span v-if="contestStore.filteredTimings.filter(a => a.event === event.id)[0]" class="ml-auto">{{ contestStore.filteredTimings.filter(a => a.event === event.id)[0].time.minutes }}:{{ contestStore.filteredTimings.filter(a => a.event === event.id)[0].time.seconds }}:{{ contestStore.filteredTimings.filter(a => a.event === event.id)[0].time.milliseconds }}</span>
<span v-else class="ml-auto">Geen tijd</span> <span v-else class="ml-auto">Geen tijd</span>
<Icon size="1.2em" name="ion:arrow-down-b" class="my-auto ml-2 transition-all" :class="{'rotate-180' : event.open }" /> <Icon size="1.2em" name="ion:arrow-down-b" class="my-auto ml-2 transition-all" :class="{'rotate-180' : event.open }" />
</div> </div>
@ -53,7 +53,7 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr @click="handleModel(time, event, index)" v-for="(time, index) in filteredTimings.filter(a => a.event === event.id)" class="even:dark:bg-neutral-700 even:bg-neutral-300 hover:cursor-pointer"> <tr @click="handleModel(time, event, index)" v-for="(time, index) in contestStore.filteredTimings.filter(a => a.event === event.id)" class="even:dark:bg-neutral-700 even:bg-neutral-300 hover:cursor-pointer">
<td class="pl-1" :class="time.dsq ? 'line-through' : ''">{{ time.time.minutes }}:{{ time.time.seconds }}:{{ time.time.milliseconds}}</td> <td class="pl-1" :class="time.dsq ? 'line-through' : ''">{{ time.time.minutes }}:{{ time.time.seconds }}:{{ time.time.milliseconds}}</td>
<td>{{ time.contest.date.toLocaleDateString('nl-NL') }}</td> <td>{{ time.contest.date.toLocaleDateString('nl-NL') }}</td>
<td>{{ time.contest.type }}</td> <td>{{ time.contest.type }}</td>
@ -74,23 +74,13 @@ definePageMeta({
key: 'back' key: 'back'
}) })
const { db, userData } = inject('firebase')
const toast = useToast() const toast = useToast()
const contestStore = useContestStore()
const userStore = useUserStore()
const timings = ref([])
const showModel = ref(false) const showModel = ref(false)
const disableButtons = ref(false) const disableButtons = ref(false)
const filteredTimings = computed(() => {
timings.value = timings.value.sort((a, b) => a.time.combined.localeCompare(b.time.combined))
timings.value.forEach((time, index) => {
if (time.dsq === true) {
timings.value.push(timings.value.splice(index, 1)[0])
}
})
return timings.value
})
const modelData = ref({ const modelData = ref({
time: { time: {
minutes: null, minutes: null,
@ -146,23 +136,8 @@ const events = ref({
}) })
onMounted(async () => { onMounted(async () => {
const citiesRef = collection(db, "timings"); await contestStore.getTimings()
contestStore.selectCompetitors('user', userStore.userData.relatiecodes)
const q = query(citiesRef, where("relatiecode", "in", userData.value.relatiecodes ));
const querySnapshot = await getDocs(q);
querySnapshot.forEach((doc) => {
// doc.data() is never undefined for query doc snapshots
const data = doc.data()
data.id = doc.id
data.contest.date = data.contest.date.toDate()
timings.value.push(data)
});
timings.value = timings.value.sort((a, b) => a.time.combined.localeCompare(b.time.combined))
}) })

View File

@ -0,0 +1,52 @@
export const useCalendarStore = defineStore('calendar', () => {
const events = ref([])
const getEvents = async (group) => {
let fridayEvents = []
let saturdayEvents = []
let matchEvents = []
if (group.includes('Zaterdag')){
let data = await fetch('https://www.googleapis.com/calendar/v3/calendars/c_astg0d0auheip5o4269v1qvv3g@group.calendar.google.com/events?key=AIzaSyBLmxWNEnbkiW_0c2UrsHMbxXv73dX-KYw')
if (!data.ok){
throw Error('No data available')
}
const jsonEvents = await data.json()
saturdayEvents = jsonEvents.items
} if (group.includes('Vrijdag')){
let data = await fetch('https://www.googleapis.com/calendar/v3/calendars/c_u3895n01jt8qnusm7f6pmqjb0k%40group.calendar.google.com/events?key=AIzaSyBLmxWNEnbkiW_0c2UrsHMbxXv73dX-KYw')
if (!data.ok){
throw Error('No data available')
}
const jsonEvents = await data.json()
fridayEvents = jsonEvents.items
} if (group.includes('Wedstrijd')){
let data = await fetch('https://www.googleapis.com/calendar/v3/calendars/c_c2296iboq07n24galuobeesovs@group.calendar.google.com/events?key=AIzaSyBLmxWNEnbkiW_0c2UrsHMbxXv73dX-KYw')
if (!data.ok){
throw Error('No data available')
}
const jsonEvents = await data.json()
matchEvents = jsonEvents.items
}
const allEvents = [...fridayEvents, ...saturdayEvents, ...matchEvents]
const now = new Date()
now.setHours(0, 0, 0, 0)
const sortedEvents = allEvents.sort((a, b) => new Date(a.start.dateTime) - new Date(b.start.dateTime))
const filteredEvents = sortedEvents.filter((event) => new Date(event.start.dateTime) > now )
filteredEvents.forEach(element => {
events.value.push({
description: element.description,
date: new Date(element.start.dateTime)
})
});
}
return { events, getEvents }
})

View File

@ -0,0 +1,74 @@
import { getDocs, collection, getFirestore, query } from 'firebase/firestore'
export const useContestStore = defineStore('contest', () => {
// General
const db = getFirestore()
// Contest competitors
const competitors = ref([])
const getCompetitors = async () => {
if (competitors.value[0]) return
const querySnapshot = await getDocs(collection(db, "competitors"))
querySnapshot.forEach((doc) => {
const data = doc.data()
data.checked = true
competitors.value.push(data)
})
}
const selectCompetitors = (x, relatiecodes) => {
if (x === 'user') {
competitors.value.forEach(competitor => {
if (relatiecodes.includes(competitor.relatiecode)) {
competitor.checked = true
} else {
competitor.checked = false
}
})
} else if (x === 'all') {
competitors.value.forEach(competitor => competitor.checked = true)
} else if (x === 'none') {
competitors.value.forEach(competitor => competitor.checked = false)
}
}
// Contest Timings
const timings = ref([])
const filteredTimings = computed(() => {
timings.value = timings.value.sort((a, b) => a.time.combined.localeCompare(b.time.combined))
timings.value.forEach((time, index) => {
if (time.dsq === true) {
timings.value.push(timings.value.splice(index, 1)[0])
}
})
return timings.value.filter(a => competitors.value.filter(b => b.checked === true).map(x => x.relatiecode).includes(a.relatiecode))
})
const getTimings = async () => {
if (timings.value[0]) return;
if (!competitors.value[0]) await getCompetitors()
const timingsRef = collection(db, "timings");
const q = query(timingsRef);
const querySnapshot = await getDocs(q);
querySnapshot.forEach((doc) => {
const data = doc.data()
data.id = doc.id
data.contest.date = data.contest.date.toDate()
timings.value.push(data)
});
}
return { competitors, getCompetitors, getTimings, timings, filteredTimings, selectCompetitors }
})

View File

@ -0,0 +1,80 @@
import { getDocs, collection, getFirestore, deleteDoc, doc, serverTimestamp, Timestamp, addDoc } from 'firebase/firestore'
import { getAuth } from 'firebase/auth'
export const useNewsStore = defineStore('news', () => {
const db = getFirestore()
const auth = getAuth()
const news = ref([])
const loaded = ref(false)
const toast = useToast()
const getNews = async () => {
news.value = []
try {
const querySnapshot = await getDocs(collection(db, "news"));
querySnapshot.forEach((doc) => {
let data = doc.data()
data.id = doc.id
news.value.push(data)
});
news.value.sort((a, b) => b.date.seconds - a.date.seconds)
loaded.value = true
} catch (e) {
console.log(e)
}
}
const deleteNews = async (item, index) => {
if (!item.id) return toast.error('Refresh eerst voordat je dit bericht kan verwijderen')
try {
await deleteDoc(doc(db, "news", item.id));
news.value.splice(index, 1)
toast.success('Bericht verwijderd')
} catch (e) {
console.log(e)
toast.error('Error tijdens bericht verwijderen')
}
}
const send = async (form) => {
const idToken = await auth.currentUser.getIdToken(true)
console.log(idToken)
const { error } = await useFetch('/api/sendmessage', {
method: 'post',
body: { title: form.title, body: form.description, token: idToken, topic: form.topic }
})
if (error.value) {
console.log(error.value)
return toast.error('Error tijdens het versturen van het bericht')
}
await addDoc(collection(db, "news"), {
title: form.title,
description: form.description,
date: serverTimestamp()
});
if (news.value) {
news.value.unshift({
title: form.title,
description: form.description,
date: Timestamp.now()
})
}
toast.success('Bericht is verstuurd')
navigateTo('/news')
}
return { getNews, news, loaded, deleteNews, send }
});

View File

@ -0,0 +1,93 @@
import { onAuthStateChanged, getAuth } from 'firebase/auth'
import { getDoc, doc, getFirestore } from 'firebase/firestore'
export const useUserStore = defineStore('user', () => {
const db = getFirestore()
const route = useRoute()
const auth = ref(null)
auth.value = getAuth()
const user = ref(null)
const isAuthenticated = ref(false)
const userData = ref(false)
const userPersons = ref([])
const userLoaded = ref(false)
const registrationToken = ref('')
const userAllPersons = ref([])
const init = () => {
onAuthStateChanged(auth.value, async (usr) => {
if (usr) {
user.value = usr
let docRef = doc(db, "users", user.value.uid);
let docSnap = await getDoc(docRef);
if (docSnap.exists()) {
const data = docSnap.data()
userData.value = data
getPersons(userData.value.relatiecodes)
} else {
setTimeout(() => window.location.reload(true), 1000)
}
if (!userData.value.sendNews && route.path === '/news/newmessage') navigateTo('/')
if (!userData.value.admin && route.path.startsWith('/settings/admin')) navigateTo('/')
isAuthenticated.value = true
setupNotifications()
} else {
isAuthenticated.value = false
user.value = null
userData.value = null
userPersons.value = []
}
userLoaded.value = true
})
}
const getPersons = async (persons) => {
userPersons.value = [];
for (let i = 0; i < persons.length; i++) {
const docRef = doc(db, "ledenlijst", persons[i]);
const docSnap = await getDoc(docRef);
if (docSnap.exists()) {
userPersons.value.push(docSnap.data())
}
}
}
const getAllPersons = async () => {
if (userPersons.value.length === 0) return setTimeout(() => getAllPersons(), 50)
const idToken = await auth.value.currentUser.getIdToken(true)
const { data: response, error } = await useFetch('/api/getrelatiecodes', {
method: 'post',
body: { email: user.value.email, token: idToken }
})
if (error.value) {
console.log(error.value)
return toast.error('Error tijdens het krijgen van relateicodes')
}
response.value.persons.forEach(person => {
if (userPersons.value.map(a => a.relatiecode).includes(person.relatiecode)) {
person.checked = true
} else {
person.checked = false
}
})
userAllPersons.value = response.value.persons
}
return { init, auth, isAuthenticated, userData, userPersons, userAllPersons, getAllPersons, registrationToken, getPersons, userLoaded, user }
})

View File

@ -0,0 +1,38 @@
import { getDocs, collection, getFirestore } from 'firebase/firestore'
export const useUsersStore = defineStore('users', () => {
const ledenlijst = ref([])
const users = ref([])
const db = getFirestore()
const getLedenlijst = async () => {
if (ledenlijst[0]) return;
try {
const querySnapshot = await getDocs(collection(db, "ledenlijst"));
querySnapshot.forEach((doc) => {
ledenlijst.value.push(doc.data())
});
} catch (e) {
console.log(e)
}
ledenlijst.value.sort((a, b) => a.fullName.localeCompare(b.fullName))
}
const getUsers = async () => {
if (users[0]) return
try {
const querySnapshot = await getDocs(collection(db, "users"));
querySnapshot.forEach((doc) => {
users.value.push(doc.data())
});
} catch (e) {
console.log(e)
}
}
return { ledenlijst, users, getLedenlijst, getUsers }
});