Merged PR 3: Added client to call backend authentication logic

Added client to call backend authentication logic
This commit is contained in:
Dominic Villemure
2024-03-12 02:53:29 +00:00
12 changed files with 233 additions and 276 deletions

View File

@@ -1,3 +1,7 @@
{ {
"recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"] "recommendations": [
} "Vue.volar",
"dbaeumer.vscode-eslint",
"vuetifyjs.vuetify-vscode"
]
}

View File

@@ -1,6 +1,7 @@
{ {
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.fixAll.eslint": true "source.fixAll": "always",
"source.organizeImports": "always"
}, },
"eslint.validate": [ "eslint.validate": [
"javascript" "javascript"

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 {
//todo: Need to have the baseURL in the config for later ( dev and prod env. )
install: (app) => {
const axiosInstance = axios.create({
baseURL: 'https://localhost:5001/',
timeout: 2000,
});
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);
}
};
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,66 @@
<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 </template>
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 <script setup>
here</RouterLink> to create one! </p> import { useClient } from '@/plugins/clientPlugin';
</div> import { useAuthStore } from '@/plugins/store/authStore';
</div> import { ref } from 'vue';
import { useRouter } from 'vue-router';
const authStore = useAuthStore();
const client = useClient();
const router = useRouter()
<div class="main-right"> let user = ref({});
<div class="p-12 bg-white border border-gray-200 rounded-lg"> let errorSnackBar = ref(false);
<form class="space-y-6">
<div> const goHome = () => {
<label>E-mail</label> <br> router.push('/');
<input type="email" placeholder="Your e-mail adress" }
class="w-full mt-2 py-4 px-6 border border-gray-200 rounded-lg">
</div> async function login(){
try {
await authStore.login(client, user.value.email, user.value.password)
router.push('/');
} catch (error) {
errorSnackBar.value = true;
}
}
<div> </script>
<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>

View File

@@ -13,14 +13,61 @@
</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 { useClient } from '@/plugins/clientPlugin';
import { ref } from 'vue';
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);
itemList.value = orderedResponseItems
} catch (error) {
errorNoAccessSnackBar.value = true;
}
}
const goToLoginPage = () => {
router.push('/login');
}
</script>