Refactor/Virtual lottery #70

Merged
KevinMidboe merged 17 commits from refactor/virtual-lottery into master 2021-01-02 12:52:59 +00:00
15 changed files with 576 additions and 347 deletions

View File

@@ -166,8 +166,16 @@ const drawWinner = async (req, res) => {
Math.floor(Math.random() * attendeeListDemocratic.length) Math.floor(Math.random() * attendeeListDemocratic.length)
]; ];
let winners = await VirtualWinner.find({ timestamp_sent: undefined }).sort({
timestamp_drawn: 1
});
var io = req.app.get('socketio'); var io = req.app.get('socketio');
io.emit("winner", { color: colorToChooseFrom, name: winner.name }); io.emit("winner", {
color: colorToChooseFrom,
name: winner.name,
winner_count: winners.length + 1
});
let newWinnerElement = new VirtualWinner({ let newWinnerElement = new VirtualWinner({
name: winner.name, name: winner.name,

View File

@@ -17,12 +17,12 @@
/> />
</div> </div>
<router-link to="/lottery/game" class="participate-button"> <router-link to="/lottery" class="participate-button">
<i class="icon icon--arrow-right"></i> <i class="icon icon--arrow-right"></i>
<p>Trykk her for å delta</p> <p>Trykk her for å delta</p>
</router-link> </router-link>
<router-link to="/lottery/generate" class="see-details-link"> <router-link to="/generate" class="see-details-link">
Se vipps detaljer og QR-kode Se vipps detaljer og QR-kode
</router-link> </router-link>

View File

@@ -1,48 +1,60 @@
<template> <template>
<div> <div>
<header ref="header">
<div class="container">
<div class="instructions">
<h1 class="title">Virtuelt lotteri</h1> <h1 class="title">Virtuelt lotteri</h1>
<h2 <ol>
v-if=" <li>Vurder om du ønsker å bruke <router-link to="/generate" class="vin-link">loddgeneratoren</router-link>, eller sjekke ut <router-link to="/dagens" class="vin-link">dagens fangst.</router-link></li>
attendees.length <= 0 && <li>Send vipps med melding "Vinlotteri" for å bli registrert til lotteriet.</li>
winners.length <= 0 && <li>Send gjerne melding om fargeønske også.</li>
attendeesFetched && </ol>
winnersFetched
"
>Her var det lite.. Sikker på at det er en virtuell trekning nå?</h2>
<div class="title-info">
<h2>Send vipps med melding "Vinlotteri" for å bli registrert til virtuelt lotteri</h2>
<p>Send gjerne melding om fargeønsker også</p>
</div> </div>
<router-link to="/dagens" class="generate-link" v-if="todayExists"> <Vipps :amount="1" class="vipps-qr desktop-only" />
Lurer du på dagens fangst?
<span class="subtext generator-link">Se her</span>
</router-link>
<hr /> <VippsPill class="vipps-pill mobile-only" />
<h2>Live oversikt av lodd kjøp i dag</h2> <p class="call-to-action">
<div class="colors"> <span class="vin-link">Følg med utviklingen</span> og <span class="vin-link">chat om trekningen</span>
<div v-for="color in Object.keys(ticketsBought)" :class="color + ' colors-box'" :key="color"> <i class="icon icon--arrow-left" @click="scrollToContent"></i></p>
<div class="colors-overlay">
<p>{{ ticketsBought[color] }} kjøpt</p>
</div>
</div>
</div> </div>
</header>
<div class="container" ref="content">
<WinnerDraw <WinnerDraw
:currentWinnerDrawn="currentWinnerDrawn" :currentWinnerDrawn="currentWinnerDrawn"
:currentWinner="currentWinner" :currentWinner="currentWinner"
:attendees="attendees" :attendees="attendees"
/> />
<Winners :winners="winners" /> <div class="todays-raffles">
<hr /> <h2>Liste av lodd kjøpt i dag</h2>
<div class="middle-elements">
<Attendees :attendees="attendees" class="outer-attendees" /> <div class="raffle-container">
<Chat class="outer-chat" /> <div v-for="color in Object.keys(ticketsBought)" :class="color + '-raffle raffle-element'" :key="color">
<span>{{ ticketsBought[color] }}</span>
</div>
</div>
</div>
<Winners :winners="winners" class="winners" :drawing="currentWinner" />
<div class="container-attendees">
<h2>Deltakere ({{ attendees.length }})</h2>
<Attendees :attendees="attendees" class="attendees" />
</div>
<div class="container-chat">
<h2>Chat</h2>
<Chat class="chat" />
</div>
</div>
<div class="container wines-container">
<h2>Dagens fangst</h2>
<Wine :wine="wine" v-for="wine in wines" :key="wine" />
</div> </div>
<Vipps class="vipps" :amount="1" />
</div> </div>
</template> </template>
@@ -50,22 +62,24 @@
import { attendees, winners, prelottery } from "@/api"; import { attendees, winners, prelottery } from "@/api";
import Chat from "@/ui/Chat"; import Chat from "@/ui/Chat";
import Vipps from "@/ui/Vipps"; import Vipps from "@/ui/Vipps";
import VippsPill from "@/ui/VippsPill";
import Attendees from "@/ui/Attendees"; import Attendees from "@/ui/Attendees";
import Wine from "@/ui/Wine";
import Winners from "@/ui/Winners"; import Winners from "@/ui/Winners";
import WinnerDraw from "@/ui/WinnerDraw"; import WinnerDraw from "@/ui/WinnerDraw";
import io from "socket.io-client"; import io from "socket.io-client";
export default { export default {
components: { Chat, Attendees, Winners, WinnerDraw, Vipps }, components: { Chat, Attendees, Winners, WinnerDraw, Vipps, VippsPill, Wine },
data() { data() {
return { return {
attendees: [], attendees: [],
winners: [], winners: [],
wines: [],
currentWinnerDrawn: false, currentWinnerDrawn: false,
currentWinner: {}, currentWinner: null,
socket: null, socket: null,
attendeesFetched: false, attendeesFetched: false,
winnersFetched: false,
wasDisconnected: false, wasDisconnected: false,
ticketsBought: {} ticketsBought: {}
}; };
@@ -73,6 +87,7 @@ export default {
mounted() { mounted() {
this.track(); this.track();
this.getAttendees(); this.getAttendees();
this.getTodaysWines();
this.getWinners(); this.getWinners();
this.socket = io(window.location.origin); this.socket = io(window.location.origin);
this.socket.on("color_winner", msg => {}); this.socket.on("color_winner", msg => {});
@@ -83,14 +98,18 @@ export default {
this.socket.on("winner", async msg => { this.socket.on("winner", async msg => {
this.currentWinnerDrawn = true; this.currentWinnerDrawn = true;
this.currentWinner = { name: msg.name, color: msg.color }; this.currentWinner = {
name: msg.name,
color: msg.color,
winnerCount: msg.winner_count
};
setTimeout(() => { setTimeout(() => {
this.getWinners(); this.getWinners();
this.getAttendees(); this.getAttendees();
this.currentWinner = null; this.currentWinner = null;
this.currentWinnerDrawn = false; this.currentWinnerDrawn = false;
}, 12000); }, 19250);
}); });
this.socket.on("refresh_data", async msg => { this.socket.on("refresh_data", async msg => {
this.getAttendees(); this.getAttendees();
@@ -104,20 +123,20 @@ export default {
this.socket.disconnect(); this.socket.disconnect();
this.socket = null; this.socket = null;
}, },
computed: {
todayExists: () => {
return prelottery()
.then(wines => wines.length > 0)
.catch(() => false);
}
},
methods: { methods: {
getWinners: async function() { getWinners: async function() {
let response = await winners(); let response = await winners();
if (response) { if (response) {
this.winners = response; this.winners = response;
} }
this.winnersFetched = true; },
getTodaysWines() {
prelottery()
.then(wines => {
this.wines = wines;
this.todayExists = wines.length > 0;
})
.catch(_ => this.todayExists = false)
}, },
getAttendees: async function() { getAttendees: async function() {
let response = await attendees(); let response = await attendees();
@@ -139,6 +158,20 @@ export default {
} }
this.attendeesFetched = true; this.attendeesFetched = true;
}, },
scrollToContent() {
console.log(window.scrollY)
const intersectingHeaderHeight = this.$refs.header.getBoundingClientRect().bottom - 50;
const { scrollY } = window;
let scrollHeight = intersectingHeaderHeight;
if (scrollY > 0) {
scrollHeight = intersectingHeaderHeight + scrollY;
}
window.scrollTo({
top: scrollHeight,
behavior: "smooth"
});
},
track() { track() {
window.ga('send', 'pageview', '/lottery/game'); window.ga('send', 'pageview', '/lottery/game');
} }
@@ -146,241 +179,197 @@ export default {
}; };
</script> </script>
<!-- TODO move link styling to global with more generic name -->
<style lang="scss" scoped> <style lang="scss" scoped>
@import "../styles/global.scss";
@import "../styles/variables.scss"; @import "../styles/variables.scss";
@import "../styles/media-queries.scss"; @import "../styles/media-queries.scss";
.generate-link {
color: #333333; .container {
text-decoration: none; width: 80vw;
display: block; padding: 0 10vw;
width: 100vw;
text-align: center;
margin-bottom: 0px;
@include mobile { @include mobile {
width: 60vw; width: 90vw;
margin: auto; padding: 0 5vw;
}
display: grid;
grid-template-columns: repeat(4, 1fr);
> div, > section {
@include mobile {
grid-column: span 5;
}
} }
} }
.vipps-image { h2 {
width: 250px; font-size: 1.1rem;
margin: auto; margin-bottom: 1.75rem;
display: block;
margin-top: 30px;
} }
.generator-link { header {
font-weight: bold; h1 {
border-bottom: 1px solid $link-color; text-align: left;
} font-weight: 500;
</style> font-size: 3rem;
margin: 4rem 0 2rem;
<style lang="scss" scoped>
@import "../styles/global.scss";
@import "../styles/variables.scss";
@import "../styles/media-queries.scss";
.color-selector {
margin-bottom: 0.65rem;
margin-right: 1rem;
@include desktop {
min-width: 175px;
}
@include mobile { @include mobile {
max-width: 25vw; margin-top: 1rem;
} font-size: 2.75rem;
.active {
border: 2px solid unset;
&.green {
border-color: $green;
}
&.blue {
border-color: $dark-blue;
}
&.red {
border-color: $red;
}
&.yellow {
border-color: $dark-yellow;
} }
} }
button { background-color: $primary;
border: 2px solid transparent; padding-bottom: 3rem;
display: inline-flex; margin-bottom: 3rem;
flex-wrap: wrap;
flex-direction: row;
height: 2.5rem;
width: 2.5rem;
// disable-dbl-tap-zoom .instructions {
touch-action: manipulation; grid-column: 1 / 4;
@include mobile { @include mobile {
margin: 2px; grid-column: span 5;
}
&.green {
background: #c8f9df;
}
&.blue {
background: #d4f2fe;
}
&.red {
background: #fbd7de;
}
&.yellow {
background: #fff6d6;
}
} }
} }
.colors { .vipps-qr {
display: flex; grid-column: 4;
flex-direction: row; margin-left: 1rem;
flex-wrap: wrap; }
justify-content: center;
max-width: 1400px; .vipps-pill {
margin: 0 auto; margin: 0 auto 2rem;
max-width: 80vw;
}
.call-to-action {
grid-column: span 5;
}
ol {
font-size: 1.4rem;
line-height: 3rem;
color: $matte-text-color;
@include mobile { @include mobile {
margin: 1.8rem auto 0; line-height: 2rem;
}
.label-div {
margin-top: 0.5rem;
width: 100%;
} }
} }
.colors-box {
width: 150px;
height: 150px;
margin: 20px;
-webkit-mask-image: url(/public/assets/images/lodd.svg);
background-repeat: no-repeat;
mask-image: url(/public/assets/images/lodd.svg);
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
@include mobile {
width: 120px;
height: 120px;
margin: 10px;
}
}
.colors-overlay {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
padding: 0 0.25rem;
position: relative;
p { p {
width: 70%; font-size: 1.4rem;
border: 0; line-height: 2rem;
padding: 0; margin-top: 0;
font-size: 1.5rem; position: relative;
height: unset;
max-height: unset; .vin-link {
cursor: default;
}
.icon {
position: absolute;
bottom: 3px;
color: $link-color;
margin-left: 0.5rem;
display: inline-block;
transform: rotate(-90deg);
cursor: pointer;
}
}
.vin-link {
font-weight: 400;
border-width: 2px;
}
}
.todays-raffles {
grid-column: 1;
@include mobile { @include mobile {
font-size: 1.3rem; order: 2;
}
}
}
.green,
.green .colors-overlay > input {
background-color: $light-green;
color: $green;
}
.blue,
.blue .colors-overlay > input {
background-color: $light-blue;
color: $blue;
}
.yellow,
.yellow .colors-overlay > input {
background-color: $light-yellow;
color: $yellow;
}
.red,
.red .colors-overlay > input {
background-color: $light-red;
color: $red;
}
</style>
<style lang="scss" scoped>
@import "../styles/global.scss";
@import "../styles/variables.scss";
@import "../styles/media-queries.scss";
hr {
width: 80%;
}
h1,
h2 {
text-align: center;
}
.current-draw {
margin: auto;
}
.title-info {
width: 100%;
text-align: center;
}
.outer-chat {
margin: 0 60px 0 10px;
@include mobile {
margin: 0;
} }
} }
.outer-attendees { .raffle-container {
margin: 0 10px 0 45px; width: 165px;
@include mobile { height: 175px;
margin: 0;
}
}
.center-new-winner {
margin: auto !important;
}
.middle-elements {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
flex-wrap: wrap;
justify-content: space-between;
@include mobile {
width: 100%;
height: 100%;
}
.raffle-element {
font-size: 1.6rem;
color: $matte-text-color;
height: 75px;
width: 75px;
display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
height: 400px;
@include mobile { margin: 0;
height: auto;
flex-direction: column;
} }
} }
.vipps { .winners {
margin-top: 70px; grid-column: 2 / 5;
display: flex;
padding-bottom: 50px;
justify-content: center;
@include mobile { @include mobile {
flex-direction: column; order: 1;
}
}
.container-attendees {
grid-column: 1 / 3;
margin-right: 1rem;
margin-top: 2rem;
@include mobile {
margin-right: 0;
order: 4;
}
> div {
padding: 1rem;
-webkit-box-shadow: 0px 0px 10px 1px rgba(0, 0, 0, 0.15);
-moz-box-shadow: 0px 0px 10px 1px rgba(0, 0, 0, 0.15);
box-shadow: 0px 0px 10px 1px rgba(0, 0, 0, 0.15);
}
}
.container-chat {
grid-column: 3 / 5;
margin-left: 1rem;
margin-top: 2rem;
@include mobile {
margin-left: 0;
order: 3;
}
> div {
padding: 1rem;
-webkit-box-shadow: 0px 0px 10px 1px rgba(0, 0, 0, 0.15);
-moz-box-shadow: 0px 0px 10px 1px rgba(0, 0, 0, 0.15);
box-shadow: 0px 0px 10px 1px rgba(0, 0, 0, 0.15);
}
}
.wines-container {
margin-bottom: 4rem;
h2 {
grid-column: 1 / 5;
} }
} }
</style> </style>

View File

@@ -1,9 +1,9 @@
const VinlottisPage = () => import( const VinlottisPage = () => import(
/* webpackChunkName: "landing-page" */ /* webpackChunkName: "landing-page" */
"@/components/VinlottisPage"); "@/components/VinlottisPage");
const LotteryPage = () => import( const VirtualLotteryPage = () => import(
/* webpackChunkName: "landing-page" */ /* webpackChunkName: "landing-page" */
"@/components/LotteryPage"); "@/components/VirtualLotteryPage");
const GeneratePage = () => import( const GeneratePage = () => import(
/* webpackChunkName: "landing-page" */ /* webpackChunkName: "landing-page" */
"@/components/GeneratePage"); "@/components/GeneratePage");
@@ -54,7 +54,7 @@ const routes = [
{ {
path: "/lottery", path: "/lottery",
name: "Lotteri", name: "Lotteri",
component: LotteryPage component: VirtualLotteryPage
}, },
{ {
path: "/dagens", path: "/dagens",
@@ -82,8 +82,8 @@ const routes = [
component: AdminPage component: AdminPage
}, },
{ {
path: "/lottery/:tab", path: "/generate/",
component: LotteryPage component: GeneratePage
}, },
{ {
path: "/winner/:id", path: "/winner/:id",

View File

@@ -112,10 +112,9 @@ textarea {
.vin-button { .vin-button {
font-family: Arial; font-family: Arial;
$color: #b7debd;
position: relative; position: relative;
display: inline-block; display: inline-block;
background: $color; background: $primary;
color: #333; color: #333;
padding: 10px 30px; padding: 10px 30px;
margin: 0; margin: 0;
@@ -188,7 +187,7 @@ textarea {
.vin-link { .vin-link {
font-weight: bold; font-weight: bold;
border-bottom: 1px solid $link-color; border-bottom: 1px solid $link-color;
font-size: 1rem; font-size: inherit;
cursor: pointer; cursor: pointer;
text-decoration: none; text-decoration: none;

View File

@@ -26,3 +26,15 @@ $desktop-max: 2004px;
@content; @content;
} }
} }
.desktop-only {
@include mobile {
display: none;
}
}
.mobile-only {
@include tablet {
display: none;
}
}

View File

@@ -1,6 +1,5 @@
<template> <template>
<div class="attendees" v-if="attendees.length > 0"> <div class="attendees" v-if="attendees.length > 0">
<h2>Deltakere ({{ attendees.length }})</h2>
<div class="attendees-container" ref="attendees"> <div class="attendees-container" ref="attendees">
<div class="attendee" v-for="(attendee, index) in flipList(attendees)" :key="index"> <div class="attendee" v-for="(attendee, index) in flipList(attendees)" :key="index">
<span class="attendee-name">{{ attendee.name }}</span> <span class="attendee-name">{{ attendee.name }}</span>
@@ -42,10 +41,16 @@ export default {
@import "../styles/global.scss"; @import "../styles/global.scss";
@import "../styles/variables.scss"; @import "../styles/variables.scss";
@import "../styles/media-queries.scss"; @import "../styles/media-queries.scss";
.attendee-name { .attendee-name {
width: 60%; width: 60%;
} }
hr {
border: 2px solid black;
width: 100%;
}
.raffle-element { .raffle-element {
font-size: 0.75rem; font-size: 0.75rem;
width: 45px; width: 45px;
@@ -56,20 +61,24 @@ export default {
font-weight: bold; font-weight: bold;
font-size: 0.75rem; font-size: 0.75rem;
text-align: center; text-align: center;
&:not(:last-of-type) {
margin-right: 1rem;
}
} }
.attendees { .attendees {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
width: 65%; height: auto;
height: 100%;
} }
.attendees-container { .attendees-container {
width: 100%; width: 100%;
height: 100%; height: 100%;
overflow-y: scroll; overflow-y: scroll;
max-height: 550px;
} }
.attendee { .attendee {
@@ -78,5 +87,9 @@ export default {
align-items: center; align-items: center;
width: 100%; width: 100%;
margin: 0 auto; margin: 0 auto;
&:not(:last-of-type) {
border-bottom: 2px solid #d7d8d7;
}
} }
</style> </style>

View File

@@ -1,7 +1,7 @@
<template> <template>
<div class="chat-container"> <div class="chat-container">
<hr /> <span class="logged-in-username" v-if="username">Logget inn som: <span class="username">{{ username }}</span> <button @click="removeUsername">Logg ut</button></span>
<h2>Chat</h2>
<div class="history" ref="history" v-if="chatHistory.length > 0"> <div class="history" ref="history" v-if="chatHistory.length > 0">
<div class="opaque-skirt"></div> <div class="opaque-skirt"></div>
<div v-if="hasMorePages" class="fetch-older-history"> <div v-if="hasMorePages" class="fetch-older-history">
@@ -19,11 +19,12 @@
<span class="message">{{ history.message }}</span> <span class="message">{{ history.message }}</span>
</div> </div>
</div> </div>
<div v-if="username" class="input">
<div v-if="username" class="user-actions">
<input @keyup.enter="sendMessage" type="text" v-model="message" placeholder="Melding.." /> <input @keyup.enter="sendMessage" type="text" v-model="message" placeholder="Melding.." />
<button @click="sendMessage">Send</button> <button @click="sendMessage">Send</button>
<button @click="removeUsername">Logg ut</button>
</div> </div>
<div v-else class="username-dialog"> <div v-else class="username-dialog">
<input <input
type="text" type="text"
@@ -53,7 +54,7 @@ export default {
hasMorePages: true, hasMorePages: true,
message: "", message: "",
page: 1, page: 1,
pageSize: 10, pageSize: 100,
temporaryUsername: null, temporaryUsername: null,
username: null, username: null,
validationError: undefined validationError: undefined
@@ -206,33 +207,37 @@ export default {
<style lang="scss" scoped> <style lang="scss" scoped>
@import "@/styles/media-queries.scss"; @import "@/styles/media-queries.scss";
@import "@/styles/variables.scss"; @import "@/styles/variables.scss";
h2 {
text-align: center;
}
hr {
display: none;
@include mobile {
display: block;
width: 80%;
}
}
.chat-container { .chat-container {
height: 100%;
width: 50%;
position: relative; position: relative;
transform: translate3d(0,0,0);
@include mobile {
width: 100%;
}
} }
input { input {
width: 80%; width: 100%;
height: 3.25rem;
} }
.input { .logged-in-username {
position: absolute;
top: 0.75rem;
left: 1rem;
color: $matte-text-color;
width: calc(100% - 2rem);
button {
width: unset;
padding: 5px 10px;
position: absolute;
right: 0rem;
}
.username {
border-bottom: 2px solid $link-color;
}
}
.user-actions {
display: flex; display: flex;
} }
@@ -241,6 +246,8 @@ input {
height: 75%; height: 75%;
overflow-y: scroll; overflow-y: scroll;
position: relative; position: relative;
max-height: 550px;
margin-top: 2rem;
&-message { &-message {
display: flex; display: flex;
@@ -265,9 +272,9 @@ input {
} }
& .opaque-skirt { & .opaque-skirt {
width: 100%; width: calc(100% - 2rem);
position: absolute; position: fixed;
height: 1rem; height: 2rem;
z-index: 1; z-index: 1;
background: linear-gradient( background: linear-gradient(
to bottom, to bottom,
@@ -332,5 +339,9 @@ button {
transition: transform 0.5s ease; transition: transform 0.5s ease;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
touch-action: manipulation; touch-action: manipulation;
@include mobile {
padding: 10px 10px;
}
} }
</style> </style>

View File

@@ -1,5 +1,20 @@
<template> <template>
<footer> <footer>
<ul>
<li>
<a href="https://github.com/KevinMidboe/vinlottis" class="github">
<span>Open-sourced at github</span>
<img src="/public/assets/images/logo-github.png" alt="github logo">
</a>
</li>
<li>
<a href="mailto:questions@vinlottis.no" class="mail">
<span class="vin-link">questions@vinlottis.no</span>
</a>
</li>
</ul>
<router-link to="/" class="company-logo"> <router-link to="/" class="company-logo">
<img src="/public/assets/images/knowit.svg" alt="knowit logo"> <img src="/public/assets/images/knowit.svg" alt="knowit logo">
</router-link> </router-link>
@@ -13,20 +28,70 @@ export default {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "../styles/variables.scss";
@import "../styles/media-queries.scss";
footer { footer {
width: 100%; width: 100%;
height: 100px; height: 100px;
display: flex; display: flex;
justify-content: flex-end; justify-content: space-between;
align-items: center; align-items: center;
background: #f4f4f4; background: #f4f4f4;
ul {
list-style-type: none;
padding: 0;
margin-left: 5rem;
li:not(:first-of-type) {
margin-top: 0.75rem;
}
}
a {
color: $matte-text-color;
}
.github {
display: flex;
align-items: center;
img {
margin-left: 0.5rem;
height: 30px;
}
}
.mail {
display: flex;
align-items: center;
img {
margin-left: 0.5rem;
height: 23px;
}
}
.company-logo{ .company-logo{
padding: 0 5em 0 0; margin-right: 5em;
img { img {
width: 100px; width: 100px;
} }
} }
@include mobile {
$margin: 1rem;
ul {
margin-left: $margin;
}
.company-logo {
margin-right: $margin;
}
}
} }
</style> </style>

81
frontend/ui/VippsPill.vue Normal file
View File

@@ -0,0 +1,81 @@
<template>
<div aria-label="button" role="button" @click="openVipps" tabindex="0">
<img src="public/assets/images/vipps-pay_with_vipps_pill.png" />
</div>
</template>
<script>
export default {
props: {
amount: {
type: Number,
default: 1
}
},
data() {
return {
phone: __PHONE__,
name: __NAME__,
price: __PRICE__,
message: __MESSAGE__
};
},
computed: {
isMobile: function() {
return this.isMobileFunction();
},
priceToPay: function() {
return this.amount * (this.price * 100);
},
vippsUrlBasedOnUserAgent: function() {
if (navigator.userAgent.includes("iPhone")) {
return (
"https://qr.vipps.no/28/2/01/031/47" +
this.phone.replace(/ /g, "") +
"?v=1&m=" +
this.message +
"&a=" +
this.priceToPay
);
}
return (
"https://qr.vipps.no/28/2/01/031/47" +
this.phone.replace(/ /g, "") +
"?v=1&m=" +
this.message
);
}
},
methods: {
openVipps() {
if (!this.isMobileFunction()) {
return;
}
window.location.assign(this.vippsUrlBasedOnUserAgent);
},
isMobileFunction() {
if (
navigator.userAgent.match(/Android/i) ||
navigator.userAgent.match(/webOS/i) ||
navigator.userAgent.match(/iPhone/i) ||
navigator.userAgent.match(/iPad/i) ||
navigator.userAgent.match(/iPod/i) ||
navigator.userAgent.match(/BlackBerry/i) ||
navigator.userAgent.match(/Windows Phone/i)
) {
return true;
} else {
return false;
}
}
}
};
</script>
<style lang="scss" scoped>
img {
cursor: pointer;
width: 100%;
}
</style>

View File

@@ -1,36 +1,18 @@
<template> <template>
<div class="current-drawn-container"> <div class="current-drawn-container" v-if="drawing">
<div class="current-draw" v-if="drawing"> <h2 v-if="winnersNameDrawn !== true">TREKKER {{ ordinalNumber() }} VINNER</h2>
<h2>TREKKER</h2> <h2 v-else>VINNER</h2>
<div <div
:class="currentColor + '-raffle'" :class="currentColor + '-raffle'"
class="raffle-element center-new-winner" class="raffle-element"
:style="{ transform: 'rotate(' + getRotation() + 'deg)' }" :style="{ transform: 'rotate(' + getRotation() + 'deg)' }"
> >
<span v-if="currentName && colorDone">{{ currentName }}</span> <span v-if="currentName && colorDone">{{ currentName }}</span>
</div> </div>
<br />
<br />
<br />
<br />
<br />
</div>
<div class="current-draw" v-if="drawingDone">
<h2>VINNER</h2>
<div
:class="currentColor + '-raffle'"
class="raffle-element center-new-winner"
:style="{ transform: 'rotate(' + getRotation() + 'deg)' }"
>
<span v-if="currentName && colorDone">{{ currentName }}</span>
</div>
<br /> <br />
<br /> <br />
<br />
<br />
<br />
</div>
</div> </div>
</template> </template>
@@ -61,14 +43,13 @@ export default {
nameTimeout: null, nameTimeout: null,
colorDone: false, colorDone: false,
drawing: false, drawing: false,
drawingDone: false, winnersNameDrawn: false,
winnerQueue: [] winnerQueue: []
}; };
}, },
watch: { watch: {
currentWinner: function(currentWinner) { currentWinner: function(currentWinner) {
if (currentWinner == null) { if (currentWinner == null) {
this.drawingDone = false;
return; return;
} }
if (this.drawing) { if (this.drawing) {
@@ -76,6 +57,7 @@ export default {
return; return;
} }
this.drawing = true; this.drawing = true;
this.winnersNameDrawn = false;
this.currentName = null; this.currentName = null;
this.currentColor = null; this.currentColor = null;
this.nameRounds = 0; this.nameRounds = 0;
@@ -99,8 +81,7 @@ export default {
this.drawColor(this.currentWinnerLocal.color); this.drawColor(this.currentWinnerLocal.color);
return; return;
} }
this.drawing = false; this.winnersNameDrawn = true;
this.drawingDone = true;
this.startConfetti(this.currentName); this.startConfetti(this.currentName);
return; return;
} }
@@ -114,7 +95,7 @@ export default {
}, 50); }, 50);
}, },
drawColor: function(winnerColor) { drawColor: function(winnerColor) {
this.drawingDone = false; this.winnersNameDrawn = false;
if (this.colorRounds == 100) { if (this.colorRounds == 100) {
this.currentColor = winnerColor; this.currentColor = winnerColor;
this.colorDone = true; this.colorDone = true;
@@ -129,7 +110,7 @@ export default {
clearTimeout(this.colorTimeout); clearTimeout(this.colorTimeout);
this.colorTimeout = setTimeout(() => { this.colorTimeout = setTimeout(() => {
this.drawColor(winnerColor); this.drawColor(winnerColor);
}, 50); }, 70);
}, },
getRotation: function() { getRotation: function() {
if (this.colorDone) { if (this.colorDone) {
@@ -161,9 +142,13 @@ export default {
function randomInRange(min, max) { function randomInRange(min, max) {
return Math.random() * (max - min) + min; return Math.random() * (max - min) + min;
} }
const self = this;
var interval = setInterval(function() { var interval = setInterval(function() {
var timeLeft = animationEnd - Date.now(); var timeLeft = animationEnd - Date.now();
if (timeLeft <= 0) { if (timeLeft <= 0) {
self.drawing = false;
console.time("drawing finished")
return clearInterval(interval); return clearInterval(interval);
} }
if (currentName == "Amund Brandsrud") { if (currentName == "Amund Brandsrud") {
@@ -176,7 +161,6 @@ export default {
runCannon(defaults, {x: 0 }, {angle: 45}); runCannon(defaults, {x: 0 }, {angle: 45});
runCannon(defaults, {x: 1 }, {angle: 135}); runCannon(defaults, {x: 1 }, {angle: 135});
runCannon(defaults, {y: 1 }, {angle: 90}); runCannon(defaults, {y: 1 }, {angle: 90});
} }
}, 250); }, 250);
@@ -184,6 +168,23 @@ export default {
confetti(Object.assign({}, confettiDefaultValues, {origin: originPoint }, launchAngle)) confetti(Object.assign({}, confettiDefaultValues, {origin: originPoint }, launchAngle))
} }
}, },
ordinalNumber(number=this.currentWinnerLocal.winnerCount) {
const dictonary = {
1: "første",
2: "andre",
3: "tredje",
4: "fjerde",
5: "femte",
6: "sjette",
7: "syvende",
8: "åttende",
9: "niende",
10: "tiende",
11: "ellevte",
12: "tolvte"
};
return number in dictonary ? dictonary[number] : number;
}
} }
}; };
@@ -196,22 +197,27 @@ export default {
h2 { h2 {
text-align: center; text-align: center;
text-transform: uppercase;
} }
.current-drawn-container { .current-drawn-container {
display: flex; grid-column: 1 / 5;
justify-content: center; display: grid;
align-items: center; place-items: center;
position: relative;
} }
.raffle-element { .raffle-element {
width: 140px; width: 280px;
height: 140px; height: 300px;
font-size: 1.2rem; font-size: 2rem;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
font-size: 0.75rem;
text-align: center; text-align: center;
-webkit-mask-size: cover;
-moz-mask-size: cover;
mask-size: cover;
} }
</style> </style>

View File

@@ -1,14 +1,22 @@
<template> <template>
<div> <section>
<h2 v-if="winners.length > 0"> {{ title ? title : 'Vinnere' }}</h2> <h2>{{ title ? title : 'Vinnere' }}</h2>
<div class="winners" v-if="winners.length > 0"> <div class="winning-raffles" v-if="winners.length > 0">
<div v-for="(winner, index) in winners" :key="index"> <div v-for="(winner, index) in winners" :key="index">
<router-link :to="`/highscore/${ encodeURIComponent(winner.name) }`"> <router-link :to="`/highscore/${ encodeURIComponent(winner.name) }`">
<div :class="winner.color + '-raffle'" class="raffle-element">{{ winner.name }}</div> <div :class="winner.color + '-raffle'" class="raffle-element">{{ winner.name }}</div>
</router-link> </router-link>
</div> </div>
</div> </div>
<div v-else-if="drawing" class="container">
<h3>Trekningen er igang!</h3>
</div> </div>
<div v-else class="container">
<h3>Trekningen har ikke startet enda <button></button></h3>
</div>
</section>
</template> </template>
<script> <script>
@@ -17,6 +25,9 @@ export default {
winners: { winners: {
type: Array type: Array
}, },
drawing: {
type: Boolean,
},
title: { title: {
type: String, type: String,
required: false required: false
@@ -30,11 +41,28 @@ export default {
@import "../styles/variables.scss"; @import "../styles/variables.scss";
@import "../styles/media-queries.scss"; @import "../styles/media-queries.scss";
section {
width: 100%;
display: flex;
flex-direction: column;
position: relative;
}
h2 { h2 {
font-size: 1.1rem;
margin-bottom: 0.6rem;
width: 100%;
text-align: center; text-align: center;
} }
.winners { h3 {
margin: auto;
color: $matte-text-color;
font-size: 1.6rem;
text-align: center;
}
.winning-raffles {
display: flex; display: flex;
flex-flow: wrap; flex-flow: wrap;
justify-content: space-around; justify-content: space-around;
@@ -52,4 +80,21 @@ h2 {
font-weight: bold; font-weight: bold;
text-align: center; text-align: center;
} }
.container {
display: flex;
height: 100%;
button {
-webkit-appearance: unset;
background-color: unset;
padding: 0;
margin: 0;
font-size: inherit;
border: unset;
height: auto;
width: auto;
cursor: pointer;
}
}
</style> </style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB