update du access token et update des cards

This commit is contained in:
Guamss 2025-12-11 22:40:02 +01:00
parent b36e3981ba
commit d2b8c5ade4
4 changed files with 184 additions and 33 deletions

View File

@ -11,11 +11,13 @@ const { isAuthenticated, logout } = useAuth();
const toastService = useToast(); const toastService = useToast();
const authenticationService = new AuthenticationService(); const authenticationService = new AuthenticationService();
const { login } = useAuth();
const fetchUser = async () => { const fetchUser = async () => {
if (isAuthenticated.value) { if (isAuthenticated.value) {
try { try {
authenticatedUser.value = await authenticationService.findMe(); authenticatedUser.value = await authenticationService.findMe();
} catch (error) { } catch (error: any) {
console.error(error); console.error(error);
} }
} else { } else {
@ -68,6 +70,11 @@ function handleLogout() {
<style> <style>
@import "styles.css"; @import "styles.css";
.nav-links-div {
display: flex;
gap: 10em;
}
.logout { .logout {
cursor: pointer; cursor: pointer;
} }
@ -131,8 +138,17 @@ nav {
justify-content: space-around; justify-content: space-around;
} }
.nav-links-div { @media (max-width: 820px) {
nav {
display: flex; display: flex;
gap: 10em; flex-direction: column;
}
.nav-links-div {
display: flex;
justify-content: flex-start;
gap: 0;
flex-direction: column;
}
} }
</style> </style>

View File

@ -1,6 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import type { Book } from "@/models/book"; import type { Book } from "@/models/book";
import { BookService } from "@/services/book.service"; import { BookService } from "@/services/book.service";
import { convertState } from "@/utils";
import { onMounted, ref } from "vue"; import { onMounted, ref } from "vue";
const API_URL = import.meta.env.VITE_MBL_API_URL const API_URL = import.meta.env.VITE_MBL_API_URL
@ -10,10 +11,23 @@ const bookService = new BookService();
onMounted(async () => { onMounted(async () => {
books.value = await bookService.findBooks(); books.value = await bookService.findBooks();
books.value.map((book) => {
book.added_at = new Date(book.added_at);
});
books.value.sort((a, b) => {
if (a.updated_at > b.updated_at) {
return -1;
} else if (a.updated_at < b.updated_at) {
return 1;
}
return 0;
})
}); });
</script> </script>
<template> <template>
<main class="cards">
<div v-for="book in books" class="card"> <div v-for="book in books" class="card">
<img :src="API_URL + book.illustration" class="image"></img> <img :src="API_URL + book.illustration" class="image"></img>
<div class="content"> <div class="content">
@ -21,16 +35,58 @@ onMounted(async () => {
<span class="title">{{ book.title }}</span> <span class="title">{{ book.title }}</span>
</a> </a>
<p class="desc"> <p class="desc">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Recusandae dolores, possimus Livre écrit par <strong>{{ book.author }}</strong>
pariatur animi temporibus nesciunt praesentium
</p> </p>
<p v-if="book.state === 'COMPLETED' || book.state === 'DROPPED'">Note {{ book.note }}/5</p>
<div class="state-div">
<p>{{ convertState(book.state) }}</p>
<div :class="book.state" class="state-indicator"></div>
</div> </div>
</div> </div>
</div>
</main>
</template> </template>
<style scoped> <style scoped>
.COMPLETED {
background-color: #2d4276;
}
.PLAN {
background-color: #747474;
}
.READING {
background-color: #338543;
}
.DROPPED {
background-color: #832f30;
}
.state-div {
display: flex;
align-items: center;
flex-direction: column;
}
.state-indicator {
width: 100%;
height: 10px;
border-radius: 25px;
}
.cards {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 2em;
}
.card { .card {
max-width: 300px; max-width: 300px;
min-width: 300px;;
border-radius: 0.5rem; border-radius: 0.5rem;
background-color: light-dark(#efedea, #282828); background-color: light-dark(#efedea, #282828);
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
@ -46,6 +102,7 @@ onMounted(async () => {
} }
.image { .image {
border-radius: 0.5rem;
object-fit: cover; object-fit: cover;
width: 100%; width: 100%;
height: 150px; height: 150px;

View File

@ -1,33 +1,100 @@
import type { User } from "@/models/user"; import type { User } from "@/models/user";
import axios from "axios"; import axios, { type AxiosInstance, type AxiosError } from "axios";
export class AuthenticationService { export class AuthenticationService {
private client: AxiosInstance;
constructor() { constructor() {
const apiURL = import.meta.env.VITE_MBL_API_URL; const apiURL = import.meta.env.VITE_MBL_API_URL;
axios.defaults.baseURL = `${apiURL}/api/`;
this.client = axios.create({
baseURL: `${apiURL}/api/`,
headers: {
"Content-Type": "application/json",
},
});
this.setupInterceptors();
} }
private setupInterceptors() {
this.client.interceptors.request.use(
(config) => {
const token = sessionStorage.getItem("access");
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => Promise.reject(error)
);
this.client.interceptors.response.use(
(response) => response,
async (error: AxiosError) => {
const originalRequest = error.config as any;
if (error.response?.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
try {
const newData = await this.refreshToken();
sessionStorage.setItem("access", newData.access);
originalRequest.headers.Authorization = `Bearer ${newData.access}`;
return this.client(originalRequest);
} catch (refreshError) {
console.error("Session expirée, impossible de rafraîchir.");
sessionStorage.removeItem("access");
localStorage.removeItem("refresh");
return Promise.reject(refreshError);
}
}
return Promise.reject(error);
}
);
}
async findUsers() { async findUsers() {
return await axios.get('users').then((res) => { return await this.client.get("users").then((res) => {
return res.data as User return res.data as User;
}) });
} }
async findMe() { async findMe() {
const headers = { 'Authorization': `Bearer ${sessionStorage.getItem('access')}` };
return await axios return await this.client
.get('profile', { headers }) .get("profile")
.then((res) => { .then((res) => {
return res.data as User; return res.data as User;
}) })
.catch((error) => { .catch((error) => {
throw error; throw error;
});
}
async refreshToken() {
const refreshToken = localStorage.getItem("refresh");
const apiURL = import.meta.env.VITE_MBL_API_URL;
return await axios
.post(`${apiURL}/api/token/refresh/`, {
refresh: refreshToken,
}) })
.then((res) => {
return res.data;
})
.catch((error) => {
throw error;
});
} }
async authenticate(username: string, password: string) { async authenticate(username: string, password: string) {
return await axios return await this.client
.post('token/', { .post("token/", {
username: username, username: username,
password: password, password: password,
}) })
@ -36,6 +103,6 @@ export class AuthenticationService {
}) })
.catch((error) => { .catch((error) => {
throw error; throw error;
}) });
} }
} }

11
src/utils.ts Normal file
View File

@ -0,0 +1,11 @@
export function convertState(state: string) {
if (state === 'COMPLETED') {
return "Complété"
} else if (state === 'PLAN') {
return "À lire"
} else if (state === 'READING') {
return "En lecture"
} else if (state === 'DROPPED') {
return "Lâché"
}
}