Added login and register view and removed vue boilerplate

main
Filip Borum Poulsen 3 years ago
parent 8ba822814b
commit c9cde25d3b

@ -0,0 +1 @@
VITE_BASE_API_URL=/api

@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Fitness World</title>
</head>
<body>

@ -8,6 +8,8 @@
"name": "merit-opgave-client",
"version": "0.0.0",
"dependencies": {
"bootstrap": "^5.2.3",
"pinia": "^2.0.34",
"vue": "^3.2.47",
"vue-router": "^4.1.6"
},
@ -384,6 +386,16 @@
"node": ">=12"
}
},
"node_modules/@popperjs/core": {
"version": "2.11.7",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.7.tgz",
"integrity": "sha512-Cr4OjIkipTtcXKjAsm8agyleBuDHvxzeBoa1v543lbv1YaIwQjESsVcmjiWiPEbC1FIeHOG/Op9kdCmAmiS3Kw==",
"peer": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/popperjs"
}
},
"node_modules/@types/node": {
"version": "18.15.11",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.11.tgz",
@ -645,6 +657,24 @@
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
},
"node_modules/bootstrap": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.2.3.tgz",
"integrity": "sha512-cEKPM+fwb3cT8NzQZYEu4HilJ3anCrWqh3CHAok1p9jXqMPsPTBhU25fBckEJHJ/p+tTxTFTsFQGM+gaHpi3QQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/twbs"
},
{
"type": "opencollective",
"url": "https://opencollective.com/bootstrap"
}
],
"peerDependencies": {
"@popperjs/core": "^2.11.6"
}
},
"node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@ -1541,6 +1571,56 @@
"node": ">=4"
}
},
"node_modules/pinia": {
"version": "2.0.34",
"resolved": "https://registry.npmjs.org/pinia/-/pinia-2.0.34.tgz",
"integrity": "sha512-cgOoGUiyqX0SSgX8XelK9+Ri4XA2/YyNtgjogwfzIx1g7iZTaZPxm7/bZYMCLU2qHRiHhxG7SuQO0eBacFNc2Q==",
"dependencies": {
"@vue/devtools-api": "^6.5.0",
"vue-demi": "*"
},
"funding": {
"url": "https://github.com/sponsors/posva"
},
"peerDependencies": {
"@vue/composition-api": "^1.4.0",
"typescript": ">=4.4.4",
"vue": "^2.6.14 || ^3.2.0"
},
"peerDependenciesMeta": {
"@vue/composition-api": {
"optional": true
},
"typescript": {
"optional": true
}
}
},
"node_modules/pinia/node_modules/vue-demi": {
"version": "0.14.0",
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.0.tgz",
"integrity": "sha512-gt58r2ogsNQeVoQ3EhoUAvUsH9xviydl0dWJj7dabBC/2L4uBId7ujtCwDRD0JhkGsV1i0CtfLAeyYKBht9oWg==",
"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/postcss": {
"version": "8.4.22",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.22.tgz",
@ -1866,7 +1946,7 @@
"version": "4.8.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz",
"integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==",
"dev": true,
"devOptional": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"

@ -10,6 +10,8 @@
"type-check": "vue-tsc --noEmit"
},
"dependencies": {
"bootstrap": "^5.2.3",
"pinia": "^2.0.34",
"vue": "^3.2.47",
"vue-router": "^4.1.6"
},

@ -1,15 +1,22 @@
<script setup lang="ts">
import { RouterLink, RouterView } from 'vue-router'
import HelloWorld from './components/HelloWorld.vue'
import NavBar from './components/NavBar.vue'
</script>
<template>
<NavBar/>
<NavBar />
<RouterView />
</template>
<style scoped>
<style>
@font-face {
font-family: "noto-sans";
src: url("./assets/elevio-noto-sans-400.woff");
}
body {
font-family: "noto-sans", Verdana, Geneva, sans-serif;
margin: 0px;
}
</style>

@ -1,40 +0,0 @@
<script setup lang="ts">
defineProps<{
msg: string
}>()
</script>
<template>
<div class="greetings">
<h1 class="green">{{ msg }}</h1>
<h3>
Youve successfully created a project with
<a href="https://vitejs.dev/" target="_blank" rel="noopener">Vite</a> +
<a href="https://vuejs.org/" target="_blank" rel="noopener">Vue 3</a>. What's next?
</h3>
</div>
</template>
<style scoped>
h1 {
font-weight: 500;
font-size: 2.6rem;
top: -10px;
}
h3 {
font-size: 1.2rem;
}
.greetings h1,
.greetings h3 {
text-align: center;
}
@media (min-width: 1024px) {
.greetings h1,
.greetings h3 {
text-align: left;
}
}
</style>

@ -1,24 +1,50 @@
<script setup lang="ts">
import { RouterLink } from 'vue-router'
import { useLoginStore } from '@/stores/login'
import { storeToRefs } from "pinia";
const loginStore = useLoginStore();
const { loggedIn } = storeToRefs(loginStore);
</script>
<template>
<div class="NavBar">
<img alt="Fitness World Logo" class="logo" src="@/assets/logo.svg" width="125" />
<div class="links">
<RouterLink to="/about">test</RouterLink>
<div class="NavBar">
<div class="NavBarContent">
<RouterLink to="/">
<img alt="Fitness World Logo" class="logo" src="@/assets/logo.svg" width="125" />
</RouterLink>
<div class="links">
<!-- <RouterLink to="/center">Fitness center</RouterLink>
<RouterLink to="/trainer">Personlig træner</RouterLink> -->
</div>
<div class="buttons">
<RouterLink to="/login" v-if="!loggedIn">Login</RouterLink>
</div>
</div>
</div>
</div>
</template>
<style scoped>
.NavBar {
width: 100vw;
position: absolute;
/* position: absolute;
top: 0;
left: 0;
right: 0;
right: 0; */
}
.NavBarContent {
display: flex;
justify-content: space-between;
padding: 20px;
align-items: center;
}
a {
text-decoration: inherit;
color: inherit;
margin: 0px 10px;
font-size: 1.5em;
font-weight: 500;
}
</style>

@ -1,86 +0,0 @@
<template>
<div class="item">
<i>
<slot name="icon"></slot>
</i>
<div class="details">
<h3>
<slot name="heading"></slot>
</h3>
<slot></slot>
</div>
</div>
</template>
<style scoped>
.item {
margin-top: 2rem;
display: flex;
}
.details {
flex: 1;
margin-left: 1rem;
}
i {
display: flex;
place-items: center;
place-content: center;
width: 32px;
height: 32px;
color: var(--color-text);
}
h3 {
font-size: 1.2rem;
font-weight: 500;
margin-bottom: 0.4rem;
color: var(--color-heading);
}
@media (min-width: 1024px) {
.item {
margin-top: 0;
padding: 0.4rem 0 1rem calc(var(--section-gap) / 2);
}
i {
top: calc(50% - 25px);
left: -26px;
position: absolute;
border: 1px solid var(--color-border);
background: var(--color-background);
border-radius: 8px;
width: 50px;
height: 50px;
}
.item:before {
content: ' ';
border-left: 1px solid var(--color-border);
position: absolute;
left: 0;
bottom: calc(50% + 25px);
height: calc(50% - 25px);
}
.item:after {
content: ' ';
border-left: 1px solid var(--color-border);
position: absolute;
left: 0;
top: calc(50% + 25px);
height: calc(50% - 25px);
}
.item:first-of-type:before {
display: none;
}
.item:last-of-type:after {
display: none;
}
}
</style>

@ -1,9 +1,16 @@
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
import 'bootstrap/dist/css/bootstrap.css';
const pinia = createPinia()
const app = createApp(App)
app.use(router)
app.use(pinia)
app.mount('#app')
import "bootstrap/dist/js/bootstrap.js";

@ -1,5 +1,5 @@
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
import Home from '../views/Home.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
@ -7,15 +7,23 @@ const router = createRouter({
{
path: '/',
name: 'home',
component: HomeView
component: Home
},
{
path: '/about',
name: 'about',
path: '/login',
name: 'login',
// route level code-splitting
// this generates a separate chunk (About.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import('../views/AboutView.vue')
component: () => import('../views/Login.vue')
},
{
path: '/register',
name: 'register',
// route level code-splitting
// this generates a separate chunk (About.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import('../views/Register.vue')
}
]
})

