Added login and register view and removed vue boilerplate
parent
8ba822814b
commit
c9cde25d3b
@ -0,0 +1 @@
|
|||||||
|
VITE_BASE_API_URL=/api
|
||||||
@ -1,15 +1,22 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { RouterLink, RouterView } from 'vue-router'
|
import { RouterLink, RouterView } from 'vue-router'
|
||||||
import HelloWorld from './components/HelloWorld.vue'
|
|
||||||
import NavBar from './components/NavBar.vue'
|
import NavBar from './components/NavBar.vue'
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NavBar/>
|
<NavBar />
|
||||||
|
|
||||||
<RouterView />
|
<RouterView />
|
||||||
</template>
|
</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>
|
</style>
|
||||||
|
|||||||
Binary file not shown.
@ -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 { createApp } from 'vue'
|
||||||
|
import { createPinia } from 'pinia'
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import router from './router'
|
import router from './router'
|
||||||
|
|
||||||
|
import 'bootstrap/dist/css/bootstrap.css';
|
||||||
|
|
||||||
|
const pinia = createPinia()
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
|
|
||||||
app.use(router)
|
app.use(router)
|
||||||
|
app.use(pinia)
|
||||||
|
|
||||||
app.mount('#app')
|
app.mount('#app')
|
||||||
|
|
||||||
|
import "bootstrap/dist/js/bootstrap.js";
|
||||||
@ -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…
Reference in New Issue