@@ -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> |