@ -0,0 +1,12 @@
import { defineStore } from 'pinia'
export const useLoginStore = defineStore('login', {
state: () => {
return { loggedIn: false }
},
actions: {
setLoginState(login: boolean) {
this.loggedIn = login;
}
},
})

@ -1,15 +0,0 @@
<template>
<div class="about">
<h1>This is an about page</h1>
</div>
</template>
<style>
@media (min-width: 1024px) {
.about {
min-height: 100vh;
display: flex;
align-items: center;
}
}
</style>

@ -0,0 +1,3 @@
<template>
</template>

@ -1,9 +0,0 @@
<script setup lang="ts">
// import TheWelcome from '../components/TheWelcome.vue'
</script>
<template>
<main>
<TheWelcome />
</main>
</template>

@ -0,0 +1,141 @@
<script lang="ts">
import { RouterLink } from 'vue-router'
import { useLoginStore } from '@/stores/login'
export default {
name: "Login",
data() {
return {
email: "",
emailBlured: false,
emailError: "",
password: "",
passwordBlured: false,
passwordError: ""
}
},
setup() {
const loginStore = useLoginStore();
const { setLoginState } = loginStore;
return { setLoginState };
},
methods: {
async submit() {
this.emailError = "";
this.passwordError = "";
const res = await fetch(`${import.meta.env.VITE_BASE_API_URL}/login`, {
method: "POST",
body: JSON.stringify({ password: this.password, email: this.email }),
headers: {
"Content-Type": "Application/json"
}
});
if (res.status === 400) {
const errors = await res.json();
console.log(errors);
for (const error of errors) {
if (error.path?.[0] === "email") {
this.emailError = error.message;
}
else if (error.path?.[0] === "password") {
this.passwordError = error.message;
}
else if (error.type === "login.invalid") {
this.passwordError = "The username or password was incorrect";
}
}
} else if (res.status === 204) {
this.setLoginState(true);
if (this.$route.query.ref) {
this.$router.push({ path: this.$route.query.ref.toString() });
} else {
this.$router.push({ path: '/' });
}
}
}
}
}
</script>
<template>
<div class="container mt-5">
<div class="row d-flex justify-content-center">
<div class="col-md-6">
<div class="card px-5 py-5" id="form1">
<div class="form-data">
<div class="forms-inputs mb-4">
<span>Email</span>
<input autocomplete="off" type="email" v-model="email"
v-bind:class="{ 'form-control': true, 'is-invalid': emailError }"
v-on:blur="emailError = ''" v-on:keypress="if ($event.key === 'Enter') submit();">
<div class="invalid-feedback">{{ emailError }}</div>
</div>
<div class="forms-inputs mb-4">
<span>Password</span>
<input autocomplete="off" type="password" v-model="password"
v-bind:class="{ 'form-control': true, 'is-invalid': passwordError }"
v-on:blur="passwordError = ''" v-on:keypress="if ($event.key === 'Enter') submit();">
<div class="invalid-feedback">{{ passwordError }}</div>
</div>
<RouterLink class="mb-3" to="/register">
<p>Don't have an account yet?</p>
</RouterLink>
<div class="mb-3">
<button v-on:click.stop.prevent="submit" class="btn btn-dark w-100">Login</button>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<style scoped>
a {
color: inherit;
}
.card {
border: none;
height: 320px
}
.forms-inputs {
position: relative
}
.forms-inputs span {
position: absolute;
top: -18px;
left: 10px;
background-color: #fff;
padding: 5px 10px;
font-size: 15px
}
.forms-inputs input {
height: 50px;
border: 2px solid #eee
}
.forms-inputs input:focus {
box-shadow: none;
outline: none;
border: 2px solid #000
}
.btn {
height: 50px
}
.success-data {
display: flex;
flex-direction: column
}
.bxs-badge-check {
font-size: 90px
}
</style>

