Added client to call backend authentication logic

This commit is contained in:
Dominic Villemure
2024-03-11 12:16:13 -04:00
parent a4765405c3
commit e7a14fe380
11 changed files with 233 additions and 274 deletions

View File

@@ -1,3 +1,6 @@
{ {
"recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"] "recommendations": [
"Vue.volar",
"Vue.vscode-typescript-vue-plugin"
]
} }

View File

@@ -1,6 +1,6 @@
# Hutopia # Hutopia
This template should help get you started developing with Vue 3 in Vite. Hutopia frontEnd. Using vue3 and vuetify3.
## Recommended IDE Setup ## Recommended IDE Setup

View File

@@ -1,44 +0,0 @@
<script setup>
defineProps({
msg: {
type: String,
required: true
}
})
</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>.
</h3>
</div>
</template>
<style scoped>
h1 {
font-weight: 500;
font-size: 2.6rem;
position: relative;
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>

View File

@@ -1,88 +0,0 @@
<script setup>
import WelcomeItem from './WelcomeItem.vue'
import DocumentationIcon from './icons/IconDocumentation.vue'
import ToolingIcon from './icons/IconTooling.vue'
import EcosystemIcon from './icons/IconEcosystem.vue'
import CommunityIcon from './icons/IconCommunity.vue'
import SupportIcon from './icons/IconSupport.vue'
</script>
<template>
<WelcomeItem>
<template #icon>
<DocumentationIcon />
</template>
<template #heading>Documentation</template>
Vues
<a href="https://vuejs.org/" target="_blank" rel="noopener">official documentation</a>
provides you with all information you need to get started.
</WelcomeItem>
<WelcomeItem>
<template #icon>
<ToolingIcon />
</template>
<template #heading>Tooling</template>
This project is served and bundled with
<a href="https://vitejs.dev/guide/features.html" target="_blank" rel="noopener">Vite</a>. The
recommended IDE setup is
<a href="https://code.visualstudio.com/" target="_blank" rel="noopener">VSCode</a> +
<a href="https://github.com/johnsoncodehk/volar" target="_blank" rel="noopener">Volar</a>. If
you need to test your components and web pages, check out
<a href="https://www.cypress.io/" target="_blank" rel="noopener">Cypress</a> and
<a href="https://on.cypress.io/component" target="_blank" rel="noopener"
>Cypress Component Testing</a
>.
<br />
More instructions are available in <code>README.md</code>.
</WelcomeItem>
<WelcomeItem>
<template #icon>
<EcosystemIcon />
</template>
<template #heading>Ecosystem</template>
Get official tools and libraries for your project:
<a href="https://pinia.vuejs.org/" target="_blank" rel="noopener">Pinia</a>,
<a href="https://router.vuejs.org/" target="_blank" rel="noopener">Vue Router</a>,
<a href="https://test-utils.vuejs.org/" target="_blank" rel="noopener">Vue Test Utils</a>, and
<a href="https://github.com/vuejs/devtools" target="_blank" rel="noopener">Vue Dev Tools</a>. If
you need more resources, we suggest paying
<a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">Awesome Vue</a>
a visit.
</WelcomeItem>
<WelcomeItem>
<template #icon>
<CommunityIcon />
</template>
<template #heading>Community</template>
Got stuck? Ask your question on
<a href="https://chat.vuejs.org" target="_blank" rel="noopener">Vue Land</a>, our official
Discord server, or
<a href="https://stackoverflow.com/questions/tagged/vue.js" target="_blank" rel="noopener"
>StackOverflow</a
>. You should also subscribe to
<a href="https://news.vuejs.org" target="_blank" rel="noopener">our mailing list</a> and follow
the official
<a href="https://twitter.com/vuejs" target="_blank" rel="noopener">@vuejs</a>
twitter account for latest news in the Vue world.
</WelcomeItem>
<WelcomeItem>
<template #icon>
<SupportIcon />
</template>
<template #heading>Support Vue</template>
As an independent project, Vue relies on community backing for its sustainability. You can help
us by
<a href="https://vuejs.org/sponsor/" target="_blank" rel="noopener">becoming a sponsor</a>.
</WelcomeItem>
</template>

View File

@@ -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;
position: relative;
}
.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>

View File

