| @@ -1,6 +1,5 @@ | |||
| import Vue from 'vue' | |||
| import VueRouter from 'vue-router' | |||
| import Home from '../views/Home.vue' | |||
| Vue.use(VueRouter) | |||
| @@ -8,15 +7,49 @@ const routes = [ | |||
| { | |||
| path: '/', | |||
| 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', | |||
| 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 | |||
| }) | |||
| 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 | |||
| @@ -1,5 +1,5 @@ | |||
| <template> | |||
| <div class="cancel"> | |||
| <div class="cancel-view"> | |||
| <h1 v-if="!success">cancelling reservation {{$route.params.token}} ...</h1> | |||
| <h1 v-else> | |||
| your reservation got cancelled.<br> | |||
| @@ -60,4 +60,8 @@ | |||
| </script> | |||
| <style> | |||
| .cancel-view { | |||
| min-height: 100vh; | |||
| margin: 2em; | |||
| } | |||
| </style> | |||
| @@ -1,296 +1,25 @@ | |||
| <template> | |||
| <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> | |||
| </template> | |||
| <script> | |||
| import VueWordCloud from 'vuewordcloud'; | |||
| export default { | |||
| name: 'App', | |||
| data() { | |||
| return { | |||
| anzeigename: '', | |||
| name: '', | |||
| email: '', | |||
| 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 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.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> | |||
| .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> | |||
| @@ -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> | |||