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>
<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>
<Icon size="4em" name="ion:load-c" class="animate-spin" />
<h2 class="mt-2 font-bold">Loading...</h2>
</div>
</div>
<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 />
<div class="overflow-y-auto pt-3">
<NuxtPage />
@ -20,35 +20,12 @@
</template>
<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 { useToast } from 'vue-toastification'
const db = getFirestore()
const route = useRoute()
const toast = useToast()
const userStore = useUserStore()
const isAuthenticated = ref(false)
const user = ref('frikandel')
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()
onMounted(async () => {
userStore.init()
Device.getInfo().then(info => {
if (info.platform === 'ios') document.getElementsByClassName('top-right')[0].classList.add('toastios')
@ -57,176 +34,12 @@ onMounted(() => {
if (process.client) {
if ('serviceWorker' in navigator && window.isSecureContext) {
Device.getInfo().then(info => {
if (info.platform === 'web') registerSW()
if (info.platform === 'web') registerServiceWorker()
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>
<style>

View File

@ -43,17 +43,17 @@
<script setup>
import { useToast } from 'vue-toastification'
import { createUserWithEmailAndPassword, signInWithEmailAndPassword, sendPasswordResetEmail } from "firebase/auth";
import { doc, setDoc } from "firebase/firestore";
const { auth, db, userAllPersons } = inject('firebase')
import { doc, setDoc, getFirestore } from "firebase/firestore";
const toast = useToast()
const userStore = useUserStore()
const showPassword = ref(false)
const creatingAccount = ref(false)
const disableButtons = ref(false)
const db = getFirestore()
const form = ref({
email: '',
password: '',
@ -63,8 +63,7 @@ const form = ref({
const submitLoginForm = () => {
disableButtons.value = true
signInWithEmailAndPassword(auth.value, form.value.email, form.value.password)
signInWithEmailAndPassword(userStore.auth, form.value.email, form.value.password)
.then(() => disableButtons.value = false)
.catch(async (error) => {
@ -101,9 +100,9 @@ const submitCreateForm = () => {
disableButtons.value = true
createUserWithEmailAndPassword(auth.value, form.value.email, form.value.newPassword)
createUserWithEmailAndPassword(userStore.auth, form.value.email, form.value.newPassword)
.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', {
method: 'post',
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) {
return navigateTo('/settings/config/managerelatiecodes')
@ -150,7 +149,7 @@ const submitCreateForm = () => {
}
const forgotPassword = () => {
sendPasswordResetEmail(auth.value, form.value.email)
sendPasswordResetEmail(userStore.auth, form.value.email)
.then(() => {
toast.info('Wachtwoord vergeten email verstuurd!')
})

View File

@ -17,7 +17,7 @@
<Icon size="1.8em" name="ion:settings-sharp" />
<span>Settings</span>
</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" />
<span>Wedstrijd</span>
</NuxtLink>
@ -29,7 +29,7 @@
import { Device } from '@capacitor/device';
const route = useRoute()
const { userData, userPersons } = inject('firebase')
const userStore = useUserStore()
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,12 +1,16 @@
// https://v3.nuxtjs.org/api/configuration/nuxt.config
export default defineNuxtConfig({
ssr: false,
imports: {
dirs: ['stores']
},
modules: [
'@nuxtjs/tailwindcss',
'nuxt-icon',
'@vueuse/nuxt',
'@nuxtjs/robots',
'@nuxtjs/plausible',
[ '@pinia/nuxt', { autoImports: [ 'defineStore' ]} ],
],
plausible: {
domain: 'wrbapp.xeovalyte.com',

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -1,7 +1,7 @@
<template>
<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-for="(event, index) in calEvents" :key="index">
<div v-if="calendarStore.events[0]" class="flex flex-col gap-3">
<div v-for="(event, index) in calendarStore.events" :key="index">
<div class="item container flex flex-col">
<h2 class="">{{ longEventDate(event.date) }}</h2>
<p class="whitespace-pre overflow-x-auto font-bold text-xl">{{ event.description}}</p>
@ -20,12 +20,24 @@ definePageMeta({
title: 'Agenda'
})
const { userPersons, calEvents } = inject('firebase')
const userStore = useUserStore()
const calendarStore = useCalendarStore()
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 date = new Date(eventDate)
@ -39,58 +51,5 @@ const longEventDate = (eventDate) => {
}
)}
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>

View File

@ -1,7 +1,7 @@
<template>
<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>
<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">
<NuxtLink to="/news" class="rounded-t item-hover py-2 flex items-center">
<span>Nieuws</span>
@ -26,6 +26,5 @@
definePageMeta({
title: 'Home',
})
const { userPersons } = inject('firebase')
const userStore = useUserStore()
</script>

View File

@ -1,15 +1,15 @@
<template>
<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">
<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">
<div v-if="newsStore.loaded && newsStore.news" class="flex flex-col gap-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
</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">
<h3 class="text-sm">{{ longEventDate(item.date.toDate()) }}</h3>
<h2 class="text-2xl font-bold">{{ item.title }}</h2>
<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">
@ -24,22 +24,15 @@
</template>
<script setup>
import { getDocs, collection, deleteDoc, doc } from 'firebase/firestore'
import { useToast } from 'vue-toastification'
definePageMeta({
title: 'Nieuws'
})
const { news, userData, db } = inject('firebase')
const toast = useToast()
const loadedNews = ref(false)
const userStore = useUserStore()
const newsStore = useNewsStore()
onMounted(() => {
if (!news.value) getNews()
else loadedNews.value = true
newsStore.getNews()
})
const longEventDate = (eventDate) => {
@ -55,38 +48,4 @@ const longEventDate = (eventDate) => {
}
)}
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)
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>
<script setup>
import { addDoc, collection, serverTimestamp, Timestamp } from 'firebase/firestore'
import { useToast } from 'vue-toastification'
definePageMeta({
title: 'Nieuw Bericht',
key: 'back'
})
const { news, db, auth } = inject('firebase')
const router = useRouter()
const toast = useToast()
const newsStore = useNewsStore()
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({
title: '',
description: '',
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>

View File

@ -42,7 +42,7 @@
</template>
<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'
definePageMeta({
@ -50,11 +50,11 @@ definePageMeta({
key: 'back'
})
const { db, ledenlijst, users } = inject('firebase')
const toast = useToast()
const usersStore = useUsersStore()
const modelData = ref(null)
const db = getFirestore()
const file = ref(null)
const disableButtons = ref(false)
@ -63,18 +63,7 @@ const newLedenlijst = ref([])
const showModel = ref(false)
onMounted(async () => {
if (!ledenlijst.value.length) {
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))
}
usersStore.getLedenlijst()
})
const handleModel = (lid) => {
@ -88,7 +77,7 @@ const handleModel = (lid) => {
const submitModelForm = async () => {
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) {
disableButtons.value = false
@ -123,7 +112,7 @@ const submitModelForm = async () => {
}
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) => {
@ -224,7 +213,7 @@ const uploadLedenlijst = async () => {
console.log(e)
}
ledenlijst.value = newLedenlijst.value
usersStore.ledenlijst = newLedenlijst.value
updateUsers()
}

View File

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

View File

@ -32,10 +32,9 @@ definePageMeta({
key: 'back'
})
const { user, auth } = inject('firebase')
const toast = useToast()
const router = useRouter()
const userStore = useUserStore()
const form = ref({
oldPassword: '',
@ -57,12 +56,12 @@ const savePassword = () => {
disableButtons.value = true
const credential = EmailAuthProvider.credential(
user.value.email,
userStore.user.email,
form.value.oldPassword
)
reauthenticateWithCredential(auth.value.currentUser, credential).then(() => {
updatePassword(auth.value.currentUser, form.value.newPassword).then(() => {
reauthenticateWithCredential(userStore.auth.currentUser, credential).then(() => {
updatePassword(userStore.auth.currentUser, form.value.newPassword).then(() => {
toast.success('Wachtwoord is veranderd')
navigateTo('/settings')
@ -81,7 +80,6 @@ const savePassword = () => {
console.log(error)
});
}
const showPassword = ref(false)

View File

@ -1,9 +1,9 @@
<template>
<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-for="person in 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'">
<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">
<div v-if="userStore.userAllPersons.length !== 0 && userStore.userPersons.length !== 0" class="flex flex-col gap-3">
<div v-for="person in userStore.userAllPersons" :key="person.relatiecode">
<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 === 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>
</div>
</div>
@ -20,7 +20,7 @@
</template>
<script setup>
import { updateDoc, doc } from 'firebase/firestore'
import { updateDoc, doc, getFirestore } from 'firebase/firestore'
import { useToast } from 'vue-toastification'
const router = useRouter()
@ -30,23 +30,14 @@ definePageMeta({
key: 'back'
})
const { user, userAllPersons, userPersons, db, getPersons, auth } = inject('firebase')
const toast = useToast()
const userStore = useUserStore()
const db = getFirestore()
const buttonsDisabled = ref(false)
onMounted(() => {
if (userAllPersons.value.length === 0) {
getAllPersons()
} else {
userAllPersons.value.forEach(person => {
if (userPersons.value.map(a => a.relatiecode).includes(person.relatiecode)) {
person.checked = true
} else {
person.checked = false
}
})
}
userStore.getAllPersons()
})
const save = async () => {
@ -54,52 +45,25 @@ const save = async () => {
const newRelatiecodes = []
userAllPersons.value.forEach(person => {
userStore.userAllPersons.forEach(person => {
if (person.checked) {
newRelatiecodes.push(person.relatiecode)
}
})
await updateDoc(doc(db, "users", user.value.uid), {
await updateDoc(doc(db, "users", userStore.user.uid), {
relatiecodes: newRelatiecodes
})
getPersons(newRelatiecodes)
userStore.getPersons(newRelatiecodes)
buttonsDisabled.value = false
navigateTo('/settings')
}
const updateCheckbox = (person) => {
if (person.relatiecode === userPersons.value[0].relatiecode) return;
if (person.relatiecode === userStore.userPersons[0].relatiecode) return;
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>

View File

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

View File

@ -15,11 +15,11 @@
<h1 class="text-xl ml-2 font-bold"></h1>
<div class="container">
<div class="item break-words ">
Registration Token: <b>{{ registrationToken }}</b>
Registration Token: <b>{{ userStore.registrationToken }}</b>
</div>
<div class="divider" />
<div class="item break-words ">
User ID: <b>{{ userData.id }}</b>
User ID: <b>{{ userStore.userData.id }}</b>
</div>
</div>
</div>
@ -32,5 +32,5 @@ definePageMeta({
key: 'back'
})
const { registrationToken, userData } = inject('firebase')
const userStore = useUserStore()
</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>
<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>
</select>
@ -45,7 +45,7 @@
<label class="font-bold">Onderdelen</label>
<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">
<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 }" />
@ -61,11 +61,11 @@
</thead>
<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">
<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.dsq }}</td>
</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></td>
<td></td>
@ -81,7 +81,7 @@
</template>
<script setup>
import { getDocs, collection, writeBatch, doc } from "firebase/firestore"
import { getDocs, collection, writeBatch, doc, getFirestore } from "firebase/firestore"
import { useToast } from 'vue-toastification'
definePageMeta({
@ -91,7 +91,9 @@ definePageMeta({
const toast = useToast()
const { db, competitors } = inject('firebase')
const contestStore = useContestStore()
const userStore = useUserStore()
const db = getFirestore()
const showModel = 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) => {
if(!competitor) competitor = {
relatiecode: '',
@ -206,7 +200,11 @@ const submitContestForm = async () => {
const docRef = doc(collection(db, 'timings'))
batch.set(docRef, {
relatiecode: competitor.relatiecode,
<<<<<<< HEAD
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,
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,
@ -223,7 +221,7 @@ const submitContestForm = async () => {
}
onMounted(() => {
getCompetitors()
contestStore.getCompetitors()
})
</script>

View File

@ -4,44 +4,44 @@
<h1 class="font-bold text-center text-lg mb-5">Tijd {{ modelData.eventName }}</h1>
<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>
<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="25m">25 Meter</option>
</select>
<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>
<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>
<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>
<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 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>
</div>
<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>
</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="relative">
<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">
<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">
<label class="hover:cursor-pointer">{{ competitor.name }}</label>
</li>
@ -63,14 +63,14 @@
<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">
<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">
{{ filteredTimings.filter(a => a.event === event.id)[0].contest.date.toLocaleDateString('nl-NL') }} |
{{ 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.date.toLocaleDateString('nl-NL') }} |
{{ contestStore.filteredTimings.filter(a => a.event === event.id)[0].contest.location }} |
{{ contestStore.filteredTimings.filter(a => a.event === event.id)[0].contest.type }} |
</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 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 }" />
@ -83,10 +83,10 @@
</tr>
</thead>
<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.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.location.enabled">{{ time.contest.location }}</td>
</tr>
@ -107,10 +107,10 @@ definePageMeta({
key: 'back'
})
const { db, userData, competitors } = inject('firebase')
const toast = useToast()
const userStore = useUserStore()
const contestStore = useContestStore()
const timings = ref([])
const showModel = ref(false)
const disableButtons = ref(false)
const showDeelnemersDropdown = ref(false)
@ -124,16 +124,6 @@ const handlePropertiesDropdown = () => {
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({
time: {
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 () => {
const citiesRef = collection(db, "timings");
await contestStore.getCompetitors()
await contestStore.getTimings()
const q = query(citiesRef);
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()
contestStore.selectCompetitors('all')
})
const dateToYYYYMMDD = (d) => {

View File

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

View File

@ -4,42 +4,42 @@
<h1 class="font-bold text-center text-lg mb-5">Tijd {{ modelData.eventName }}</h1>
<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>
<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>
<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="25m">25 Meter</option>
</select>
<label class="font-bold">Tijd</label>
<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>
<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>
<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 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>
</div>
<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>
</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 @click="event.open = !event.open" class="flex hover:cursor-pointer">
<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>
<Icon size="1.2em" name="ion:arrow-down-b" class="my-auto ml-2 transition-all" :class="{'rotate-180' : event.open }" />
</div>
@ -53,7 +53,7 @@
</tr>
</thead>
<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>{{ time.contest.date.toLocaleDateString('nl-NL') }}</td>
<td>{{ time.contest.type }}</td>
@ -74,23 +74,13 @@ definePageMeta({
key: 'back'
})
const { db, userData } = inject('firebase')
const toast = useToast()
const contestStore = useContestStore()
const userStore = useUserStore()
const timings = ref([])
const showModel = 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({
time: {
minutes: null,
@ -146,23 +136,8 @@ const events = ref({
})
onMounted(async () => {
const citiesRef = collection(db, "timings");
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))
await contestStore.getTimings()
contestStore.selectCompetitors('user', userStore.userData.relatiecodes)
})

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 }
});