@@ -35,12 +35,16 @@
</svg> </svg>
</router-link> </router-link>
</div> </div>
<div v-if="user">
Hello, {{ user.email }}
</div>
<div class="menu-right"> <div class="menu-right">
<router-link :to="{ name: 'yourprofile' }"> <router-link :to="{ name: 'yourprofile' }">
<img src="/images/anonyme.png" class="img-small mr-2 logob rounded-full img-small" alt="Logo"> <img src="/images/anonyme.png" class="img-small mr-2 logob rounded-full img-small" alt="Logo">
</router-link> </router-link>
</div> </div>
<v-btn color="red" variant="text" @click="logout()">Logout</v-btn>
</div> </div>
</div> </div>
</nav> </nav>
@@ -50,10 +54,20 @@
</main> </main>
</template> </template>
<script> <script setup>
export default { import { useAuthStore } from '@/plugins/store/authStore';
name: 'App', import { useRouter } from 'vue-router'
};
const authStore = useAuthStore();
const router = useRouter()
const logout = () => {
authStore.logout();
router.push('/login');
}
const user = authStore.user;
</script> </script>
<style> <style>
@@ -112,4 +126,4 @@ export default {
/* Réduire la marge entre le logo et le texte */ /* Réduire la marge entre le logo et le texte */
}</style> }</style>
@/plugins/store/authStore

View File

@@ -3,24 +3,25 @@ import { createApp } from 'vue'
import { createPinia } from 'pinia' import { createPinia } from 'pinia'
import App from './App.vue' import App from './App.vue'
import router from './router' import router from './router'
import axios from 'axios'
import '@mdi/font/css/materialdesignicons.css' import '@mdi/font/css/materialdesignicons.css'
import 'vuetify/styles' import 'vuetify/styles'
import { createVuetify } from 'vuetify' import { createVuetify } from 'vuetify'
import * as components from 'vuetify/components' import * as components from 'vuetify/components'
import * as directives from 'vuetify/directives' import * as directives from 'vuetify/directives'
import clientPlugin from './plugins/clientPlugin'
const vuetify = createVuetify({ const vuetify = createVuetify({
components, components,
directives directives
}); });
axios.defaults.baseURL = 'http://127.0.0.1:8000';
const app = createApp(App); const app = createApp(App);
// Create an axios client preconfigured to the Hutopy API.
app.use(clientPlugin);
app.use(createPinia()); app.use(createPinia());
app.use(vuetify); app.use(vuetify);
app.use(router, axios); app.use(router);
app.mount('#app') app.mount('#app')

View File

@@ -0,0 +1,34 @@
import axios from "axios";
import { inject } from 'vue';
const clientKey = Symbol('client');
export default {
install: (app) => {
const axiosInstance = axios.create({
baseURL: 'https://localhost:5001/',
timeout: 5000,
});
axiosInstance.interceptors.request.use((config) => {
const token = localStorage.getItem('jwt');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
}, (error) => {
return Promise.reject(error);
});
app.provide(clientKey, axiosInstance);
console.log("client provided");
}
};
export function useClient() {
const client = inject(clientKey);
if (!client) {
throw new Error('Axios instance is not provided');
}
return client;
}

View File

@@ -0,0 +1,55 @@
import { defineStore } from 'pinia';
const baseUrl = '/api/Users';
export const useAuthStore = defineStore({
id: 'auth',
state: () => ({
user: null,
refreshTokenTimeout: null
}),
actions: {
async login(client, email, password) {
const requestBody = {
email: email,
password: password
};
try {
const response = await client.post(`${baseUrl}/login`, requestBody)
this.user = {
accessToken: response.data.accessToken,
refreshToken: response.data.refreshToken,
email: email
}
localStorage.setItem('jwt', this.user.accessToken);
this.startRefreshTokenTimer();
} catch (error) {
throw new Error('Login failed.')
}
},
logout() {
localStorage.setItem('jwt', '');
this.stopRefreshTokenTimer();
this.user = null;
},
async refreshToken(client) {
const response = await client.post(`${baseUrl}/refresh`, {});
this.user.accessToken = response.accessToken;
localStorage.setItem('jwt', this.user.accessToken);
this.startRefreshTokenTimer();
},
startRefreshTokenTimer() {
const timeout = 50 * 1000;
this.refreshTokenTimeout = setTimeout(this.refreshToken, timeout);
},
stopRefreshTokenTimer() {
clearTimeout(this.refreshTokenTimeout);
}
}
});

View File

