diff --git a/package-lock.json b/package-lock.json index 448b405646d04ca6f9fc3a0e45d3bc1bd3d115a6..e56d5d8bbcf7d19cb2b502c8275c09a8d30eb7b6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,8 @@ "version": "0.1.0", "dependencies": { "@mdi/font": "5.9.55", + "@vuelidate/core": "^2.0.0-alpha.40", + "@vuelidate/validators": "^2.0.0-alpha.28", "core-js": "^3.8.3", "roboto-fontface": "*", "vue": "^3.2.13", @@ -3852,6 +3854,72 @@ "integrity": "sha512-Iu8Tbg3f+emIIMmI2ycSI8QcEuAUgPTgHwesDU1eKMLE4YC/c/sFbGc70QgMq31ijRftV0R7vCm9co6rldCeOA==", "dev": true }, + "node_modules/@vuelidate/core": { + "version": "2.0.0-alpha.40", + "resolved": "https://registry.npmjs.org/@vuelidate/core/-/core-2.0.0-alpha.40.tgz", + "integrity": "sha512-f4Uo0yV2BHDi5+MXWDXRcqBv0u1Cqc/RudJLGqke3wLnIcH2r8sXEqRyuVoaN2KEPqbZiTFw/Uo50wx1J+ZDFQ==", + "dependencies": { + "vue-demi": "^0.12.0" + } + }, + "node_modules/@vuelidate/core/node_modules/vue-demi": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.12.5.tgz", + "integrity": "sha512-BREuTgTYlUr0zw0EZn3hnhC3I6gPWv+Kwh4MCih6QcAeaTlaIX0DwOVN0wHej7hSvDPecz4jygy/idsgKfW58Q==", + "hasInstallScript": true, + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@vuelidate/validators": { + "version": "2.0.0-alpha.28", + "resolved": "https://registry.npmjs.org/@vuelidate/validators/-/validators-2.0.0-alpha.28.tgz", + "integrity": "sha512-FLI4D6SfYas5gkRxc2Q8RU1Jv3mhO2wdNgYpnOEWdKB2S6vhy8ABFMXiyr4P764xY9zBmNg6OwceRfq8vYy6vA==", + "dependencies": { + "vue-demi": "^0.12.0" + } + }, + "node_modules/@vuelidate/validators/node_modules/vue-demi": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.12.5.tgz", + "integrity": "sha512-BREuTgTYlUr0zw0EZn3hnhC3I6gPWv+Kwh4MCih6QcAeaTlaIX0DwOVN0wHej7hSvDPecz4jygy/idsgKfW58Q==", + "hasInstallScript": true, + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, "node_modules/@vuetify/loader-shared": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@vuetify/loader-shared/-/loader-shared-1.3.0.tgz", @@ -18773,7 +18841,8 @@ "version": "5.0.4", "resolved": "https://registry.npmjs.org/@vue/cli-plugin-vuex/-/cli-plugin-vuex-5.0.4.tgz", "integrity": "sha512-dBwiD6mT9+V2HTHcwaWE8qFNgTk5I/NUvxYVeUN3Mmmpo4y/1RxXnr7BlKGnaQsTypb2RFk3KowqIJtg7s+E3Q==", - "dev": true + "dev": true, + "requires": {} }, "@vue/cli-service": { "version": "5.0.4", @@ -19103,7 +19172,8 @@ "version": "2.0.0-rc.20", "resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-2.0.0-rc.20.tgz", "integrity": "sha512-aSkOAzM/ZlIyYgN7yj661FTjhFZZy5i9+FUbbDNoMGYA4F1WKwDdcDCPj9B/qzt3wGFkuCP5PO6SBtdSTMEhIA==", - "dev": true + "dev": true, + "requires": {} }, "@vue/vue-loader-v15": { "version": "npm:vue-loader@15.9.8", @@ -19154,6 +19224,38 @@ "integrity": "sha512-Iu8Tbg3f+emIIMmI2ycSI8QcEuAUgPTgHwesDU1eKMLE4YC/c/sFbGc70QgMq31ijRftV0R7vCm9co6rldCeOA==", "dev": true }, + "@vuelidate/core": { + "version": "2.0.0-alpha.40", + "resolved": "https://registry.npmjs.org/@vuelidate/core/-/core-2.0.0-alpha.40.tgz", + "integrity": "sha512-f4Uo0yV2BHDi5+MXWDXRcqBv0u1Cqc/RudJLGqke3wLnIcH2r8sXEqRyuVoaN2KEPqbZiTFw/Uo50wx1J+ZDFQ==", + "requires": { + "vue-demi": "^0.12.0" + }, + "dependencies": { + "vue-demi": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.12.5.tgz", + "integrity": "sha512-BREuTgTYlUr0zw0EZn3hnhC3I6gPWv+Kwh4MCih6QcAeaTlaIX0DwOVN0wHej7hSvDPecz4jygy/idsgKfW58Q==", + "requires": {} + } + } + }, + "@vuelidate/validators": { + "version": "2.0.0-alpha.28", + "resolved": "https://registry.npmjs.org/@vuelidate/validators/-/validators-2.0.0-alpha.28.tgz", + "integrity": "sha512-FLI4D6SfYas5gkRxc2Q8RU1Jv3mhO2wdNgYpnOEWdKB2S6vhy8ABFMXiyr4P764xY9zBmNg6OwceRfq8vYy6vA==", + "requires": { + "vue-demi": "^0.12.0" + }, + "dependencies": { + "vue-demi": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.12.5.tgz", + "integrity": "sha512-BREuTgTYlUr0zw0EZn3hnhC3I6gPWv+Kwh4MCih6QcAeaTlaIX0DwOVN0wHej7hSvDPecz4jygy/idsgKfW58Q==", + "requires": {} + } + } + }, "@vuetify/loader-shared": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@vuetify/loader-shared/-/loader-shared-1.3.0.tgz", @@ -19378,13 +19480,15 @@ "version": "1.8.0", "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", - "dev": true + "dev": true, + "requires": {} }, "acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true + "dev": true, + "requires": {} }, "acorn-walk": { "version": "8.2.0", @@ -19452,7 +19556,8 @@ "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true + "dev": true, + "requires": {} }, "ansi-colors": { "version": "4.1.1", @@ -20516,7 +20621,8 @@ "version": "6.2.2", "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.2.2.tgz", "integrity": "sha512-Ufadglr88ZLsrvS11gjeu/40Lw74D9Am/Jpr3LlYm5Q4ZP5KdlUhG+6u2EjyXeZcxmZ2h1ebCKngDjolpeLHpg==", - "dev": true + "dev": true, + "requires": {} }, "css-loader": { "version": "6.7.1", @@ -20701,7 +20807,8 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-3.1.0.tgz", "integrity": "sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==", - "dev": true + "dev": true, + "requires": {} }, "csso": { "version": "4.2.0", @@ -21493,7 +21600,8 @@ "version": "8.5.0", "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz", "integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==", - "dev": true + "dev": true, + "requires": {} }, "eslint-plugin-prettier": { "version": "4.0.0", @@ -22450,7 +22558,8 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", - "dev": true + "dev": true, + "requires": {} }, "ieee754": { "version": "1.2.1", @@ -23557,7 +23666,8 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", - "dev": true + "dev": true, + "requires": {} }, "jest-regex-util": { "version": "27.5.1", @@ -25644,25 +25754,29 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.1.1.tgz", "integrity": "sha512-5JscyFmvkUxz/5/+TB3QTTT9Gi9jHkcn8dcmmuN68JQcv3aQg4y88yEHHhwFB52l/NkaJ43O0dbksGMAo49nfQ==", - "dev": true + "dev": true, + "requires": {} }, "postcss-discard-duplicates": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz", "integrity": "sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==", - "dev": true + "dev": true, + "requires": {} }, "postcss-discard-empty": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz", "integrity": "sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==", - "dev": true + "dev": true, + "requires": {} }, "postcss-discard-overridden": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz", "integrity": "sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==", - "dev": true + "dev": true, + "requires": {} }, "postcss-loader": { "version": "6.2.1", @@ -25752,7 +25866,8 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", - "dev": true + "dev": true, + "requires": {} }, "postcss-modules-local-by-default": { "version": "4.0.0", @@ -25787,7 +25902,8 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz", "integrity": "sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==", - "dev": true + "dev": true, + "requires": {} }, "postcss-normalize-display-values": { "version": "5.1.0", @@ -27741,7 +27857,8 @@ "vuetify": { "version": "3.0.0-beta.1", "resolved": "https://registry.npmjs.org/vuetify/-/vuetify-3.0.0-beta.1.tgz", - "integrity": "sha512-698CB/Xlvxku2Tm4AsFrmQ7LzMXOjleX7A5tbyQTnhPyt0NI1OTkf5zJUoXL3TNi2TN7DglN+adI6AE69e6CeQ==" + "integrity": "sha512-698CB/Xlvxku2Tm4AsFrmQ7LzMXOjleX7A5tbyQTnhPyt0NI1OTkf5zJUoXL3TNi2TN7DglN+adI6AE69e6CeQ==", + "requires": {} }, "vuetify-loader": { "version": "2.0.0-alpha.9", @@ -28117,7 +28234,8 @@ "version": "8.5.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz", "integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==", - "dev": true + "dev": true, + "requires": {} } } }, @@ -28272,7 +28390,8 @@ "version": "7.5.7", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.7.tgz", "integrity": "sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A==", - "dev": true + "dev": true, + "requires": {} }, "xml-name-validator": { "version": "3.0.0", diff --git a/package.json b/package.json index fe5b5c467deb1c096bd8dbc09feb4ab072a0bb9a..5dddaffb68bedb7cf3877ac2223fbd6b4be0e135 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,8 @@ }, "dependencies": { "@mdi/font": "5.9.55", + "@vuelidate/core": "^2.0.0-alpha.40", + "@vuelidate/validators": "^2.0.0-alpha.28", "core-js": "^3.8.3", "roboto-fontface": "*", "vue": "^3.2.13", diff --git a/src/components/LoginForm.vue b/src/components/LoginForm.vue new file mode 100644 index 0000000000000000000000000000000000000000..bacf064c42dd00e267ecc5993084fbd99dd70acc --- /dev/null +++ b/src/components/LoginForm.vue @@ -0,0 +1,191 @@ +<template> + <div class="loginForm" > + + + <v-img + :src="require('../assets/logo3.svg')" + class="image" + contain + /> + <form @submit.prevent="onSubmit"> + + <!-- + <div class="inputFields"> + <br><label class="label">E-post</label><br> + <input class="loginInputs" type="text" v-model="v$.user.email.$model" /> + + <br><label class="label"><br>Passord</label><br> + <input class="loginInputs" type="password" v-model="v$.user.password.$model" /> + <br><a href="url" id="forgottenPasswordLink">Glemt passord</a> + </div> + + + <br><br> + <div class="buttonLink"> + <button class="loginButton" type="submit" @click="loginClicked">LOGG INN</button> + <br><a id="newUserLink" href="url">Ny bruker</a> + <p id="messageUser">{{ message }}</p> + </div> --> + + + + + <div class="inputFields"> + <div :class="{ error: v$.user.email.$errors.length }"> + <br><label class="label" id="emailLabelId">E-post </label><br> + <input class="loginInputs" type="email" v-model="v$.user.email.$model"> + + <!-- error message --> + <div class="input-errors" v-for="(error, index) of v$.user.email.$errors" :key="index"> + <div class="error-msg">{{ error.$message }}</div> + </div> + </div> + + <!-- password --> + <div :class="{ error: v$.user.password.$errors.length }"> + <br><label class="label" id="passwordLabelId">Passord </label><br> + <input class="loginInputs" type="password" v-model="v$.user.password.$model"> + + <!-- error message --> + <div class="input-errors" v-for="(error, index) of v$.user.password.$errors" :key="index"> + <div class="error-msg">{{ error.$message }}</div> + </div> + + <!-- Link to forgot password page will be added here --> + <br><a href="url" id="forgottenPasswordLink">Glemt passord</a> + </div> + + </div> + + <div class="buttonLink"> + <!-- Submit Button --> + <div class="buttons-w"> + <br><br><button :disabled="v$.user.$invalid" v-on:click="loginClicked" class="loginButton">Logg inn</button> + + <!-- Link to register new user page will be added here --> + <br><a id="newUserLink" href="url">Ny bruker</a> + + <p id="messageUser">{{ message }}</p> + </div> + + </div> + + + + + </form> + + + </div> +</template> + +<script> +import useVuelidate from '@vuelidate/core' +import { required, email, minLength, helpers } from '@vuelidate/validators' +export default { + name: "LoginForm.vue", + + setup () { + return { v$: useVuelidate() } + }, + + validations() { + return { + user: { + email: { + required, + email: helpers.withMessage(`E-posten er ugyldig`, email), + }, + password: { + required, + min: helpers.withMessage( + ({$params}) => `Passordet må inneholde minst ${$params.min} tegn`, + minLength(8) + ) + }, + }, + } + }, + + data(){ + return{ + message: '', + user: { + email: '', + password: '' + } + } + }, + + methods: { + loginClicked: function (){ + console.log(this.user.email + " " + this.user.password); + }, + + }, + +} +</script> + +<style scoped> +.loginForm{ + background-color: white; + border-radius: 10px; + margin: auto; + width: 80%; + margin-top: 20%; + justify-content: center; + padding: 10px; + font-size: 18px; +} +.label{ + float: left; + margin-left: 5%; +} +.loginInputs{ + background-color: #C4C4C4; + border-radius: 5px; + width: 90%; + height: 40px; + padding: 5px; +} +.loginButton{ + width: 55%; + height: 50px; + background-color: #1071B8; + color: white; + border-radius: 10px; + justify-content: center; + text-align: center; + margin: auto; + font-size: 25px; + margin-bottom: 20px; +} +.buttonLink{ + margin: auto; + text-align: center; + margin-bottom: 40px; +} +.image{ + width: 45%; + margin: auto; + margin-top: 20px; +} +#forgottenPasswordLink{ + float: right; + margin:10px 5% 0 0; +} + +#newUserLink{ + text-decoration: none; + margin-bottom: 40px; +} +.inputFields{ + margin: auto; + text-align: center; +} + +.input-errors{ + color: red; +} +</style> diff --git a/src/router/index.js b/src/router/index.js index 76687a1d81cad5e7b347e5de0c5dd4643e98c004..6772a927160ed46c2df96bade0eadcd972de60d6 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -1,5 +1,7 @@ import { createRouter, createWebHistory } from "vue-router"; import HomeView from "../views/HomeView.vue"; +import LoginView from "../views/LoginView.vue"; + const routes = [ { @@ -16,6 +18,11 @@ const routes = [ component: () => import(/* webpackChunkName: "about" */ "../views/AboutView.vue"), }, + { + path: "/login", + name: "login", + component: LoginView, + } ]; const router = createRouter({ diff --git a/src/views/LoginView.vue b/src/views/LoginView.vue new file mode 100644 index 0000000000000000000000000000000000000000..ae5b1c4a6d99ec9a47ce6d9644d972a6fa4c8b91 --- /dev/null +++ b/src/views/LoginView.vue @@ -0,0 +1,25 @@ +<template> + <div class="loginPage"> + <LoginForm></LoginForm> + </div> +</template> + +<script> +import LoginForm from "@/components/LoginForm"; +export default { + name: "LoginView.vue", + components: { + LoginForm, + } +} +</script> + +<style scoped> + +.loginPage{ + + background-color: white; + height: 100%; + overflow: auto; +} +</style> diff --git a/tests/unit/LoginFormComponentTest.spec.js b/tests/unit/LoginFormComponentTest.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..1f892f5e720420f3aae91f6f3716955cd3b7daf2 --- /dev/null +++ b/tests/unit/LoginFormComponentTest.spec.js @@ -0,0 +1,16 @@ +import { shallowMount } from "@vue/test-utils"; +import LoginForm from "@/components/LoginForm"; + +describe("Tests labels in LoginForm component", () => { + it("checks the E-post label", () => { + const wrapper = shallowMount(LoginForm); + + expect(wrapper.find('#emailLabelId').text()).toMatch("E-post"); + }); + + it("checks the password label", () => { + const wrapper = shallowMount(LoginForm); + + expect(wrapper.find('#passwordLabelId').text()).toMatch("Passord"); + }); +}); diff --git a/tests/unit/example.spec.js b/tests/unit/example.spec.js deleted file mode 100644 index 0811d8a88a7c35b1a7d0c87f283815edbdbc12e8..0000000000000000000000000000000000000000 --- a/tests/unit/example.spec.js +++ /dev/null @@ -1,12 +0,0 @@ -import { shallowMount } from "@vue/test-utils"; -import HelloWorld from "@/components/HelloWorld.vue"; - -describe("HelloWorld.vue", () => { - it("renders props.msg when passed", () => { - const msg = "new message"; - const wrapper = shallowMount(HelloWorld, { - props: { msg }, - }); - expect(wrapper.text()).toMatch(msg); - }); -});