@ -0,0 +1,163 @@
<script lang="ts">
import { RouterLink } from 'vue-router'
import { useLoginStore } from '@/stores/login'
export default {
name: "Register",
data() {
return {
firstname: "",
firstnameError: "",
lastname: "",
lastnameError: "",
email: "",
emailError: "",
password: "",
passwordError: ""
}
},
setup() {
const loginStore = useLoginStore();
const { setLoginState } = loginStore;
return { setLoginState };
},
methods: {
async submit() {
this.emailError = "";
this.passwordError = "";
const res = await fetch(`${import.meta.env.VITE_BASE_API_URL}/register`, {
method: "POST",
body: JSON.stringify({
password: this.password,
email: this.email,
firstname: this.firstname,
lastname: this.lastname
}),
headers: {
"Content-Type": "Application/json"
}
});
if (res.status === 400) {
const errors = await res.json();
console.log(errors);
for (const error of errors) {
if (error.path?.[0] === "email") {
this.emailError = error.message;
}
else if (error.path?.[0] === "password") {
this.passwordError = error.message;
}
else if (error.path?.[0] === "firstname") {
this.firstnameError = error.message;
}
else if (error.path?.[0] === "lastname") {
this.lastnameError = error.message;
}
}
} else if (res.status === 204) {
if (this.$route.query.ref) {
this.$router.push({ path: this.$route.query.ref.toString() });
} else {
this.$router.push({ path: '/' });
}
}
}
}
}
</script>
<template>
<div class="container mt-5">
<div class="row d-flex justify-content-center">
<div class="col-md-6">
<div class="card px-5 py-5" id="form1">
<div class="form-data">
<div class="forms-inputs mb-4">
<span>Firstname</span>
<input autocomplete="off" type="text" v-model="firstname"
v-bind:class="{ 'form-control': true, 'is-invalid': firstnameError }"
v-on:blur="firstnameError = ''" v-on:keypress="if ($event.key === 'Enter') submit();">
<div class="invalid-feedback">{{ firstnameError }}</div>
</div>
<div class="forms-inputs mb-4">
<span>Lastname</span>
<input autocomplete="off" type="text" v-model="lastname"
v-bind:class="{ 'form-control': true, 'is-invalid': lastnameError }"
v-on:blur="lastnameError = ''" v-on:keypress="if ($event.key === 'Enter') submit();">
<div class="invalid-feedback">{{ lastnameError }}</div>
</div>
<div class="forms-inputs mb-4">
<span>Email</span>
<input autocomplete="off" type="email" v-model="email"
v-bind:class="{ 'form-control': true, 'is-invalid': emailError }"
v-on:blur="emailError = ''" v-on:keypress="if ($event.key === 'Enter') submit();">
<div class="invalid-feedback">{{ emailError }}</div>
</div>
<div class="forms-inputs mb-4">
<span>Password</span>
<input autocomplete="off" type="password" v-model="password"
v-bind:class="{ 'form-control': true, 'is-invalid': passwordError }"
v-on:blur="passwordError = ''" v-on:keypress="if ($event.key === 'Enter') submit();">
<div class="invalid-feedback">{{ passwordError }}</div>
</div>
<RouterLink class="mb-3" to="/login">
<p>Already have an account?</p>
</RouterLink>
<div class="mb-3">
<button v-on:click.stop.prevent="submit" class="btn btn-dark w-100">Login</button>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<style scoped>
a {
color: inherit;
}
.card {
border: none;
height: 320px
}
.forms-inputs {
position: relative
}
.forms-inputs span {
position: absolute;
top: -18px;
left: 10px;
background-color: #fff;
padding: 5px 10px;
font-size: 15px
}
.forms-inputs input {
height: 50px;
border: 2px solid #eee
}
.forms-inputs input:focus {
box-shadow: none;
outline: none;
border: 2px solid #000
}
.btn {
height: 50px
}
.success-data {
display: flex;
flex-direction: column
}
.bxs-badge-check {
font-size: 90px
}
</style>
Loading…
Cancel
Save