Merge pull request 'Merge stable version before large change' (#16) from dev into main
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #16
This commit is contained in:
commit
72fd5778ae
@ -1,25 +1,5 @@
|
|||||||
pipeline:
|
pipeline:
|
||||||
build-frontend:
|
publish-dev:
|
||||||
image: node:16
|
|
||||||
commands:
|
|
||||||
- cd frontend
|
|
||||||
- npm i
|
|
||||||
- npm run generate
|
|
||||||
publish-frontend:
|
|
||||||
image: node:16
|
|
||||||
secrets:
|
|
||||||
- meli_api_token
|
|
||||||
commands:
|
|
||||||
- cd frontend
|
|
||||||
- npx -p "@getmeli/cli" meli upload .output/public --url https://meli.xeovalyte.dev --site 1e43e574-3eea-4e90-8c52-8a9bcab54f3a --token $$MELI_API_TOKEN --branch "main"
|
|
||||||
|
|
||||||
build-backend:
|
|
||||||
image: node:16
|
|
||||||
commands:
|
|
||||||
- cd backend
|
|
||||||
- npm i
|
|
||||||
|
|
||||||
publish-backend:
|
|
||||||
image: plugins/docker
|
image: plugins/docker
|
||||||
secrets:
|
secrets:
|
||||||
- docker_password
|
- docker_password
|
||||||
@ -31,5 +11,5 @@ pipeline:
|
|||||||
tags:
|
tags:
|
||||||
- latest
|
- latest
|
||||||
registry: gitea.xeovalyte.dev
|
registry: gitea.xeovalyte.dev
|
||||||
|
when:
|
||||||
|
branch: dev
|
||||||
|
11
Dockerfile
11
Dockerfile
@ -1,9 +1,12 @@
|
|||||||
FROM node:16
|
FROM node:18
|
||||||
|
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
COPY ./backend .
|
COPY ./frontend .
|
||||||
|
|
||||||
EXPOSE 7289
|
RUN npm install
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
CMD [ "node", "index.js" ]
|
EXPOSE 3000
|
||||||
|
|
||||||
|
CMD [ "node", ".output/server/index.mjs" ]
|
||||||
|
1
backend/.gitignore
vendored
1
backend/.gitignore
vendored
@ -1 +0,0 @@
|
|||||||
node_modules
|
|
@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"type": "service_account",
|
|
||||||
"project_id": "wrbapp",
|
|
||||||
"private_key_id": "1a8c65688260fed51f37d6d9383f6510eb066362",
|
|
||||||
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDPIOoKcZwGuW3V\nBwImp+1MV0VroKKOpg2XIwcUEvoGe4DDQ2EyTnCXOc14DRoQhlzU3YdfVX6WwXP6\nvRAKj5Bk1ML24Rm5/0ZuiJ2NvQ1A3le3htuKrLyfKjkBhNNc4m8/7ObHZ/t8wiKM\naR9XOCk6wLX7uB74YQdGxEdholRdvpNqPTPXSms+hWXchTBWDygNO2IZyMEdwswc\nlN8Lw0CfPrOZw+bUaGdNa8wf26jUECLZXKETeNdw+ZHqOT+0dG4pmeXt98/Oyui+\njyLs1hqbzRHCc3ap0/QjsKJKfjns8m7KHr6wNjlAeCbURb/lJddYTztsJINDX/ak\n5R4oj34dAgMBAAECggEAFROTlSP53TpNqY1ow9+3FxtTSlAicnMFs4EiNYH31KSJ\nFfV7hLe0H8NHO+XTZuaROCzhm0sTEkqVn2hRI034Araeryn5KPLHqzQ2GgfoexcU\n5G0x1lLc77JHcjbenhdBfEcCNbzIO6nyg4in4oNpuJuG+RoDdsFC0QnkfJQc0xwE\nOuWQRFmy2t2fmwP/HQ+SLioSlpfnRgo8CIJyQ5IsCXF7CAFWMhur9mMRSAUiloLU\nv/8+PUrwf8/1YAX+pI95NQ8Xl2HwLwBpvye/P/5v7Xd5tNJnBB5/QQYVmXC1T/01\nuw11Xvg/Y4LReOy9Fo67hDkSsSmTMUT9ypH5gDFKcQKBgQDrLh9E1UqfMiWmss+J\nS4zxwGmvBIU0cOVngeGBCzPJpbIZZ+VIe455vOrQB49xa5mEf3Hc063HYWGs9+hR\n2gkK1XVoBwfKPxPGoL2Tk1M3msjOtFaaW0AcrbWREniSmVMZjDypHRt7D0DTMkke\ngnVvkhX0kusVGSuhE2spIXa4cQKBgQDhdw9eYmIZ0ejv3MQ5s0FWM3ktPKZ4jXGl\nB3lN76cn1WChPVLquN1hVLG2PCfDTEU91z6bRMRmm0n1oHfi9V98rsZUcauyuO5H\nF1JYg0K5ZSLjkCrITlPVovpJAViDluWB5WawYIOdY63GBQn6tm4HmEn/fFz0U8pw\nSLEvZl1WbQKBgQCv+SzwhmB1ykId/8IGy39FDWKG0O0TFj6xOqAPvOAdTFx9Yh5Q\nJBOxx8gzrNSKW6bdW7dJMyLfA2Dg2gb96BXIA3z8P/Z2QMh9YZ04pY4pFyqWcJ40\nlX7ddqVbTeTmXM+vWB2ztNHxPLKW1ROdPqS8vSSsgppgiRr6RdtzRVTeIQKBgErd\nHrRHVK2gHolus5U5KSu3Qbg8mEYVKTQT7DptpgI6/q/rTdn0ckW8Opn5FXbqn18u\nVnJ1/gTX8VHm64fn08HxwpcNe2aHs07Vtpj/VKt8on4PQ7VpFLsuN48ALGTdOO3N\nvzA3i9w52dyTlcGyy4woDAISSEc0f1aTPIoxojJtAoGAFn7IuYlO+7mludy/LaiN\ndfz4VbXJglSIqSH6JNy2OnHJSK43bQV2MPJ9FVxN1WKHixdxTcrEdLWMvtlymHJq\ne90seug41Op+nT3e3HdhIf1HeVAFr5vxNbFABgpdkVDJs8lnVJP13D0mEjF5CRUS\nLUFh+r1bedz4f1R2s8ODhWQ=\n-----END PRIVATE KEY-----\n",
|
|
||||||
"client_email": "firebase-adminsdk-iqgyy@wrbapp.iam.gserviceaccount.com",
|
|
||||||
"client_id": "101347063281519043654",
|
|
||||||
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
|
||||||
"token_uri": "https://oauth2.googleapis.com/token",
|
|
||||||
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
|
|
||||||
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-iqgyy%40wrbapp.iam.gserviceaccount.com"
|
|
||||||
}
|
|
179
backend/index.js
179
backend/index.js
@ -1,179 +0,0 @@
|
|||||||
const express = require('express');
|
|
||||||
const cors = require('cors');
|
|
||||||
const { initializeApp, applicationDefault, cert } = require('firebase-admin/app');
|
|
||||||
const { getFirestore, Timestamp, FieldValue } = require('firebase-admin/firestore');
|
|
||||||
const { getMessaging } = require('firebase-admin/messaging')
|
|
||||||
const { getAuth } = require('firebase-admin/auth')
|
|
||||||
|
|
||||||
const serviceAccount = require('./firebase.json');
|
|
||||||
|
|
||||||
initializeApp({
|
|
||||||
credential: cert(serviceAccount)
|
|
||||||
});
|
|
||||||
|
|
||||||
const db = getFirestore();
|
|
||||||
|
|
||||||
const app = express();
|
|
||||||
|
|
||||||
app.use(express.json());
|
|
||||||
app.use(cors({
|
|
||||||
origin: '*',
|
|
||||||
}));
|
|
||||||
|
|
||||||
app.listen(7289, () => console.log('API is online!'));
|
|
||||||
|
|
||||||
app.get('/', (req, res) => {
|
|
||||||
res.status(200).send({
|
|
||||||
status: 'success',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
app.post('/checkrelatiecode', async (req, res) => {
|
|
||||||
const { relatiecode, email } = req.body;
|
|
||||||
|
|
||||||
if (!relatiecode) return res.status(400).send({ code: 'no-relatiecode'})
|
|
||||||
if (!email) return res.status(400).send({ code: 'no-email'})
|
|
||||||
|
|
||||||
try {
|
|
||||||
const docRef = db.collection('ledenlijst').doc(relatiecode);
|
|
||||||
const doc = await docRef.get();
|
|
||||||
|
|
||||||
if (!doc.exists) return res.status(400).send({ code: 'incorrect'})
|
|
||||||
|
|
||||||
const data = doc.data()
|
|
||||||
|
|
||||||
if (data.email[0] === email || data.email[1] === email) {
|
|
||||||
return res.status(200).send({ code: 'correct'})
|
|
||||||
} else {
|
|
||||||
return res.status(400).send({ code: 'incorrect'})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
} catch (e) {
|
|
||||||
return res.status(500).send({ code: 'error', error: e })
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
app.post('/getrelatiecodes', async (req, res) => {
|
|
||||||
const { email } = req.body;
|
|
||||||
|
|
||||||
if (!email) return res.status(400).send({ code: 'no-email'})
|
|
||||||
|
|
||||||
try {
|
|
||||||
const ledenlijstRef = db.collection('ledenlijst')
|
|
||||||
|
|
||||||
const snapshot = await ledenlijstRef.where("email", "array-contains", email).get()
|
|
||||||
|
|
||||||
if (snapshot.empty) {
|
|
||||||
res.status(400).send({ code: 'no-relatiecodes'})
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let relatiecodes = [];
|
|
||||||
let persons = [];
|
|
||||||
|
|
||||||
snapshot.forEach(doc => {
|
|
||||||
relatiecodes.push(doc.id)
|
|
||||||
const data = doc.data()
|
|
||||||
persons.push({ fullName: data.fullName, relatiecode: doc.id })
|
|
||||||
});
|
|
||||||
|
|
||||||
res.status(200).send({ code: 'success', relatiecodes: relatiecodes, persons: persons })
|
|
||||||
|
|
||||||
|
|
||||||
} catch (e) {
|
|
||||||
return res.status(500).send({ code: 'error', error: e })
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
app.post('/subscribetotopic', async (req, res) => {
|
|
||||||
const { topic, registrationToken } = req.body;
|
|
||||||
|
|
||||||
if (!topic) return res.status(400).send({ code: 'no-topic'})
|
|
||||||
if (!registrationToken) return res.status(400).send({ code: 'no-registrationToken'})
|
|
||||||
|
|
||||||
try {
|
|
||||||
getMessaging().subscribeToTopic([registrationToken], topic)
|
|
||||||
.then((response) => {
|
|
||||||
console.log('Successfully subscribed to topic:', response);
|
|
||||||
|
|
||||||
res.status(200).send({ code: 'success' })
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.log('Error subscribing to topic:', error);
|
|
||||||
|
|
||||||
return res.status(500).send({ code: 'error', error: error })
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
} catch (e) {
|
|
||||||
return res.status(500).send({ code: 'error', error: e })
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
app.post('/sendmessage', async (req, res) => {
|
|
||||||
const { title, body, token } = req.body;
|
|
||||||
|
|
||||||
if (!title) return res.status(400).send({ code: 'no-topic'})
|
|
||||||
if (!body) return res.status(400).send({ code: 'no-registrationToken'})
|
|
||||||
if (!token) return res.status(400).send({ code: 'no-token'})
|
|
||||||
|
|
||||||
try {
|
|
||||||
getAuth()
|
|
||||||
.verifyIdToken(token)
|
|
||||||
.then(async (decodedToken) => {
|
|
||||||
const uid = decodedToken.uid;
|
|
||||||
|
|
||||||
const docRef = db.collection('users').doc(uid);
|
|
||||||
const doc = await docRef.get();
|
|
||||||
|
|
||||||
if (!doc.exists) return res.status(400).send({ code: 'not-found'})
|
|
||||||
|
|
||||||
const data = doc.data()
|
|
||||||
|
|
||||||
if (!data.sendNews) return res.status(400).send({ code: 'no-permissions'})
|
|
||||||
|
|
||||||
const message = {
|
|
||||||
notification: {
|
|
||||||
title: title,
|
|
||||||
body: body,
|
|
||||||
icon: 'https://wrbapp.xeovalyte.com/ios/256.png',
|
|
||||||
},
|
|
||||||
topic: 'all',
|
|
||||||
apns: {
|
|
||||||
payload: {
|
|
||||||
aps: {
|
|
||||||
sound: 'default'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
getMessaging().send(message)
|
|
||||||
.then((response) => {
|
|
||||||
// Response is a message ID string.
|
|
||||||
console.log('Successfully sent message:', response);
|
|
||||||
|
|
||||||
res.status(200).send({ code: 'success', response: response })
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.log('Error sending message:', error);
|
|
||||||
|
|
||||||
return res.status(500).send({ code: 'error', error: error })
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.log(error)
|
|
||||||
|
|
||||||
return res.status(500).send({ code: 'error', error: error })
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
} catch (e) {
|
|
||||||
return res.status(500).send({ code: 'error', error: e })
|
|
||||||
}
|
|
||||||
})
|
|
Binary file not shown.
4036
backend/package-lock.json
generated
4036
backend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,17 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "backend",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"description": "",
|
|
||||||
"main": "index.js",
|
|
||||||
"scripts": {
|
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
|
||||||
},
|
|
||||||
"keywords": [],
|
|
||||||
"author": "",
|
|
||||||
"license": "ISC",
|
|
||||||
"dependencies": {
|
|
||||||
"cors": "^2.8.5",
|
|
||||||
"express": "^4.18.1",
|
|
||||||
"firebase-admin": "^11.0.1"
|
|
||||||
}
|
|
||||||
}
|
|
2
frontend/.gitignore
vendored
2
frontend/.gitignore
vendored
@ -6,3 +6,5 @@ node_modules
|
|||||||
.output
|
.output
|
||||||
.env
|
.env
|
||||||
dist
|
dist
|
||||||
|
|
||||||
|
service-account.json
|
||||||
|
@ -50,10 +50,15 @@ const messaging = ref(null)
|
|||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
auth.value = getAuth()
|
auth.value = getAuth()
|
||||||
|
|
||||||
|
Device.getInfo().then(info => {
|
||||||
|
if (info.platform === 'ios') document.getElementsByClassName('top-right')[0].classList.add('toastios')
|
||||||
|
});
|
||||||
|
|
||||||
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') registerSW()
|
||||||
|
else document.getElementsByClassName('top-right')[0].classList.add('toastios')
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -122,26 +127,21 @@ const setupNotifications = () => {
|
|||||||
|
|
||||||
// On success, we should be able to receive notifications
|
// On success, we should be able to receive notifications
|
||||||
PushNotifications.addListener('registration',
|
PushNotifications.addListener('registration',
|
||||||
(token) => {
|
async (token) => {
|
||||||
// alert('Push registration success, token: ' + token.value);
|
// alert('Push registration success, token: ' + token.value);
|
||||||
registrationToken.value = token
|
registrationToken.value = token
|
||||||
|
|
||||||
|
const { error } = await useFetch('/api/subscribetotopic', {
|
||||||
fetch('https://api.xeovalyte.com/subscribetotopic', {
|
method: 'post',
|
||||||
method: 'POST',
|
body: { topic: 'all', registrationToken: token.value }
|
||||||
headers: {
|
|
||||||
Authorization: 'Basic WGVvdmFseXRlOmtNKjhuRXMzNTchalJlXm1KYnZrRSFOIw==',
|
|
||||||
'content-type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ topic: 'all', registrationToken: token.value })
|
|
||||||
}).then(response => response.json())
|
|
||||||
.then(response => {
|
|
||||||
console.log(response)
|
|
||||||
})
|
})
|
||||||
.catch(err => {
|
|
||||||
console.log(err)
|
if (error.value) {
|
||||||
toast.error('Error tijdens het registreren van push notificaties')
|
console.log(error.value)
|
||||||
});
|
return toast.error('Error tijdens het krijgen van relateicodes')
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Subscribed to topic!')
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -174,23 +174,22 @@ const setupNotifications = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const registerFCM = () => {
|
const registerFCM = () => {
|
||||||
getToken(messaging.value, { vapidKey: 'BI7l3nyGV6wJcFh7wrwmQ42W7RSXl46bmhXZJmDd4P-0K_JFP0ClTqjO-rr5H5DXBbmVR4kXwxFpUlo_d6cUy4Q' }).then((currentToken) => {
|
getToken(messaging.value, { vapidKey: 'BI7l3nyGV6wJcFh7wrwmQ42W7RSXl46bmhXZJmDd4P-0K_JFP0ClTqjO-rr5H5DXBbmVR4kXwxFpUlo_d6cUy4Q' }).then(async (currentToken) => {
|
||||||
if (currentToken) {
|
if (currentToken) {
|
||||||
console.log(currentToken)
|
console.log(currentToken)
|
||||||
fetch('https://api.xeovalyte.com/subscribetotopic', {
|
|
||||||
method: 'POST',
|
const { error} = await useFetch('/api/subscribetotopic', {
|
||||||
headers: {
|
method: 'post',
|
||||||
Authorization: 'Basic WGVvdmFseXRlOmtNKjhuRXMzNTchalJlXm1KYnZrRSFOIw==',
|
body: { topic: 'all', registrationToken: currentToken }
|
||||||
'content-type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ topic: 'all', registrationToken: currentToken })
|
|
||||||
}).then(response => response.json())
|
|
||||||
.then(response => {
|
|
||||||
console.log(response)
|
|
||||||
})
|
})
|
||||||
.catch(err => {
|
|
||||||
console.log(err)
|
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 {
|
} else {
|
||||||
// Show permission request UI
|
// Show permission request UI
|
||||||
console.log('No registration token available. Request permission to generate one.');
|
console.log('No registration token available. Request permission to generate one.');
|
||||||
@ -227,13 +226,16 @@ const logDeviceInfo = async () => {
|
|||||||
|
|
||||||
const ledenlijst = ref([])
|
const ledenlijst = ref([])
|
||||||
|
|
||||||
provide('firebase', { db, ledenlijst, isAuthenticated, user, userData, userPersons, auth, users, userAllPersons, getPersons, calEvents, news, registrationToken, contestTimes, competitors })
|
provide('firebase', { db, ledenlijst, isAuthenticated, user, userData, userPersons, auth, users, userAllPersons, getPersons, calEvents, news, registrationToken, contestTimes, competitors, registrationToken })
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style>
|
||||||
.body {
|
.body {
|
||||||
padding-top: 10px;
|
padding-top: 10px;
|
||||||
margin-bottom: env(safe-area-inset-bottom);
|
margin-bottom: env(safe-area-inset-bottom);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.toastios {
|
||||||
|
padding-top: 20px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -6,34 +6,34 @@
|
|||||||
<label class="font-bold">Email</label>
|
<label class="font-bold">Email</label>
|
||||||
<input v-model="form.email" required="true" placeholder="user@example.com" class="input mb-5" type="email">
|
<input v-model="form.email" required="true" placeholder="user@example.com" class="input mb-5" type="email">
|
||||||
|
|
||||||
<label class="font-bold">Password</label>
|
<label class="font-bold">Wachtwoord</label>
|
||||||
<input v-model="form.password" required="true" class="input" :type="showPassword ? 'text' : 'password'">
|
<input v-model="form.password" 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>Show Password</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="Login" class="btn w-full sm:w-24 mb-1">
|
<input :disabled="disableButtons" type="submit" value="Login" class="btn w-full sm:w-24 mb-1">
|
||||||
<button @click="forgotPassword" class="hover:underline font-bold w-full sm:w-max sm:ml-auto">Forgot Password?</button>
|
<button @click="forgotPassword" class="hover:underline font-bold w-full sm:w-max sm:ml-auto">Wachtwoord vergeten?</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<form v-else @submit.prevent="submitCreateForm" class="flex flex-col">
|
<form v-else @submit.prevent="submitCreateForm" class="flex flex-col">
|
||||||
<h3 class="text-center text-default text-lg mb-5">Creating account for <br><b>{{ form.email }}</b></h3>
|
<h3 class="text-center text-default text-lg mb-5">Account aanmaken voor <br><b>{{ form.email }}</b></h3>
|
||||||
<label class="font-bold">New Password</label>
|
<label class="font-bold">Nieuw wachtwoord</label>
|
||||||
<input v-model="form.newPassword" required="true" class="input mb-1" :type="showPassword ? 'text' : 'password'">
|
<input v-model="form.newPassword" required="true" class="input mb-1" :type="showPassword ? 'text' : 'password'">
|
||||||
<span class="mb-5 text-default italic text-sm">Minimaal 8 karakters</span>
|
<span class="mb-5 text-default italic text-sm">Minimaal 8 karakters</span>
|
||||||
|
|
||||||
<label class="font-bold">Confirm New Password</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>Show Password</span>
|
<span>Toon wachtwoord</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="w-full flex flex-wrap">
|
<div class="w-full flex flex-wrap">
|
||||||
<input :disabled="disableButtons" type="submit" value="Create Account" class="btn w-full sm:w-40 mb-1">
|
<input :disabled="disableButtons" type="submit" value="Account Aanmaken" class="btn w-full sm:w-40 mb-1">
|
||||||
<button @click="goBack" class="hover:underline font-bold w-full sm:w-max sm:ml-auto">Not you? Go back</button>
|
<button @click="goBack" class="hover:underline font-bold w-full sm:w-max sm:ml-auto">Jij niet? Ga terug</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@ -46,7 +46,7 @@ import { createUserWithEmailAndPassword, signInWithEmailAndPassword, sendPasswor
|
|||||||
import { doc, setDoc } from "firebase/firestore";
|
import { doc, setDoc } from "firebase/firestore";
|
||||||
|
|
||||||
|
|
||||||
const { auth, db } = inject('firebase')
|
const { auth, db, userAllPersons } = inject('firebase')
|
||||||
|
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
|
||||||
@ -67,30 +67,23 @@ const submitLoginForm = () => {
|
|||||||
signInWithEmailAndPassword(auth.value, 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) => {
|
||||||
const errorCode = error.code;
|
|
||||||
const errorMessage = error.message;
|
|
||||||
|
|
||||||
if (error.code === 'auth/user-not-found') {
|
if (error.code === 'auth/user-not-found') {
|
||||||
|
const { error: err, data } = await useFetch('/api/checkrelatiecode', {
|
||||||
fetch('https://api.xeovalyte.com/checkrelatiecode', {
|
method: 'post',
|
||||||
method: 'POST',
|
body: { email: form.value.email, relatiecode: form.value.password.toUpperCase() }
|
||||||
headers: {
|
|
||||||
Authorization: 'Basic WGVvdmFseXRlOmtNKjhuRXMzNTchalJlXm1KYnZrRSFOIw==',
|
|
||||||
'content-type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ email: form.value.email, relatiecode: form.value.password.toUpperCase() })
|
|
||||||
}).then(response => response.json())
|
|
||||||
.then(response => {
|
|
||||||
disableButtons.value = false
|
|
||||||
if (response.code === 'incorrect') return toast.error('Email, wachtwoord of relatiecode onjuist')
|
|
||||||
else if (response.code === 'correct') return creatingAccount.value = true
|
|
||||||
})
|
})
|
||||||
.catch(err => {
|
|
||||||
disableButtons.value = false
|
|
||||||
console.log(err)
|
|
||||||
|
|
||||||
toast.error('Error met het controleren van relatiecode')
|
if (err.value) {
|
||||||
});
|
console.log(err.value)
|
||||||
|
disableButtons.value = false
|
||||||
|
return toast.error('Error tijdens het controleren van relatiecode')
|
||||||
|
}
|
||||||
|
|
||||||
|
disableButtons.value = false
|
||||||
|
if (data.value.code === 'incorrect') return toast.error('Email, wachtwoord of relatiecode onjuist')
|
||||||
|
else if (data.value.code === 'correct') return creatingAccount.value = true
|
||||||
|
|
||||||
} else if (error.code === 'auth/wrong-password') {
|
} else if (error.code === 'auth/wrong-password') {
|
||||||
toast.error('Verkeerde wachtwoord')
|
toast.error('Verkeerde wachtwoord')
|
||||||
} else {
|
} else {
|
||||||
@ -109,37 +102,43 @@ const submitCreateForm = () => {
|
|||||||
disableButtons.value = true
|
disableButtons.value = true
|
||||||
|
|
||||||
createUserWithEmailAndPassword(auth.value, form.value.email, form.value.newPassword)
|
createUserWithEmailAndPassword(auth.value, form.value.email, form.value.newPassword)
|
||||||
.then((userCredential) => {
|
.then(async (userCredential) => {
|
||||||
fetch('https://api.xeovalyte.com/getrelatiecodes', {
|
const idToken = await auth.value.currentUser.getIdToken(true)
|
||||||
method: 'POST',
|
const { error, data } = await useFetch('/api/getrelatiecodes', {
|
||||||
headers: {
|
method: 'post',
|
||||||
Authorization: 'Basic WGVvdmFseXRlOmtNKjhuRXMzNTchalJlXm1KYnZrRSFOIw==',
|
body: { email: form.value.email, token: idToken }
|
||||||
'content-type': 'application/json'
|
})
|
||||||
},
|
|
||||||
body: JSON.stringify({ email: form.value.email })
|
if (error.value) {
|
||||||
}).then(response => response.json())
|
console.log(error.value)
|
||||||
.then(response => {
|
|
||||||
disableButtons.value = false
|
disableButtons.value = false
|
||||||
if (response.code === 'error') return toast.error('Error tijdens maken van account')
|
return toast.error('Error tijdens het controleren van relatiecode')
|
||||||
else if (response.code === 'success') {
|
}
|
||||||
setDoc(doc(db, "users", userCredential.user.uid), {
|
|
||||||
|
disableButtons.value = false
|
||||||
|
if (data.value.code === 'error') return toast.error('Error tijdens maken van account')
|
||||||
|
else if (data.value.code === 'success') {
|
||||||
|
await setDoc(doc(db, "users", userCredential.user.uid), {
|
||||||
email: form.value.email,
|
email: form.value.email,
|
||||||
relatiecodes: [form.value.password.toUpperCase()],
|
relatiecodes: [form.value.password.toUpperCase()],
|
||||||
allRelatiecodes: response.relatiecodes,
|
allRelatiecodes: data.value.relatiecodes,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.relatiecodes.length > 1) {
|
data.value.persons.forEach(person => {
|
||||||
|
if (person.relatiecode === form.value.password.toUpperCase()) {
|
||||||
|
person.checked = true
|
||||||
|
} else {
|
||||||
|
person.checked = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
userAllPersons.value = data.value.persons
|
||||||
|
|
||||||
|
if (data.value.relatiecodes.length > 1) {
|
||||||
return navigateTo('/settings/config/managerelatiecodes')
|
return navigateTo('/settings/config/managerelatiecodes')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(err => {
|
|
||||||
disableButtons.value = false
|
|
||||||
console.log(err)
|
|
||||||
|
|
||||||
toast.error('Error met het controleren van relatiecode')
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
const errorCode = error.code;
|
const errorCode = error.code;
|
||||||
const errorMessage = error.message;
|
const errorMessage = error.message;
|
||||||
@ -173,7 +172,6 @@ const goBack = () => {
|
|||||||
newPassword: '',
|
newPassword: '',
|
||||||
confirmNewPassword: ''
|
confirmNewPassword: ''
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
@ -7,17 +7,17 @@
|
|||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
<NuxtLink to="/news" class="flex flex-col items-center hover:cursor-pointer drop-shadow" :class="route.path.startsWith('/news') ? 'text-primary' : ''">
|
<NuxtLink to="/news" class="flex flex-col items-center hover:cursor-pointer drop-shadow" :class="route.path.startsWith('/news') ? 'text-primary' : ''">
|
||||||
<Icon size="1.8em" name="ion:newspaper-outline" />
|
<Icon size="1.8em" name="ion:newspaper-outline" />
|
||||||
<span>News</span>
|
<span>Nieuws</span>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
<NuxtLink to="/calendar" class="flex flex-col items-center hover:cursor-pointer drop-shadow" :class="route.path === '/calendar' ? 'text-primary' : ''">
|
<NuxtLink to="/calendar" class="flex flex-col items-center hover:cursor-pointer drop-shadow" :class="route.path === '/calendar' ? 'text-primary' : ''">
|
||||||
<Icon size="1.8em" name="ion:calendar-outline" />
|
<Icon size="1.8em" name="ion:calendar-outline" />
|
||||||
<span>Calendar</span>
|
<span>Agenda</span>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
<NuxtLink to="/settings" class="flex flex-col items-center hover:cursor-pointer drop-shadow" :class="route.path.startsWith('/settings') ? 'text-primary' : ''">
|
<NuxtLink to="/settings" class="flex flex-col items-center hover:cursor-pointer drop-shadow" :class="route.path.startsWith('/settings') ? 'text-primary' : ''">
|
||||||
<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="userData.wedstrijdteam" to="/wedstrijd" class="flex flex-col items-center hover:cursor-pointer drop-shadow" :class="route.path.startsWith('/wedstrijd') ? 'text-primary' : ''">
|
<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' : ''">
|
||||||
<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 } = inject('firebase')
|
const { userData, userPersons } = inject('firebase')
|
||||||
|
|
||||||
const platform = ref(null)
|
const platform = ref(null)
|
||||||
|
|
||||||
|
@ -5,8 +5,13 @@ export default defineNuxtConfig({
|
|||||||
'@nuxtjs/tailwindcss',
|
'@nuxtjs/tailwindcss',
|
||||||
'nuxt-icon',
|
'nuxt-icon',
|
||||||
'@vueuse/nuxt',
|
'@vueuse/nuxt',
|
||||||
'@nuxtjs/robots'
|
'@nuxtjs/robots',
|
||||||
|
'@nuxtjs/plausible',
|
||||||
],
|
],
|
||||||
|
plausible: {
|
||||||
|
domain: 'wrbapp.xeovalyte.com',
|
||||||
|
apiHost: 'https://plausible.xeovalyte.dev',
|
||||||
|
},
|
||||||
build: {
|
build: {
|
||||||
transpile: ['vue-toastification'],
|
transpile: ['vue-toastification'],
|
||||||
},
|
},
|
||||||
@ -24,5 +29,10 @@ export default defineNuxtConfig({
|
|||||||
{ rel: 'icon', href: '/favicon.ico', type: 'image/x-icon' }
|
{ rel: 'icon', href: '/favicon.ico', type: 'image/x-icon' }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
runtimeConfig: {
|
||||||
|
privateKeyId: '',
|
||||||
|
privateKey: '',
|
||||||
|
clientId: ''
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
10538
frontend/package-lock.json
generated
10538
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -8,24 +8,27 @@
|
|||||||
"postinstall": "nuxt prepare"
|
"postinstall": "nuxt prepare"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@capacitor/cli": "^4.3.0",
|
"@capacitor/cli": "^4.6.3",
|
||||||
"@nuxtjs/tailwindcss": "^6.1.3",
|
"@nuxtjs/plausible": "^0.2.0",
|
||||||
|
"@nuxtjs/tailwindcss": "^6.3.1",
|
||||||
"@tailwindcss/forms": "^0.5.3",
|
"@tailwindcss/forms": "^0.5.3",
|
||||||
"@vueuse/core": "^9.6.0",
|
"@vueuse/core": "^9.12.0",
|
||||||
"@vueuse/nuxt": "^9.6.0",
|
"@vueuse/nuxt": "^9.12.0",
|
||||||
"nuxt": "^3.0.0-rc.14",
|
"nuxt": "^3.2.0",
|
||||||
"nuxt-icon": "^0.1.7"
|
"nuxt-icon": "^0.2.11"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@capacitor/core": "^4.3.0",
|
"@capacitor/core": "^4.6.3",
|
||||||
"@capacitor/device": "^4.0.1",
|
"@capacitor/device": "^4.1.0",
|
||||||
"@capacitor/ios": "^4.3.0",
|
"@capacitor/ios": "^4.6.3",
|
||||||
"@capacitor/push-notifications": "^4.1.0",
|
"@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/firebase": "^9.2.0",
|
"@vueuse/components": "^9.12.0",
|
||||||
"@vueuse/shared": "^9.4.0",
|
"@vueuse/firebase": "^9.12.0",
|
||||||
"firebase": "^9.14.0",
|
"@vueuse/shared": "^9.12.0",
|
||||||
|
"firebase": "^9.17.1",
|
||||||
|
"firebase-admin": "^11.5.0",
|
||||||
"vue-toastification": "^2.0.0-rc.5"
|
"vue-toastification": "^2.0.0-rc.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
title: 'Calendar'
|
title: 'Agenda'
|
||||||
})
|
})
|
||||||
|
|
||||||
const { userPersons, calEvents } = inject('firebase')
|
const { userPersons, calEvents } = inject('firebase')
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
<div class="divider" />
|
<div class="divider" />
|
||||||
<NuxtLink to="/calendar" class="item-hover py-2 flex items-center">
|
<NuxtLink to="/calendar" class="item-hover py-2 flex items-center">
|
||||||
<span>Calendar</span>
|
<span>Agenda</span>
|
||||||
<Icon class="ml-auto" size="2em" name="ion:arrow-forward"/>
|
<Icon class="ml-auto" size="2em" name="ion:arrow-forward"/>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
<div class="divider" />
|
<div class="divider" />
|
||||||
|
@ -1,15 +1,21 @@
|
|||||||
<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="sendNews" class="flex flex-col">
|
<form @submit.prevent="sendNews" class="flex flex-col">
|
||||||
<label class="font-bold">Title</label>
|
<label class="font-bold">Titel</label>
|
||||||
<input v-model="form.title" required="true" class="input mb-5" type="text">
|
<input v-model="form.title" required="true" class="input mb-5" type="text">
|
||||||
|
|
||||||
<label class="font-bold">Description</label>
|
<label class="font-bold">Beschrijving</label>
|
||||||
<textarea v-model="form.description" required="true" class="input mb-5" />
|
<textarea v-model="form.description" required="true" class="input mb-5" />
|
||||||
|
|
||||||
|
<label class="font-bold">Groep</label>
|
||||||
|
<select v-model="form.topic" required="true" class="input mb-5">
|
||||||
|
<option value="all">Iedereen</option>
|
||||||
|
<option value="test">Test</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
<div class="w-full flex flex-wrap justify-between">
|
<div class="w-full flex flex-wrap justify-between">
|
||||||
<input :disabled="disableButtons" type="submit" value="Stuur Bericht" class="btn w-full sm:w-40 mb-1">
|
<input :disabled="disableButtons" type="submit" value="Stuur Bericht" class="btn w-full sm:w-40 mb-1">
|
||||||
<button @click="router.back()" class="hover:underline font-bold w-full sm:w-max sm:ml-auto">Cancel</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>
|
||||||
@ -24,7 +30,7 @@ definePageMeta({
|
|||||||
key: 'back'
|
key: 'back'
|
||||||
})
|
})
|
||||||
|
|
||||||
const { news, userData, db, auth } = inject('firebase')
|
const { news, db, auth } = inject('firebase')
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
|
||||||
@ -33,6 +39,7 @@ const disableButtons = ref(false)
|
|||||||
const form = ref({
|
const form = ref({
|
||||||
title: '',
|
title: '',
|
||||||
description: '',
|
description: '',
|
||||||
|
topic: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
const sendNews = async () => {
|
const sendNews = async () => {
|
||||||
@ -42,16 +49,15 @@ const sendNews = async () => {
|
|||||||
const idToken = await auth.value.currentUser.getIdToken(true)
|
const idToken = await auth.value.currentUser.getIdToken(true)
|
||||||
console.log(idToken)
|
console.log(idToken)
|
||||||
|
|
||||||
await fetch('https://api.xeovalyte.com/sendmessage', {
|
const { error } = await useFetch('/api/sendmessage', {
|
||||||
method: 'POST',
|
method: 'post',
|
||||||
headers: {
|
body: { title: form.value.title, body: form.value.description, token: idToken, topic: form.value.topic }
|
||||||
Authorization: 'Basic WGVvdmFseXRlOmtNKjhuRXMzNTchalJlXm1KYnZrRSFOIw==',
|
})
|
||||||
'content-type': 'application/json'
|
|
||||||
},
|
if (error.value) {
|
||||||
body: JSON.stringify({ title: form.value.title, body: form.value.description, token: idToken })
|
console.log(error.value)
|
||||||
}).then(response => response.json())
|
return toast.error('Error tijdens het versturen van het bericht')
|
||||||
.then(async response => {
|
}
|
||||||
console.log(response)
|
|
||||||
|
|
||||||
await addDoc(collection(db, "news"), {
|
await addDoc(collection(db, "news"), {
|
||||||
title: form.value.title,
|
title: form.value.title,
|
||||||
@ -69,17 +75,10 @@ const sendNews = async () => {
|
|||||||
toast.success('Bericht is verstuurd')
|
toast.success('Bericht is verstuurd')
|
||||||
|
|
||||||
navigateTo('/news')
|
navigateTo('/news')
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
console.log(err)
|
|
||||||
|
|
||||||
toast.error('Error tijdens het berict sturen')
|
|
||||||
});
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e)
|
console.log(e)
|
||||||
|
|
||||||
toast.error('Error tijdens het berict sturen')
|
toast.error('Error tijdens het berict sturen')
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,16 +1,39 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<div @click.self="showModel = false" v-if="showModel" class="fixed flex justify-center items-center h-screen w-full bg-black top-0 left-0 z-50 bg-opacity-50" >
|
||||||
|
<form @submit.prevent="submitModelForm" class="dark:bg-neutral-800 bg-neutral-200 p-10 rounded-xl flex flex-col w-full max-w-sm">
|
||||||
|
<h1 class="font-bold text-center text-lg mb-5">Beheer Persoon</h1>
|
||||||
|
|
||||||
|
<div class="text-default mb-2">
|
||||||
|
Relatiecode: <b>{{ modelData.relatiecode }}</b>
|
||||||
|
</div>
|
||||||
|
<div class="text-default mb-2">
|
||||||
|
Naam: <b>{{ modelData.fullName }}</b>
|
||||||
|
</div>
|
||||||
|
<div class="text-default mb-5">
|
||||||
|
Groepen: <b>{{ modelData.groups.join(', ') }}</b>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label class="relative inline-flex items-center cursor-pointer mb-5">
|
||||||
|
<input type="checkbox" value="" v-model="modelData.wedstrijdteam" class="sr-only peer">
|
||||||
|
<div class="w-11 h-6 bg-neutral-300 peer-focus:outline-none peer-focus:ring-none rounded-full peer dark:bg-neutral-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-primary"></div>
|
||||||
|
<span class="ml-3 font-medium text-gray-900 dark:text-gray-300">Wedstrijd Team</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<input :disabled="disableButtons" type="submit" class="btn" :value="'Bewerken'" />
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
<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 class="mb-5">
|
<div class="mb-5">
|
||||||
<form @submit.prevent="submitLedenlijst" class="flex flex-col">
|
<form @submit.prevent="submitLedenlijst" class="flex flex-col">
|
||||||
<input required="true" @change="handleFileChanged" accept=".csv" class="my-2" type="file">
|
<input required="true" @change="handleFileChanged" accept=".csv" class="my-2" type="file">
|
||||||
<span class="text-sm"><i>Met de volgende kolommen: Relatiecode, Volledige naam(1), Roepnaam, E-mail, 2e E-mail, Verenigingssporten, Diploma</i></span>
|
<span class="text-sm"><i>Met de volgende kolommen: Relatiecode, Volledige naam(1), Roepnaam, E-mail, 2e E-mail, Verenigingssporten, Diploma</i></span>
|
||||||
<button :disabled="disableButtons" class="enabled:btn mx-auto mt-2">Publish Ledenlijst</button>
|
<button :disabled="disableButtons" class="btn mx-auto mt-2">Publish Ledenlijst</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-3">
|
<div class="flex flex-col gap-3">
|
||||||
<input v-model="searchTerm" class="input mb-2 font-bold" type="search" placeholder="Search">
|
<input v-model="searchTerm" class="input mb-2 font-bold" type="search" placeholder="Search">
|
||||||
<div v-for="lid in filteredLedenlijst" :key="lid.relatiecode">
|
<div v-for="lid in filteredLedenlijst" :key="lid.relatiecode">
|
||||||
<div class="item container flex flex-wrap">
|
<div @click="handleModel(lid)" class="item container flex flex-wrap hover:cursor-pointer">
|
||||||
<b class="w-24">{{ lid.relatiecode }}</b> {{ lid.fullName }}
|
<b class="w-24">{{ lid.relatiecode }}</b> {{ lid.fullName }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -19,7 +42,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { doc, getDocs, collection, writeBatch, runTransaction } from "firebase/firestore";
|
import { doc, getDocs, collection, writeBatch, updateDoc, setDoc } from "firebase/firestore";
|
||||||
import { useToast } from 'vue-toastification'
|
import { useToast } from 'vue-toastification'
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
@ -31,9 +54,13 @@ const { db, ledenlijst, users } = inject('firebase')
|
|||||||
|
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
|
||||||
|
const modelData = ref(null)
|
||||||
|
|
||||||
const file = ref(null)
|
const file = ref(null)
|
||||||
const disableButtons = ref(false)
|
const disableButtons = ref(false)
|
||||||
const searchTerm = ref('')
|
const searchTerm = ref('')
|
||||||
|
const newLedenlijst = ref([])
|
||||||
|
const showModel = ref(false)
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
if (!ledenlijst.value.length) {
|
if (!ledenlijst.value.length) {
|
||||||
@ -50,6 +77,51 @@ onMounted(async () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const handleModel = (lid) => {
|
||||||
|
modelData.value = lid
|
||||||
|
if (!modelData.value.hasOwnProperty('wedstrijdteam')) modelData.value.wedstrijdteam = false
|
||||||
|
|
||||||
|
modelData.value.oldWedstrijdteam = modelData.value.wedstrijdteam
|
||||||
|
|
||||||
|
showModel.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const submitModelForm = async () => {
|
||||||
|
disableButtons.value = true
|
||||||
|
ledenlijst.value.filter(a => a.relatiecode === modelData.value.relatiecode)[0].wedstrijdteam = modelData.value.wedstrijdteam
|
||||||
|
|
||||||
|
if (modelData.value.wedstrijdteam === modelData.value.oldWedstrijdteam) {
|
||||||
|
disableButtons.value = false
|
||||||
|
showModel.value = false
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let docRef = doc(db, "ledenlijst", modelData.value.relatiecode)
|
||||||
|
|
||||||
|
await updateDoc(docRef, {
|
||||||
|
wedstrijdteam: modelData.value.wedstrijdteam
|
||||||
|
})
|
||||||
|
|
||||||
|
docRef = doc(db, "competitors", modelData.value.relatiecode)
|
||||||
|
|
||||||
|
if (modelData.value.wedstrijdteam) {
|
||||||
|
await setDoc(docRef, {
|
||||||
|
relatiecode: modelData.value.relatiecode,
|
||||||
|
name: modelData.value.fullName,
|
||||||
|
active: true,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
await updateDoc(docRef, {
|
||||||
|
active: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.success('Persoon is bewerkt')
|
||||||
|
|
||||||
|
disableButtons.value = false
|
||||||
|
showModel.value = false
|
||||||
|
}
|
||||||
|
|
||||||
const filteredLedenlijst = computed(() => {
|
const filteredLedenlijst = computed(() => {
|
||||||
return ledenlijst.value.filter(lid => lid.fullName.toLowerCase().includes(searchTerm.value.toLowerCase()))
|
return ledenlijst.value.filter(lid => lid.fullName.toLowerCase().includes(searchTerm.value.toLowerCase()))
|
||||||
})
|
})
|
||||||
@ -92,57 +164,31 @@ const csvToJson = (csv) => {
|
|||||||
result.push(obj);
|
result.push(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
let array = csv.split("\n")
|
|
||||||
let result = [];
|
|
||||||
|
|
||||||
let headers = array[0].split(",")
|
|
||||||
|
|
||||||
for (let i = 1; i < array.length - 1; i++) {
|
|
||||||
let obj = {}
|
|
||||||
|
|
||||||
let str = array[i]
|
|
||||||
let s = ''
|
|
||||||
|
|
||||||
let flag = 0
|
|
||||||
for (let ch of str) {
|
|
||||||
if (ch === '"' && flag === 0) {
|
|
||||||
flag = 1
|
|
||||||
}
|
|
||||||
else if (ch === '"' && flag == 1) flag = 0
|
|
||||||
if (ch === ',' && flag === 0) ch = '|'
|
|
||||||
if (ch !== '"') s += ch
|
|
||||||
}
|
|
||||||
|
|
||||||
let properties = s.split("|")
|
|
||||||
|
|
||||||
for (let j in headers) {
|
|
||||||
if (properties[j].includes(",")) {
|
|
||||||
obj[headers[j]] = properties[j]
|
|
||||||
.split(",").map(item => item.trim())
|
|
||||||
}
|
|
||||||
else obj[headers[j]] = properties[j]
|
|
||||||
}
|
|
||||||
result.push(obj)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
if (!Object.hasOwn(result[0], 'Relatiecode') || !Object.hasOwn(result[0], 'Volledige naam (1)') || !Object.hasOwn(result[0], 'E-mail') || !Object.hasOwn(result[0], '2e E-mail') || !Object.hasOwn(result[0], 'Verenigingssporten') || !Object.hasOwn(result[0], 'Diploma dropdown 1')) return toast.error('Missing properties')
|
if (!Object.hasOwn(result[0], 'Relatiecode') || !Object.hasOwn(result[0], 'Volledige naam (1)') || !Object.hasOwn(result[0], 'E-mail') || !Object.hasOwn(result[0], '2e E-mail') || !Object.hasOwn(result[0], 'Verenigingssporten') || !Object.hasOwn(result[0], 'Diploma dropdown 1')) return toast.error('Missing properties')
|
||||||
|
|
||||||
ledenlijst.value = []
|
newLedenlijst.value = []
|
||||||
|
|
||||||
for (let i in result) {
|
for (let i in result) {
|
||||||
let groups = []
|
let groups = []
|
||||||
const correctGroups = result[i].Verenigingssporten.replace(/,/g, " -")
|
let correctGroups = null
|
||||||
|
if (!result[i].Relatiecode) break;
|
||||||
|
|
||||||
|
if (result[i].Verenigingssporten.includes(',')) correctGroups = result[i].Verenigingssporten.split(',')
|
||||||
|
else correctGroups = [result[i].Verenigingssporten]
|
||||||
|
|
||||||
|
correctGroups.forEach(group => {
|
||||||
|
const x = group.split(' - ')
|
||||||
|
|
||||||
|
if (x[2] === 'Week') groups.push('Vrijdag')
|
||||||
|
else if (x[2] === 'Zaterdag' && x[1] !== 'Wedstrijd') groups.push('Zaterdag')
|
||||||
|
|
||||||
|
groups.push(x[1])
|
||||||
|
})
|
||||||
|
|
||||||
console.log(correctGroups)
|
|
||||||
groups = correctGroups.split(' - ')
|
|
||||||
if (groups[2] === 'Week') groups[2] = 'Vrijdag'
|
if (groups[2] === 'Week') groups[2] = 'Vrijdag'
|
||||||
|
|
||||||
groups = groups.filter((item) => item !== "Groep")
|
newLedenlijst.value.push({ relatiecode: result[i].Relatiecode, fullName: result[i]['Volledige naam (1)'], email: [result[i]['E-mail'], result[i]['2e E-mail']], groups: [...new Set(groups)], diploma: result[i]['Diploma dropdown 1'] })
|
||||||
|
|
||||||
ledenlijst.value.push({ relatiecode: result[i].Relatiecode, fullName: result[i]['Volledige naam (1)'], email: [result[i]['E-mail'], result[i]['2e E-mail']], groups: [...new Set(groups)], diploma: result[i]['Diploma dropdown 1'] })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uploadLedenlijst()
|
uploadLedenlijst()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,11 +196,26 @@ const uploadLedenlijst = async () => {
|
|||||||
try {
|
try {
|
||||||
const batch = writeBatch(db)
|
const batch = writeBatch(db)
|
||||||
|
|
||||||
for (let i = 0; i < ledenlijst.value.length; i++) {
|
newLedenlijst.value.forEach(lid => {
|
||||||
const docRef = doc(db, "ledenlijst", ledenlijst.value[i].relatiecode)
|
const docRef = doc(db, "ledenlijst", lid.relatiecode)
|
||||||
batch.set(docRef, ledenlijst.value[i]);
|
|
||||||
|
const exists = ledenlijst.value.filter(a => a.relatiecode === lid.relatiecode).length > 1
|
||||||
|
|
||||||
|
if (!exists) {
|
||||||
|
return batch.set(docRef, lid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
batch.update(docRef, { fullName: lid.relatiecode, email: lid.email, groups: lid.groups, diploma: lid.diploma})
|
||||||
|
})
|
||||||
|
|
||||||
|
const deleteLeden = ledenlijst.value.filter(a => newLedenlijst.value.map(x => x.relatiecode).indexOf(a.relatiecode) === -1)
|
||||||
|
|
||||||
|
deleteLeden.forEach(lid => {
|
||||||
|
const docRef = doc(db, "ledenlijst", lid.relatiecode)
|
||||||
|
|
||||||
|
batch.delete(docRef)
|
||||||
|
})
|
||||||
|
|
||||||
await batch.commit();
|
await batch.commit();
|
||||||
|
|
||||||
toast.success('Published ledenlijst')
|
toast.success('Published ledenlijst')
|
||||||
@ -163,6 +224,8 @@ const uploadLedenlijst = async () => {
|
|||||||
console.log(e)
|
console.log(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ledenlijst.value = newLedenlijst.value
|
||||||
|
|
||||||
updateUsers()
|
updateUsers()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,7 +253,7 @@ const updateUsers = async () => {
|
|||||||
|
|
||||||
user.allRelatiecodes = newRelatiecodes
|
user.allRelatiecodes = newRelatiecodes
|
||||||
user.relatiecodes.forEach((relatiecode, index) => {
|
user.relatiecodes.forEach((relatiecode, index) => {
|
||||||
if (!newRelatiecodes.includes(relatiecode)) { user.relatiecodes.splice(index, 1); console.log('removed item')}
|
if (!newRelatiecodes.includes(relatiecode)) { user.relatiecodes.splice(index, 1); console.log('removed item', relatiecode)}
|
||||||
})
|
})
|
||||||
|
|
||||||
const userRef = doc(db, "users", user.id)
|
const userRef = doc(db, "users", user.id)
|
||||||
|
@ -1,13 +1,106 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div @click.self="showModel = false" v-if="showModel" class="fixed flex justify-center items-center h-screen w-full bg-black top-0 left-0 z-50 bg-opacity-50" >
|
||||||
Users
|
<form @submit.prevent="submitModelForm" class="dark:bg-neutral-800 bg-neutral-200 p-10 rounded-xl flex flex-col w-full max-w-sm">
|
||||||
|
<h1 class="font-bold text-center text-lg mb-5">Beheer Gebruiker</h1>
|
||||||
|
|
||||||
|
<div class="text-default mb-2">
|
||||||
|
Relatiecodes: <b>{{ modelData.relatiecodes.join(', ') }}</b>
|
||||||
|
</div>
|
||||||
|
<div class="text-default mb-2">
|
||||||
|
Email: <b>{{ modelData.email }}</b>
|
||||||
|
</div>
|
||||||
|
<div class="text-default mb-5">
|
||||||
|
ID: <b>{{ modelData.id }}</b>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label class="relative inline-flex items-center cursor-pointer mb-5">
|
||||||
|
<input type="checkbox" value="" v-model="modelData.admin" class="sr-only peer">
|
||||||
|
<div class="w-11 h-6 bg-neutral-300 peer-focus:outline-none peer-focus:ring-none rounded-full peer dark:bg-neutral-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-primary"></div>
|
||||||
|
<span class="ml-3 font-medium text-gray-900 dark:text-gray-300">Admin</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="relative inline-flex items-center cursor-pointer mb-5">
|
||||||
|
<input type="checkbox" value="" v-model="modelData.sendNews" class="sr-only peer">
|
||||||
|
<div class="w-11 h-6 bg-neutral-300 peer-focus:outline-none peer-focus:ring-none rounded-full peer dark:bg-neutral-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-primary"></div>
|
||||||
|
<span class="ml-3 font-medium text-gray-900 dark:text-gray-300">Berichten Sturen</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="relative inline-flex items-center cursor-pointer mb-5">
|
||||||
|
<input type="checkbox" value="" v-model="modelData.wedstrijdAdmin" class="sr-only peer">
|
||||||
|
<div class="w-11 h-6 bg-neutral-300 peer-focus:outline-none peer-focus:ring-none rounded-full peer dark:bg-neutral-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-primary"></div>
|
||||||
|
<span class="ml-3 font-medium text-gray-900 dark:text-gray-300">Wedstrijd Admin</span>
|
||||||
|
</label>
|
||||||
|
<input :disabled="disableButtons" type="submit" class="btn" :value="'Bewerken'" />
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-5 mx-auto p-2 w-full max-w-md">
|
||||||
|
<input v-model="searchTerm" class="input mb-2 font-bold" type="search" placeholder="Search">
|
||||||
|
<div v-for="user in filteredUsers" :key="user.relatiecode">
|
||||||
|
<div @click="handleModel(user)" class="item container flex flex-wrap hover:cursor-pointer">
|
||||||
|
<b class="w-24">{{ user.email }}</b>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import { getDocs, collection, doc, updateDoc } from 'firebase/firestore'
|
||||||
|
import { useToast } from 'vue-toastification'
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
title: 'Manage users',
|
title: 'Manage users',
|
||||||
key: 'back'
|
key: 'back'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const { users, db } = inject('firebase')
|
||||||
|
const toast = useToast()
|
||||||
|
|
||||||
|
const searchTerm = ref('')
|
||||||
|
const disableButtons = ref(false)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleModel = (user) => {
|
||||||
|
modelData.value = user
|
||||||
|
if (!modelData.value.hasOwnProperty('admin')) modelData.value.admin = false
|
||||||
|
if (!modelData.value.hasOwnProperty('sendNews')) modelData.value.sendNews = false
|
||||||
|
if (!modelData.value.hasOwnProperty('wedstrijdAdmin')) modelData.value.wedstrijdAdmin = false
|
||||||
|
|
||||||
|
showModel.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const filteredUsers = computed(() => {
|
||||||
|
return users.value.filter(user => user.email.toLowerCase().includes(searchTerm.value.toLowerCase()))
|
||||||
|
})
|
||||||
|
|
||||||
|
const submitModelForm = async () => {
|
||||||
|
disableButtons.value = true
|
||||||
|
|
||||||
|
const docRef = doc(db, "users", modelData.value.id)
|
||||||
|
|
||||||
|
await updateDoc(docRef, {
|
||||||
|
admin: modelData.value.admin,
|
||||||
|
sendNews: modelData.value.sendNews,
|
||||||
|
wedstrijdAdmin: modelData.value.wedstrijdAdmin
|
||||||
|
})
|
||||||
|
|
||||||
|
toast.success('Gebruiker is bewerkt')
|
||||||
|
|
||||||
|
disableButtons.value = false
|
||||||
|
showModel.value = false
|
||||||
|
}
|
||||||
</script>
|
</script>
|
@ -1,77 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="flex flex-col gap-5 mx-auto p-2 w-full max-w-md">
|
|
||||||
<form @submit.prevent="saveEmail" class="flex flex-col">
|
|
||||||
<p class="mb-5 text-lg text-red-500 font-bold">Let op! Je verandert alleen het Email van de app dus <u>NIET</u> van de vereniging!</p>
|
|
||||||
<label class="font-bold">Password</label>
|
|
||||||
<input v-model="password" required="true" class="input " :type="showPassword ? 'text' : 'password'">
|
|
||||||
|
|
||||||
<div class="mb-5 mt-1 flex items-center text-default">
|
|
||||||
<input v-model="showPassword" type="checkbox" class="mr-1 checkbox ">
|
|
||||||
<span>Show Password</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<label class="font-bold">New Email</label>
|
|
||||||
<input v-model="email" required="true" placeholder="user@example.com" class="input mb-5" type="email">
|
|
||||||
|
|
||||||
<div class="w-full flex flex-wrap justify-between">
|
|
||||||
<input :disabled="disableButtons" type="submit" value="Change Email" class="btn w-full sm:w-40 mb-1">
|
|
||||||
<button @click="router.back()" class="hover:underline font-bold w-full sm:w-max sm:ml-auto">Cancel</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { reauthenticateWithCredential, EmailAuthProvider, updateEmail } from 'firebase/auth'
|
|
||||||
import { updateDoc, doc } from 'firebase/firestore'
|
|
||||||
import { useToast } from 'vue-toastification'
|
|
||||||
|
|
||||||
definePageMeta({
|
|
||||||
title: 'Change Email',
|
|
||||||
key: 'back'
|
|
||||||
})
|
|
||||||
|
|
||||||
const { user, auth, db } = inject('firebase')
|
|
||||||
|
|
||||||
const toast = useToast()
|
|
||||||
const router = useRouter()
|
|
||||||
|
|
||||||
const password = ref('')
|
|
||||||
const email = ref('')
|
|
||||||
const disableButtons = ref(false)
|
|
||||||
const showPassword = ref(false)
|
|
||||||
|
|
||||||
const saveEmail = () => {
|
|
||||||
disableButtons.value = true
|
|
||||||
|
|
||||||
const credential = EmailAuthProvider.credential(
|
|
||||||
user.value.email,
|
|
||||||
password.value
|
|
||||||
)
|
|
||||||
|
|
||||||
reauthenticateWithCredential(auth.value.currentUser, credential).then(() => {
|
|
||||||
updateEmail(auth.value.currentUser, email.value).then(async () => {
|
|
||||||
await updateDoc(doc(db, "users", user.value.uid), {
|
|
||||||
email: email.value
|
|
||||||
})
|
|
||||||
|
|
||||||
toast.success('Email is veranderd')
|
|
||||||
|
|
||||||
navigateTo('/settings')
|
|
||||||
disableButtons.value = false
|
|
||||||
}).catch((error) => {
|
|
||||||
toast.error('Error tijdens het email veranderen')
|
|
||||||
console.log(error)
|
|
||||||
disableButtons.value = false
|
|
||||||
});
|
|
||||||
}).catch((error) => {
|
|
||||||
disableButtons.value = false
|
|
||||||
|
|
||||||
if (error.code === 'auth/wrong-password') return toast.error('Wachtwoord is onjuist')
|
|
||||||
|
|
||||||
toast.error('Error tijdens het email veranderen')
|
|
||||||
|
|
||||||
console.log(error)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
|
@ -1,23 +1,23 @@
|
|||||||
<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">Old Password</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">New Password</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">Confirm New Password</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>Show Password</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="Change Password" class="btn w-full sm:w-40 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">Cancel</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>
|
||||||
@ -28,7 +28,7 @@ import { reauthenticateWithCredential, EmailAuthProvider, updatePassword } from
|
|||||||
import { useToast } from 'vue-toastification'
|
import { useToast } from 'vue-toastification'
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
title: 'Change Password',
|
title: 'Wachtwoord Wijzigen',
|
||||||
key: 'back'
|
key: 'back'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -8,8 +8,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full flex flex-wrap">
|
<div class="w-full flex flex-wrap">
|
||||||
<button :disabled="buttonsDisabled" @click="save" class="btn w-full sm:w-40 mb-1">Save</button>
|
<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">Cancel</span>
|
<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>
|
||||||
<div class="w-full flex flex-col justify-center items-center" v-else>
|
<div class="w-full flex flex-col justify-center items-center" v-else>
|
||||||
@ -30,7 +30,7 @@ definePageMeta({
|
|||||||
key: 'back'
|
key: 'back'
|
||||||
})
|
})
|
||||||
|
|
||||||
const { user, userAllPersons, userPersons, db, getPersons } = inject('firebase')
|
const { user, userAllPersons, userPersons, db, getPersons, auth } = inject('firebase')
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
|
||||||
const buttonsDisabled = ref(false)
|
const buttonsDisabled = ref(false)
|
||||||
@ -76,18 +76,23 @@ const updateCheckbox = (person) => {
|
|||||||
person.checked = !person.checked
|
person.checked = !person.checked
|
||||||
}
|
}
|
||||||
|
|
||||||
const getAllPersons = () => {
|
const getAllPersons = async () => {
|
||||||
if (userPersons.value.length === 0) return setTimeout(() => getAllPersons(), 50)
|
if (userPersons.value.length === 0) return setTimeout(() => getAllPersons(), 50)
|
||||||
fetch('https://api.xeovalyte.com/getrelatiecodes', {
|
|
||||||
method: 'POST',
|
const idToken = await auth.value.currentUser.getIdToken(true)
|
||||||
headers: {
|
|
||||||
Authorization: 'Basic WGVvdmFseXRlOmtNKjhuRXMzNTchalJlXm1KYnZrRSFOIw==',
|
|
||||||
'content-type': 'application/json'
|
const { data: response, error } = await useFetch('/api/getrelatiecodes', {
|
||||||
},
|
method: 'post',
|
||||||
body: JSON.stringify({ email: user.value.email })
|
body: { email: user.value.email, token: idToken }
|
||||||
}).then(response => response.json())
|
})
|
||||||
.then(response => {
|
|
||||||
response.persons.forEach(person => {
|
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)) {
|
if (userPersons.value.map(a => a.relatiecode).includes(person.relatiecode)) {
|
||||||
person.checked = true
|
person.checked = true
|
||||||
} else {
|
} else {
|
||||||
@ -95,12 +100,6 @@ const getAllPersons = () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
userAllPersons.value = response.persons
|
userAllPersons.value = response.value.persons
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
console.log(err)
|
|
||||||
|
|
||||||
toast.error('Error tijdens het ophalen van gegevens')
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
@ -12,15 +12,15 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="divider" />
|
<div class="divider" />
|
||||||
<div class="item">
|
<div class="item">
|
||||||
Groups: <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="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="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>{{ userPersons.map(a => a.diploma).filter(n => n !== '').join(', ')}}</b>
|
||||||
</div>
|
</div>
|
||||||
<div class="divider" />
|
<div class="divider" />
|
||||||
<NuxtLink to="/settings/privacypolicy" class="item-hover py-2 rounded-t flex items-center">
|
<NuxtLink to="/settings/moreinfo" class="item-hover py-2 rounded-t flex items-center">
|
||||||
<span>Privacy Policy</span>
|
<span>Meer Informatie</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>
|
||||||
@ -34,17 +34,12 @@
|
|||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
<div class="divider" />
|
<div class="divider" />
|
||||||
<NuxtLink to="/settings/config/changepassword" class="item-hover py-2 flex items-center">
|
<NuxtLink to="/settings/config/changepassword" class="item-hover py-2 flex items-center">
|
||||||
<span>Change Password</span>
|
<span>Wachtwoord Wijzigen</span>
|
||||||
<Icon class="ml-auto" size="2em" name="ion:arrow-forward"/>
|
|
||||||
</NuxtLink>
|
|
||||||
<div class="divider" />
|
|
||||||
<NuxtLink to="/settings/config/changeemail" class="item-hover py-2 flex items-center">
|
|
||||||
<span>Change Email</span>
|
|
||||||
<Icon class="ml-auto" size="2em" name="ion:arrow-forward"/>
|
<Icon class="ml-auto" size="2em" name="ion:arrow-forward"/>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
<div class="divider" />
|
<div class="divider" />
|
||||||
<div @click="logout" class="item-hover rounded-b flex items-center">
|
<div @click="logout" class="item-hover rounded-b flex items-center">
|
||||||
Logout
|
Uitloggen
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -52,7 +47,7 @@
|
|||||||
<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">
|
||||||
<span>Manage users</span>
|
<span>Beheer gebruikers</span>
|
||||||
<Icon class="ml-auto" size="2em" name="ion:arrow-forward"/>
|
<Icon class="ml-auto" size="2em" name="ion:arrow-forward"/>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
<div class="divider" />
|
<div class="divider" />
|
||||||
|
36
frontend/pages/settings/moreinfo.vue
Normal file
36
frontend/pages/settings/moreinfo.vue
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex flex-col gap-5 mx-auto p-2 w-full max-w-md text-default text-sm">
|
||||||
|
<div>
|
||||||
|
<h1 class="text-xl ml-2 font-bold">Privacybeleid</h1>
|
||||||
|
<div class="container">
|
||||||
|
<div class="item">
|
||||||
|
<h2 class="font-bold">Privacy</h2>
|
||||||
|
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.
|
||||||
|
<h2 class="font-bold mt-5">AVG</h2>
|
||||||
|
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.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h1 class="text-xl ml-2 font-bold"></h1>
|
||||||
|
<div class="container">
|
||||||
|
<div class="item break-words ">
|
||||||
|
Registration Token: <b>{{ registrationToken }}</b>
|
||||||
|
</div>
|
||||||
|
<div class="divider" />
|
||||||
|
<div class="item break-words ">
|
||||||
|
User ID: <b>{{ userData.id }}</b>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
definePageMeta({
|
||||||
|
title: 'Meer Informatie',
|
||||||
|
key: 'back'
|
||||||
|
})
|
||||||
|
|
||||||
|
const { registrationToken, userData } = inject('firebase')
|
||||||
|
</script>
|
@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
title: 'Privacy Policy',
|
title: 'Privacybeleid',
|
||||||
key: 'back'
|
key: 'back'
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,18 +1,57 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<div @click.self="showModel = false" v-if="showModel" class="fixed flex justify-center items-center h-screen w-full bg-black top-0 left-0 z-50 bg-opacity-50" >
|
||||||
|
<form @submit.prevent="submitModelForm" class="dark:bg-neutral-800 bg-neutral-200 p-10 rounded-xl flex flex-col w-full max-w-sm">
|
||||||
|
<h1 class="font-bold text-center text-lg mb-5">Deelnemer Toevoegen</h1>
|
||||||
|
|
||||||
|
<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-else v-for="user in competitors" :value="user.relatiecode">{{ user.name}} ({{ user.relatiecode }})</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<label class="font-bold text-default">Tijd</label>
|
||||||
|
<div class="mb-1">
|
||||||
|
<input 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 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 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 type="checkbox" v-model="modelData.dsq" class="mr-1 checkbox">
|
||||||
|
<span class="text-default">Diskwalificatie</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label class="font-bold">Info (Optioneel)</label>
|
||||||
|
<input v-model="modelData.info" type="text" placeholder="Bijv. Een diskwalificatie" class="input dark:bg-neutral-700 bg-neutral-300 mb-10" />
|
||||||
|
|
||||||
|
<input :disabled="disableButtons" type="submit" class="btn" :value="modelData.edit ? 'Bewerken' : 'Toevoegen'" />
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
<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 v-if="!newEvent" @submit.prevent="saveEmail" class="flex flex-col">
|
<form @submit.prevent="submitContestForm" class="flex flex-col">
|
||||||
<label class="font-bold">Naam Wedstrijd</label>
|
<label class="font-bold">Locatie Wedstrijd</label>
|
||||||
<input v-model="contest.name" required="true" class="input mb-5 " type="text">
|
<input v-model="contest.location" required="true" class="input mb-5 " type="text">
|
||||||
|
|
||||||
|
<label class="font-bold">Type Zwembad</label>
|
||||||
|
<select v-model="contest.type" required="true" class="input mb-5 " type="text">
|
||||||
|
<option value="50m">50 Meter</option>
|
||||||
|
<option value="25m">25 Meter</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
<label class="font-bold">Datum</label>
|
<label class="font-bold">Datum</label>
|
||||||
<input v-model="contest.date" required="true" class="input w-min hover:cursor-pointer pr-0 mb-5 " type="date">
|
<input v-model="contest.date" required="true" class="input w-min hover:cursor-pointer pr-0 mb-5 " type="date">
|
||||||
|
|
||||||
<label class="font-bold">Onderdelen</label>
|
<label class="font-bold">Onderdelen</label>
|
||||||
<button @click="newEvent = true" class="item-hover border-dashed border-2 container text-center font-bold border-neutral-500 mb-3">Onderdeel Toevoegen</button>
|
<div class="flex flex-col gap-y-3">
|
||||||
<div class="flex flex-col gap-y-5 mb-5">
|
<div v-if="competitors" v-for="event in contest.events" class="container p-2">
|
||||||
<div v-for="(event, index) in contest.events" :key="index" class="container p-2 flex flex-col gap-y-3">
|
<div @click="event.open = !event.open" class="flex hover:cursor-pointer">
|
||||||
<h2 class="text-center text-primary font-bold text-lg">{{ event.type }}</h2>
|
<h2 class="font-bold">{{ event.name }}</h2>
|
||||||
<table class="table-fixed text-left odd:bg-blue-500">
|
<Icon size="1.2em" name="ion:arrow-down-b" class="ml-auto my-auto mr-2 transition-all" :class="{'rotate-180' : event.open }" />
|
||||||
|
</div>
|
||||||
|
<div v-if="event.open" class="mt-2">
|
||||||
|
<table class="table-fixed text-left w-full even:bg-gray-500">
|
||||||
<thead class="font-bold">
|
<thead class="font-bold">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="w-3/6">Naam</th>
|
<th class="w-3/6">Naam</th>
|
||||||
@ -21,103 +60,95 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="competitor in event.competitors" class="even:dark:bg-neutral-700 even:bg-neutral-300">
|
<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>{{ competitors.find(x => x.relatiecode === competitor.relatiecode ).name }}</td>
|
<td class="py-1 pl-1">{{ competitors.find(x => x.relatiecode === competitor.relatiecode ).name }}</td>
|
||||||
<td>{{ competitor.time.minute}}:{{ competitor.time.seconds }}:{{ competitor.time.milliseconds }}</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">
|
||||||
|
<td @click="handleModel(null, event.id)" class="hover:cursor-pointer py-1 pl-1">+ Deelnemer toevoegen</td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="w-full flex flex-wrap justify-between">
|
|
||||||
<input :disabled="disableButtons" type="submit" value="Wedstrijd Toevoegen" class="btn w-full sm:w-48 mb-1">
|
|
||||||
<button @click="resetContest" class="hover:underline font-bold w-full sm:w-max sm:ml-auto">Reset</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
<form v-else @submit.prevent="addEvent" class="flex flex-col">
|
|
||||||
<label class="font-bold">Onderdeel</label>
|
|
||||||
<select v-model="tempEvent.type" class="input mb-5">
|
|
||||||
<option value="200m-Obstacle-Swim">200m Obstacle Swim (Hindernis)</option>
|
|
||||||
<option value="50m-Manikin-Carry">50m Manikin Carry (Popvervoeren)</option>
|
|
||||||
<option value="100m-Rescue-Medley">100m Rescue Medley (Reddingswissel)</option>
|
|
||||||
<option value="100m-Manikin-Carry-with-Fins">100m Manikin Carry with Fins (Popvervoeren met finnen)</option>
|
|
||||||
<option value="100m-Manikin-Tow-with-Fins">100m Manikin Tow with Fins (Lifesaver)</option>
|
|
||||||
<option value="200m-Super-Lifesaver">200m Super Lifesaver</option>
|
|
||||||
<option value="Line-Throw">Line Throw</option>
|
|
||||||
<option value="4x25m-Manikin-Relay">4x25m Manikin Relay (Popvervoeren)</option>
|
|
||||||
<option value="4x50m-Obstacle-Relay">4x50m Obstacle Relay (Hindernis)</option>
|
|
||||||
<option value="4x50m-Medley-Relay">4x50m Medley Relay (Torpedoboei)</option>
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<label class="font-bold">Deelnemers</label>
|
|
||||||
<button @click="tempEvent.competitors.unshift({ relatiecode: '', dsq: false, info: '', time: { minute: null, seconds: null, milliseconds: null }})" type="button" class="item-hover border-dashed border-2 container text-center font-bold border-neutral-500 mb-3">Deelnemer Toevoegen</button>
|
|
||||||
<div class="flex flex-col gap-y-3 mb-5">
|
|
||||||
<div v-for="(competitor, index) in tempEvent.competitors" :key="index">
|
|
||||||
<div class="container flex flex-col p-4">
|
|
||||||
<label class="font-bold">Deelnemer</label>
|
|
||||||
<select v-model="competitor.relatiecode" class="input dark:bg-neutral-700 bg-neutral-300 mb-5">
|
|
||||||
<option v-for="user in competitors" :value="user.relatiecode">{{ user.name}} ({{ user.relatiecode }})</option>
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<label class="font-bold">Tijd</label>
|
|
||||||
<div class="flex dark:bg-neutral-700 bg-neutral-300 gap-x-2 w-min rounded mb-2">
|
|
||||||
<select v-model="competitor.time.minute" class="input pl-3 pr-8 bg-opacity-0 shadow-none ">
|
|
||||||
<option value="null" disabled selected>mm</option>
|
|
||||||
<option v-for="n in 60" :value="n < 11 ? `0${n-1}` : `${n-1}`">{{ n < 11 ? `0${n-1}` : `${n-1}` }}</option>
|
|
||||||
</select>
|
|
||||||
<span class="my-auto text-xl">:</span>
|
|
||||||
<select v-model="competitor.time.seconds" class="input pl-2 pr-8 bg-opacity-0 shadow-none ">
|
|
||||||
<option value="null" disabled selected>ss</option>
|
|
||||||
<option v-for="n in 60" :value="n < 11 ? `0${n-1}` : `${n-1}`">{{ n < 11 ? `0${n-1}` : `${n-1}`}}</option>
|
|
||||||
</select>
|
|
||||||
<span class="my-auto text-xl">:</span>
|
|
||||||
<select v-model="competitor.time.milliseconds" class="input pl-2 pr-8 bg-opacity-0 shadow-none ">
|
|
||||||
<option value="null" disabled selected>ms</option>
|
|
||||||
<option v-for="n in 100" :value="n < 11 ? `0${n-1}` : `${n-1}`">{{ n < 11 ? `0${n-1}` : `${n-1}` }}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center mb-5">
|
|
||||||
<input type="checkbox" v-model="competitor.dsq" class="mr-1 checkbox">
|
|
||||||
<span>Diskwalificatie</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<label class="font-bold">Info (Optioneel)</label>
|
|
||||||
<input v-model="competitor.info" type="text" placeholder="Bijv. Een diskwalificatie" class="input dark:bg-neutral-700 bg-neutral-300" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="w-full flex flex-wrap justify-between">
|
|
||||||
<input :disabled="disableButtons" type="submit" value="Onderdeel Toevoegen" class="btn w-full sm:w-48 mb-1">
|
|
||||||
<button @click="backEvent" class="hover:underline font-bold w-full sm:w-max sm:ml-auto">Cancel</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
<input :disabled="disableButtons" type="submit" class="btn mt-10 px-5 w-min mx-auto" value="Wedstrijd toevoegen" />
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { getDocs, collection } from "firebase/firestore"
|
import { getDocs, collection, writeBatch, doc } from "firebase/firestore"
|
||||||
|
import { useToast } from 'vue-toastification'
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
title: 'Wedstrijd Toevoegen'
|
title: 'Wedstrijd Toevoegen',
|
||||||
|
key: 'back'
|
||||||
})
|
})
|
||||||
|
|
||||||
const { userData, db, competitors } = inject('firebase')
|
const toast = useToast()
|
||||||
|
|
||||||
|
const { db, competitors } = inject('firebase')
|
||||||
|
|
||||||
|
const showModel = ref(false)
|
||||||
const disableButtons = ref(false)
|
const disableButtons = ref(false)
|
||||||
const router = useRouter()
|
|
||||||
const newEvent = ref(false)
|
const modelData = ref({
|
||||||
|
relatiecode: '',
|
||||||
|
time: {
|
||||||
|
minutes: null,
|
||||||
|
seconds: null,
|
||||||
|
milliseconds: null,
|
||||||
|
},
|
||||||
|
dsq: false,
|
||||||
|
info: '',
|
||||||
|
})
|
||||||
|
|
||||||
const contest = ref({
|
const contest = ref({
|
||||||
name: '',
|
location: '',
|
||||||
date: null,
|
|
||||||
events: [],
|
|
||||||
})
|
|
||||||
|
|
||||||
const tempEvent = ref({
|
|
||||||
type: '',
|
type: '',
|
||||||
competitors: []
|
date: Date,
|
||||||
|
events: {
|
||||||
|
obstacleSwim: {
|
||||||
|
open: false,
|
||||||
|
name: '200m Obstacle Swim',
|
||||||
|
id: 'obstacleSwim',
|
||||||
|
competitors: [],
|
||||||
|
},
|
||||||
|
manikinCarry: {
|
||||||
|
open: false,
|
||||||
|
name: '50m Manikin Carry',
|
||||||
|
id: 'manikinCarry',
|
||||||
|
competitors: [],
|
||||||
|
},
|
||||||
|
rescueMedley: {
|
||||||
|
open: false,
|
||||||
|
name: '100m Rescue Medley',
|
||||||
|
id: 'rescueMedley',
|
||||||
|
competitors: [],
|
||||||
|
},
|
||||||
|
manikinCarryWithFins: {
|
||||||
|
open: false,
|
||||||
|
name: '100m Manikin Carry with Fins',
|
||||||
|
id: 'manikinCarryWithFins',
|
||||||
|
competitors: [],
|
||||||
|
},
|
||||||
|
manikinTowWithFins: {
|
||||||
|
open: false,
|
||||||
|
name: '100m Manikin Tow with Fins',
|
||||||
|
id: 'manikinTowWithFins',
|
||||||
|
competitors: [],
|
||||||
|
},
|
||||||
|
superLifesaver: {
|
||||||
|
open: false,
|
||||||
|
name: '200m Super Lifesaver',
|
||||||
|
id: 'superLifesaver',
|
||||||
|
competitors: [],
|
||||||
|
},
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const getCompetitors = async () => {
|
const getCompetitors = async () => {
|
||||||
@ -128,18 +159,82 @@ const getCompetitors = async () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const addEvent = () => {
|
const handleModel = (competitor, event, edit, index) => {
|
||||||
newEvent.value = false
|
if(!competitor) competitor = {
|
||||||
contest.value.events.unshift(tempEvent.value)
|
relatiecode: '',
|
||||||
tempEvent.value = { type: '', competitors: []}
|
time: {
|
||||||
|
minutes: null,
|
||||||
|
seconds: null,
|
||||||
|
milliseconds: null,
|
||||||
|
},
|
||||||
|
dsq: false,
|
||||||
|
info: '',
|
||||||
|
}
|
||||||
|
|
||||||
|
modelData.value = competitor
|
||||||
|
modelData.value.event = event
|
||||||
|
modelData.value.edit = edit
|
||||||
|
modelData.value.index = index
|
||||||
|
showModel.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
const backEvent = () => {
|
const submitModelForm = () => {
|
||||||
newEvent.value = false
|
if (!modelData.value.time.minutes) modelData.value.time.minutes = 0
|
||||||
tempEvent.value = { type: '', competitors: []}
|
if (!modelData.value.time.seconds) modelData.value.time.seconds = 0
|
||||||
|
if (!modelData.value.time.milliseconds) modelData.value.time.milliseconds = 0
|
||||||
|
const index = modelData.value.index
|
||||||
|
const edit = modelData.value.edit
|
||||||
|
|
||||||
|
delete modelData.value.index
|
||||||
|
delete modelData.value.edit
|
||||||
|
|
||||||
|
if (!edit) contest.value.events[modelData.value.event].competitors.push(modelData.value)
|
||||||
|
|
||||||
|
showModel.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const submitContestForm = async () => {
|
||||||
|
disableButtons.value = true
|
||||||
|
|
||||||
|
const batch = writeBatch(db)
|
||||||
|
|
||||||
|
Object.values(contest.value.events).forEach(event => {
|
||||||
|
event.competitors.forEach(competitor => {
|
||||||
|
const combinedTime = competitor.time.minutes.toString().padStart(2, '0') + competitor.time.seconds.toString().padStart(2, '0') + competitor.time.milliseconds.toString().padStart(2, '0')
|
||||||
|
|
||||||
|
const docRef = doc(collection(db, 'timings'))
|
||||||
|
batch.set(docRef, {
|
||||||
|
relatiecode: competitor.relatiecode,
|
||||||
|
contest: { location: contest.value.location, date: contest.value.date.toString(), type: contest.value.type },
|
||||||
|
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,
|
||||||
|
info: competitor.info || ''
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
await batch.commit()
|
||||||
|
disableButtons.value = false
|
||||||
|
|
||||||
|
toast.success('Wedstrijd is toegevoegd')
|
||||||
|
navigateTo('/wedstrijd')
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getCompetitors()
|
getCompetitors()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
input::-webkit-outer-spin-button,
|
||||||
|
input::-webkit-inner-spin-button {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=number] {
|
||||||
|
-moz-appearance: textfield;
|
||||||
|
appearance: textfield;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
296
frontend/pages/wedstrijd/alltimes.vue
Normal file
296
frontend/pages/wedstrijd/alltimes.vue
Normal file
@ -0,0 +1,296 @@
|
|||||||
|
<template>
|
||||||
|
<div @click.self="showModel = false" v-if="showModel" class="fixed flex justify-center items-center h-screen w-full bg-black top-0 left-0 z-50 bg-opacity-50" >
|
||||||
|
<form @submit.prevent="submitModalForm" class="dark:bg-neutral-800 bg-neutral-200 p-10 rounded-xl flex flex-col w-full max-w-sm">
|
||||||
|
<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" />
|
||||||
|
|
||||||
|
<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">
|
||||||
|
<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">
|
||||||
|
|
||||||
|
<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" />
|
||||||
|
<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" />
|
||||||
|
<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" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center mb-5">
|
||||||
|
<input :disabled="!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 v-if="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 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">
|
||||||
|
<input v-model="competitor.checked" type="checkbox" class="checkbox">
|
||||||
|
<label class="hover:cursor-pointer">{{ competitor.name }}</label>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="relative">
|
||||||
|
<button @click.stop="showPropertiesDropdown = !showPropertiesDropdown" class="btn">Eigenschappen <Icon size="1.2em" name="ion:arrow-down-b" /></button>
|
||||||
|
<div v-if="showPropertiesDropdown" v-on-click-outside.bubble="handlePropertiesDropdown" class="w-48 mt-2 container absolute rounded-lg shadow p-3">
|
||||||
|
<ul class="space-y-2 text-default">
|
||||||
|
<li v-for="property in Object.values(properties)" @click="property.enabled = !property.enabled" class="flex gap-x-1 items-center hover:cursor-pointer">
|
||||||
|
<input v-model="property.enabled" type="checkbox" class="checkbox">
|
||||||
|
<label class="hover:cursor-pointer">{{ property.name }}</label>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<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 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 }} |
|
||||||
|
</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>
|
||||||
|
</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 }" />
|
||||||
|
</div>
|
||||||
|
<div v-if="event.open" class="mt-2">
|
||||||
|
<table class="table-fixed text-left w-full even:bg-gray-500">
|
||||||
|
<thead class="font-bold">
|
||||||
|
<tr>
|
||||||
|
<th v-for="property in Object.values(properties).filter(a => a.enabled === true)">{{ property.name }}</th>
|
||||||
|
</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">
|
||||||
|
<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.type.enabled">{{ time.contest.type }}</td>
|
||||||
|
<td v-if="properties.location.enabled">{{ time.contest.location }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { collection, query, getDocs, doc, updateDoc } from "firebase/firestore";
|
||||||
|
import { useToast } from 'vue-toastification'
|
||||||
|
import { vOnClickOutside} from '@vueuse/components'
|
||||||
|
|
||||||
|
definePageMeta({
|
||||||
|
title: 'Brigade Tijden',
|
||||||
|
key: 'back'
|
||||||
|
})
|
||||||
|
|
||||||
|
const { db, userData, competitors } = inject('firebase')
|
||||||
|
const toast = useToast()
|
||||||
|
|
||||||
|
const timings = ref([])
|
||||||
|
const showModel = ref(false)
|
||||||
|
const disableButtons = ref(false)
|
||||||
|
const showDeelnemersDropdown = ref(false)
|
||||||
|
const showPropertiesDropdown = ref(false)
|
||||||
|
|
||||||
|
const handleDeelnemersDropdown = () => {
|
||||||
|
showDeelnemersDropdown.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
seconds: null,
|
||||||
|
milliseconds: null,
|
||||||
|
},
|
||||||
|
dsq: false,
|
||||||
|
info: '',
|
||||||
|
contest: {
|
||||||
|
type: '',
|
||||||
|
location: '',
|
||||||
|
date: Date,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const properties = ref({
|
||||||
|
time: {
|
||||||
|
enabled: true,
|
||||||
|
id: 'time',
|
||||||
|
name: 'Tijd',
|
||||||
|
},
|
||||||
|
date: {
|
||||||
|
enabled: true,
|
||||||
|
id: 'date',
|
||||||
|
name: 'Datum',
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
enabled: true,
|
||||||
|
id: 'name',
|
||||||
|
name: 'Naam',
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
enabled: false,
|
||||||
|
id: 'type',
|
||||||
|
name: 'Type zwembad',
|
||||||
|
},
|
||||||
|
location: {
|
||||||
|
enabled: false,
|
||||||
|
id: 'location',
|
||||||
|
name: 'Locatie',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const events = ref({
|
||||||
|
obstacleSwim: {
|
||||||
|
open: false,
|
||||||
|
name: '200m Obstacle Swim',
|
||||||
|
id: 'obstacleSwim',
|
||||||
|
competitors: [],
|
||||||
|
},
|
||||||
|
manikinCarry: {
|
||||||
|
open: false,
|
||||||
|
name: '50m Manikin Carry',
|
||||||
|
id: 'manikinCarry',
|
||||||
|
competitors: [],
|
||||||
|
},
|
||||||
|
rescueMedley: {
|
||||||
|
open: false,
|
||||||
|
name: '100m Rescue Medley',
|
||||||
|
id: 'rescueMedley',
|
||||||
|
competitors: [],
|
||||||
|
},
|
||||||
|
manikinCarryWithFins: {
|
||||||
|
open: false,
|
||||||
|
name: '100m Manikin Carry with Fins',
|
||||||
|
id: 'manikinCarryWithFins',
|
||||||
|
competitors: [],
|
||||||
|
},
|
||||||
|
manikinTowWithFins: {
|
||||||
|
open: false,
|
||||||
|
name: '100m Manikin Tow with Fins',
|
||||||
|
id: 'manikinTowWithFins',
|
||||||
|
competitors: [],
|
||||||
|
},
|
||||||
|
superLifesaver: {
|
||||||
|
open: false,
|
||||||
|
name: '200m Super Lifesaver',
|
||||||
|
id: 'superLifesaver',
|
||||||
|
competitors: [],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
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");
|
||||||
|
|
||||||
|
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()
|
||||||
|
})
|
||||||
|
|
||||||
|
const dateToYYYYMMDD = (d) => {
|
||||||
|
return d && new Date(d.getTime()-(d.getTimezoneOffset()*60*1000)).toISOString().split('T')[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleModel = (competitor, e, index) => {
|
||||||
|
modelData.value = competitor
|
||||||
|
modelData.value.index = index
|
||||||
|
modelData.value.eventId = e.id
|
||||||
|
showModel.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const submitModalForm = async () => {
|
||||||
|
if (!modelData.value.time.minutes) modelData.value.time.minutes = 0
|
||||||
|
if (!modelData.value.time.seconds) modelData.value.time.seconds = 0
|
||||||
|
if (!modelData.value.time.milliseconds) modelData.value.time.milliseconds = 0
|
||||||
|
const id = modelData.value.id
|
||||||
|
|
||||||
|
delete modelData.value.index
|
||||||
|
delete modelData.value.eventId
|
||||||
|
delete modelData.value.id
|
||||||
|
|
||||||
|
disableButtons.value = true
|
||||||
|
const combinedTime = modelData.value.time.minutes.toString().padStart(2, '0') + modelData.value.time.seconds.toString().padStart(2, '0') + modelData.value.time.milliseconds.toString().padStart(2, '0')
|
||||||
|
modelData.value.time.combinedTime = combinedTime
|
||||||
|
|
||||||
|
const docRef = doc(db, "timings", id);
|
||||||
|
|
||||||
|
await updateDoc(docRef, modelData.value);
|
||||||
|
|
||||||
|
toast.success('Tijd is bewerkt')
|
||||||
|
showModel.value = false
|
||||||
|
disableButtons.value = false
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
input::-webkit-outer-spin-button,
|
||||||
|
input::-webkit-inner-spin-button {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=number] {
|
||||||
|
-moz-appearance: textfield;
|
||||||
|
appearance: textfield;
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,17 +1,17 @@
|
|||||||
<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">
|
||||||
<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="/wedstrijd/owntimes" class="rounded-t item-hover py-2 flex items-center">
|
||||||
<span>Eigen Tijden</span>
|
<span>Eigen 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 class="divider" />
|
<div class="divider" />
|
||||||
<NuxtLink to="/calendar" class="item-hover py-2 flex items-center">
|
<NuxtLink to="/wedstrijd/alltimes" class="item-hover py-2 flex items-center">
|
||||||
<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 class="divider" />
|
<div v-if="userData.wedstrijdAdmin" class="divider" />
|
||||||
<NuxtLink to="/wedstrijd/addcontest" class="rounded-b item-hover py-2 flex items-center">
|
<NuxtLink v-if="userData.wedstrijdAdmin" to="/wedstrijd/addcontest" class="rounded-b item-hover py-2 flex items-center">
|
||||||
<span>Wedstrijd Toevoegen</span>
|
<span>Wedstrijd Toevoegen</span>
|
||||||
<Icon class="ml-auto" size="2em" name="ion:arrow-forward"/>
|
<Icon class="ml-auto" size="2em" name="ion:arrow-forward"/>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
@ -23,4 +23,6 @@
|
|||||||
definePageMeta({
|
definePageMeta({
|
||||||
title: 'Wedstrijd'
|
title: 'Wedstrijd'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const { userData } = inject('firebase')
|
||||||
</script>
|
</script>
|
||||||
|
215
frontend/pages/wedstrijd/owntimes.vue
Normal file
215
frontend/pages/wedstrijd/owntimes.vue
Normal file
@ -0,0 +1,215 @@
|
|||||||
|
<template>
|
||||||
|
<div @click.self="showModel = false" v-if="showModel" class="fixed flex justify-center items-center h-screen w-full bg-black top-0 left-0 z-50 bg-opacity-50" >
|
||||||
|
<form @submit.prevent="submitModalForm" class="dark:bg-neutral-800 bg-neutral-200 p-10 rounded-xl flex flex-col w-full max-w-sm">
|
||||||
|
<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" />
|
||||||
|
|
||||||
|
<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">
|
||||||
|
|
||||||
|
<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">
|
||||||
|
<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" />
|
||||||
|
<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" />
|
||||||
|
<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" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center mb-5">
|
||||||
|
<input :disabled="!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 v-if="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-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-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>
|
||||||
|
<div v-if="event.open" class="mt-2">
|
||||||
|
<table class="table-fixed text-left w-full even:bg-gray-500">
|
||||||
|
<thead class="font-bold">
|
||||||
|
<tr>
|
||||||
|
<th class="w-3/7">Tijd</th>
|
||||||
|
<th class="w-1/7">Datum</th>
|
||||||
|
<th class="w-3/7">Type</th>
|
||||||
|
</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">
|
||||||
|
<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>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { collection, query, where, getDocs, doc, updateDoc} from "firebase/firestore";
|
||||||
|
import { useToast } from 'vue-toastification'
|
||||||
|
|
||||||
|
definePageMeta({
|
||||||
|
title: 'Eigen Tijden',
|
||||||
|
key: 'back'
|
||||||
|
})
|
||||||
|
|
||||||
|
const { db, userData } = inject('firebase')
|
||||||
|
const toast = useToast()
|
||||||
|
|
||||||
|
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,
|
||||||
|
seconds: null,
|
||||||
|
milliseconds: null,
|
||||||
|
},
|
||||||
|
dsq: false,
|
||||||
|
info: '',
|
||||||
|
contest: {
|
||||||
|
type: '',
|
||||||
|
location: '',
|
||||||
|
date: '',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const events = ref({
|
||||||
|
obstacleSwim: {
|
||||||
|
open: false,
|
||||||
|
name: '200m Obstacle Swim',
|
||||||
|
id: 'obstacleSwim',
|
||||||
|
competitors: [],
|
||||||
|
},
|
||||||
|
manikinCarry: {
|
||||||
|
open: false,
|
||||||
|
name: '50m Manikin Carry',
|
||||||
|
id: 'manikinCarry',
|
||||||
|
competitors: [],
|
||||||
|
},
|
||||||
|
rescueMedley: {
|
||||||
|
open: false,
|
||||||
|
name: '100m Rescue Medley',
|
||||||
|
id: 'rescueMedley',
|
||||||
|
competitors: [],
|
||||||
|
},
|
||||||
|
manikinCarryWithFins: {
|
||||||
|
open: false,
|
||||||
|
name: '100m Manikin Carry with Fins',
|
||||||
|
id: 'manikinCarryWithFins',
|
||||||
|
competitors: [],
|
||||||
|
},
|
||||||
|
manikinTowWithFins: {
|
||||||
|
open: false,
|
||||||
|
name: '100m Manikin Tow with Fins',
|
||||||
|
id: 'manikinTowWithFins',
|
||||||
|
competitors: [],
|
||||||
|
},
|
||||||
|
superLifesaver: {
|
||||||
|
open: false,
|
||||||
|
name: '200m Super Lifesaver',
|
||||||
|
id: 'superLifesaver',
|
||||||
|
competitors: [],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
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))
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const handleModel = (competitor, e, index) => {
|
||||||
|
modelData.value = competitor
|
||||||
|
modelData.value.index = index
|
||||||
|
modelData.value.eventId = e.id
|
||||||
|
showModel.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const dateToYYYYMMDD = (d) => {
|
||||||
|
return d && new Date(d.getTime()-(d.getTimezoneOffset()*60*1000)).toISOString().split('T')[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
const submitModalForm = async () => {
|
||||||
|
if (!modelData.value.time.minutes) modelData.value.time.minutes = 0
|
||||||
|
if (!modelData.value.time.seconds) modelData.value.time.seconds = 0
|
||||||
|
if (!modelData.value.time.milliseconds) modelData.value.time.milliseconds = 0
|
||||||
|
const id = modelData.value.id
|
||||||
|
|
||||||
|
delete modelData.value.index
|
||||||
|
delete modelData.value.eventId
|
||||||
|
delete modelData.value.id
|
||||||
|
|
||||||
|
disableButtons.value = true
|
||||||
|
const combinedTime = modelData.value.time.minutes.toString().padStart(2, '0') + modelData.value.time.seconds.toString().padStart(2, '0') + modelData.value.time.milliseconds.toString().padStart(2, '0')
|
||||||
|
modelData.value.time.combinedTime = combinedTime
|
||||||
|
|
||||||
|
const docRef = doc(db, "timings", id);
|
||||||
|
|
||||||
|
await updateDoc(docRef, modelData.value);
|
||||||
|
|
||||||
|
toast.success('Tijd is bewerkt')
|
||||||
|
showModel.value = false
|
||||||
|
disableButtons.value = false
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
input::-webkit-outer-spin-button,
|
||||||
|
input::-webkit-inner-spin-button {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=number] {
|
||||||
|
-moz-appearance: textfield;
|
||||||
|
appearance: textfield;
|
||||||
|
}
|
||||||
|
</style>
|
26
frontend/server/api/checkrelatiecode.post.js
Normal file
26
frontend/server/api/checkrelatiecode.post.js
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { db } from '../utils/firebase'
|
||||||
|
|
||||||
|
export default defineEventHandler(async event => {
|
||||||
|
const { relatiecode, email } = await readBody(event);
|
||||||
|
|
||||||
|
if (!relatiecode) throw createError({ statusCode: 400, statusMessage: 'no-relatiecode'})
|
||||||
|
if (!email) throw createError({ statusCode: 400, statusMessage: 'no-email'})
|
||||||
|
|
||||||
|
try {
|
||||||
|
const docRef = db.collection('ledenlijst').doc(relatiecode);
|
||||||
|
const doc = await docRef.get();
|
||||||
|
|
||||||
|
if (!doc.exists) throw createError({ statusCode: 400, statusMessage: 'incorrect'})
|
||||||
|
|
||||||
|
const data = doc.data()
|
||||||
|
|
||||||
|
if (data.email[0] === email || data.email[1] === email) {
|
||||||
|
return { code: 'correct' }
|
||||||
|
} else {
|
||||||
|
throw createError({ statusCode: 400, statusMessage: 'incorrect'})
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
throw createError({ statusCode: 500, statusMessage: e.message })
|
||||||
|
}
|
||||||
|
})
|
39
frontend/server/api/getrelatiecodes.post.js
Normal file
39
frontend/server/api/getrelatiecodes.post.js
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { db, auth } from '../utils/firebase'
|
||||||
|
|
||||||
|
export default defineEventHandler(async event => {
|
||||||
|
const { email, token } = await readBody(event);
|
||||||
|
|
||||||
|
if (!email) throw createError({ statusCode: 400, statusMessage: 'no-email'})
|
||||||
|
if (!token) throw createError({ statusCode: 400, statusMessage: 'no-token'})
|
||||||
|
|
||||||
|
try {
|
||||||
|
await auth.verifyIdToken(token)
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
throw createError({ statusCode: 500, statusMessage: 'error-verify-id'})
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const ledenlijstRef = db.collection('ledenlijst')
|
||||||
|
|
||||||
|
const snapshot = await ledenlijstRef.where("email", "array-contains", email).get()
|
||||||
|
|
||||||
|
if (snapshot.empty) {
|
||||||
|
throw createError({ statusCode: 400, statusMessage: 'no-relatiecode'})
|
||||||
|
}
|
||||||
|
|
||||||
|
let relatiecodes = [];
|
||||||
|
let persons = [];
|
||||||
|
|
||||||
|
snapshot.forEach(doc => {
|
||||||
|
relatiecodes.push(doc.id)
|
||||||
|
const data = doc.data()
|
||||||
|
persons.push({ fullName: data.fullName, relatiecode: doc.id })
|
||||||
|
});
|
||||||
|
|
||||||
|
return { code: 'success', relatiecodes: relatiecodes, persons: persons }
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
throw createError({ statusCode: 500, statusMessage: e.message })
|
||||||
|
}
|
||||||
|
})
|
63
frontend/server/api/sendmessage.post.js
Normal file
63
frontend/server/api/sendmessage.post.js
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import { db, auth, messaging } from '../utils/firebase'
|
||||||
|
|
||||||
|
export default defineEventHandler(async event => {
|
||||||
|
const { token, body, title, topic } = await readBody(event);
|
||||||
|
|
||||||
|
if (!token) throw createError({ statusCode: 400, statusMessage: 'no-token'})
|
||||||
|
if (!body) throw createError({ statusCode: 400, statusMessage: 'no-body'})
|
||||||
|
if (!title) throw createError({ statusCode: 400, statusMessage: 'no-title'})
|
||||||
|
if (!topic) throw createError({ statusCode: 400, statusMessage: 'no-topic'})
|
||||||
|
|
||||||
|
|
||||||
|
let decodedToken = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
decodedToken = await auth.verifyIdToken(token)
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e)
|
||||||
|
throw createError({ statusCode: 500, statusMessage: 'error-verify-id'})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!decodedToken) throw createError({ statusCode: 500, statusMessage: 'error-verify-id-test'})
|
||||||
|
|
||||||
|
try {
|
||||||
|
const uid = decodedToken.uid;
|
||||||
|
|
||||||
|
const docRef = db.collection('users').doc(uid);
|
||||||
|
const doc = await docRef.get();
|
||||||
|
|
||||||
|
if (!doc.exists) throw createError({ statusCode: 500, statusMessage: 'doc-not-found'})
|
||||||
|
|
||||||
|
const data = doc.data()
|
||||||
|
|
||||||
|
if (!data.sendNews) throw createError({ statusCode: 500, statusMessage: 'no-permissions'})
|
||||||
|
|
||||||
|
const message = {
|
||||||
|
notification: {
|
||||||
|
title: title,
|
||||||
|
body: body,
|
||||||
|
},
|
||||||
|
webpush: {
|
||||||
|
notification: {
|
||||||
|
icon: '/ios/256.png',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
topic: topic,
|
||||||
|
apns: {
|
||||||
|
payload: {
|
||||||
|
aps: {
|
||||||
|
sound: 'default'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const response = await messaging.send(message)
|
||||||
|
|
||||||
|
console.log('Successfully sent message:', response);
|
||||||
|
|
||||||
|
return { code: 'success', response: response }
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
throw createError({ statusCode: 500, statusMessage: e.message })
|
||||||
|
}
|
||||||
|
})
|
19
frontend/server/api/subscribetotopic.post.js
Normal file
19
frontend/server/api/subscribetotopic.post.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { messaging } from '../utils/firebase'
|
||||||
|
|
||||||
|
export default defineEventHandler(async event => {
|
||||||
|
const { topic, registrationToken } = await readBody(event);
|
||||||
|
|
||||||
|
|
||||||
|
if (!topic) throw createError({ statusCode: 400, statusMessage: 'no-topic'})
|
||||||
|
if (!registrationToken) throw createError({ statusCode: 400, statusMessage: 'no-registrationtoken'})
|
||||||
|
|
||||||
|
try {
|
||||||
|
await messaging.subscribeToTopic([registrationToken], topic)
|
||||||
|
|
||||||
|
return { code: 'success'}
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e)
|
||||||
|
|
||||||
|
throw createError({ statusCode: 500, statusMessage: e.message })
|
||||||
|
}
|
||||||
|
})
|
26
frontend/server/utils/firebase.js
Normal file
26
frontend/server/utils/firebase.js
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { initializeApp, cert } from 'firebase-admin/app';
|
||||||
|
import { getFirestore } from 'firebase-admin/firestore';
|
||||||
|
import { getMessaging } from 'firebase-admin/messaging';
|
||||||
|
import { getAuth } from 'firebase-admin/auth'
|
||||||
|
|
||||||
|
const config = useRuntimeConfig()
|
||||||
|
|
||||||
|
export const app = initializeApp({
|
||||||
|
credential: cert({
|
||||||
|
"project_id": "wrbapp",
|
||||||
|
"private_key_id": config.privateKeyId,
|
||||||
|
"private_key": config.privateKey,
|
||||||
|
"client_email": "firebase-adminsdk-iqgyy@wrbapp.iam.gserviceaccount.com",
|
||||||
|
"client_id": config.clientId,
|
||||||
|
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
||||||
|
"token_uri": "https://oauth2.googleapis.com/token",
|
||||||
|
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
|
||||||
|
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-iqgyy%40wrbapp.iam.gserviceaccount.com"
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
export const firestore = getFirestore()
|
||||||
|
|
||||||
|
export const db = getFirestore();
|
||||||
|
export const messaging = getMessaging();
|
||||||
|
export const auth = getAuth()
|
Loading…
Reference in New Issue
Block a user