create book modal
This commit is contained in:
parent
41ea3ef924
commit
8d38708da4
@ -11,8 +11,6 @@ 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 {
|
||||||
|
|||||||
@ -1,29 +1,135 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { useAuth } from "@/composables/useAuth";
|
||||||
|
import type { Book } from "@/models/book";
|
||||||
|
import { User } from "@/models/user";
|
||||||
|
import { AuthenticationService } from "@/services/authentication.service";
|
||||||
|
import { BookService } from "@/services/book.service";
|
||||||
|
import { onMounted, ref } from "vue";
|
||||||
|
|
||||||
const props = defineProps<{ isActive: boolean }>();
|
const props = defineProps<{ isActive: boolean }>();
|
||||||
const emit = defineEmits(['closeBookFormModal'])
|
const emit = defineEmits(["closeBookFormModal", "newBook"]);
|
||||||
function sendBookData() {
|
|
||||||
console.log("PROUT")
|
const title = ref("");
|
||||||
emit('closeBookFormModal')
|
const author = ref("");
|
||||||
|
const state = ref("PLAN");
|
||||||
|
const note = ref<number>(0);
|
||||||
|
const illustrationFile = ref<File | null>(null);
|
||||||
|
const concernedUser = ref<User | undefined>(undefined);
|
||||||
|
|
||||||
|
const authenticationService = new AuthenticationService();
|
||||||
|
const bookService = new BookService();
|
||||||
|
const { isAuthenticated } = useAuth();
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
{
|
||||||
|
if (isAuthenticated.value) {
|
||||||
|
try {
|
||||||
|
concernedUser.value = await authenticationService.findMe();
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
concernedUser.value = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function handleFileUpload($event: Event) {
|
||||||
|
const target = $event.target as HTMLInputElement;
|
||||||
|
if (target && target.files) {
|
||||||
|
illustrationFile.value = target.files[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sendBookData() {
|
||||||
|
const form = new FormData();
|
||||||
|
form.append("title", title.value);
|
||||||
|
form.append("author", author.value);
|
||||||
|
form.append("note", `${note.value}`);
|
||||||
|
form.append("state", state.value);
|
||||||
|
form.append("illustration", illustrationFile.value!);
|
||||||
|
console.log(concernedUser.value)
|
||||||
|
if (concernedUser.value?.id) {
|
||||||
|
form.append("user", concernedUser.value.id.toString());
|
||||||
|
}
|
||||||
|
const newBook: Book = await bookService.createBook(form);
|
||||||
|
emit('newBook', newBook);
|
||||||
|
emit("closeBookFormModal");
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div :class="props.isActive ? 'opened-modal' : 'closed-modal'">
|
<div :class="props.isActive ? 'opened-modal' : 'closed-modal'">
|
||||||
<div class="overlay"></div>
|
<div class="overlay" @click="$emit('closeBookFormModal')"></div>
|
||||||
|
|
||||||
<div class="modale card">
|
<div class="modale card">
|
||||||
<button class="btn-close" @click="$emit('closeBookFormModal')">X</button>
|
<button class="btn-close" @click="$emit('closeBookFormModal')">X</button>
|
||||||
|
|
||||||
<h2>COUCOU</h2>
|
<h2>Ajouter un livre</h2>
|
||||||
|
|
||||||
<div class="actions">
|
<form @submit.prevent="sendBookData">
|
||||||
<button class="send" @keyup.enter="sendBookData" @click="sendBookData">prout</button>
|
<label for="title">Titre</label>
|
||||||
</div>
|
<input v-model="title" type="text" id="title" name="title" required />
|
||||||
|
|
||||||
|
<label for="author">Auteur</label>
|
||||||
|
<input v-model="author" type="text" id="author" name="author" required />
|
||||||
|
|
||||||
|
<label for="state">État</label>
|
||||||
|
<select v-model="state" id="state" name="state">
|
||||||
|
<option value="PLAN">À lire</option>
|
||||||
|
<option value="READING">En lecture</option>
|
||||||
|
<option value="COMPLETED">Complété</option>
|
||||||
|
<option value="DROPPED">Lâché</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<label for="note">Note</label>
|
||||||
|
<input v-model="note" type="number" id="note" name="note" max="5" min="0" />
|
||||||
|
|
||||||
|
<label for="illustration">Illustration</label>
|
||||||
|
<input
|
||||||
|
@change="handleFileUpload($event)"
|
||||||
|
id="illustration"
|
||||||
|
name="illustration"
|
||||||
|
accept="image/png, image/jpeg"
|
||||||
|
type="file"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="actions">
|
||||||
|
<button class="send" type="submit">Créer</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
input[type="file"]::file-selector-button {
|
||||||
|
border-radius: 0.5em;
|
||||||
|
margin-right: 8px;
|
||||||
|
border: none;
|
||||||
|
background: #1e90ff;
|
||||||
|
padding: 8px 12px;
|
||||||
|
color: #fff;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="file"]::file-selector-button:hover {
|
||||||
|
transition: 0.3s;
|
||||||
|
background-color: #176cc0;
|
||||||
|
}
|
||||||
|
|
||||||
|
form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
|
select {
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
.opened-modal {
|
.opened-modal {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
|||||||
@ -1,14 +1,62 @@
|
|||||||
import type { Book } from "@/models/book";
|
import type { Book } from "@/models/book";
|
||||||
import axios from "axios";
|
import axios, { type AxiosInstance, type AxiosError } from "axios";
|
||||||
|
|
||||||
export class BookService {
|
export class BookService {
|
||||||
|
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/books`;
|
|
||||||
|
this.client = axios.create({
|
||||||
|
baseURL: `${apiURL}/api/books`,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.setupInterceptors();
|
||||||
}
|
}
|
||||||
|
|
||||||
async findBooks() {
|
private setupInterceptors() {
|
||||||
return await axios
|
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 findBooks() {
|
||||||
|
return await this.client
|
||||||
.get("")
|
.get("")
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
return res.data as Book[];
|
return res.data as Book[];
|
||||||
@ -17,4 +65,30 @@ export class BookService {
|
|||||||
throw error;
|
throw error;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async createBook(formData: FormData) {
|
||||||
|
return await this.client
|
||||||
|
.post("", formData)
|
||||||
|
.then((res) => {
|
||||||
|
return res.data as Book;
|
||||||
|
})
|
||||||
|
.catch((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;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,10 +4,33 @@ import { BookService } from "@/services/book.service";
|
|||||||
import { onMounted, ref } from "vue";
|
import { onMounted, ref } from "vue";
|
||||||
import BookCard from "@/components/BookCard.vue";
|
import BookCard from "@/components/BookCard.vue";
|
||||||
import CreateBookForm from "@/components/CreateBookForm.vue";
|
import CreateBookForm from "@/components/CreateBookForm.vue";
|
||||||
|
import { useAuth } from "@/composables/useAuth";
|
||||||
|
import { useToast } from "vue-toast-notification";
|
||||||
|
|
||||||
const books = ref<Book[]>([]);
|
const books = ref<Book[]>([]);
|
||||||
const bookService = new BookService();
|
const bookService = new BookService();
|
||||||
const isCreatingBookFormOpened = ref<boolean>(false);
|
const isCreatingBookFormOpened = ref<boolean>(false);
|
||||||
|
const { isAuthenticated } = useAuth();
|
||||||
|
const toastService = useToast();
|
||||||
|
|
||||||
|
function sortBooks(a: Book, b: Book) {
|
||||||
|
if (a.updated_at > b.updated_at) {
|
||||||
|
return -1;
|
||||||
|
} else if (a.updated_at < b.updated_at) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function pushNewBook(book: Book) {
|
||||||
|
books.value.push(book);
|
||||||
|
books.value.sort(sortBooks);
|
||||||
|
toastService.success(`Livre créé avec succès"`, {
|
||||||
|
position: "bottom-right",
|
||||||
|
pauseOnHover: true,
|
||||||
|
dismissible: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function closeBookFormModal() {
|
function closeBookFormModal() {
|
||||||
isCreatingBookFormOpened.value = false;
|
isCreatingBookFormOpened.value = false;
|
||||||
@ -20,26 +43,21 @@ onMounted(async () => {
|
|||||||
book.added_at = new Date(book.added_at);
|
book.added_at = new Date(book.added_at);
|
||||||
});
|
});
|
||||||
|
|
||||||
books.value.sort((a, b) => {
|
books.value.sort(sortBooks);
|
||||||
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>
|
||||||
<CreateBookForm
|
<CreateBookForm
|
||||||
|
v-if="isAuthenticated"
|
||||||
:is-active="isCreatingBookFormOpened"
|
:is-active="isCreatingBookFormOpened"
|
||||||
|
@new-book="(book) => pushNewBook(book)"
|
||||||
@close-book-form-modal="closeBookFormModal"
|
@close-book-form-modal="closeBookFormModal"
|
||||||
/>
|
/>
|
||||||
<main class="cards">
|
<main class="cards">
|
||||||
<BookCard v-for="book in books" :book="book" :key="book.id" class="card" />
|
<BookCard v-for="book in books" :book="book" :key="book.id" class="card" />
|
||||||
</main>
|
</main>
|
||||||
<div class="add-button-container">
|
<div v-if="isAuthenticated" class="add-button-container">
|
||||||
<button @click="isCreatingBookFormOpened = true" class="add-button">
|
<button @click="isCreatingBookFormOpened = true" class="add-button">
|
||||||
<h3><font-awesome-icon icon="fa-plus" /> Ajouter un livre</h3>
|
<h3><font-awesome-icon icon="fa-plus" /> Ajouter un livre</h3>
|
||||||
</button>
|
</button>
|
||||||
@ -47,7 +65,6 @@ onMounted(async () => {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
||||||
.add-button-container {
|
.add-button-container {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
@ -110,5 +127,4 @@ onMounted(async () => {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user