Skip to content
Snippets Groups Projects
Commit 89867f02 authored by Titus Netland's avatar Titus Netland
Browse files

Merge branch 'main' into 'item-card'

# Conflicts:
#   src/router/index.js
parents e7e35556 8a53ce88
No related branches found
No related tags found
1 merge request!19Item card
Pipeline #176246 passed
Showing
with 1045 additions and 327 deletions
.DS_Store .DS_Store
node_modules node_modules
/dist /dist
coverage
# local env files # local env files
.env
.env.local .env.local
.env.*.local .env.*.local
......
...@@ -16,30 +16,41 @@ ...@@ -16,30 +16,41 @@
stages: # List of stages for jobs, and their order of execution stages: # List of stages for jobs, and their order of execution
- build - build
- test - test
- deploy
build-job: # This job runs in the build stage, which runs first. image: node:16
cache:
paths:
- node_modules/
install_dependencies_job: # This job runs in the build stage, which runs first.
stage: build stage: build
script:
- echo "Compiling the code..."
- echo "Compile complete."
unit-test-job: # This job runs in the test stage.
stage: test # It only starts when the job in the build stage completes successfully.
script: script:
- echo "Running unit tests... This will take about 60 seconds." - echo "Installing dependencies..."
- sleep 60 - npm install
- echo "Code coverage is 90%" - echo "Dependencies installed."
artifacts:
paths:
- node_modules/
lint-test-job: # This job also runs in the test stage. lint-test-job: # This job also runs in the test stage.
stage: test # It can run at the same time as unit-test-job (in parallel). stage: test # It can run at the same time as unit-test-job (in parallel).
script: script:
- echo "Linting code... This will take about 10 seconds." - echo "Linting the code..."
- sleep 10 - npm run lint
- echo "No lint issues found." - echo "Code-linting complete."
artifacts:
paths:
- node_modules/
deploy-job: # This job runs in the deploy stage. unit-test-job: # This job runs in the test stage.
stage: deploy # It only runs when *both* jobs in the test stage complete successfully. stage: test # It only starts when the job in the build stage completes successfully.
script: script:
- echo "Deploying application..." - echo "Running unit tests..."
- echo "Application successfully deployed." - npm run test:unit -- --coverage
- echo "Unit tests complete."
artifacts:
paths:
- node_modules/
...@@ -14,13 +14,14 @@ ...@@ -14,13 +14,14 @@
"axios": "^0.26.1", "axios": "^0.26.1",
"core-js": "^3.8.3", "core-js": "^3.8.3",
"cssom": "^0.5.0", "cssom": "^0.5.0",
"jwt-decode": "^3.1.2",
"roboto-fontface": "*", "roboto-fontface": "*",
"vue": "^3.2.13", "vue": "^3.2.13",
"vue-router": "^4.0.3", "vue-router": "^4.0.3",
"vuelidate": "^0.7.7", "vuelidate": "^0.7.7",
"vuex": "^4.0.0", "vuex": "^4.0.0",
"vuex-persistedstate": "^4.1.0", "vuex-persistedstate": "^4.1.0",
"webfontloader": "^1.0.0" "webfontloader": "^1.6.28"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.12.16", "@babel/core": "^7.12.16",
...@@ -40,6 +41,7 @@ ...@@ -40,6 +41,7 @@
"eslint-plugin-prettier": "^4.0.0", "eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-vue": "^8.0.3", "eslint-plugin-vue": "^8.0.3",
"jest": "^27.0.5", "jest": "^27.0.5",
"jest-serializer-vue": "^2.0.2",
"postcss": "^8.4.12", "postcss": "^8.4.12",
"prettier": "^2.4.1", "prettier": "^2.4.1",
"tailwindcss": "^3.0.24" "tailwindcss": "^3.0.24"
...@@ -11056,6 +11058,11 @@ ...@@ -11056,6 +11058,11 @@
"graceful-fs": "^4.1.6" "graceful-fs": "^4.1.6"
} }
}, },
"node_modules/jwt-decode": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz",
"integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A=="
},
"node_modules/kind-of": { "node_modules/kind-of": {
"version": "3.2.2", "version": "3.2.2",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
...@@ -24449,6 +24456,11 @@ ...@@ -24449,6 +24456,11 @@
"universalify": "^2.0.0" "universalify": "^2.0.0"
} }
}, },
"jwt-decode": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz",
"integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A=="
},
"kind-of": { "kind-of": {
"version": "3.2.2", "version": "3.2.2",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
...@@ -15,13 +15,14 @@ ...@@ -15,13 +15,14 @@
"axios": "^0.26.1", "axios": "^0.26.1",
"core-js": "^3.8.3", "core-js": "^3.8.3",
"cssom": "^0.5.0", "cssom": "^0.5.0",
"jwt-decode": "^3.1.2",
"roboto-fontface": "*", "roboto-fontface": "*",
"vue": "^3.2.13", "vue": "^3.2.13",
"vue-router": "^4.0.3", "vue-router": "^4.0.3",
"vuelidate": "^0.7.7", "vuelidate": "^0.7.7",
"vuex": "^4.0.0", "vuex": "^4.0.0",
"vuex-persistedstate": "^4.1.0", "vuex-persistedstate": "^4.1.0",
"webfontloader": "^1.0.0" "webfontloader": "^1.6.28"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.12.16", "@babel/core": "^7.12.16",
...@@ -41,6 +42,7 @@ ...@@ -41,6 +42,7 @@
"eslint-plugin-prettier": "^4.0.0", "eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-vue": "^8.0.3", "eslint-plugin-vue": "^8.0.3",
"jest": "^27.0.5", "jest": "^27.0.5",
"jest-serializer-vue": "^2.0.2",
"postcss": "^8.4.12", "postcss": "^8.4.12",
"prettier": "^2.4.1", "prettier": "^2.4.1",
"tailwindcss": "^3.0.24" "tailwindcss": "^3.0.24"
......
<template> <template>
<v-app> <router-view />
<v-main>
<router-view />
</v-main>
</v-app>
</template> </template>
<script>
export default {
name: "App",
data: () => ({
//
}),
};
</script>
src/assets/defaultUserProfileImage.jpg

4.95 KiB

src/assets/removeIcon.png

1.51 KiB

<template>
<div class="m-6">
<!-- Component heading -->
<div class="flex justify-center mt-6">
<p class="text-4xl">Opprett Gruppe</p>
</div>
<!-- Radio boxes -->
<div class="mt-6">
<label
class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300"
id="radioBoxLabel"
>Synlighet</label
>
<div class="form-check">
<input
class="form-check-input appearance-none rounded-full h-4 w-4 border border-gray-300 bg-white checked:bg-blue-600 checked:border-blue-600 focus:outline-none transition duration-200 mt-1 align-top bg-no-repeat bg-center bg-contain float-left mr-2 cursor-pointer"
type="radio"
name="flexRadioDefault"
id="flexRadioOpen"
value="Åpen"
@change="checkRadioButton($event)"
checked
/>
<label
class="form-check-label inline-block text-gray-800"
for="flexRadioOpen"
id="radioBoxOpenLabel"
>
Åpen
</label>
</div>
<div class="form-check">
<input
class="form-check-input appearance-none rounded-full h-4 w-4 border border-gray-300 bg-white checked:bg-blue-600 checked:border-blue-600 focus:outline-none transition duration-200 mt-1 align-top bg-no-repeat bg-center bg-contain float-left mr-2 cursor-pointer"
type="radio"
name="flexRadioDefault"
id="flexRadioPrivate"
value="Privat"
@change="checkRadioButton($event)"
/>
<label
class="form-check-label inline-block text-gray-800"
for="flexRadioPrivate"
id="radioBoxPrivateLabel"
>
Privat
</label>
</div>
</div>
<!-- Title -->
<div class="mt-6" :class="{ error: v$.group.name.$errors.length }">
<label
class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300"
id="titleLabel"
>Gruppenavn</label
>
<input
type="text"
id="title"
class="bg-gray-200 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
v-model="v$.group.name.$model"
required
/>
<!-- error message for title-->
<div
class="text-red"
v-for="(error, index) of v$.group.name.$errors"
:key="index"
>
<div class="text-red-600 text-sm">
{{ error.$message }}
</div>
</div>
</div>
<!-- Select category -->
<div class="mt-6">
<label
class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-400"
id="selectCategoryLabel"
>Kategori</label
>
<select
v-model="v$.group.select.$model"
id="categories"
class="bg-gray-200 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
>
<option class="text-gray-400" value="" disabled selected>
Select a category
</option>
<option
v-for="category in group.categories"
:key="category"
class="text-gray-900 text-sm"
>
{{ category }}
</option>
</select>
<!-- error message for select box -->
<div
class="text-red"
v-for="(error, index) of v$.group.select.$errors"
:key="index"
>
<div class="text-red-600 text-sm">
{{ error.$message }}
</div>
</div>
</div>
<!-- Description -->
<div class="mt-6" :class="{ error: v$.group.description.$errors.length }">
<label
class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-400"
id="descriptionLabel"
>Beskrivelse</label
>
<textarea
id="description"
rows="4"
v-model="v$.group.description.$model"
class="block p-2.5 w-full text-sm text-gray-900 bg-gray-200 rounded-lg border border-gray-300 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
required
></textarea>
<!-- error message for description -->
<div
class="text-red"
v-for="(error, index) of v$.group.description.$errors"
:key="index"
>
<div class="text-red-600 text-sm">
{{ error.$message }}
</div>
</div>
</div>
<!-- Images -->
<div class="mt-6">
<label
class="block mb-2 text-xl font-medium text-gray-900 dark:text-gray-400"
id="imageLabel"
>
Bilde
</label>
<input
type="file"
ref="file"
style="display: none"
@change="addImage"
multiple
accept="image/png, image/jpeg"
/>
<!-- Button for adding an image -->
<div class="inline-flex rounded-md shadow-sm">
<button
@click="$refs.file.click()"
class="text-black bg-gray-200 hover:bg-grey-800 focus:ring-4 focus:outline-none focus:ring-grey-300 font-medium rounded-lg text-sm sm:w-auto px-5 py-2.5 text-center dark:bg-grey-600 dark:hover:bg-grey-700 dark:focus:ring-grey-800 disabled:opacity-50 cursor-not-allowed"
:disabled="imageAdded"
>
Velg bilde
</button>
<!-- Button for removing an image -->
<button
class="w-1/12 ml-5 text-white bg-white-500 font-medium rounded-lg text-sm"
v-show="imageAdded"
@click="removeImage"
>
<img src="../assets/removeIcon.png" alt="Remove icon image" />
</button>
</div>
<!-- Div box for showing all chosen images -->
<div v-for="image in group.images" :key="image" class="m-2">
<img :src="image" class="w-1/2 inline" alt="Bilde av gjenstanden" />
</div>
</div>
<!-- Save item button -->
<div class="flex justify-center mt-10">
<button
@click="saveClicked"
class="content-center text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm sm:w-auto px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
id="saveButton"
>
Lagre
</button>
</div>
</div>
</template>
<script>
import useVuelidate from "@vuelidate/core";
import { required, helpers, maxLength } from "@vuelidate/validators";
export default {
name: "CreateNewGroup.vue",
setup() {
return { v$: useVuelidate() };
},
validations() {
return {
group: {
name: {
required: helpers.withMessage(
() => "Navnt kan ikke være tom",
required
),
max: helpers.withMessage(
() => `Navnet kan være på max 50 tegn`,
maxLength(50)
),
},
description: {
required: helpers.withMessage(
() => "Beskrivelsen kan ikke være tom",
required
),
max: helpers.withMessage(
() => `Beskrivelsen kan inneholde max 200 tegn`,
maxLength(200)
),
},
select: {
required: helpers.withMessage(() => `Velg en kategori`, required),
},
},
};
},
data() {
return {
group: {
name: "",
select: null,
description: "",
images: [],
categories: ["Borettslag", "Idrettsklubb", "Fritidsklubb"],
radio: null,
},
imageThere: false,
};
},
computed: {
imageAdded: function () {
if (this.imageThere) {
return true;
} else {
return false;
}
},
},
methods: {
removeImage: function () {
this.group.images.pop();
this.imageThere = false;
console.log("Bilder nå: " + this.group.images.length);
},
checkRadioButton: function (event) {
this.group.radio = event.target.value;
console.log(this.group.radio);
},
checkValidation: function () {
console.log("sjekker validering");
this.v$.group.$touch();
if (this.v$.group.$invalid) {
console.log("Invalid, avslutter...");
return false;
}
console.log("validert!");
return true;
},
async saveClicked() {
console.log("Attempting to save item");
if (this.checkValidation()) {
console.log("validert, videre...");
console.log("Navn: " + this.group.name);
console.log("Synlighet: " + this.group.radio);
console.log("Kategori: " + this.group.select);
console.log("Beskrivelse: " + this.group.description);
console.log("bilder: " + this.group.images);
}
},
addImage: function (event) {
console.log(event.target.files);
this.group.images.push(URL.createObjectURL(event.target.files[0]));
console.log("antall bilder: " + this.group.images.length);
this.imageThere = true;
console.log("image: " + this.imageThere);
},
},
};
</script>
<template> <template>
<v-container> <div class="flex justify-center">
<v-row class="text-center"> <router-link to="/" class="m-6">Logg inn</router-link>
<v-col cols="12">
<v-img
:src="require('../assets/logo.svg')"
class="my-3"
contain
height="200"
/>
</v-col>
<v-col class="mb-4"> <router-link to="/register" class="m-6">Registrer deg</router-link>
<h1 class="display-2 font-weight-bold mb-3">
Welcome to the Vuetify 3 Beta
</h1>
<p class="subheading font-weight-regular"> <router-link to="/about" class="m-6">Om BoCo</router-link>
For help and collaboration with other Vuetify developers, </div>
<br />please join our online
<a href="https://community.vuetifyjs.com" target="_blank"
>Discord Community</a
>
</p>
</v-col>
<v-col class="mb-5" cols="12">
<h2 class="headline font-weight-bold mb-5">What's next?</h2>
<v-row justify="center">
<a
v-for="(next, i) in whatsNext"
:key="i"
:href="next.href"
class="subheading mx-3"
target="_blank"
>
{{ next.text }}
</a>
</v-row>
</v-col>
<v-col class="mb-5" cols="12">
<h2 class="headline font-weight-bold mb-5">Important Links</h2>
<v-row justify="center">
<a
v-for="(link, i) in importantLinks"
:key="i"
:href="link.href"
class="subheading mx-3"
target="_blank"
>
{{ link.text }}
</a>
</v-row>
</v-col>
<v-col class="mb-5" cols="12">
<h2 class="headline font-weight-bold mb-5">Ecosystem</h2>
<v-row justify="center">
<a
v-for="(eco, i) in ecosystem"
:key="i"
:href="eco.href"
class="subheading mx-3"
target="_blank"
>
{{ eco.text }}
</a>
</v-row>
</v-col>
</v-row>
</v-container>
</template> </template>
<script> <script>
...@@ -80,52 +13,7 @@ export default { ...@@ -80,52 +13,7 @@ export default {
name: "HelloWorld", name: "HelloWorld",
data: () => ({ data: () => ({
ecosystem: [
{ }),
text: "vuetify-loader", }
href: "https://github.com/vuetifyjs/vuetify-loader",
},
{
text: "github",
href: "https://github.com/vuetifyjs/vuetify",
},
{
text: "awesome-vuetify",
href: "https://github.com/vuetifyjs/awesome-vuetify",
},
],
importantLinks: [
{
text: "Chat",
href: "https://community.vuetifyjs.com",
},
{
text: "Made with Vuetify",
href: "https://madewithvuejs.com/vuetify",
},
{
text: "Twitter",
href: "https://twitter.com/vuetifyjs",
},
{
text: "Articles",
href: "https://medium.com/vuetify",
},
],
whatsNext: [
{
text: "Explore components",
href: "https://vuetifyjs.com",
},
{
text: "Roadmap",
href: "https://vuetifyjs.com/introduction/roadmap/",
},
{
text: "Frequently Asked Questions",
href: "https://vuetifyjs.com/getting-started/frequently-asked-questions",
},
],
}),
};
</script> </script>
<template>
<div class="mt-5">
<div class="w-4/5 rounded bg-gray-200">
<img
class="w-full"
:src="item.img || require('../assets/default-product.png')"
alt="Item image"
/>
<div class="p-1 m-1">
<p class="text-gray-700 text-xs font-bold" id="adress">
{{ item.adresse }}
</p>
<p class="font-bold text-sm" id="title">{{ item.title }}</p>
<p class="text-gray-700 text-xs" id="price">{{ item.price }} kr</p>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
item: {
img: String,
adresse: String,
title: String,
price: Number,
},
},
};
</script>
<template>
<div>
<v-col align="center" justify="space-around" class="mt-16">
<v-img
max-width="45%"
:src="require('../assets/logo3.svg')"
align="center"
/>
</v-col>
<v-form ref="form" v-model="valid" lazy-validation class="mt-8">
<v-text-field
v-model="user.password"
:rules="[rules.required, rules.min]"
:type="'password'"
name="input-10-1"
label="Passord"
counter
></v-text-field>
<v-text-field
v-model="user.rePassword"
:rules="[rules.required, rules.min, rules.passwordConfirmation]"
:type="'password'"
name="input-10-1"
label="Confirm Password"
counter
></v-text-field>
<v-col justify="space-around" align="center">
<v-btn
:disabled="!valid"
color="success"
class="mb-4 mt-4"
width="50%"
height="40px"
@click="setNewPassword"
>
Endre passord
</v-btn>
</v-col>
</v-form>
</div>
</template>
<script>
export default {
name: "NewPasswordForm.vue",
data() {
return {
user: {
password: "",
rePassword: "",
},
valid: true,
rules: {
required: (value) => !!value || "Feltet er påkrevd",
min: (v) => v.length >= 8 || "Minimum 8 tegn",
passwordConfirmation: (v) =>
v === this.user.password || "Passordene må være like",
},
};
},
methods: {
async setNewPassword() {},
validate() {
this.$refs.form.validate();
},
},
};
</script>
<template> <template>
<v-form ref="form" v-model="valid" lazy-validation> <section
<v-text-field class="max-w-4xl p-6 mx-auto bg-white rounded-md shadow-md dark:bg-gray-800"
v-model="email" >
:rules="emailRules" <h2 class="text-lg font-semibold text-gray-700 capitalize dark:text-white">
label="E-mail" Opprett ny bruker
required </h2>
></v-text-field>
<form @submit.prevent>
<v-text-field <div class="grid grid-cols-1 gap-6 mt-4 sm:grid-cols-2">
v-model="password" <div>
:counter="32" <label class="text-gray-700 dark:text-gray-200" for="email"
:rules="passwordRules" >E-mail</label
label="Passord" >
:append-icon="passwordHidden ? 'mdi-eye' : 'mdi-eye-off'" <input
:type="passwordHidden ? 'text' : 'password'" v-model="email"
@click:append="passwordHidden = !passwordHidden" id="email"
required type="email"
></v-text-field> class="block w-full px-4 py-2 mt-2 text-gray-700 bg-white border border-gray-200 rounded-md dark:bg-gray-800 dark:text-gray-300 dark:border-gray-600 focus:border-blue-400 focus:ring-blue-300 focus:ring-opacity-40 dark:focus:border-blue-300 focus:outline-none focus:ring"
/>
<v-container class="grey lighten-5"> </div>
<v-row>
<v-text-field <div>
class="pr-2" <label class="text-gray-700 dark:text-gray-200" for="password"
v-model="firstName" >Passord</label
:counter="32" >
:rules="firstNameRules" <input
label="Fornavn" v-model="password"
required id="password"
></v-text-field> type="password"
class="block w-full px-4 py-2 mt-2 text-gray-700 bg-white border border-gray-200 rounded-md dark:bg-gray-800 dark:text-gray-300 dark:border-gray-600 focus:border-blue-400 focus:ring-blue-300 focus:ring-opacity-40 dark:focus:border-blue-300 focus:outline-none focus:ring"
<v-text-field />
class="pl-2" </div>
v-model="lastName"
:counter="32" <div>
:rules="lastNameRules" <label class="text-gray-700 dark:text-gray-200" for="confirmPassword"
label="Etternavn" >Bekreft Passord</label
required >
></v-text-field> <input
</v-row> v-model="confirmPassword"
</v-container> id="confirmPassword"
type="password"
<v-text-field class="block w-full px-4 py-2 mt-2 text-gray-700 bg-white border border-gray-200 rounded-md dark:bg-gray-800 dark:text-gray-300 dark:border-gray-600 focus:border-blue-400 focus:ring-blue-300 focus:ring-opacity-40 dark:focus:border-blue-300 focus:outline-none focus:ring"
v-model="address" />
:counter="32" </div>
:rules="addressRules"
label="Addresse" <div>
required <label class="text-gray-700 dark:text-gray-200" for="firstName"
></v-text-field> >Fornavn</label
>
<!-- <v-text-field <input
v-model="confirmPassword" data-test="firstNameTest"
:rules="confirmPasswordRules" v-model="firstName"
label="Bekreft passord" id="firstName"
:append-icon="confirmPasswordHidden ? 'mdi-eye' : 'mdi-eye-off'" type="text"
:type="confirmPasswordHidden ? 'text' : 'password'" class="block w-full px-4 py-2 mt-2 text-gray-700 bg-white border border-gray-200 rounded-md dark:bg-gray-800 dark:text-gray-300 dark:border-gray-600 focus:border-blue-400 focus:ring-blue-300 focus:ring-opacity-40 dark:focus:border-blue-300 focus:outline-none focus:ring"
@click:append="confirmPasswordHidden = !confirmPasswordHidden" />
required </div>
></v-text-field> -->
<div>
<!-- <v-select <label class="text-gray-700 dark:text-gray-200" for="lastName"
v-model="select" >Etternavn</label
:items="items" >
:rules="[(v) => !!v || 'Item is required']" <input
label="Item" v-model="lastName"
required id="lastName"
></v-select> --> type="text"
class="block w-full px-4 py-2 mt-2 text-gray-700 bg-white border border-gray-200 rounded-md dark:bg-gray-800 dark:text-gray-300 dark:border-gray-600 focus:border-blue-400 focus:ring-blue-300 focus:ring-opacity-40 dark:focus:border-blue-300 focus:outline-none focus:ring"
<!-- <v-checkbox />
v-model="checkbox" </div>
:rules="[(v) => !!v || 'You must agree to continue!']"
label="Do you agree?" <div>
required <label class="text-gray-700 dark:text-gray-200" for="address"
></v-checkbox> --> >Addresse</label
>
<v-btn :disabled="!valid" color="success" class="mr-4" @click="submit()" <input
>Registrer</v-btn v-model="address"
> id="address"
type="text"
<v-btn color="error" class="mr-4" @click="reset()">Tøm felter</v-btn> class="block w-full px-4 py-2 mt-2 text-gray-700 bg-white border border-gray-200 rounded-md dark:bg-gray-800 dark:text-gray-300 dark:border-gray-600 focus:border-blue-400 focus:ring-blue-300 focus:ring-opacity-40 dark:focus:border-blue-300 focus:outline-none focus:ring"
</v-form> />
</div>
</div>
<div class="flex justify-end mt-6">
<button
class="px-6 py-2 leading-5 text-white transition-colors duration-200 transform bg-gray-700 rounded-md hover:bg-gray-600 focus:outline-none focus:bg-gray-600"
@click="submit()"
type="submit"
:disabled="loading"
>
<div v-if="loading">
<div v-if="loading" class="lds-ring">
<div></div>
<div></div>
<div></div>
<div></div>
</div>
</div>
<div v-else>Lagre</div>
</button>
</div>
</form>
</section>
<ul data-test="errorMessageList">
<li v-if="errorMessage" data-test="customErrorMsg">{{ errorMessage }}</li>
<li v-for="error of v$.$errors" :key="error.$uid">
<!-- {{ error.$validator }} -->
Field
{{ error.$property }}
has error:
{{ error.$message }}
</li>
</ul>
</template> </template>
<script> <script>
import axios from "axios"; import useVuelidate from "@vuelidate/core";
import { doLogin, registerUser } from "@/utils/apiutil";
import { required, email, minLength, sameAs } from "@vuelidate/validators";
export default { // const isEmailTaken = (value) =>
data: () => ({ // fetch(`/api/unique/${value}`).then((r) => r.json()); // check the email in the server
passwordHidden: false,
// confirmPasswordHidden: false,
valid: true,
firstName: "",
firstNameRules: [
(v) => !!v || "Fornavn er påkrevd",
(v) => (v && v.length <= 32) || "Fornavn må være mindre enn 32 bokstaver",
],
lastName: "",
lastNameRules: [
(v) => !!v || "Etternavn er påkrevd",
(v) =>
(v && v.length <= 32) || "Etternavn må være mindre enn 32 bokstaver",
],
address: "",
addressRules: [
(v) => !!v || "Addresse er påkrevd",
(v) =>
(v && v.length <= 32) || "Addresse må være mindre enn 32 bokstaver",
],
password: "",
passwordRules: [
(v) => !!v || "Passord er påkrevd",
(v) => (v && v.length <= 32) || "Passord må være mindre enn 32 tegn",
(v) => (v && v.length >= 8) || "Passord må være større enn 8 tegn",
],
// confirmPassword: "",
// confirmPasswordRules: [
// (v) => !!v || "Passord er påkrevd",
// (v) => (v && v.length <= 32) || "Passord må være mindre enn 32 bokstaver",
// // (v) => v === this.password || "Passordene må være like",
// ],
email: "",
emailRules: [
(v) => !!v || "E-mail is required",
(v) => /.+@.+\..+/.test(v) || "E-mail must be valid",
],
// select: null,
// items: ["Item 1", "Item 2", "Item 3", "Item 4"],
// checkbox: false,
}),
export default {
setup: () => ({ v$: useVuelidate() }),
data() {
return {
errorMessage: "",
loading: false,
email: "",
password: "",
confirmPassword: "",
firstName: "",
lastName: "",
address: "",
};
},
validations() {
return {
email: {
required,
email,
// isUnique: helpers.withAsync(isEmailTaken),
},
password: {
required,
minLength: minLength(8),
},
confirmPassword: { sameAs: sameAs(this.password) },
firstName: { required },
lastName: { required },
address: { required },
};
},
methods: { methods: {
submit() { async submit() {
console.log("Attempting to register user"); //Display loading symbol
this.valid = this.$refs.form.validate(); this.loading = true;
if (!this.valid) return;
this.valid = false; //Validate form
console.log("User is validated"); const result = await this.v$.$validate();
axios if (!result) {
.post("http://localhost:3000/api/register", { this.loading = false;
email: this.email, return;
firstName: this.firstName, }
lastname: this.lastName,
password: this.password, //Send a request to create a user and save success as a bool
address: this.address, const userCreated = await this.sendRegisterRequest();
})
.then(console.log("Sent")) //If a user is created succsessfully, try to login
.catch((e) => console.log(e)); //If we get this far, we will be pushed anyway so there is no point updating "loading"
if (!userCreated) {
this.errorMessage = "Could not create user.";
return;
}
const loginRequest = {
email: this.email,
password: this.password,
};
const loginResponse = await doLogin(loginRequest);
if (loginResponse === "Failed login") {
this.errorMessage = "Failed to log in with new user";
this.$store.commit("logout");
this.$router.push("/login");
return;
}
this.$store.commit("saveToken", loginResponse);
this.$router.push("/");
}, },
reset() { async sendRegisterRequest() {
this.$refs.form.reset(); const registerInfo = {
this.$refs.form.resetValidation(); email: this.email,
this.valid = true; firstName: this.firstName,
lastname: this.lastName,
password: this.password,
address: this.address,
};
const response = await registerUser(registerInfo);
if (response.status === 200) return true;
return false;
}, },
}, },
}; };
</script> </script>
<style scoped>
/* https://loading.io/css/ */
.lds-ring {
display: inline-block;
position: relative;
width: 20px;
height: 20px;
}
.lds-ring div {
box-sizing: border-box;
display: block;
position: absolute;
width: 16px;
height: 16px;
margin: 2px;
border: 2px solid #fff;
border-radius: 50%;
animation: lds-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
border-color: #fff transparent transparent transparent;
}
.lds-ring div:nth-child(1) {
animation-delay: -0.45s;
}
.lds-ring div:nth-child(2) {
animation-delay: -0.3s;
}
.lds-ring div:nth-child(3) {
animation-delay: -0.15s;
}
@keyframes lds-ring {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>
<template>
<section class="relative w-full max-w-md px-5 py-4 mx-auto rounded-md">
<div class="relative" id="searchComponent">
<span class="absolute inset-y-0 left-0 flex items-center pl-3">
<svg class="w-5 h-5 text-gray-400" viewBox="0 0 24 24" fill="none">
<path
d="M21 21L15 15M17 10C17 13.866 13.866 17 10 17C6.13401 17 3 13.866 3 10C3 6.13401 6.13401 3 10 3C13.866 3 17 6.13401 17 10Z"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
></path>
</svg>
</span>
<input
type="text"
id="searchInput"
class="w-full py-3 pl-10 pr-4 text-gray-700 bg-white border rounded-md dark:bg-gray-800 dark:text-gray-300 dark:border-gray-600 focus:border-blue-500 dark:focus:border-blue-500 focus:outline-none focus:ring"
placeholder="Search"
v-model="search"
/>
</div>
<div class="absolute inset-x-0 px-6 py-3 mt-4 border-2 border-slate-500">
<div class="grid grid-cols-2">
<ItemCard v-for="item in searchedItems" :key="item" :item="item" />
</div>
</div>
</section>
</template>
<script>
import ItemCard from "@/components/ItemCard";
export default {
name: "SearchItemListComponent",
components: {
ItemCard,
},
computed: {
searchedItems() {
let filteredItems = [];
filteredItems = this.items.filter(
(p) =>
p.title.toLowerCase().includes(this.search.toLowerCase()) ||
p.adresse.toLowerCase().includes(this.search.toLowerCase()) ||
p.price === Number(this.search)
);
return filteredItems;
},
},
/**
* Her må det lages en metode som henter alle items (i en gruppe) fra databasen.
* De kan deretter bli pusha inn i items array, og da burde de bli displayet i lista.
* Når denne metoden er på plass kan items[] i data tømmes. Da vil alt dataen komme fra db.
*/
data() {
return {
items: [
{ img: "", adresse: "Oslo", title: "Dyson", price: 1000 },
{ img: "", adresse: "Trondheim", title: "Gressklipper", price: 500 },
{ img: "", adresse: "Bergen", title: "Bil", price: 500 },
],
item: {
img: "",
adresse: "",
title: "",
price: 0,
},
search: "",
};
},
};
</script>
<template>
<div
class="max-w-sm bg-white rounded-lg border border-gray-200 shadow-md dark:bg-gray-800 dark:border-gray-700"
>
<div v-show="isCurrentUser" class="flex justify-end px-4 pt-4">
<button
id="dropdownDefault"
data-dropdown-toggle="dropdown"
@click="dropdown = !dropdown"
class="hidden sm:inline-block text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 focus:outline-none focus:ring-4 focus:ring-gray-200 dark:focus:ring-gray-700 rounded-lg text-sm p-1.5"
type="button"
>
<svg
class="w-6 h-6"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M10 6a2 2 0 110-4 2 2 0 010 4zM10 12a2 2 0 110-4 2 2 0 010 4zM10 18a2 2 0 110-4 2 2 0 010 4z"
></path>
</svg>
</button>
<div
id="dropdown"
v-show="dropdown"
zindex="2"
class="z-10 w-44 text-base list-none bg-white rounded divide-y divide-gray-100 shadow dark:bg-gray-700"
>
<ul class="py-1" aria-labelledby="dropdownDefault">
<li>
<router-link
to=""
class="block py-2 px-4 text-sm text-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-200 dark:hover:text-white"
>Mine gjenstander</router-link
>
</li>
<li>
<router-link
to=""
class="block py-2 px-4 text-sm text-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-200 dark:hover:text-white"
>Mine grupper
</router-link>
</li>
<li>
<router-link
to=""
class="block py-2 px-4 text-sm text-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-200 dark:hover:text-white"
>Leiehistorikk</router-link
>
</li>
<li>
<router-link
to="/newPassword"
class="block py-2 px-4 text-sm text-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-200 dark:hover:text-white"
>Endre passord</router-link
>
</li>
<li>
<router-link
to=""
class="block py-2 px-4 text-sm text-red-600 hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-200 dark:hover:text-white"
>Slett bruker</router-link
>
</li>
</ul>
</div>
</div>
<div class="flex flex-col items-center pb-10">
<img
class="mb-3 w-24 h-24 rounded-full shadow-lg"
src="../../assets/defaultUserProfileImage.jpg"
alt="Profile picture"
/>
<h5 class="mb-1 text-xl font-medium text-gray-900 dark:text-white">
{{ user.firstName }} {{ user.lastName }}
</h5>
<div>
<rating-component :rating="renterRating" :ratingType="'Leietaker'" />
<rating-component :rating="ownerRating" :ratingType="'Utleier'" />
</div>
<div v-show="!isCurrentUser" class="flex mt-4 space-x-3 lg:mt-6">
<a
href="#"
class="inline-flex items-center py-2 px-4 text-sm font-medium text-center text-gray-900 bg-white rounded-lg border border-gray-300 hover:bg-gray-100 focus:ring-4 focus:outline-none focus:ring-gray-200 dark:bg-gray-800 dark:text-white dark:border-gray-600 dark:hover:bg-gray-700 dark:hover:border-gray-700 dark:focus:ring-gray-700"
>Åpne chat</a
>
</div>
</div>
</div>
</template>
<script>
import RatingComponent from "@/components/UserProfileComponents/RatingComponent.vue";
import { parseCurrentUser } from "@/utils/token-utils";
import { getUser, getRenterRating, getOwnerRating } from "@/utils/apiutil";
import router from "@/router";
export default {
name: "LargeProfileCard",
data() {
return {
user: {},
currentUser: {},
id: -1,
isCurrentUser: false,
renterRating: -1, //getRenterRating(this.userID),
ownerRating: -1, //getOwnerRating(this.userID),
dropdown: false,
};
},
components: {
RatingComponent,
},
methods: {
async getUser() {
this.currentUser = parseCurrentUser();
this.id = router.currentRoute.value.params.id;
if (this.id == this.currentUser.account_id) {
this.isCurrentUser = true;
this.user = this.currentUser;
return;
}
this.user = await getUser(this.id);
this.renterRating = getRenterRating(this.id);
this.ownerRating = getOwnerRating(this.id);
},
getProfilePicture() {
/* if (this.user.picture != "") {
return this.user.picture;
} */
return "../assets/defaultUserProfileImage.jpg";
},
},
beforeMount() {
this.getUser();
},
};
</script>
<template>
<ul v-if="compRating != -1" class="flex justify-center">
<li>
<p class="ml-2 text-sm font-medium text-gray-500 dark:text-gray-400">
{{ ratingType }}:&nbsp;
</p>
</li>
<li v-for="i in 5" :key="i">
<svg
:class="getFill(i)"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"
></path>
</svg>
</li>
<li>
<p class="ml-2 text-sm font-medium text-gray-500 dark:text-gray-400">
{{ compRating }} out of 5
</p>
</li>
</ul>
<ul v-else class="flex justify-center">
<li>
<p class="ml-2 text-sm font-medium text-gray-500 dark:text-gray-400">
{{ ratingType }}:&nbsp;
</p>
</li>
<li>
<p class="ml-2 text-sm font-medium text-gray-500 dark:text-gray-400">
Rating ikke tilgjengelig
</p>
</li>
</ul>
</template>
<script>
export default {
name: "RatingComponent",
data() {
return {
compRating: this.rating + 0,
};
},
props: {
rating: Number,
ratingType: String,
},
methods: {
getFill(i) {
if (i <= this.rating) {
return "w-5 h-5 text-yellow-400";
}
return "w-5 h-5 text-gray-300 dark:text-gray-500";
},
},
};
</script>
<template>
<div
class="select-none cursor-pointer hover:bg-gray-50 flex flex-1 items-center p-4"
>
<div class="flex flex-col w-10 h-10 justify-center items-center mr-4">
<router-link to="">
<img alt="profil" :src="getProfilePicture" />
</router-link>
</div>
<div class="flex-1 pl-1">
<div class="font-medium dark:text-white">
{{ user.first_name }} {{ user.last_name }}
</div>
</div>
<div class="flex flex-row justify-center">
<button class="w-10 text-right flex justify-end">Åpne chat</button>
<button v-if="admin" class="w-10 text-right flex justify-end">
Fjern bruker
</button>
</div>
</div>
</template>
<script>
export default {
name: "UserListItem",
props: {
user: Object,
admin: Boolean,
},
methods: {
getProfilePicture() {
if (this.user.picture != "") {
return this.user.picture;
}
return "../assets/defaultUserProfileImage.jpg";
},
},
};
</script>
...@@ -2,9 +2,6 @@ import { createApp } from "vue"; ...@@ -2,9 +2,6 @@ import { createApp } from "vue";
import App from "./App.vue"; import App from "./App.vue";
import router from "./router"; import router from "./router";
import store from "./store"; import store from "./store";
import { loadFonts } from "./plugins/webfontloader";
import "./index.css"; import "./index.css";
loadFonts();
createApp(App).use(router).use(store).mount("#app"); createApp(App).use(router).use(store).mount("#app");
// Styles
import "@mdi/font/css/materialdesignicons.css";
import "vuetify/styles";
// Vuetify
import { createVuetify } from "vuetify";
export default createVuetify();
// https://vuetifyjs.com/en/introduction/why-vuetify/#feature-guides
/**
* plugins/webfontloader.js
*
* webfontloader documentation: https://github.com/typekit/webfontloader
*/
export async function loadFonts() {
const webFontLoader = await import(
/* webpackChunkName: "webfontloader" */ "webfontloader"
);
webFontLoader.load({
google: {
families: ["Roboto:100,300,400,500,700,900&display=swap"],
},
});
}
import jwt_decode from "jwt-decode";
import store from "@/store";
export function tokenHeader() {
let token = store.state.user.token;
return { Authorization: token };
}
export function parseCurrentUser() {
let token = store.state.user.token;
return jwt_decode(token);
}
export function parseUserFromToken(token) {
return jwt_decode(token);
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment