@@ -1,6 +1,5 @@ | |||||
import Vue from 'vue' | import Vue from 'vue' | ||||
import VueRouter from 'vue-router' | import VueRouter from 'vue-router' | ||||
import Home from '../views/Home.vue' | |||||
Vue.use(VueRouter) | Vue.use(VueRouter) | ||||
@@ -8,15 +7,49 @@ const routes = [ | |||||
{ | { | ||||
path: '/', | path: '/', | ||||
name: 'Home', | name: 'Home', | ||||
component: Home | |||||
component: () => import('../views/Home.vue') | |||||
}, | |||||
// { | |||||
// path: '/event/gtblank-init', | |||||
// name: 'GtblankInit', | |||||
// meta: { title: 'gtblank - init (content warning: flashy lights)' }, | |||||
// component: () => import('../views/events/gtblank-init/GtblankInit.vue') | |||||
// }, | |||||
{ | |||||
path: '/event/gtblank-init/directions', | |||||
name: 'GtblankInit', | |||||
meta: { title: 'gtblank - init >> directions' }, | |||||
component: () => import('../views/events/gtblank-init/GtBlankDirections.vue') | |||||
}, | |||||
// { | |||||
// path: '/event/okt28', | |||||
// name: 'Okt28', | |||||
// component: () => import('../views/events/okt-28/Okt28.vue') | |||||
// }, | |||||
{ | |||||
path: '/event/ignaz', | |||||
name: 'Ignaz', | |||||
component: () => import('../views/events/ignaz/Ignaz.vue') | |||||
}, | }, | ||||
{ | { | ||||
path: '/cancel/:token', | path: '/cancel/:token', | ||||
name: 'Cancel', | name: 'Cancel', | ||||
// route level code-splitting | |||||
// this generates a separate chunk (about.[hash].js) for this route | |||||
// which is lazy-loaded when the route is visited. | |||||
component: () => import(/* webpackChunkName: "about" */ '../views/Cancel.vue') | |||||
component: () => import('../views/Cancel.vue') | |||||
}, | |||||
{ | |||||
path: '/reservation/:token', | |||||
name: 'Reservation', | |||||
component: () => import('../views/Reservation.vue') | |||||
}, | |||||
{ | |||||
path: '/invalidate', | |||||
name: 'Reservation', | |||||
component: () => import('../views/Invalidate.vue') | |||||
}, | |||||
{ | |||||
path: '/reservations/:secret', | |||||
name: 'Reservations', | |||||
component: () => import('../views/Reservations.vue') | |||||
} | } | ||||
] | ] | ||||
@@ -26,4 +59,14 @@ const router = new VueRouter({ | |||||
routes | routes | ||||
}) | }) | ||||
const DEFAULT_TITLE = 'event reservation'; | |||||
router.afterEach((to) => { | |||||
// Use next tick to handle router history correctly | |||||
// see: https://github.com/vuejs/vue-router/issues/914#issuecomment-384477609 | |||||
Vue.nextTick(() => { | |||||
document.title = to.meta.title || DEFAULT_TITLE; | |||||
}); | |||||
}); | |||||
export default router | export default router |
@@ -1,5 +1,5 @@ | |||||
<template> | <template> | ||||
<div class="cancel"> | |||||
<div class="cancel-view"> | |||||
<h1 v-if="!success">cancelling reservation {{$route.params.token}} ...</h1> | <h1 v-if="!success">cancelling reservation {{$route.params.token}} ...</h1> | ||||
<h1 v-else> | <h1 v-else> | ||||
your reservation got cancelled.<br> | your reservation got cancelled.<br> | ||||
@@ -60,4 +60,8 @@ | |||||
</script> | </script> | ||||
<style> | <style> | ||||
.cancel-view { | |||||
min-height: 100vh; | |||||
margin: 2em; | |||||
} | |||||
</style> | </style> |
@@ -1,296 +1,25 @@ | |||||
<template> | <template> | ||||
<div id="app"> | <div id="app"> | ||||
<div class="selection-text">{{selectionText}}</div> | |||||
<div class="otf" id="teilnehmen"> | |||||
<!-- <h1 style="padding-top:1em">Party</h1> --> | |||||
<div class="form-wrapper"> | |||||
</div> | |||||
<form v-if="!success" class="input-form" @submit.prevent="onClickTheButton" style="margin-top: 5vh"> | |||||
<div class="input-headline">reservation</div> | |||||
<input class="input-text" v-model="anzeigename" placeholder="nickname (public)" size="1" /> | |||||
<input class="input-text" v-model="name" placeholder="real name" size="1" /> | |||||
<input class="input-text" v-model="email" placeholder="email" size="1" /> | |||||
<!-- <label class="input-cb" for="newsletter"> | |||||
<input name="newsletter" id="newsletter" type="checkbox" class="input-checkbox" />stay informed | |||||
</label> --> | |||||
<input class="input-btn" type="submit" :value="(sending ? 'send ...' : 'reserve')" /> | |||||
<div v-if="error"> | |||||
oh no something went wrong!<br> | |||||
{{errorReason.detail}} | |||||
</div> | |||||
</form> | |||||
<div v-if="showHelp" style="margin-bottom: 1em;"> | |||||
*robot voice* give us your data for the reservation! | |||||
</div> | |||||
<div v-if="success"> | |||||
<p style="font-size: 8vw"> | |||||
Thanks! | |||||
</p> | |||||
<button @click="resetForm" class="input-btn">✓ see you there!</button> | |||||
</div> | |||||
<vue-word-cloud class="wordcloud" style="height: 70vh; width: 80%" :words="words" | |||||
:color="() => { return nameColors[Math.floor(Math.random() * nameColors.length)] }" | |||||
font-family="'JetBrains Mono'" /> | |||||
</div> | |||||
<!-- <div class="details" ref="details"> | |||||
TEXT! | |||||
</div> --> | |||||
<div | |||||
v-if="canShare" | |||||
@click="share" | |||||
style="cursor: pointer; margin-top: 5em;" | |||||
> | |||||
<svg class="share" fill="#fff" version="1.1" viewBox="0 0 350.22 180.68" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><g transform="translate(-622.81 -392.3)"><path d="m659.49 392.3v16.427h-16.299v19.182h-20.385v50.605h21.942v18.918h17.175v19.63h55.254l-37.054 55.916h30.57l37.054-55.916h190.84v-16.06h15.991v-18.048h18.443v-50.605h-19.999v-20.05h-16.867v-19.998zm6.9343 23.419h263.37v19.998h19.999v41.866h-18.443v16.06h-263.37v-19.63h-21.942v-41.866h20.385z" stroke-width="0" style="paint-order:stroke fill markers"/><g transform="matrix(.42673 0 0 .42673 457.43 276.68)" aria-label="WILLTEILEN"><path d="m590.71 451.07h-21.891l-6.5625-29.859q-0.46875-1.875-1.5-7.4531-0.98437-5.5781-1.4531-9.3281-0.375 3.0469-1.2188 7.5938-0.84375 4.5-1.6875 8.2969-0.79688 3.7969-6.7969 30.75h-21.891l-16.969-68.531h17.859l7.4531 34.359q2.5312 11.391 3.4688 18.141 0.60937-4.7812 2.1562-12.984 1.5938-8.2031 2.9531-13.594l6.0469-25.922h17.156l5.8594 25.922q1.5 6.2344 3.0469 14.391 1.5469 8.1562 2.0625 12.188 0.60937-5.2031 3.3281-18.047l7.5938-34.453h17.859z"/><path d="m616.44 451.07v-68.531h18.609v68.531z"/><path d="m649.86 451.07v-68.531h18.516v53.578h26.391v14.953z"/><path d="m705.74 451.07v-68.531h18.516v53.578h26.391v14.953z"/><path d="m791.85 451.07h-18.516v-53.391h-16.734v-15.141h51.938v15.141h-16.688z"/><path d="m859.02 451.07h-40.688v-68.531h40.688v14.859h-22.172v10.781h20.531v14.859h-20.531v12.938h22.172z"/><path d="m871.02 451.07v-68.531h18.609v68.531z"/><path d="m904.44 451.07v-68.531h18.516v53.578h26.391v14.953z"/><path d="m1001 451.07h-40.687v-68.531h40.687v14.859h-22.172v10.781h20.531v14.859h-20.531v12.938h22.172z"/><path d="m1078.3 451.07h-24.281l-25.031-48.281h-0.4218q0.8906 11.391 0.8906 17.391v30.891h-16.406v-68.531h24.188l24.938 47.625h0.2813q-0.6563-10.359-0.6563-16.641v-30.984h16.5z"/></g></g></svg> | |||||
</div> | |||||
<a | |||||
v-else | |||||
href="mailto:?subject=invitation%20-%20oct%2028&body=%40SWK%0D%0Areservation%20required%3A%20reservation.ck.si " | |||||
style="cursor: pointer; margin-top: 5em;" | |||||
> | |||||
<svg class="share" fill="#fff" version="1.1" viewBox="0 0 350.22 180.68" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><g transform="translate(-622.81 -392.3)"><path d="m659.49 392.3v16.427h-16.299v19.182h-20.385v50.605h21.942v18.918h17.175v19.63h55.254l-37.054 55.916h30.57l37.054-55.916h190.84v-16.06h15.991v-18.048h18.443v-50.605h-19.999v-20.05h-16.867v-19.998zm6.9343 23.419h263.37v19.998h19.999v41.866h-18.443v16.06h-263.37v-19.63h-21.942v-41.866h20.385z" stroke-width="0" style="paint-order:stroke fill markers"/><g transform="matrix(.42673 0 0 .42673 457.43 276.68)" aria-label="WILLTEILEN"><path d="m590.71 451.07h-21.891l-6.5625-29.859q-0.46875-1.875-1.5-7.4531-0.98437-5.5781-1.4531-9.3281-0.375 3.0469-1.2188 7.5938-0.84375 4.5-1.6875 8.2969-0.79688 3.7969-6.7969 30.75h-21.891l-16.969-68.531h17.859l7.4531 34.359q2.5312 11.391 3.4688 18.141 0.60937-4.7812 2.1562-12.984 1.5938-8.2031 2.9531-13.594l6.0469-25.922h17.156l5.8594 25.922q1.5 6.2344 3.0469 14.391 1.5469 8.1562 2.0625 12.188 0.60937-5.2031 3.3281-18.047l7.5938-34.453h17.859z"/><path d="m616.44 451.07v-68.531h18.609v68.531z"/><path d="m649.86 451.07v-68.531h18.516v53.578h26.391v14.953z"/><path d="m705.74 451.07v-68.531h18.516v53.578h26.391v14.953z"/><path d="m791.85 451.07h-18.516v-53.391h-16.734v-15.141h51.938v15.141h-16.688z"/><path d="m859.02 451.07h-40.688v-68.531h40.688v14.859h-22.172v10.781h20.531v14.859h-20.531v12.938h22.172z"/><path d="m871.02 451.07v-68.531h18.609v68.531z"/><path d="m904.44 451.07v-68.531h18.516v53.578h26.391v14.953z"/><path d="m1001 451.07h-40.687v-68.531h40.687v14.859h-22.172v10.781h20.531v14.859h-20.531v12.938h22.172z"/><path d="m1078.3 451.07h-24.281l-25.031-48.281h-0.4218q0.8906 11.391 0.8906 17.391v30.891h-16.406v-68.531h24.188l24.938 47.625h0.2813q-0.6563-10.359-0.6563-16.641v-30.984h16.5z"/></g></g></svg> | |||||
</a> | |||||
</div> | </div> | ||||
</template> | </template> | ||||
<script> | <script> | ||||
import VueWordCloud from 'vuewordcloud'; | |||||
export default { | export default { | ||||
name: 'App', | name: 'App', | ||||
data() { | data() { | ||||
return { | return { | ||||
anzeigename: '', | |||||
name: '', | |||||
email: '', | |||||
newsletter: false, | |||||
guests: [], | |||||
sending: false, | |||||
showHelp: false, | |||||
selection: null, | |||||
selectionText: null, | |||||
success: false, | |||||
error: false, | |||||
errorReason: null, | |||||
} | } | ||||
}, | }, | ||||
components: { | components: { | ||||
[VueWordCloud.name]: VueWordCloud, | |||||
}, | }, | ||||
computed: { | computed: { | ||||
nameColors() { | |||||
return ['DeepPink', 'Crimson', 'Blue', 'BlueViolet', 'Fuchsia', 'Navy', 'Indigo'] | |||||
}, | |||||
words() { | |||||
const names = this.guests.map(guest => { | |||||
return guest.anzeigename | |||||
}) | |||||
const nameLengths = names.map(name => { | |||||
return name.length | |||||
}) | |||||
const maxNameLength = Math.max(...nameLengths) | |||||
return this.guests.map(guest => { | |||||
const nameLength = guest.anzeigename.length | |||||
let size = maxNameLength - nameLength + 12 | |||||
return [` ${guest.anzeigename} `, size] | |||||
}) | |||||
}, | |||||
// guestlist() { | |||||
// return this.guests.map( (g) => { | |||||
// g.name = g.anzeigename | |||||
// return g | |||||
// }) | |||||
// } | |||||
canShare () { | |||||
return !!navigator.share | |||||
} | |||||
}, | }, | ||||
methods: { | methods: { | ||||
async fetchGuests() { | |||||
const response = await fetch(`${process.env.VUE_APP_API_URL}guest`) | |||||
const data = await response.json() | |||||
this.guests = data | |||||
}, | |||||
async onClickTheButton() { | |||||
if (this.name.length == 0) { | |||||
this.showHelp = true | |||||
return false | |||||
} | |||||
this.showHelp = false | |||||
this.sending = true | |||||
let data = { | |||||
anzeigename: this.anzeigename, | |||||
name: this.name, | |||||
email: this.email, | |||||
newsletter: false, | |||||
} | |||||
const response = await fetch( | |||||
`${process.env.VUE_APP_API_URL}guest`, { | |||||
method: 'POST', | |||||
headers: { | |||||
'Content-Type': 'application/json', | |||||
}, | |||||
body: JSON.stringify(data), | |||||
} | |||||
) | |||||
if (response.status === 200) { | |||||
this.anzeigename = '' | |||||
this.name = '' | |||||
this.email = '' | |||||
this.success = true | |||||
this.fetchGuests() | |||||
} else { | |||||
this.success = false | |||||
this.fetchGuests() | |||||
this.error = true | |||||
this.errorReason = await response.json() | |||||
} | |||||
this.sending = false | |||||
return false | |||||
}, | |||||
resetForm () { | |||||
this.anzeigename = '' | |||||
this.name = '' | |||||
this.email = '' | |||||
this.success = false | |||||
}, | |||||
async share () { | |||||
location.href = "#"; | |||||
try { | |||||
await navigator.share({ url:"https://reservation.ck.si/" }) | |||||
} catch (err) { | |||||
console.log(`Error: ${err}`) | |||||
} | |||||
} | |||||
}, | }, | ||||
mounted() { | mounted() { | ||||
this.fetchGuests() | |||||
document.addEventListener('selectionchange', () => { | |||||
this.selection = document.getSelection() | |||||
// console.log(this.selection) | |||||
if (this.selection) { | |||||
this.selectionText = this.selection.toString() | |||||
} | |||||
}); | |||||
// this.$refs.details.$el | |||||
}, | }, | ||||
} | } | ||||
</script> | </script> | ||||
<style> | <style> | ||||
.otf { | |||||
width: 100%; | |||||
/* background-color: rgb(167, 204, 247); */ | |||||
/* background-image: url('bg.gif'); */ | |||||
background-size: cover; | |||||
display: flex; | |||||
flex-direction: column; | |||||
align-items: center; | |||||
min-height: 100vh; | |||||
position: relative; | |||||
} | |||||
.input-headline { | |||||
color: rgb(255, 255, 255); | |||||
font-size: 2.8vh; | |||||
margin-bottom: .8vh; | |||||
} | |||||
.input-form { | |||||
display: flex; | |||||
flex-direction: column; | |||||
width: 100%; | |||||
max-width: 800px; | |||||
mix-blend-mode: exclusion; | |||||
margin-top: 4vh; | |||||
z-index: 9; | |||||
position: relative; | |||||
} | |||||
.input-text, | |||||
.input-btn, | |||||
.input-cb { | |||||
color: #000; | |||||
background: #fff; | |||||
font-size: 2.8vh; | |||||
font-family: 'JetBrains Mono', monospace; | |||||
border: 0; | |||||
padding: .5em 1em; | |||||
line-height: 1.4; | |||||
max-width: 100%; | |||||
} | |||||
.input-btn { | |||||
color: #fff; | |||||
background-color: #1e3a8e; | |||||
border-top: 2px solid rgb(0, 0, 0); | |||||
cursor: pointer; | |||||
} | |||||
.input-btn:hover { | |||||
color: #fff; | |||||
background: #3260eb; | |||||
} | |||||
.input-checkbox { | |||||
margin: 0 1em 0 0; | |||||
} | |||||
@media screen and (max-width: 1000px) { | |||||
.form-wrapper { | |||||
margin-top: 4em; | |||||
} | |||||
.input-text, | |||||
.input-btn { | |||||
width: 100%; | |||||
max-width: 100%; | |||||
min-width: 0; | |||||
text-align: center; | |||||
} | |||||
/* .input-form { | |||||
position: absolute; | |||||
bottom: 0; | |||||
display: flex; | |||||
flex-direction: column; | |||||
} */ | |||||
} | |||||
.input-text { | |||||
width: auto; | |||||
flex-grow: 1; | |||||
} | |||||
.details { | |||||
color: #fff; | |||||
background-color: #000; | |||||
font-size: 10vw; | |||||
max-width: 80em; | |||||
margin: 3% 3% 200px; | |||||
padding: 3%; | |||||
} | |||||
.share { | |||||
max-width: 30em; | |||||
width: 80%; | |||||
mix-blend-mode: soft-light; | |||||
} | |||||
.share:hover { | |||||
mix-blend-mode: difference; | |||||
} | |||||
.selection-text { | |||||
position: fixed; | |||||
z-index: 9999; | |||||
font-size: 10vh; | |||||
line-height: 1em; | |||||
pointer-events: none; | |||||
color: #ec4b73; | |||||
mix-blend-mode: multiply; | |||||
top: 10%; | |||||
} | |||||
</style> | </style> |
@@ -0,0 +1,113 @@ | |||||
<template> | |||||
<div class="invalidation-view"> | |||||
<qrcode-stream | |||||
v-if="!token" | |||||
@decode="onDecode" | |||||
@init="onInit"> | |||||
</qrcode-stream> | |||||
<div v-else> | |||||
<div v-if="status && !status.valid"> | |||||
reservation {{ token }} from {{status.anzeigename}} is not valid!<br> | |||||
reason: {{status.reason || status.detail}}<br><br> | |||||
<button @click="onReset" class="btn reset-btn">reset</button> | |||||
</div> | |||||
<div v-if="status && status.valid"> | |||||
reservation from {{status.anzeigename}} is valid :) <br> | |||||
<div> | |||||
<button @click="onInvalidate" class="btn invalidate-btn">invalidate now</button> | |||||
<button @click="onReset" class="btn reset-btn">reset</button> | |||||
</div> | |||||
<div v-if="error"> | |||||
{{error}} | |||||
</div> | |||||
</div> | |||||
</div> | |||||
<!-- {{status}} --> | |||||
</div> | |||||
</template> | |||||
<script> | |||||
import { QrcodeStream } from 'vue-qrcode-reader' | |||||
export default { | |||||
name: 'Invalidate', | |||||
components: { | |||||
QrcodeStream, | |||||
}, | |||||
data() { | |||||
return { | |||||
token: null, | |||||
status: null, | |||||
success: null, | |||||
error: null, | |||||
} | |||||
}, | |||||
methods: { | |||||
async onDecode(token) { | |||||
this.token = token | |||||
this.status = await this.getStatus(token) | |||||
}, | |||||
async getStatus (token) { | |||||
const response = await fetch(`${process.env.VUE_APP_API_URL}guest/${token}`) | |||||
const data = await response.json() | |||||
return data | |||||
}, | |||||
onInit(promise) { | |||||
promise | |||||
.then(console.log) | |||||
.catch(console.error) | |||||
}, | |||||
onReset() { | |||||
this.token = null | |||||
this.status = null | |||||
this.success = null | |||||
this.error = null | |||||
}, | |||||
async onInvalidate() { | |||||
let data = { | |||||
token: this.token | |||||
} | |||||
const response = await fetch( | |||||
`${process.env.VUE_APP_API_URL}guest/invalidate`, { | |||||
method: 'POST', | |||||
headers: { 'Content-Type': 'application/json' }, | |||||
body: JSON.stringify(data), | |||||
} | |||||
) | |||||
if (response.status === 200) { | |||||
this.success = true | |||||
this.onReset() | |||||
} else { | |||||
this.error = await response.json() | |||||
} | |||||
} | |||||
}, | |||||
} | |||||
</script> | |||||
<style> | |||||
.invalidation-view { | |||||
min-height: 100vh; | |||||
display: flex; | |||||
align-items: center; | |||||
justify-content: center; | |||||
text-align: center; | |||||
} | |||||
.btn { | |||||
padding: 1em; | |||||
font-size: 2em; | |||||
border: none; | |||||
outline: none; | |||||
margin: .2em 0; | |||||
} | |||||
.invalidate-btn { | |||||
background: rgb(255, 255, 255); | |||||
color: #000; | |||||
margin-right: 1em; | |||||
} | |||||
.reset-btn { | |||||
background: rgb(138, 138, 138); | |||||
color: #000; | |||||
} | |||||
</style> |
@@ -0,0 +1,67 @@ | |||||
<template> | |||||
<div class="qrview" > | |||||
<VueQRCodeComponent | |||||
v-if="!status || (status && status.valid)" | |||||
class="qrcode" | |||||
:text="token" | |||||
color="#fff" | |||||
bg-color="#000" | |||||
/> | |||||
<div v-if="status && status.valid && status.anzeigename"> | |||||
{{status.anzeigename}} | |||||
</div> | |||||
<div v-if="status && !status.valid && status.reason"> | |||||
reservation {{ token }} from {{status.anzeigename}} is not valid<br> | |||||
reason: {{status.reason }} | |||||
</div> | |||||
</div> | |||||
</template> | |||||
<script> | |||||
import VueQRCodeComponent from 'vue-qrcode-component' | |||||
export default { | |||||
components: { | |||||
VueQRCodeComponent | |||||
}, | |||||
data() { | |||||
return { | |||||
status: null | |||||
} | |||||
}, | |||||
computed: { | |||||
token () { | |||||
return this.$route.params.token | |||||
} | |||||
}, | |||||
methods: { | |||||
async getStatus () { | |||||
const response = await fetch(`${process.env.VUE_APP_API_URL}guest/${this.token}`) | |||||
const data = await response.json() | |||||
this.status = data | |||||
} | |||||
}, | |||||
mounted () { | |||||
this.getStatus() | |||||
}, | |||||
} | |||||
</script> | |||||
<style> | |||||
.qrview { | |||||
display: flex; | |||||
align-items: center; | |||||
justify-content: center; | |||||
flex-direction: column; | |||||
min-height: 100vh; | |||||
mix-blend-mode: difference; | |||||
} | |||||
.qrcode > img{ | |||||
/* padding: 100px; */ | |||||
/* background-color: #fff; */ | |||||
display: inline-flex; | |||||
width: 400px; | |||||
max-width: 80%; | |||||
margin: 10%; | |||||
} | |||||
</style> |
@@ -0,0 +1,287 @@ | |||||
<template> | |||||
<div class="dark"> | |||||
<div v-if="reservations" class="stats"> | |||||
<h1> | |||||
{{reservations.length}} active reservations | |||||
</h1> | |||||
<h2> | |||||
{{invalidatedCount}} invalidated<br> | |||||
{{validCount}} to go | |||||
</h2> | |||||
</div> | |||||
<div class="guest-table"> | |||||
<vuetable v-if="reservations" | |||||
:fields="fields" | |||||
:api-mode="false" | |||||
:data="reservations" | |||||
:sort-order="sortOrder" | |||||
:show-sort-icons="true" | |||||
> | |||||
<template v-slot:timestamp="props"> | |||||
<template v-if="props.rowData.timestamp"> | |||||
{{new Date(props.rowData.timestamp).toLocaleString('de-DE', {day:'numeric',month:'numeric',hour:'numeric',minute:'numeric'})}} | |||||
</template> | |||||
</template> | |||||
<template v-slot:invalidated="props"> | |||||
<template v-if="props.rowData.invalidated"> | |||||
{{new Date(props.rowData.invalidated).toLocaleString('de-DE', {day:'numeric',month:'numeric',hour:'numeric',minute:'numeric'})}} | |||||
</template> | |||||
<button v-else @click="invalidate(props.rowData.token)">invalidate</button> | |||||
</template> | |||||
<template v-slot:actions="props"> | |||||
<button @click="cancel(props.rowData.token)">cancel</button> | |||||
</template> | |||||
<template v-slot:headsup_sent="props"> | |||||
<template v-if="props.rowData.headsup_sent"> | |||||
{{new Date(props.rowData.headsup_sent).toLocaleString('de-DE', {day:'numeric',month:'numeric',hour:'numeric',minute:'numeric'})}} | |||||
</template> | |||||
<button @click="sendHeadsup(props.rowData.id)">X</button> | |||||
</template> | |||||
</vuetable> | |||||
</div> | |||||
<label for="showSensitiveData"> | |||||
<input type="checkbox" name="showSensitiveData" id="showSensitiveData" :value="showSensitiveData" @change="showSensitiveData = !showSensitiveData"> | |||||
show sensitive data | |||||
</label> | |||||
<div class="add-guest-form"> | |||||
<form class="input-form" @submit.prevent="onAddGuest"> | |||||
<div class="input-headline">add new reservation</div> | |||||
<input class="input-text" v-model="anzeigename" placeholder="nickname (public)" /> | |||||
<input class="input-text" v-model="name" placeholder="real name" /> | |||||
<input class="input-text" v-model="email" placeholder="email" /> | |||||
<input class="input-btn" type="submit" :value="(addGuestSending ? 'sending ...' : 'reserve')" :disabled="(addGuestSending ? true : false)" /> | |||||
<div v-if="addGuestError"> | |||||
oh no something went wrong!<br> | |||||
{{addGuestError.detail}} | |||||
</div> | |||||
</form> | |||||
<button @click="sendHeadsups">SEND ALL EMAILS</button> | |||||
</div> | |||||
<!-- {{reservations}} --> | |||||
</div> | |||||
</template> | |||||
<script> | |||||
import Vuetable from 'vuetable-2' | |||||
export default { | |||||
name: 'Reservations', | |||||
components: { | |||||
Vuetable | |||||
}, | |||||
data() { | |||||
return { | |||||
reservations: null, | |||||
sortOrder: [ | |||||
{ | |||||
field: 'name', | |||||
direction: 'asc', | |||||
} | |||||
], | |||||
showSensitiveData: false, | |||||
addGuestSending: false, | |||||
addGuestError: null, | |||||
anzeigename: '', | |||||
name: '', | |||||
email: '', | |||||
} | |||||
}, | |||||
computed: { | |||||
secret () { | |||||
return this.$route.params.secret | |||||
}, | |||||
invalidatedCount () { | |||||
if (this.reservations && this.reservations.length) { | |||||
const filtered = this.reservations.filter(r => !!r.invalidated) | |||||
return filtered.length | |||||
} | |||||
return null | |||||
}, | |||||
validCount () { | |||||
if (this.reservations && this.reservations.length) { | |||||
const filtered = this.reservations.filter(r => !r.invalidated) | |||||
return filtered.length | |||||
} | |||||
return null | |||||
}, | |||||
fields () { | |||||
if (this.showSensitiveData) { | |||||
return ['id','timestamp', 'anzeigename', 'name', 'email', 'token', 'invalidated','actions', 'headsup_sent'] | |||||
} else { | |||||
return ['id','timestamp', 'anzeigename', 'invalidated','actions', 'headsup_sent'] | |||||
} | |||||
} | |||||
}, | |||||
methods: { | |||||
async getReservations() { | |||||
const response = await fetch(`${process.env.VUE_APP_API_URL}guest/management/${this.$route.params.secret}`) | |||||
const data = await response.json() | |||||
this.reservations = data | |||||
}, | |||||
async cancel(token) { | |||||
let data = { token } | |||||
const response = await fetch( | |||||
`${process.env.VUE_APP_API_URL}guest/cancel`, { | |||||
method: 'POST', | |||||
headers: { 'Content-Type': 'application/json' }, | |||||
body: JSON.stringify(data), | |||||
} | |||||
) | |||||
if (response.status === 200) { | |||||
this.getReservations() | |||||
} else { | |||||
alert(await response.json()) | |||||
} | |||||
}, | |||||
async invalidate(token) { | |||||
let data = { token } | |||||
const response = await fetch( | |||||
`${process.env.VUE_APP_API_URL}guest/invalidate`, { | |||||
method: 'POST', | |||||
headers: { 'Content-Type': 'application/json' }, | |||||
body: JSON.stringify(data), | |||||
} | |||||
) | |||||
if (response.status === 200) { | |||||
this.getReservations() | |||||
} else { | |||||
alert(await response.json()) | |||||
} | |||||
}, | |||||
async sendHeadsup(id) { | |||||
let data = { | |||||
guest_id: id, | |||||
secret: this.secret | |||||
} | |||||
const response = await fetch( | |||||
`${process.env.VUE_APP_API_URL}guest/management/send_headsup`, { | |||||
method: 'POST', | |||||
headers: { 'Content-Type': 'application/json' }, | |||||
body: JSON.stringify(data), | |||||
} | |||||
) | |||||
if (response.status === 200) { | |||||
this.getReservations() | |||||
} else { | |||||
alert(await response.json()) | |||||
} | |||||
}, | |||||
async sendHeadsups() { | |||||
let data = { | |||||
secret: this.secret | |||||
} | |||||
const response = await fetch( | |||||
`${process.env.VUE_APP_API_URL}guest/management/send_headsups`, { | |||||
method: 'POST', | |||||
headers: { 'Content-Type': 'application/json' }, | |||||
body: JSON.stringify(data), | |||||
} | |||||
) | |||||
if (response.status === 200) { | |||||
this.getReservations() | |||||
} else { | |||||
alert(await response.json()) | |||||
} | |||||
}, | |||||
async onAddGuest() { | |||||
if (this.name.length == 0) { | |||||
return false | |||||
} | |||||
this.addGuestSending = true | |||||
let data = { | |||||
anzeigename: this.anzeigename, | |||||
name: this.name, | |||||
email: this.email, | |||||
newsletter: false, | |||||
} | |||||
const response = await fetch( | |||||
`${process.env.VUE_APP_API_URL}guest`, { | |||||
method: 'POST', | |||||
headers: { | |||||
'Content-Type': 'application/json', | |||||
}, | |||||
body: JSON.stringify(data), | |||||
} | |||||
) | |||||
if (response.status === 200) { | |||||
this.anzeigename = '' | |||||
this.name = '' | |||||
this.email = '' | |||||
this.getReservations() | |||||
} else { | |||||
this.getReservations() | |||||
this.addGuestError = true | |||||
this.addGuestError = await response.json() | |||||
} | |||||
this.addGuestSending = false | |||||
return false | |||||
}, | |||||
}, | |||||
mounted () { | |||||
this.getReservations() | |||||
}, | |||||
} | |||||
</script> | |||||
<style> | |||||
.stats { | |||||
display: flex; | |||||
flex-direction: column; | |||||
align-items: center; | |||||
} | |||||
.vuetable thead { | |||||
background-color: rgb(255, 0, 0); | |||||
} | |||||
.vuetable-body tr { | |||||
border-bottom: 1px dashed rgb(255, 0, 0); | |||||
} | |||||
.vuetable-body tr:hover { | |||||
background-color: rgba(255, 0, 0, 0.281) | |||||
} | |||||
.vuetable-body td { | |||||
padding: .5em; | |||||
margin: 0; | |||||
} | |||||
.guest-table { | |||||
display: flex; | |||||
flex-direction: column; | |||||
align-items: center; | |||||
} | |||||
.add-guest-form { | |||||
display: flex; | |||||
flex-direction: column; | |||||
align-items: center; | |||||
margin-top: 120px | |||||
} | |||||
button { | |||||
background: #fff; | |||||
color: #000; | |||||
border: none; | |||||
text-transform: uppercase; | |||||
cursor: pointer; | |||||
font-size: 18px; | |||||
font-family: inherit; | |||||
} | |||||
button:hover { | |||||
background: #ff0; | |||||
color: #000; | |||||
} | |||||
</style> |
@@ -0,0 +1,36 @@ | |||||
<template> | |||||
<div> | |||||
<p> | |||||
closest station: D-tram station Liechtenwerder Platz<br> | |||||
(Station Spittelau [U4, U6, S-Bahn] is also close by)<br> | |||||
</p> | |||||
<img src="/gt-blank-way0.jpg"> | |||||
<p> | |||||
Take the stairs up to the main entrance "Augasse 2-6". | |||||
</p> | |||||
<img src="/gt-blank-way1.jpg"> | |||||
<p> | |||||
After entering the building, just turn right and head to the next door (Alteliers, Büro). | |||||
</p> | |||||
<p> | |||||
<a href="geo:48.23201,16.35773">native geo location link</a> | | |||||
<a href="https://www.openstreetmap.org/?mlat=48.23201&mlon=16.35773#map=19/48.23201/16.35773" target="_blank">location pin @ osm.org</a> | |||||
</p> | |||||
</div> | |||||
</template> | |||||
<script> | |||||
export default { | |||||
} | |||||
</script> | |||||
<style> | |||||
img { | |||||
max-width: 100%; | |||||
max-height: 90vh; | |||||
} | |||||
a { | |||||
color: #fff; | |||||
} | |||||
</style> |
@@ -0,0 +1,315 @@ | |||||
<template> | |||||
<div id="gtblank-init"> | |||||
<video autoplay muted loop class="video-bg"> | |||||
<source src="/gt-blank-init-bg.mp4" type="video/mp4"> | |||||
</video> | |||||
<div class="selection-text">{{selectionText}}</div> | |||||
<div class="otf"> | |||||
<!-- <h1 style="padding-top:1em">Party</h1> --> | |||||
<div class="form-wrapper"> | |||||
</div> | |||||
<template v-if="status && status.open"> | |||||
<form v-if="!success" class="input-form" @submit.prevent="onClickTheButton" style="margin-top: 5vh"> | |||||
<div class="input-headline">gtblank-init 2022-11-11 rsvp</div> | |||||
<input class="input-text" v-model="anzeigename" placeholder="nickname (public)" size="1" /> | |||||
<input class="input-text" v-model="name" placeholder="full name (for orga reasons)" size="1" /> | |||||
<input class="input-text" v-model="email" placeholder="email (for self service)" size="1" /> | |||||
<input class="input-btn" type="submit" :value="(sending ? 'sending ...' : 'join')" :disabled="(sending ? true : false)" /> | |||||
<div v-if="error"> | |||||
oh no something went wrong!<br> | |||||
{{errorReason.detail}} | |||||
</div> | |||||
</form> | |||||
<div v-if="showHelp" style="margin-bottom: 1em;"> | |||||
*robot voice* give us your data for the reservation! | |||||
</div> | |||||
<div v-if="success"> | |||||
<p style="font-size: 6vw"> | |||||
Thanks! | |||||
</p> | |||||
<button @click="resetForm" class="input-btn">✓ see you there!</button> | |||||
</div> | |||||
</template> | |||||
<template v-if="status && !status.open"> | |||||
{{status.reason}} | |||||
</template> | |||||
<vue-word-cloud class="wordcloud" style="height: 70vh; width: 80%" :words="words" | |||||
font-family="'JetBrains Mono'" /> | |||||
<div v-if="status && status.open" class="details" ref="details"> | |||||
official start 21:00<br> | |||||
augasse 2-6, 1090 wien<br> | |||||
<small> | |||||
alte wu -> enter main entrance -> turn right to "ateliers" <br> | |||||
<a href="https://www.openstreetmap.org/?mlat=48.23201&mlon=16.35773#map=19/48.23201/16.35773" target="_blank">location pin @ osm.org</a> | |||||
</small> | |||||
</div> | |||||
</div> | |||||
<!-- :color="() => { return nameColors[Math.floor(Math.random() * nameColors.length)] }" --> | |||||
</div> | |||||
</template> | |||||
<script> | |||||
import VueWordCloud from 'vuewordcloud'; | |||||
export default { | |||||
name: 'Invitation', | |||||
data() { | |||||
return { | |||||
anzeigename: '', | |||||
name: '', | |||||
email: '', | |||||
status: null, | |||||
newsletter: false, | |||||
guests: [], | |||||
sending: false, | |||||
showHelp: false, | |||||
selection: null, | |||||
selectionText: null, | |||||
success: false, | |||||
error: false, | |||||
errorReason: null, | |||||
} | |||||
}, | |||||
components: { | |||||
[VueWordCloud.name]: VueWordCloud, | |||||
}, | |||||
computed: { | |||||
nameColors() { | |||||
return ['White'] | |||||
}, | |||||
words() { | |||||
const names = this.guests.map(guest => { | |||||
return guest.anzeigename | |||||
}) | |||||
const nameLengths = names.map(name => { | |||||
return name.length | |||||
}) | |||||
const maxNameLength = Math.max(...nameLengths) | |||||
return this.guests.map(guest => { | |||||
const nameLength = guest.anzeigename.length | |||||
let size = maxNameLength - nameLength + 12 | |||||
return [` ${guest.anzeigename} `, size] | |||||
}) | |||||
}, | |||||
// guestlist() { | |||||
// return this.guests.map( (g) => { | |||||
// g.name = g.anzeigename | |||||
// return g | |||||
// }) | |||||
// } | |||||
}, | |||||
methods: { | |||||
async fetchStatus() { | |||||
const response = await fetch(`${process.env.VUE_APP_API_URL}status`) | |||||
const data = await response.json() | |||||
this.status = data | |||||
}, | |||||
async fetchGuests() { | |||||
const response = await fetch(`${process.env.VUE_APP_API_URL}guest`) | |||||
const data = await response.json() | |||||
this.guests = data | |||||
}, | |||||
async onClickTheButton() { | |||||
if (this.name.length == 0) { | |||||
this.showHelp = true | |||||
return false | |||||
} | |||||
this.showHelp = false | |||||
this.sending = true | |||||
let data = { | |||||
anzeigename: this.anzeigename, | |||||
name: this.name, | |||||
email: this.email, | |||||
newsletter: false, | |||||
} | |||||
const response = await fetch( | |||||
`${process.env.VUE_APP_API_URL}guest`, { | |||||
method: 'POST', | |||||
headers: { | |||||
'Content-Type': 'application/json', | |||||
}, | |||||
body: JSON.stringify(data), | |||||
} | |||||
) | |||||
if (response.status === 200) { | |||||
this.anzeigename = '' | |||||
this.name = '' | |||||
this.email = '' | |||||
this.success = true | |||||
this.fetchGuests() | |||||
} else { | |||||
this.success = false | |||||
this.fetchGuests() | |||||
this.error = true | |||||
this.errorReason = await response.json() | |||||
} | |||||
this.sending = false | |||||
return false | |||||
}, | |||||
resetForm () { | |||||
this.anzeigename = '' | |||||
this.name = '' | |||||
this.email = '' | |||||
this.success = false | |||||
}, | |||||
}, | |||||
mounted() { | |||||
this.fetchStatus() | |||||
this.fetchGuests() | |||||
document.addEventListener('selectionchange', () => { | |||||
this.selection = document.getSelection() | |||||
// console.log(this.selection) | |||||
if (this.selection) { | |||||
this.selectionText = this.selection.toString() | |||||
} | |||||
}); | |||||
// this.$refs.details.$el | |||||
}, | |||||
} | |||||
</script> | |||||
<style> | |||||
:root { | |||||
cursor: url('cursor.png'), auto; | |||||
} | |||||
#gtblank-init { | |||||
background: #999; | |||||
} | |||||
.video-bg { | |||||
position: fixed; | |||||
right: 0; | |||||
bottom: 0; | |||||
min-width: 100%; | |||||
min-height: 100%; | |||||
} | |||||
a { | |||||
color: inherit; | |||||
cursor: inherit; | |||||
} | |||||
.otf { | |||||
width: 100%; | |||||
/* background-color: rgb(167, 204, 247); */ | |||||
/* background-image: url('bg.gif'); */ | |||||
background-size: cover; | |||||
display: flex; | |||||
flex-direction: column; | |||||
align-items: center; | |||||
min-height: 100vh; | |||||
position: relative; | |||||
} | |||||
.input-headline { | |||||
color: #000; | |||||
mix-blend-mode: multiply; | |||||
font-size: 4vh; | |||||
margin-bottom: .8vh; | |||||
text-align: center; | |||||
} | |||||
.input-form { | |||||
display: flex; | |||||
flex-direction: column; | |||||
width: 100%; | |||||
max-width: 800px; | |||||
mix-blend-mode: hard-light; | |||||
margin-top: 4vh; | |||||
z-index: 9; | |||||
position: relative; | |||||
} | |||||
.input-text, | |||||
.input-btn, | |||||
.input-cb { | |||||
color: #000; | |||||
background: #fff; | |||||
font-size: 2.8vh; | |||||
font-family: 'JetBrains Mono', monospace; | |||||
border: 0; | |||||
padding: .5em 1em; | |||||
line-height: 1.4; | |||||
max-width: 100%; | |||||
cursor: inherit; | |||||
} | |||||
.input-btn { | |||||
color: #000; | |||||
background-color: #01ffcf; | |||||
} | |||||
.input-btn:hover { | |||||
background: #00d9b1; | |||||
} | |||||
.input-checkbox { | |||||
margin: 0 1em 0 0; | |||||
} | |||||
@media screen and (max-width: 1000px) { | |||||
.form-wrapper { | |||||
margin-top: 4em; | |||||
} | |||||
.input-text, | |||||
.input-btn { | |||||
width: 100%; | |||||
max-width: 100%; | |||||
min-width: 0; | |||||
text-align: center; | |||||
} | |||||
/* .input-form { | |||||
position: absolute; | |||||
bottom: 0; | |||||
display: flex; | |||||
flex-direction: column; | |||||
} */ | |||||
} | |||||
.input-text { | |||||
width: auto; | |||||
flex-grow: 1; | |||||
} | |||||
.wordcloud { | |||||
mix-blend-mode: overlay; | |||||
} | |||||
.details { | |||||
color: #000; | |||||
background-color: #fff; | |||||
font-size: 30px; | |||||
max-width: 80em; | |||||
margin: 3% 3% 200px; | |||||
padding: 3%; | |||||
mix-blend-mode: lighten; | |||||
box-shadow: 10px 10px 0 #000fee, -10px -10px 0 #3400ee; | |||||
} | |||||
.share { | |||||
max-width: 30em; | |||||
width: 80%; | |||||
mix-blend-mode: soft-light; | |||||
} | |||||
.share:hover { | |||||
mix-blend-mode: difference; | |||||
} | |||||
.selection-text { | |||||
position: fixed; | |||||
z-index: 9999; | |||||
font-size: 10vh; | |||||
line-height: 1em; | |||||
pointer-events: none; | |||||
color: #fff; | |||||
mix-blend-mode: difference; | |||||
top: 10%; | |||||
} | |||||
</style> |
@@ -0,0 +1,322 @@ | |||||
<template> | |||||
<div id="okt-28"> | |||||
<div class="selection-text">{{selectionText}}</div> | |||||
<div class="otf" id="teilnehmen"> | |||||
<!-- <h1 style="padding-top:1em">Party</h1> --> | |||||
<div class="form-wrapper"> | |||||
</div> | |||||
<template v-if="status && status.open"> | |||||
<form v-if="!success" class="input-form" @submit.prevent="onClickTheButton" style="margin-top: 5vh"> | |||||
<div class="input-headline">reservation</div> | |||||
<input class="input-text" v-model="anzeigename" placeholder="nickname (public)" size="1" /> | |||||
<input class="input-text" v-model="name" placeholder="real name" size="1" /> | |||||
<input class="input-text" v-model="email" placeholder="email" size="1" /> | |||||
<!-- <label class="input-cb" for="newsletter"> | |||||
<input name="newsletter" id="newsletter" type="checkbox" class="input-checkbox" />stay informed | |||||
</label> --> | |||||
<input class="input-btn" type="submit" :value="(sending ? 'sending ...' : 'reserve')" :disabled="(sending ? true : false)" /> | |||||
<div v-if="error"> | |||||
oh no something went wrong!<br> | |||||
{{errorReason.detail}} | |||||
</div> | |||||
</form> | |||||
<div v-if="showHelp" style="margin-bottom: 1em;"> | |||||
*robot voice* give us your data for the reservation! | |||||
</div> | |||||
<div v-if="success"> | |||||
<p style="font-size: 6vw"> | |||||
Thanks! | |||||
</p> | |||||
<button @click="resetForm" class="input-btn">✓ see you there!</button> | |||||
</div> | |||||
</template> | |||||
<template v-if="status && !status.open"> | |||||
{{status.reason}} | |||||
</template> | |||||
<vue-word-cloud class="wordcloud" style="height: 70vh; width: 80%" :words="words" | |||||
:color="() => { return nameColors[Math.floor(Math.random() * nameColors.length)] }" | |||||
font-family="'JetBrains Mono'" /> | |||||
</div> | |||||
<!-- <div class="details" ref="details"> | |||||
TEXT! | |||||
</div> --> | |||||
<div | |||||
v-if="canShare" | |||||
@click="share" | |||||
style="cursor: pointer; margin-top: 5em;" | |||||
> | |||||
<svg class="share" fill="#fff" version="1.1" viewBox="0 0 350.22 180.68" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><g transform="translate(-622.81 -392.3)"><path d="m659.49 392.3v16.427h-16.299v19.182h-20.385v50.605h21.942v18.918h17.175v19.63h55.254l-37.054 55.916h30.57l37.054-55.916h190.84v-16.06h15.991v-18.048h18.443v-50.605h-19.999v-20.05h-16.867v-19.998zm6.9343 23.419h263.37v19.998h19.999v41.866h-18.443v16.06h-263.37v-19.63h-21.942v-41.866h20.385z" stroke-width="0" style="paint-order:stroke fill markers"/><g transform="matrix(.42673 0 0 .42673 457.43 276.68)" aria-label="WILLTEILEN"><path d="m590.71 451.07h-21.891l-6.5625-29.859q-0.46875-1.875-1.5-7.4531-0.98437-5.5781-1.4531-9.3281-0.375 3.0469-1.2188 7.5938-0.84375 4.5-1.6875 8.2969-0.79688 3.7969-6.7969 30.75h-21.891l-16.969-68.531h17.859l7.4531 34.359q2.5312 11.391 3.4688 18.141 0.60937-4.7812 2.1562-12.984 1.5938-8.2031 2.9531-13.594l6.0469-25.922h17.156l5.8594 25.922q1.5 6.2344 3.0469 14.391 1.5469 8.1562 2.0625 12.188 0.60937-5.2031 3.3281-18.047l7.5938-34.453h17.859z"/><path d="m616.44 451.07v-68.531h18.609v68.531z"/><path d="m649.86 451.07v-68.531h18.516v53.578h26.391v14.953z"/><path d="m705.74 451.07v-68.531h18.516v53.578h26.391v14.953z"/><path d="m791.85 451.07h-18.516v-53.391h-16.734v-15.141h51.938v15.141h-16.688z"/><path d="m859.02 451.07h-40.688v-68.531h40.688v14.859h-22.172v10.781h20.531v14.859h-20.531v12.938h22.172z"/><path d="m871.02 451.07v-68.531h18.609v68.531z"/><path d="m904.44 451.07v-68.531h18.516v53.578h26.391v14.953z"/><path d="m1001 451.07h-40.687v-68.531h40.687v14.859h-22.172v10.781h20.531v14.859h-20.531v12.938h22.172z"/><path d="m1078.3 451.07h-24.281l-25.031-48.281h-0.4218q0.8906 11.391 0.8906 17.391v30.891h-16.406v-68.531h24.188l24.938 47.625h0.2813q-0.6563-10.359-0.6563-16.641v-30.984h16.5z"/></g></g></svg> | |||||
</div> | |||||
<a | |||||
v-else | |||||
href="mailto:?subject=invitation%20-%20oct%2028&body=%40SWK%0D%0Areservation%20required%3A%20reservation.ck.si " | |||||
style="cursor: pointer; margin-top: 5em;" | |||||
> | |||||
<svg class="share" fill="#fff" version="1.1" viewBox="0 0 350.22 180.68" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><g transform="translate(-622.81 -392.3)"><path d="m659.49 392.3v16.427h-16.299v19.182h-20.385v50.605h21.942v18.918h17.175v19.63h55.254l-37.054 55.916h30.57l37.054-55.916h190.84v-16.06h15.991v-18.048h18.443v-50.605h-19.999v-20.05h-16.867v-19.998zm6.9343 23.419h263.37v19.998h19.999v41.866h-18.443v16.06h-263.37v-19.63h-21.942v-41.866h20.385z" stroke-width="0" style="paint-order:stroke fill markers"/><g transform="matrix(.42673 0 0 .42673 457.43 276.68)" aria-label="WILLTEILEN"><path d="m590.71 451.07h-21.891l-6.5625-29.859q-0.46875-1.875-1.5-7.4531-0.98437-5.5781-1.4531-9.3281-0.375 3.0469-1.2188 7.5938-0.84375 4.5-1.6875 8.2969-0.79688 3.7969-6.7969 30.75h-21.891l-16.969-68.531h17.859l7.4531 34.359q2.5312 11.391 3.4688 18.141 0.60937-4.7812 2.1562-12.984 1.5938-8.2031 2.9531-13.594l6.0469-25.922h17.156l5.8594 25.922q1.5 6.2344 3.0469 14.391 1.5469 8.1562 2.0625 12.188 0.60937-5.2031 3.3281-18.047l7.5938-34.453h17.859z"/><path d="m616.44 451.07v-68.531h18.609v68.531z"/><path d="m649.86 451.07v-68.531h18.516v53.578h26.391v14.953z"/><path d="m705.74 451.07v-68.531h18.516v53.578h26.391v14.953z"/><path d="m791.85 451.07h-18.516v-53.391h-16.734v-15.141h51.938v15.141h-16.688z"/><path d="m859.02 451.07h-40.688v-68.531h40.688v14.859h-22.172v10.781h20.531v14.859h-20.531v12.938h22.172z"/><path d="m871.02 451.07v-68.531h18.609v68.531z"/><path d="m904.44 451.07v-68.531h18.516v53.578h26.391v14.953z"/><path d="m1001 451.07h-40.687v-68.531h40.687v14.859h-22.172v10.781h20.531v14.859h-20.531v12.938h22.172z"/><path d="m1078.3 451.07h-24.281l-25.031-48.281h-0.4218q0.8906 11.391 0.8906 17.391v30.891h-16.406v-68.531h24.188l24.938 47.625h0.2813q-0.6563-10.359-0.6563-16.641v-30.984h16.5z"/></g></g></svg> | |||||
</a> | |||||
</div> | |||||
</template> | |||||
<script> | |||||
import VueWordCloud from 'vuewordcloud'; | |||||
export default { | |||||
name: 'Invitation', | |||||
data() { | |||||
return { | |||||
anzeigename: '', | |||||
name: '', | |||||
email: '', | |||||
status: null, | |||||
newsletter: false, | |||||
guests: [], | |||||
sending: false, | |||||
showHelp: false, | |||||
selection: null, | |||||
selectionText: null, | |||||
success: false, | |||||
error: false, | |||||
errorReason: null, | |||||
} | |||||
}, | |||||
components: { | |||||
[VueWordCloud.name]: VueWordCloud, | |||||
}, | |||||
computed: { | |||||
nameColors() { | |||||
return ['DeepPink', 'Crimson', 'Blue', 'BlueViolet', 'Fuchsia', 'Navy', 'Indigo'] | |||||
}, | |||||
words() { | |||||
const names = this.guests.map(guest => { | |||||
return guest.anzeigename | |||||
}) | |||||
const nameLengths = names.map(name => { | |||||
return name.length | |||||
}) | |||||
const maxNameLength = Math.max(...nameLengths) | |||||
return this.guests.map(guest => { | |||||
const nameLength = guest.anzeigename.length | |||||
let size = maxNameLength - nameLength + 12 | |||||
return [` ${guest.anzeigename} `, size] | |||||
}) | |||||
}, | |||||
// guestlist() { | |||||
// return this.guests.map( (g) => { | |||||
// g.name = g.anzeigename | |||||
// return g | |||||
// }) | |||||
// } | |||||
canShare () { | |||||
return !!navigator.share | |||||
} | |||||
}, | |||||
methods: { | |||||
async fetchStatus() { | |||||
const response = await fetch(`${process.env.VUE_APP_API_URL}status`) | |||||
const data = await response.json() | |||||
this.status = data | |||||
}, | |||||
async fetchGuests() { | |||||
const response = await fetch(`${process.env.VUE_APP_API_URL}guest`) | |||||
const data = await response.json() | |||||
this.guests = data | |||||
}, | |||||
async onClickTheButton() { | |||||
if (this.name.length == 0) { | |||||
this.showHelp = true | |||||
return false | |||||
} | |||||
this.showHelp = false | |||||
this.sending = true | |||||
let data = { | |||||
anzeigename: this.anzeigename, | |||||
name: this.name, | |||||
email: this.email, | |||||
newsletter: false, | |||||
} | |||||
const response = await fetch( | |||||
`${process.env.VUE_APP_API_URL}guest`, { | |||||
method: 'POST', | |||||
headers: { | |||||
'Content-Type': 'application/json', | |||||
}, | |||||
body: JSON.stringify(data), | |||||
} | |||||
) | |||||
if (response.status === 200) { | |||||
this.anzeigename = '' | |||||
this.name = '' | |||||
this.email = '' | |||||
this.success = true | |||||
this.fetchGuests() | |||||
} else { | |||||
this.success = false | |||||
this.fetchGuests() | |||||
this.error = true | |||||
this.errorReason = await response.json() | |||||
} | |||||
this.sending = false | |||||
return false | |||||
}, | |||||
resetForm () { | |||||
this.anzeigename = '' | |||||
this.name = '' | |||||
this.email = '' | |||||
this.success = false | |||||
}, | |||||
async share () { | |||||
location.href = "#"; | |||||
try { | |||||
await navigator.share({ url:"https://reservation.ck.si/" }) | |||||
} catch (err) { | |||||
console.log(`Error: ${err}`) | |||||
} | |||||
} | |||||
}, | |||||
mounted() { | |||||
this.fetchStatus() | |||||
this.fetchGuests() | |||||
document.addEventListener('selectionchange', () => { | |||||
this.selection = document.getSelection() | |||||
// console.log(this.selection) | |||||
if (this.selection) { | |||||
this.selectionText = this.selection.toString() | |||||
} | |||||
}); | |||||
// this.$refs.details.$el | |||||
}, | |||||
} | |||||
</script> | |||||
<style> | |||||
body { | |||||
font-family: 'JetBrains Mono', monospace; | |||||
background-color: rgb(0, 0, 0); | |||||
background-image: url('bg.gif'); | |||||
background-size: cover; | |||||
background-position: center center; | |||||
background-attachment: fixed; | |||||
color: rgb(251, 251, 251); | |||||
margin: 0 0 100px; | |||||
} | |||||
.otf { | |||||
width: 100%; | |||||
/* background-color: rgb(167, 204, 247); */ | |||||
/* background-image: url('bg.gif'); */ | |||||
background-size: cover; | |||||
display: flex; | |||||
flex-direction: column; | |||||
align-items: center; | |||||
min-height: 100vh; | |||||
position: relative; | |||||
} | |||||
.input-headline { | |||||
color: rgb(255, 255, 255); | |||||
font-size: 2.8vh; | |||||
margin-bottom: .8vh; | |||||
} | |||||
.input-form { | |||||
display: flex; | |||||
flex-direction: column; | |||||
width: 100%; | |||||
max-width: 800px; | |||||
mix-blend-mode: exclusion; | |||||
margin-top: 4vh; | |||||
z-index: 9; | |||||
position: relative; | |||||
} | |||||
.input-text, | |||||
.input-btn, | |||||
.input-cb { | |||||
color: #000; | |||||
background: #fff; | |||||
font-size: 2.8vh; | |||||
font-family: 'JetBrains Mono', monospace; | |||||
border: 0; | |||||
padding: .5em 1em; | |||||
line-height: 1.4; | |||||
max-width: 100%; | |||||
} | |||||
.input-btn { | |||||
color: #fff; | |||||
background-color: #1e3a8e; | |||||
border-top: 2px solid rgb(0, 0, 0); | |||||
cursor: pointer; | |||||
} | |||||
.input-btn:hover { | |||||
color: #fff; | |||||
background: #3260eb; | |||||
} | |||||
.input-checkbox { | |||||
margin: 0 1em 0 0; | |||||
} | |||||
@media screen and (max-width: 1000px) { | |||||
.form-wrapper { | |||||
margin-top: 4em; | |||||
} | |||||
.input-text, | |||||
.input-btn { | |||||
width: 100%; | |||||
max-width: 100%; | |||||
min-width: 0; | |||||
text-align: center; | |||||
} | |||||
/* .input-form { | |||||
position: absolute; | |||||
bottom: 0; | |||||
display: flex; | |||||
flex-direction: column; | |||||
} */ | |||||
} | |||||
.input-text { | |||||
width: auto; | |||||
flex-grow: 1; | |||||
} | |||||
.details { | |||||
color: #fff; | |||||
background-color: #000; | |||||
font-size: 10vw; | |||||
max-width: 80em; | |||||
margin: 3% 3% 200px; | |||||
padding: 3%; | |||||
} | |||||
.share { | |||||
max-width: 30em; | |||||
width: 80%; | |||||
mix-blend-mode: soft-light; | |||||
} | |||||
.share:hover { | |||||
mix-blend-mode: difference; | |||||
} | |||||
.selection-text { | |||||
position: fixed; | |||||
z-index: 9999; | |||||
font-size: 10vh; | |||||
line-height: 1em; | |||||
pointer-events: none; | |||||
color: #ec4b73; | |||||
mix-blend-mode: multiply; | |||||
top: 10%; | |||||
} | |||||
</style> |