Added client to call backend authentication logic
This commit is contained in:
5
.vscode/extensions.json
vendored
5
.vscode/extensions.json
vendored
@@ -1,3 +1,6 @@
|
||||
{
|
||||
"recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
|
||||
"recommendations": [
|
||||
"Vue.volar",
|
||||
"Vue.vscode-typescript-vue-plugin"
|
||||
]
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
# Hutopia
|
||||
|
||||
This template should help get you started developing with Vue 3 in Vite.
|
||||
Hutopia frontEnd. Using vue3 and vuetify3.
|
||||
|
||||
## Recommended IDE Setup
|
||||
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
<script setup>
|
||||
defineProps({
|
||||
msg: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="greetings">
|
||||
<h1 class="green">{{ msg }}</h1>
|
||||
<h3>
|
||||
You’ve 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>
|
||||
@@ -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>
|
||||
|
||||
Vue’s
|
||||
<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>
|
||||
@@ -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>
|
||||
@@ -35,12 +35,16 @@
|
||||
</svg>
|
||||
</router-link>
|
||||
</div>
|
||||
<div v-if="user">
|
||||
Hello, {{ user.email }}
|
||||
</div>
|
||||
<div class="menu-right">
|
||||
<router-link :to="{ name: 'yourprofile' }">
|
||||
|
||||
<img src="/images/anonyme.png" class="img-small mr-2 logob rounded-full img-small" alt="Logo">
|
||||
</router-link>
|
||||
|
||||
</div>
|
||||
<v-btn color="red" variant="text" @click="logout()">Logout</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
@@ -50,10 +54,20 @@
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'App',
|
||||
};
|
||||
<script setup>
|
||||
import { useAuthStore } from '@/plugins/store/authStore';
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const authStore = useAuthStore();
|
||||
const router = useRouter()
|
||||
|
||||
|
||||
const logout = () => {
|
||||
authStore.logout();
|
||||
router.push('/login');
|
||||
}
|
||||
|
||||
const user = authStore.user;
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@@ -112,4 +126,4 @@ export default {
|
||||
/* Réduire la marge entre le logo et le texte */
|
||||
|
||||
}</style>
|
||||
|
||||
@/plugins/store/authStore
|
||||
@@ -3,24 +3,25 @@ import { createApp } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import axios from 'axios'
|
||||
import '@mdi/font/css/materialdesignicons.css'
|
||||
import 'vuetify/styles'
|
||||
import { createVuetify } from 'vuetify'
|
||||
import * as components from 'vuetify/components'
|
||||
import * as directives from 'vuetify/directives'
|
||||
import clientPlugin from './plugins/clientPlugin'
|
||||
|
||||
const vuetify = createVuetify({
|
||||
components,
|
||||
directives
|
||||
});
|
||||
|
||||
axios.defaults.baseURL = 'http://127.0.0.1:8000';
|
||||
|
||||
const app = createApp(App);
|
||||
|
||||
// Create an axios client preconfigured to the Hutopy API.
|
||||
app.use(clientPlugin);
|
||||
|
||||
app.use(createPinia());
|
||||
app.use(vuetify);
|
||||
app.use(router, axios);
|
||||
app.use(router);
|
||||
|
||||
app.mount('#app')
|
||||
|
||||
34
src/plugins/clientPlugin.js
Normal file
34
src/plugins/clientPlugin.js
Normal 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;
|
||||
}
|
||||
55
src/plugins/store/authStore.js
Normal file
55
src/plugins/store/authStore.js
Normal 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);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1,47 +1,68 @@
|
||||
<template>
|
||||
<div class="max-w-7x1 max-auto grid grid-cols-2 gap-4">
|
||||
<div class="main-left">
|
||||
<div class="p-12 bg-white border border-gray-200 rounded-lg">
|
||||
<h1 class="mb-6 text-2xl font-bold">Log in</h1>
|
||||
<v-row align="center" justify="center">
|
||||
<v-col cols="4">
|
||||
<v-container>
|
||||
<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.
|
||||
Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer
|
||||
took a galley of type and scrambled it to make a type specimen book. It has survived not only five
|
||||
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>
|
||||
</v-container>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
||||
@@ -13,14 +13,63 @@
|
||||
|
||||
</div>
|
||||
|
||||
<v-btn variant="outlined">
|
||||
Button
|
||||
<v-btn variant="outlined" @click="callBackend()">
|
||||
Get items
|
||||
</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>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
<script async setup>
|
||||
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>
|
||||
Reference in New Issue
Block a user