@@ -1,47 +1,68 @@
<template> <template>
<div class="max-w-7x1 max-auto grid grid-cols-2 gap-4"> <v-row align="center" justify="center">
<div class="main-left"> <v-col cols="4">
<div class="p-12 bg-white border border-gray-200 rounded-lg"> <v-container>
<h1 class="mb-6 text-2xl font-bold">Log in</h1> <v-card>
<v-toolbar color="primary" :dark="true">
<v-toolbar-title>Login</v-toolbar-title>
</v-toolbar>
<v-card-text>
<v-form>
<v-text-field prepend-icon="person" type="text" v-model="user.email" label="E-mail"></v-text-field>
<v-text-field
prepend-icon="lock"
type="password"
v-model="user.password"
label="password"
></v-text-field>
</v-form>
<v-snackbar v-model="errorSnackBar">
Email ou mot de passe invalide.
<template v-slot:actions>
<v-btn color="red" variant="text" @click="errorSnackBar = false">
Fermer
</v-btn>
</template>
</v-snackbar>
</v-card-text>
<v-card-actions>
<div class="flex-grow-1"></div>
<v-btn color="primary" @click="login">Login</v-btn>
</v-card-actions>
</v-card>
<v-btn color="red" @click="goHome()">Continuer sans se connecter</v-btn>
<p class="mb-6 texte-gray-500"> Lorem Ipsum is simply dummy text of the printing and typesetting industry. </v-container>
Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer </v-col>
took a galley of type and scrambled it to make a type specimen book. It has survived not only five </v-row>
centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was
popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more
recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum. </p>
<p class="font-bold"> Don't have an account ? <RouterLink :to="{ 'name': 'signup' }" class="underline">Click
here</RouterLink> to create one! </p>
</div>
</div>
<div class="main-right">
<div class="p-12 bg-white border border-gray-200 rounded-lg">
<form class="space-y-6">
<div>
<label>E-mail</label> <br>
<input type="email" placeholder="Your e-mail adress"
class="w-full mt-2 py-4 px-6 border border-gray-200 rounded-lg">
</div>
<div>
<label>Password</label> <br>
<input type="password" placeholder="Your password"
class="w-full mt-2 py-4 px-6 border border-gray-200 rounded-lg">
</div>
<div>
<button class="py-4 px-6 bg-purple-600 text-white rounded-lg"> Log in</button>
</div>
</form>
</div>
</div>
</div>
</template> </template>
<script setup>
import { useAuthStore } from '@/plugins/store/authStore';
import { useClient } from '@/plugins/clientPlugin'
import { ref } from 'vue';
import { useRouter } from 'vue-router'
const authStore = useAuthStore();
const client = useClient();
const router = useRouter()
let user = ref({});
let errorSnackBar = ref(false);
const goHome = () => {
router.push('/');
}
async function login(){
try {
await authStore.login(client, user.value.email, user.value.password)
router.push('/');
} catch (error) {
console.log(error);
errorSnackBar.value = true;
}
}
</script>

View File

@@ -13,14 +13,63 @@
</div> </div>
<v-btn variant="outlined"> <v-btn variant="outlined" @click="callBackend()">
Button Get items
</v-btn> </v-btn>
<v-snackbar v-model="errorNoAccessSnackBar">
Vous n'etes pas connecter !
<template v-slot:actions>
<v-btn color="red" variant="text" @click="errorNoAccessSnackBar = false">
Fermer
</v-btn>
<v-btn color="green" variant="text" @click="goToLoginPage()">
Se connecter
</v-btn>
</template>
</v-snackbar>
<v-list lines="one">
<v-list-item
v-for="item in itemList"
:key="item"
:title="item.id"
:subtitle="item.title"
></v-list-item>
</v-list>
</main> </main>
</template> </template>
<script setup> <script async setup>
import DefaultLayout from '@/layouts/DefaultLayout.vue'; import DefaultLayout from '@/layouts/DefaultLayout.vue';
</script> import { ref } from 'vue'
import { useClient } from '@/plugins/clientPlugin';
import { useRouter } from 'vue-router'
const router = useRouter()
const client = useClient();
let itemList = ref([]);
let errorNoAccessSnackBar = ref(false);
async function callBackend() {
try {
const response = await client.get('/api/TodoItems?ListId=1&PageNumber=1&PageSize=10');
const responseItems = response.data.items;
const orderedResponseItems = responseItems.sort((a, b) => a.id - b.id);
console.log(orderedResponseItems);
itemList.value = orderedResponseItems
} catch (error) {
errorNoAccessSnackBar.value = true;
console.log(error);
}
}
const goToLoginPage = () => {
router.push('/login');
}
</script>