Merge branch 'master' of github.com:KevinMidboe/vinlottis into feat/navigation-in-header
This commit is contained in:
@@ -74,6 +74,7 @@ export default {
|
||||
|
||||
<style lang="scss">
|
||||
@import "./styles/global.scss";
|
||||
@import "./styles/positioning.scss";
|
||||
@font-face {
|
||||
font-family: "knowit";
|
||||
font-weight: 600;
|
||||
|
||||
111
src/api.js
111
src/api.js
@@ -1,3 +1,5 @@
|
||||
import fetch from "node-fetch";
|
||||
|
||||
const BASE_URL = __APIURL__ || window.location.origin;
|
||||
|
||||
const statistics = () => {
|
||||
@@ -24,6 +26,16 @@ const overallWineStatistics = () => {
|
||||
return fetch(url.href).then(resp => resp.json());
|
||||
};
|
||||
|
||||
const allRequestedWines = () => {
|
||||
const url = new URL("/api/request/all", BASE_URL);
|
||||
|
||||
return fetch(url.href)
|
||||
.then(resp => {
|
||||
const isAdmin = resp.headers.get("Vinlottis-Admin") || false;
|
||||
return Promise.all([resp.json(), isAdmin]);
|
||||
});
|
||||
};
|
||||
|
||||
const chartWinsByColor = () => {
|
||||
const url = new URL("/api/purchase/statistics/color", BASE_URL);
|
||||
|
||||
@@ -42,8 +54,22 @@ const prelottery = () => {
|
||||
return fetch(url.href).then(resp => resp.json());
|
||||
};
|
||||
|
||||
const log = sendObject => {
|
||||
const url = new URL("/api/log", BASE_URL);
|
||||
const sendLottery = sendObject => {
|
||||
const url = new URL("/api/lottery", BASE_URL);
|
||||
|
||||
const options = {
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
method: "POST",
|
||||
body: JSON.stringify(sendObject)
|
||||
};
|
||||
|
||||
return fetch(url.href, options).then(resp => resp.json());
|
||||
};
|
||||
|
||||
const sendLotteryWinners = sendObject => {
|
||||
const url = new URL("/api/lottery/winners", BASE_URL);
|
||||
|
||||
const options = {
|
||||
headers: {
|
||||
@@ -57,7 +83,7 @@ const log = sendObject => {
|
||||
};
|
||||
|
||||
const addAttendee = sendObject => {
|
||||
const url = new URL("/api/virtual/attendee", BASE_URL);
|
||||
const url = new URL("/api/virtual/attendee/add", BASE_URL);
|
||||
|
||||
const options = {
|
||||
headers: {
|
||||
@@ -71,31 +97,46 @@ const addAttendee = sendObject => {
|
||||
};
|
||||
|
||||
const getVirtualWinner = () => {
|
||||
const url = new URL("/api/virtual/winner", BASE_URL);
|
||||
const url = new URL("/api/virtual/winner/draw", BASE_URL);
|
||||
|
||||
return fetch(url.href).then(resp => resp.json());
|
||||
};
|
||||
|
||||
const attendeesSecure = () => {
|
||||
const url = new URL("/api/virtual/attendees/secure", BASE_URL);
|
||||
const url = new URL("/api/virtual/attendee/all/secure", BASE_URL);
|
||||
|
||||
return fetch(url.href).then(resp => resp.json());
|
||||
};
|
||||
|
||||
const winnersSecure = () => {
|
||||
const url = new URL("/api/virtual/winners/secure", BASE_URL);
|
||||
const url = new URL("/api/virtual/winner/all/secure", BASE_URL);
|
||||
|
||||
return fetch(url.href).then(resp => resp.json());
|
||||
};
|
||||
|
||||
const winners = () => {
|
||||
const url = new URL("/api/virtual/winners", BASE_URL);
|
||||
const url = new URL("/api/virtual/winner/all", BASE_URL);
|
||||
|
||||
return fetch(url.href).then(resp => resp.json());
|
||||
};
|
||||
|
||||
const deleteRequestedWine = wineToBeDeleted => {
|
||||
|
||||
const url = new URL("api/request/"+ wineToBeDeleted._id, BASE_URL);
|
||||
|
||||
const options = {
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
method: "DELETE",
|
||||
body: JSON.stringify(wineToBeDeleted)
|
||||
};
|
||||
|
||||
return fetch(url.href, options).then(resp => resp.json())
|
||||
}
|
||||
|
||||
const deleteWinners = () => {
|
||||
const url = new URL("/api/virtual/winners", BASE_URL);
|
||||
const url = new URL("/api/virtual/winner/all", BASE_URL);
|
||||
|
||||
const options = {
|
||||
headers: {
|
||||
@@ -108,7 +149,7 @@ const deleteWinners = () => {
|
||||
};
|
||||
|
||||
const deleteAttendees = () => {
|
||||
const url = new URL("/api/virtual/attendees", BASE_URL);
|
||||
const url = new URL("/api/virtual/attendee/all", BASE_URL);
|
||||
|
||||
const options = {
|
||||
headers: {
|
||||
@@ -121,11 +162,28 @@ const deleteAttendees = () => {
|
||||
};
|
||||
|
||||
const attendees = () => {
|
||||
const url = new URL("/api/virtual/attendees", BASE_URL);
|
||||
const url = new URL("/api/virtual/attendee/all", BASE_URL);
|
||||
|
||||
return fetch(url.href).then(resp => resp.json());
|
||||
};
|
||||
|
||||
const requestNewWine = (wine) => {
|
||||
const options = {
|
||||
body: JSON.stringify({
|
||||
wine: wine
|
||||
}),
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
method: "post"
|
||||
}
|
||||
|
||||
const url = new URL("/api/request/new-wine", BASE_URL)
|
||||
|
||||
return fetch(url.href, options).then(resp => resp.json())
|
||||
}
|
||||
|
||||
const logWines = wines => {
|
||||
const url = new URL("/api/log/wines", BASE_URL);
|
||||
|
||||
@@ -141,7 +199,7 @@ const logWines = wines => {
|
||||
};
|
||||
|
||||
const wineSchema = () => {
|
||||
const url = new URL("/api/log/schema", BASE_URL);
|
||||
const url = new URL("/api/wineinfo/schema", BASE_URL);
|
||||
|
||||
return fetch(url.href).then(resp => resp.json());
|
||||
};
|
||||
@@ -160,6 +218,21 @@ const barcodeToVinmonopolet = id => {
|
||||
});
|
||||
};
|
||||
|
||||
const searchForWine = searchString => {
|
||||
const url = new URL("/api/wineinfo/search?query=" + searchString, BASE_URL);
|
||||
|
||||
return fetch(url.href).then(async resp => {
|
||||
if (!resp.ok) {
|
||||
if (resp.status == 404) {
|
||||
throw await resp.json();
|
||||
}
|
||||
} else {
|
||||
return resp.json();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
const handleErrors = async resp => {
|
||||
if ([400, 409].includes(resp.status)) {
|
||||
throw await resp.json();
|
||||
@@ -217,17 +290,20 @@ const getChatHistory = (skip = null, take = null) => {
|
||||
|
||||
const finishedDraw = () => {
|
||||
const url = new URL("/api/virtual/finish", BASE_URL);
|
||||
const options = {
|
||||
method: 'POST'
|
||||
}
|
||||
|
||||
return fetch(url.href).then(resp => resp.json());
|
||||
return fetch(url.href, options).then(resp => resp.json());
|
||||
};
|
||||
|
||||
const getAmIWinner = id => {
|
||||
const url = new URL(`/api/virtual-registration/${id}`, BASE_URL);
|
||||
const url = new URL(`/api/winner/${id}`, BASE_URL);
|
||||
return fetch(url.href).then(resp => resp.json());
|
||||
};
|
||||
|
||||
const postWineChosen = (id, wineName) => {
|
||||
const url = new URL(`/api/virtual-registration/${id}`, BASE_URL);
|
||||
const url = new URL(`/api/winner/${id}`, BASE_URL);
|
||||
const options = {
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
@@ -265,10 +341,14 @@ export {
|
||||
chartWinsByColor,
|
||||
chartPurchaseByColor,
|
||||
prelottery,
|
||||
log,
|
||||
sendLottery,
|
||||
sendLotteryWinners,
|
||||
logWines,
|
||||
wineSchema,
|
||||
barcodeToVinmonopolet,
|
||||
searchForWine,
|
||||
requestNewWine,
|
||||
allRequestedWines,
|
||||
login,
|
||||
register,
|
||||
addAttendee,
|
||||
@@ -279,6 +359,7 @@ export {
|
||||
winnersSecure,
|
||||
deleteWinners,
|
||||
deleteAttendees,
|
||||
deleteRequestedWine,
|
||||
getChatHistory,
|
||||
finishedDraw,
|
||||
getAmIWinner,
|
||||
|
||||
52
src/components/AllRequestedWines.vue
Normal file
52
src/components/AllRequestedWines.vue
Normal file
@@ -0,0 +1,52 @@
|
||||
<template>
|
||||
<main>
|
||||
<h1>
|
||||
Alle foreslåtte viner
|
||||
</h1>
|
||||
<section class="requested-wines-container">
|
||||
<p v-if="wines == undefined || wines.length == 0">Ingen har foreslått noe enda!</p>
|
||||
<RequestedWineCard v-for="requestedEl in wines" :key="requestedEl.id" :requestedElement="requestedEl" @wineDeleted="filterOutDeletedWine" :showDeleteButton="isAdmin"/>
|
||||
</section>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { allRequestedWines } from "@/api";
|
||||
import RequestedWineCard from "@/ui/RequestedWineCard";
|
||||
export default {
|
||||
components: {
|
||||
RequestedWineCard
|
||||
},
|
||||
data(){
|
||||
return{
|
||||
wines: undefined,
|
||||
canRequest: true,
|
||||
isAdmin: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
filterOutDeletedWine(wine){
|
||||
this.wines = this.wines.filter(item => item.wine._id !== wine._id)
|
||||
},
|
||||
async refreshData(){
|
||||
[this.wines, this.isAdmin] = await allRequestedWines() || [[], false]
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.refreshData()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
h1{
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.requested-wines-container{
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
flex-flow: row wrap;
|
||||
align-items: stretch
|
||||
}
|
||||
</style>
|
||||
@@ -78,6 +78,10 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="button-container">
|
||||
<button class="vin-button" @click="submitLottery">Send inn lotteri</button>
|
||||
</div>
|
||||
|
||||
<h3>Vinnere</h3>
|
||||
<a class="wine-link" @click="fetchColorsAndWinners()">Refresh data fra virtuelt lotteri</a>
|
||||
<div class="winner-container" v-if="winners.length > 0">
|
||||
@@ -130,8 +134,8 @@
|
||||
</div>
|
||||
</wine>
|
||||
|
||||
<div class="button-container">
|
||||
<button class="vin-button" @click="sendInfo">Send inn vinnere</button>
|
||||
<div class="button-container column">
|
||||
<button class="vin-button" @click="submitLotteryWinners">Send inn vinnere</button>
|
||||
<button class="vin-button" @click="resetWinnerDataInStorage">Reset local wines</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -142,9 +146,11 @@
|
||||
|
||||
<script>
|
||||
import eventBus from "@/mixins/EventBus";
|
||||
import { dateString } from '@/utils'
|
||||
import {
|
||||
prelottery,
|
||||
log,
|
||||
sendLotteryWinners,
|
||||
sendLottery,
|
||||
logWines,
|
||||
wineSchema,
|
||||
winnersSecure,
|
||||
@@ -306,7 +312,7 @@ export default {
|
||||
},
|
||||
sendWines: async function() {
|
||||
let response = await logWines(this.wines);
|
||||
if (response == true) {
|
||||
if (response.success == true) {
|
||||
alert("Sendt!");
|
||||
window.location.reload();
|
||||
} else {
|
||||
@@ -324,7 +330,7 @@ export default {
|
||||
}
|
||||
});
|
||||
},
|
||||
sendInfo: async function(event) {
|
||||
submitLottery: async function(event) {
|
||||
const colors = {
|
||||
red: this.lotteryColors.filter(c => c.css == "red")[0].value,
|
||||
green: this.lotteryColors.filter(c => c.css == "green")[0].value,
|
||||
@@ -333,48 +339,63 @@ export default {
|
||||
};
|
||||
|
||||
let sendObject = {
|
||||
purchase: {
|
||||
date: new Date(),
|
||||
lottery: {
|
||||
date: dateString(new Date()),
|
||||
...colors
|
||||
},
|
||||
winners: this.winners
|
||||
}
|
||||
};
|
||||
|
||||
if (sendObject.purchase.red == undefined) {
|
||||
if (sendObject.lottery.red == undefined) {
|
||||
alert("Rød må defineres");
|
||||
return;
|
||||
}
|
||||
if (sendObject.purchase.green == undefined) {
|
||||
if (sendObject.lottery.green == undefined) {
|
||||
alert("Grønn må defineres");
|
||||
return;
|
||||
}
|
||||
if (sendObject.purchase.yellow == undefined) {
|
||||
if (sendObject.lottery.yellow == undefined) {
|
||||
alert("Gul må defineres");
|
||||
return;
|
||||
}
|
||||
if (sendObject.purchase.blue == undefined) {
|
||||
if (sendObject.lottery.blue == undefined) {
|
||||
alert("Blå må defineres");
|
||||
return;
|
||||
}
|
||||
|
||||
sendObject.purchase.bought =
|
||||
sendObject.lottery.bought =
|
||||
parseInt(colors.blue) +
|
||||
parseInt(colors.red) +
|
||||
parseInt(colors.green) +
|
||||
parseInt(colors.yellow);
|
||||
const stolen = sendObject.purchase.bought - parseInt(this.payed) / 10;
|
||||
const stolen = sendObject.lottery.bought - parseInt(this.payed) / 10;
|
||||
if (isNaN(stolen) || stolen == undefined) {
|
||||
alert("Betalt må registreres");
|
||||
return;
|
||||
}
|
||||
sendObject.purchase.stolen = stolen;
|
||||
sendObject.lottery.stolen = stolen;
|
||||
|
||||
if (sendObject.winners.length == 0) {
|
||||
let response = await sendLottery(sendObject);
|
||||
if (response == true) {
|
||||
alert("Sendt!");
|
||||
window.location.reload();
|
||||
} else {
|
||||
alert(response.message || "Noe gikk galt under innsending");
|
||||
}
|
||||
},
|
||||
submitLotteryWinners: async function(event) {
|
||||
let sendObject = {
|
||||
lottery: {
|
||||
date: dateString(new Date()),
|
||||
winners: this.winners
|
||||
}
|
||||
}
|
||||
|
||||
if (sendObject.lottery.winners.length == 0) {
|
||||
alert("Det må være med vinnere");
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < sendObject.winners.length; i++) {
|
||||
let currentWinner = sendObject.winners[i];
|
||||
for (let i = 0; i < sendObject.lottery.winners.length; i++) {
|
||||
let currentWinner = sendObject.lottery.winners[i];
|
||||
|
||||
if (currentWinner.name == undefined || currentWinner.name == "") {
|
||||
alert("Navn må defineres");
|
||||
@@ -386,7 +407,7 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
let response = await log(sendObject);
|
||||
let response = await sendLotteryWinners(sendObject);
|
||||
if (response == true) {
|
||||
alert("Sendt!");
|
||||
window.location.reload();
|
||||
@@ -613,7 +634,7 @@ hr {
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
max-width: 1400px;
|
||||
margin: 3rem auto 0;
|
||||
margin: 3rem auto 1rem;
|
||||
|
||||
@include mobile {
|
||||
margin: 1.8rem auto 0;
|
||||
|
||||
195
src/components/RequestWine.vue
Normal file
195
src/components/RequestWine.vue
Normal file
@@ -0,0 +1,195 @@
|
||||
<template>
|
||||
<main>
|
||||
<h1>
|
||||
Foreslå en vin!
|
||||
</h1>
|
||||
<Modal
|
||||
v-if="showModal"
|
||||
modalText="Ønsket ditt har blitt lagt til"
|
||||
:buttons="modalButtons"
|
||||
@click="emitFromModalButton"
|
||||
></Modal>
|
||||
<section>
|
||||
<section class="search-section">
|
||||
<input type="text" v-model="searchString" @keyup.enter="fetchWineFromVin()" placeholder="Søk etter en vin du liker her!🍷">
|
||||
<button :disabled="!searchString" @click="fetchWineFromVin()" class="vin-button">Søk</button>
|
||||
</section>
|
||||
<section v-for="(wine, index) in this.wines" :key="index" class="search-results-container">
|
||||
<img
|
||||
v-if="wine.image"
|
||||
:src="wine.image"
|
||||
class="wine-image"
|
||||
:class="{ 'fullscreen': fullscreen }"
|
||||
/>
|
||||
<img v-else class="wine-placeholder" alt="Wine image" />
|
||||
<section class="wine-info">
|
||||
<h2 v-if="wine.name">{{ wine.name }}</h2>
|
||||
<h2 v-else>(no name)</h2>
|
||||
<div class="__details">
|
||||
<span v-if="wine.rating">{{ wine.rating }}%</span>
|
||||
<span v-if="wine.price">{{ wine.price }} NOK</span>
|
||||
<span v-if="wine.country">{{ wine.country }}</span>
|
||||
</div>
|
||||
</section>
|
||||
<section class="buttons">
|
||||
<button class="vin-button" @click="request(wine)">Foreslå denne</button>
|
||||
<a
|
||||
v-if="wine.vivinoLink"
|
||||
:href="wine.vivinoLink"
|
||||
class="wine-link"
|
||||
>Les mer på polet</a>
|
||||
</section>
|
||||
</section>
|
||||
<p v-if="this.wines && this.wines.length == 0">
|
||||
Fant ingen viner med det navnet!
|
||||
</p>
|
||||
</section>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { searchForWine, requestNewWine } from "@/api";
|
||||
import Wine from "@/ui/Wine";
|
||||
import Modal from "@/ui/Modal";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Wine,
|
||||
Modal
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
searchString: undefined,
|
||||
wines: undefined,
|
||||
showModal: false,
|
||||
modalButtons: [
|
||||
{
|
||||
text: "Legg til flere viner",
|
||||
action: "stay"
|
||||
},
|
||||
{
|
||||
text: "Se alle viner",
|
||||
action: "move"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
fetchWineFromVin(){
|
||||
if(this.searchString){
|
||||
this.wines = []
|
||||
let localSearchString = this.searchString.replace(/ /g,"_");
|
||||
searchForWine(localSearchString)
|
||||
.then(res => this.wines = res)
|
||||
}
|
||||
},
|
||||
request(wine){
|
||||
requestNewWine(wine)
|
||||
.then(() => this.showModal = true)
|
||||
},
|
||||
emitFromModalButton(action){
|
||||
if(action == "stay"){
|
||||
this.showModal = false
|
||||
} else {
|
||||
this.$router.push("/requested-wines");
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "./src/styles/media-queries";
|
||||
@import "./src/styles/global";
|
||||
@import "./src/styles/variables";
|
||||
|
||||
main{
|
||||
margin: auto;
|
||||
width: 80%;
|
||||
text-align: center;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
input[type="text"] {
|
||||
width: 100%;
|
||||
color: black;
|
||||
border-radius: 4px;
|
||||
padding: 0.5rem 1rem;
|
||||
border: 1px solid black;
|
||||
max-width: 80%;
|
||||
}
|
||||
|
||||
.search-section{
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
flex-flow: row;
|
||||
}
|
||||
|
||||
.search-results-container{
|
||||
display: flex;
|
||||
padding: 3px;
|
||||
border-radius: 1px;
|
||||
box-shadow: 0px 0px 0px 1px rgba(0,0,0,0.3);
|
||||
margin: 1rem 0;
|
||||
justify-content: space-around;
|
||||
flex-flow: row wrap;
|
||||
align-items: stretch;
|
||||
|
||||
|
||||
.wine-image {
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.wine-placeholder {
|
||||
height: 100px;
|
||||
width: 70px;
|
||||
}
|
||||
|
||||
.wine-info{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.__details{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
.wine-link {
|
||||
color: #333333;
|
||||
font-family: Arial;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
border-bottom: 1px solid #ff5fff;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.buttons{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
order: 2;
|
||||
justify-content: space-between;
|
||||
width: 40%;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
@include mobile {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.wine-image {
|
||||
height: 100px;
|
||||
width: 50px;
|
||||
align-self: center;
|
||||
}
|
||||
.buttons{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-self: center;
|
||||
margin: 1em;
|
||||
.wine-link{
|
||||
margin-top: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
@@ -106,6 +106,8 @@
|
||||
</div>
|
||||
<br />
|
||||
<button class="vin-button" @click="sendAttendee">Send deltaker</button>
|
||||
|
||||
<TextToast v-if="showToast" :text="toastText" v-on:closeToast="showToast = false" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -119,13 +121,16 @@ import {
|
||||
winnersSecure,
|
||||
deleteWinners,
|
||||
deleteAttendees,
|
||||
finishedDraw
|
||||
finishedDraw,
|
||||
prelottery
|
||||
} from "@/api";
|
||||
import TextToast from "@/ui/TextToast";
|
||||
import RaffleGenerator from "@/ui/RaffleGenerator";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
RaffleGenerator
|
||||
RaffleGenerator,
|
||||
TextToast
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -144,7 +149,9 @@ export default {
|
||||
drawTime: 20,
|
||||
currentWinners: 1,
|
||||
numberOfWinners: 4,
|
||||
socket: null
|
||||
socket: null,
|
||||
toastText: undefined,
|
||||
showToast: false
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
@@ -168,13 +175,21 @@ export default {
|
||||
});
|
||||
|
||||
window.finishedDraw = finishedDraw;
|
||||
console.log("here");
|
||||
},
|
||||
methods: {
|
||||
setWithRandomColors(colors) {
|
||||
Object.keys(colors).forEach(color => (this[color] = colors[color]));
|
||||
},
|
||||
sendAttendee: async function() {
|
||||
if (this.red == 0 && this.blue == 0 && this.green == 0 && this.yellow == 0) {
|
||||
alert('Ingen farger valgt!')
|
||||
return;
|
||||
}
|
||||
if (this.name == 0 && this.phoneNumber) {
|
||||
alert('Ingen navn eller tlf satt!')
|
||||
return;
|
||||
}
|
||||
|
||||
let response = await addAttendee({
|
||||
name: this.name,
|
||||
phoneNumber: this.phoneNumber,
|
||||
@@ -184,10 +199,15 @@ export default {
|
||||
yellow: this.yellow,
|
||||
ballots: this.ballots
|
||||
});
|
||||
|
||||
if (response == true) {
|
||||
alert("Sendt inn deltaker!");
|
||||
this.toastText = `Sendt inn deltaker: ${this.name}`;
|
||||
this.showToast = true;
|
||||
|
||||
this.name = null;
|
||||
this.phoneNumber = null;
|
||||
this.yellow = 0;
|
||||
this.green = 0;
|
||||
this.red = 0;
|
||||
this.blue = 0;
|
||||
|
||||
@@ -208,6 +228,7 @@ export default {
|
||||
if (window.confirm("Er du sikker på at du vil trekke vinnere?")) {
|
||||
this.drawingWinner = true;
|
||||
let response = await getVirtualWinner();
|
||||
|
||||
if (response) {
|
||||
if (this.currentWinners < this.numberOfWinners) {
|
||||
this.countdown();
|
||||
|
||||
@@ -62,11 +62,9 @@ export default {
|
||||
this.name = winnerObject.name;
|
||||
const _wines = await fetch("/api/wines/prelottery");
|
||||
this.wines = await _wines.json();
|
||||
console.log(this.wines);
|
||||
},
|
||||
methods: {
|
||||
chosenWine: async function(name) {
|
||||
console.log("chosen a wine");
|
||||
let posted = await postWineChosen(this.id, name);
|
||||
console.log("response", posted);
|
||||
if (posted.success) {
|
||||
@@ -82,7 +80,8 @@ export default {
|
||||
.container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 4rem;
|
||||
margin-top: 2rem;
|
||||
padding: 2rem;
|
||||
}
|
||||
.sent-container {
|
||||
width: 100%;
|
||||
|
||||
@@ -12,6 +12,9 @@ import WinnerPage from "@/components/WinnerPage";
|
||||
import LotteryPage from "@/components/LotteryPage";
|
||||
import HistoryPage from "@/components/HistoryPage";
|
||||
|
||||
import RequestWine from "@/components/RequestWine";
|
||||
import AllRequestedWines from "@/components/AllRequestedWines";
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: "*",
|
||||
@@ -52,6 +55,14 @@ const routes = [
|
||||
{
|
||||
path: "/history",
|
||||
component: HistoryPage
|
||||
},
|
||||
{
|
||||
path: "/request",
|
||||
component: RequestWine
|
||||
},
|
||||
{
|
||||
path: "/requested-wines",
|
||||
component: AllRequestedWines
|
||||
}
|
||||
];
|
||||
|
||||
@@ -74,6 +74,16 @@ body {
|
||||
margin-right: 2rem;
|
||||
}
|
||||
|
||||
&.column {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
> * {
|
||||
margin-right: unset;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
@include mobile {
|
||||
&:not(.row) {
|
||||
flex-direction: column;
|
||||
@@ -108,6 +118,7 @@ textarea {
|
||||
border: 0;
|
||||
width: fit-content;
|
||||
font-size: 1.3rem;
|
||||
line-height: 1.3rem;
|
||||
height: 4rem;
|
||||
max-height: 4rem;
|
||||
cursor: pointer;
|
||||
@@ -133,13 +144,17 @@ textarea {
|
||||
0 16px 32px rgba(0, 0, 0, 0.07), 0 32px 64px rgba(0, 0, 0, 0.07);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&:hover:not(:disabled) {
|
||||
transform: scale(1.02) translateZ(0);
|
||||
|
||||
&::after {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
&:disabled{
|
||||
opacity: 0.25;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
.no-margin {
|
||||
|
||||
7
src/styles/positioning.scss
Normal file
7
src/styles/positioning.scss
Normal file
@@ -0,0 +1,7 @@
|
||||
.flex {
|
||||
display: flex;
|
||||
|
||||
&-column {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
101
src/ui/Modal.vue
Normal file
101
src/ui/Modal.vue
Normal file
@@ -0,0 +1,101 @@
|
||||
<template>
|
||||
<transition name="modal-fade">
|
||||
<main class="modal-backdrop">
|
||||
<section class="modal">
|
||||
<header class="modal-header" v-if="headerText">
|
||||
{{headerText}}
|
||||
</header>
|
||||
<section class="modal-body">
|
||||
<p>
|
||||
{{modalText}}
|
||||
</p>
|
||||
<section class="button-container">
|
||||
<button v-for="(button, index) in buttons" :key="index" @click="modalButtonClicked(button.action)" class="vin-button">
|
||||
{{button.text}}
|
||||
</button>
|
||||
</section>
|
||||
</section>
|
||||
</section>
|
||||
</main>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
headerText: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
modalText: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
buttons: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
},
|
||||
methods:{
|
||||
modalButtonClicked(action){
|
||||
this.$emit('click', action)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../styles/global.scss";
|
||||
|
||||
.modal-fade-enter,
|
||||
.modal-fade-leave-active {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.modal-fade-enter-active,
|
||||
.modal-fade-leave-active {
|
||||
transition: opacity .5s ease
|
||||
}
|
||||
|
||||
.modal-backdrop {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 1;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.modal {
|
||||
background: #FFFFFF;
|
||||
-webkit-box-shadow: 0px 0px 22px 1px rgba(0, 0, 0, 0.65);
|
||||
-moz-box-shadow: 0px 0px 22px 1px rgba(0, 0, 0, 0.65);
|
||||
box-shadow: 0px 0px 22px 1px rgba(0, 0, 0, 0.65);
|
||||
overflow-x: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
padding: 15px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
border-bottom: 1px solid #eeeeee;
|
||||
color: #4AAE9B;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
position: relative;
|
||||
padding: 20px 10px;
|
||||
}
|
||||
|
||||
</style>
|
||||
80
src/ui/RequestedWineCard.vue
Normal file
80
src/ui/RequestedWineCard.vue
Normal file
@@ -0,0 +1,80 @@
|
||||
<template>
|
||||
<div class="requested-wine">
|
||||
<img
|
||||
v-if="wine.image"
|
||||
:src="wine.image"
|
||||
class="wine-image"
|
||||
:class="{ 'fullscreen': fullscreen }"
|
||||
/>
|
||||
<img v-else class="wine-placeholder" alt="Wine image" />
|
||||
<section class="wine-info">
|
||||
<h3 v-if="wine.name">{{ wine.name }}</h3>
|
||||
<h3 v-else>(no name)</h3>
|
||||
<p>Antall ganger denne har blitt foreslått: {{requestedElement.count}}</p>
|
||||
<section class="buttons">
|
||||
<button class="vin-button" @click="request(wine)" v-if="!locallyRequested">Foreslå denne</button>
|
||||
<a
|
||||
v-if="wine.vivinoLink"
|
||||
:href="wine.vivinoLink"
|
||||
class="wine-link"
|
||||
>Les mer på polet</a>
|
||||
</section>
|
||||
<button @click="deleteWine(wine)" v-if="showDeleteButton == true">
|
||||
Slett vinen
|
||||
</button>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { deleteRequestedWine, requestNewWine } from "@/api";
|
||||
|
||||
export default {
|
||||
data(){
|
||||
return {
|
||||
wine: this.requestedElement.wine,
|
||||
locallyRequested: false
|
||||
}
|
||||
},
|
||||
props: {
|
||||
requestedElement: {
|
||||
required: true,
|
||||
type: Object
|
||||
},
|
||||
showDeleteButton: {
|
||||
required: false,
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
request(wine){
|
||||
this.locallyRequested = true
|
||||
this.requestedElement.count = this.requestedElement.count +1
|
||||
requestNewWine(wine)
|
||||
},
|
||||
async deleteWine(wine) {
|
||||
if (window.confirm("Er du sikker på at du vil slette vinen?")) {
|
||||
let response = await deleteRequestedWine(wine);
|
||||
if (response['success'] == true) {
|
||||
this.$emit('wineDeleted', wine);
|
||||
} else {
|
||||
alert("Klarte ikke slette vinen");
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.requested-wine{
|
||||
padding: 20px;
|
||||
border-radius: 1px;
|
||||
margin: 1rem 0;
|
||||
-webkit-box-shadow: 0px 0px 10px 1px rgba(0, 0, 0, 0.65);
|
||||
-moz-box-shadow: 0px 0px 10px 1px rgba(0, 0, 0, 0.65);
|
||||
box-shadow: 0px 0px 10px 1px rgba(0, 0, 0, 0.65);
|
||||
}
|
||||
</style>
|
||||
@@ -22,6 +22,7 @@
|
||||
:href="wine.vivinoLink"
|
||||
class="wine-link"
|
||||
>Les mer på {{ hostname(wine.vivinoLink) }}</a>
|
||||
|
||||
<button
|
||||
v-if="winner"
|
||||
@click="choseWine(wine.name)"
|
||||
@@ -174,4 +175,8 @@ a:visited {
|
||||
color: $red;
|
||||
}
|
||||
}
|
||||
|
||||
.vin-button {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
</style>
|
||||
@@ -34,8 +34,10 @@ h2 {
|
||||
|
||||
.winners {
|
||||
display: flex;
|
||||
flex-flow: wrap;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.ballot-element {
|
||||
|
||||
12
src/utils.js
Normal file
12
src/utils.js
Normal file
@@ -0,0 +1,12 @@
|
||||
|
||||
const dateString = (date) => {
|
||||
const ye = new Intl.DateTimeFormat('en', { year: 'numeric' }).format(date)
|
||||
const mo = new Intl.DateTimeFormat('en', { month: '2-digit' }).format(date)
|
||||
const da = new Intl.DateTimeFormat('en', { day: '2-digit' }).format(date)
|
||||
|
||||
return `${ye}-${mo}-${da}`
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
dateString
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import Vue from "vue";
|
||||
import VueRouter from "vue-router";
|
||||
import { routes } from "@/routes/vinlottisRouter";
|
||||
import { routes } from "@/router.js";
|
||||
import Vinlottis from "@/Vinlottis";
|
||||
import VueAnalytics from "vue-analytics";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user