diff --git a/backend/src/entities/ProductOption.ts b/backend/src/entities/ProductOption.ts index fd4e5e4..b2ffe91 100644 --- a/backend/src/entities/ProductOption.ts +++ b/backend/src/entities/ProductOption.ts @@ -37,7 +37,4 @@ export class ProductOption extends BaseEntity { ) orders: ProductInOrder[]; - // Ce champ sert seulement à pouvoir renvoyer la valeur de available_quantity qui est calculée dans un resolver - @Field(() => Number) - availableQuantity: number; } diff --git a/backend/src/inputs/UserInput.ts b/backend/src/inputs/UserInput.ts index 43bd865..7de72b0 100644 --- a/backend/src/inputs/UserInput.ts +++ b/backend/src/inputs/UserInput.ts @@ -42,7 +42,7 @@ export class UpdateUserInput { @InputType() export class UpdateOrCreateUserInput { @Field({ nullable: true }) - id: number; + userId: number; @Field() first_name: string; @@ -102,4 +102,10 @@ export class ForgottenPasswordRequestInput { @Field() @IsEmail() email: string; +} + +@InputType() +export class DeleteUserInput { + @Field() + userId!: number; } \ No newline at end of file diff --git a/backend/src/resolvers/InventoryResolver.ts b/backend/src/resolvers/InventoryResolver.ts index b0e983c..a660ea4 100644 --- a/backend/src/resolvers/InventoryResolver.ts +++ b/backend/src/resolvers/InventoryResolver.ts @@ -1,8 +1,7 @@ -import { ProductOption } from "../entities/ProductOption"; -import { ProductInOrder } from "../entities/ProductInOrder"; - -import { Resolver, Query, Arg } from "type-graphql"; +import { Resolver, Query, Arg, Ctx } from "type-graphql"; import { OptionAvailability, OptionInventory } from "../entities/Inventory"; +import { GraphQLError } from "graphql"; +import { getInventoryByOptionsService } from "../services/InventoryService"; @Resolver() export class InventoryResolver { @@ -10,77 +9,19 @@ export class InventoryResolver { async getInventoryByOptions( @Arg("startDate") startDate: string, @Arg("endDate") endDate: string, - @Arg("productId", { nullable: true }) productId?: number + @Arg("productId", { nullable: true }) productId?: number, + @Ctx() context?: any, ) { - const getAllDates = (startDate: string, endDate: string) => { - const allDates = []; - let currentDate = new Date(startDate); - const lastDate = new Date(endDate); - while (currentDate <= lastDate) { - allDates.push(new Date(currentDate)); - currentDate.setDate(currentDate.getDate() + 1); - } - return allDates; - }; - - const allDates = getAllDates(startDate, endDate); - - let productOptions; - let reservations; - - if (productId) { - productOptions = await ProductOption.find({ - where: { id: productId }, - relations: ["product"], - }); - reservations = await ProductInOrder.find({ - where: { productOption: { id: productId } }, - relations: ["productOption.product"], - }); - } else { - productOptions = await ProductOption.find({ relations: ["product"] }); - reservations = await ProductInOrder.find({ - relations: ["productOption.product"], - }); - } - - let inventory = []; - for (const option of productOptions) { - const optionInventory: OptionInventory = { - id: option.id, - product: option.product.name, - category: option.product.category, - option: option.size, - totalQty: option.total_quantity, - reservations: [], - }; - for (const date of allDates) { - const reservedItems = reservations.filter((item) => { - return ( - item.productOption.id === option.id && - new Date(item.order.rental_start_date) <= date && - new Date(item.order.rental_end_date) >= date - ); - }); - - if (reservedItems.length > 0) { - const reservedQty = reservedItems.reduce( - (sum, item) => sum + item.quantity, - 0 - ); - const availableQty = option.total_quantity - reservedQty; - optionInventory.reservations.push({ - date, - reservedQty, - availableQty, - }); + if(!productId){ + if(context.user.role !== "ADMIN"){ + throw new GraphQLError("Only admins can query all inventory", { + extensions: { code: "FORBIDDEN" }, + }); } - } - inventory.push(optionInventory); } - return inventory; + return getInventoryByOptionsService(startDate, endDate, productId) } @Query(() => OptionAvailability) @@ -115,4 +56,6 @@ export class InventoryResolver { availableQty: minAvailableQty, }; } + + } diff --git a/backend/src/resolvers/PaymentResolver.ts b/backend/src/resolvers/PaymentResolver.ts index d89b5ba..a1c7b48 100644 --- a/backend/src/resolvers/PaymentResolver.ts +++ b/backend/src/resolvers/PaymentResolver.ts @@ -1,4 +1,4 @@ -import { Arg, Field, InputType, Int, Query, Resolver } from "type-graphql"; +import { Arg, Field, InputType, Int, Mutation, Resolver } from "type-graphql"; import { GraphQLJSON } from "graphql-scalars"; import paymentServices from "../services/paymentServices"; @@ -25,7 +25,7 @@ export default class PaymentResolver { *? retourne énormément d'informations, impossible de tout couvrir *? facilement avec un type custom à nous *------------------------**/ - @Query(() => GraphQLJSON) + @Mutation(() => GraphQLJSON) async createCheckoutSession( @Arg("data", () => [ProductForSessionInput]) data: ProductForSessionInput[] ) { diff --git a/backend/src/resolvers/ProductResolver.ts b/backend/src/resolvers/ProductResolver.ts index 37e3df5..3b41404 100644 --- a/backend/src/resolvers/ProductResolver.ts +++ b/backend/src/resolvers/ProductResolver.ts @@ -7,7 +7,8 @@ import { Category } from "../entities/Category"; import { FindManyOptions, In, Raw } from "typeorm"; import { merge } from "../assets/utils"; import { Tag } from "../entities/Tag"; -import { ProductInOrder } from "../entities/ProductInOrder"; +//import { ProductInOrder } from "../entities/ProductInOrder"; +import { getInventoryByOptionsService } from "../services/InventoryService"; @Resolver(Product) export class ProductResolver { @@ -77,9 +78,10 @@ export class ProductResolver { .andWhere("product.price <= :maxPrice", { maxPrice: maxPrice }) .andWhere("product.price >= :minPrice", { minPrice: minPrice }); - if (keyword.length > 0) { - queryBuilder.andWhere("product.name ILIKE :keyword", { keyword: `%${keyword}%` }); + queryBuilder.andWhere("product.name ILIKE :keyword", { + keyword: `%${keyword}%`, + }); } if (tags && tags.length > 0) { @@ -97,112 +99,102 @@ export class ProductResolver { @Arg("endDate") endDate: Date, @Arg("categoryId", { nullable: true }) categoryId?: number, @Arg("keyword", { nullable: true }) keyword?: string, - @Arg("minPrice", { nullable: true }) minPrice?: number, - @Arg("maxPrice", { nullable: true }) maxPrice?: number, - @Arg("tags", () => [String], { nullable: true }) tags?: string[], - @Arg("productId", {nullable: true}) productId?:number, + @Arg("minPrice", { nullable: true }) minPrice?: number, + @Arg("maxPrice", { nullable: true }) maxPrice?: number, + @Arg("tags", () => [String], { nullable: true }) tags?: string[], + @Arg("productId", { nullable: true }) productId?: number ) { - - - - const queryBuilder = ProductOption.createQueryBuilder("po") - .leftJoinAndSelect("po.product", "product") - .leftJoinAndSelect("product.category", "category") - .leftJoinAndSelect("product.tags", "tag") - .leftJoinAndSelect("product.pictures", "pictures"); - - // Filtre par catégorie + const queryBuilder = ProductOption.createQueryBuilder("po") + .leftJoinAndSelect("po.product", "product") + .leftJoinAndSelect("product.category", "category") + .leftJoinAndSelect("product.tags", "tag") + .leftJoinAndSelect("product.pictures", "pictures"); + // Filtre par catégorie if (categoryId) { - queryBuilder.andWhere("category.id = :categoryId", {categoryId}); + queryBuilder.andWhere("category.id = :categoryId", { categoryId }); } // Filtre par mot-clé if (keyword) { - queryBuilder.andWhere("product.name ILIKE :keyword", { keyword: `%${keyword}%` }); + queryBuilder.andWhere("product.name ILIKE :keyword", { + keyword: `%${keyword}%`, + }); } // Filtre par prix - if(maxPrice){ - queryBuilder.andWhere("product.price <= :maxPrice", { maxPrice: maxPrice }) + if (maxPrice) { + queryBuilder.andWhere("product.price <= :maxPrice", { + maxPrice: maxPrice, + }); } - if(minPrice){ - queryBuilder.andWhere("product.price >= :minPrice", { minPrice: minPrice }) + if (minPrice) { + queryBuilder.andWhere("product.price >= :minPrice", { + minPrice: minPrice, + }); } // Filtre par tags if (tags && tags.length > 0) { queryBuilder.andWhere("tag.label IN (:...tags)", { tags }); } - - // Filtre par product ID + // Filtre par product ID if (productId) { - queryBuilder.andWhere("product.id = :productId", { productId: productId }); + queryBuilder.andWhere("product.id = :productId", { + productId: productId, + }); } - // Objectif seul de récupérer la donnée "reserved_quantity" et de la sauvegarder à l'aide du addSelect - queryBuilder.addSelect(subQuery => { - return subQuery - .select("COALESCE(SUM(pio.quantity), 0)") - .from(ProductInOrder, "pio") - .leftJoin("pio.order", "o") - .where("pio.productOptionId = po.id") - .andWhere("o.rental_start_date <= :endDate") - .andWhere("o.rental_end_date >= :startDate") - }, "reserved_quantity"); - - // Filtre uniquement les products options qui sont disponibles pour les dates et options sélectionnées - queryBuilder.andWhere(qb => { - const reservedQty = qb.subQuery() - .select("SUM(pio.quantity)") - .from(ProductInOrder, "pio") - .leftJoin("pio.order", "o") - .where("pio.productOptionId = po.id") - .andWhere("o.rental_start_date <= :endDate") - .andWhere("o.rental_end_date >= :startDate") - .andWhere("o.status != 'CANCELLED'") - .getQuery(); - - return `po.total_quantity - COALESCE((${reservedQty}), 0) > 0`; - }); + const productOptions = await queryBuilder.getMany(); + + const inventoryForDates = await getInventoryByOptionsService( + startDate.toISOString().split("T")[0], + endDate.toISOString().split("T")[0] + ); - queryBuilder.setParameters({startDate, endDate}); - - // Récupère les ProductOptions avec la quantité disponible - const result = await queryBuilder.getRawAndEntities(); + const unavailableProducts = inventoryForDates.filter((item) => { + if (!item.reservations || item.reservations.length === 0) { + return false; + } + const maxReserved = Math.max( + ...item.reservations.map((r) => r.reservedQty) + ); + return maxReserved >= item.totalQty; + }); - const productOptionWithAvailableQty = result.entities.map(entity=>{ - const rawForThisEntity = result.raw.find(r => r.po_id === entity.id) - const reserved = rawForThisEntity ? Number(rawForThisEntity.reserved_quantity) : 0 + const availableProductOptions = productOptions.filter((option) => { + return !unavailableProducts.find((item) => item.id === option.id); + }); - return { - ...entity, - availableQuantity: entity.total_quantity - reserved - } - }) - return productOptionWithAvailableQty + return availableProductOptions; } - @Query(()=> [Product]) + @Query(() => [Product]) async getAvailableProductForDates( @Arg("startDate") startDate: Date, @Arg("endDate") endDate: Date, @Arg("categoryId", { nullable: true }) categoryId?: number, @Arg("keyword", { nullable: true }) keyword?: string, - @Arg("minPrice", { nullable: true }) minPrice?: number, - @Arg("maxPrice", { nullable: true }) maxPrice?: number, + @Arg("minPrice", { nullable: true }) minPrice?: number, + @Arg("maxPrice", { nullable: true }) maxPrice?: number, @Arg("tags", () => [String], { nullable: true }) tags?: string[] ) { - console.log("args:", startDate, endDate, categoryId, keyword, minPrice, maxPrice, tags) - - const availableProductOptions = await this.getAvailableProductOptions(startDate, endDate, categoryId, keyword, minPrice, maxPrice, tags); + const availableProductOptions = await this.getAvailableProductOptions( + startDate, + endDate, + categoryId, + keyword, + minPrice, + maxPrice, + tags + ); // Extrait les Products disponibles à partir des products Options dispo let availableProducts: Product[] = []; - availableProductOptions.forEach(option => { + availableProductOptions.forEach((option) => { const product = option.product; - if (!availableProducts.some((item)=> item.id === product.id )) { + if (!availableProducts.some((item) => item.id === product.id)) { availableProducts.push(product); } }); - + return availableProducts; } diff --git a/backend/src/resolvers/TempUserResolver.ts b/backend/src/resolvers/TempUserResolver.ts index bed952e..7ba00dc 100644 --- a/backend/src/resolvers/TempUserResolver.ts +++ b/backend/src/resolvers/TempUserResolver.ts @@ -1,10 +1,12 @@ +import { IsAdmin } from "../middleware/AuthChecker"; import { TempUser } from "../entities/TempUser"; -import { Arg, Ctx, Mutation, Query, Resolver } from "type-graphql"; +import { Arg, Mutation, Query, Resolver, UseMiddleware } from "type-graphql"; @Resolver(TempUser) export class TempUserResolver { @Query(() => [TempUser]) + @UseMiddleware(IsAdmin) async getAllTempUsers( ) { const tempUsers = await TempUser.find( {order: { id: "ASC", @@ -13,17 +15,15 @@ export class TempUserResolver { return tempUsers; } - @Mutation(() => String) - async deleteTempUser(@Arg("id") id: number, @Ctx() context: any) { - if(context.user.role !== "ADMIN" ){ - throw new Error("Unauthorized") - } - const result = await TempUser.delete(id); - if (result.affected === 1) { - return "L'utilisateur a bien été supprimé"; - } else { - throw new Error("L'utilisateur n'a pas été trouvé"); - } + @Mutation(() => String) + @UseMiddleware(IsAdmin) + async deleteTempUser(@Arg("id") id: number) { + const result = await TempUser.delete(id); + if (result.affected === 1) { + return "L'utilisateur a bien été supprimé"; + } else { + throw new Error("L'utilisateur n'a pas été trouvé"); } + } } \ No newline at end of file diff --git a/backend/src/resolvers/UserResolver.ts b/backend/src/resolvers/UserResolver.ts index e5511e4..756146a 100644 --- a/backend/src/resolvers/UserResolver.ts +++ b/backend/src/resolvers/UserResolver.ts @@ -6,7 +6,6 @@ import { Resolver, Arg, Ctx, - Authorized, Int, ObjectType, Field, @@ -16,6 +15,7 @@ import { User } from "../entities/User"; import { TempUser } from "../entities/TempUser"; import { ChangePasswordInput, + DeleteUserInput, ForgottenPasswordRequestInput, ResetPasswordInput, UpdateOrCreateUserInput, @@ -30,8 +30,9 @@ import { v4 as uuidv4 } from "uuid"; import * as jwt from "jsonwebtoken"; import Cookies from "cookies"; import { Address } from "../entities/Address"; -import { IsCurrentUserOrAdmin } from "../middleware/AuthChecker"; +import { IsAdmin, IsCurrentUserOrAdmin } from "../middleware/AuthChecker"; import { CreateOrUpdateAddressInput } from "../inputs/AddressInput"; +import { GraphQLError } from "graphql"; @ObjectType() class PaginatedUsers { @@ -106,7 +107,7 @@ if (process.env.APP_ENV === "production") { @Resolver(User) export class UserResolver { @Query(() => PaginatedUsers) - @Authorized() + @UseMiddleware(IsAdmin) async getAllUsers( @Arg("offset") offset: number, @Arg("limit") limit: number, @@ -248,35 +249,26 @@ export class UserResolver { } @Mutation(() => String) - async deleteUser(@Arg("id") id: number, @Ctx() context: any) { - if (context.user.role !== "ADMIN" && context.user.id !== id) { - throw new Error("Unauthorized"); - } - const result = await User.delete(id); + @UseMiddleware(IsCurrentUserOrAdmin) + async deleteUser(@Arg("data") data: DeleteUserInput) { + const result = await User.delete(data.userId); if (result.affected === 1) { return "L'utilisateur a bien été supprimé"; - } else { - throw new Error("L'utilisateur n'a pas été trouvé"); - } + } + throw new GraphQLError("L'utilisateur n'a pas été trouvé",{ extensions: { code: "NOT_FOUND" }}); } @Mutation(() => User) + @UseMiddleware(IsCurrentUserOrAdmin) async editUser( - @Arg("data") updateUserData: UpdateOrCreateUserInput, - @Ctx() context: any + @Arg("data") updateUserData: UpdateOrCreateUserInput ) { - if ( - context.user.role !== "ADMIN" && - context.user.email !== updateUserData.email - ) { - throw new Error("Unauthorized"); - } let userToUpdate = await User.findOne({ - where: { id: updateUserData.id }, + where: { id: updateUserData.userId }, relations: ["address"], }); if (!userToUpdate) { - throw new Error("User not found"); + throw new GraphQLError("L'utilisateur n'a pas été trouvé",{ extensions: { code: "NOT_FOUND" }}); } // Modifie les champs users @@ -304,16 +296,24 @@ export class UserResolver { await newAddress.save(); userToUpdate.address = newAddress; } - await userToUpdate.save(); return userToUpdate; } @Mutation(() => String) + @UseMiddleware(IsAdmin) async addUser(@Arg("data") new_user_data: UpdateOrCreateUserInput) { + const existing = await User.findOne({ where: { email: new_user_data.email } }); + + if(existing){ + throw new GraphQLError("Un compte existe déjà avec cette adresse mail", { + extensions: { code: "EMAIL_TAKEN" }, + }); + } + const random_code = uuidv4(); - const result = TempUser.save({ + const result = await TempUser.save({ first_name: new_user_data.first_name, last_name: new_user_data.last_name, email: new_user_data.email, @@ -326,23 +326,23 @@ export class UserResolver { }); const resend = new Resend(process.env.RESEND_API_KEY); - - (async function () { - const { data, error } = await resend.emails.send({ - from: "wild-rent@test.anniec.eu", - to: [new_user_data.email], - subject: "Verify Email", - html: ` -
Veuillez cliquer sur le lien suivant pour compléter votre inscription à Wild Rent
- - http://localhost:7000/confirmation/enregistrement/${random_code} - `, + const { data, error } = await resend.emails.send({ + from: "wild-rent@test.anniec.eu", + to: [new_user_data.email], + subject: "Verify Email", + html: ` +Veuillez cliquer sur le lien suivant pour compléter votre inscription à Wild Rent
+ + http://localhost:7000/confirmation/enregistrement/${random_code} + `, + }); + if (error) { + console.error({ error }); + throw new GraphQLError("Erreur lors de l'envoi de l'email", { + extensions: { code: "EMAIL_ERROR" }, }); - if (error) { - return console.error({ error }); - } - console.log({ data }); - })(); + } + console.log({ data }); console.log("result", result); return "Temp user was created, validate with confirmation email"; } @@ -352,34 +352,39 @@ export class UserResolver { @Arg("random_code") random_code: string, @Arg("password") password: string ) { - const tempUser = await TempUser.findOneByOrFail({ - random_code: random_code, - }); + const tempUser = await TempUser.findOne({ + where:{ random_code: random_code} + }); - const hashed_password = await argon2.hash(password); + if(!tempUser){ + throw new GraphQLError("Code de confirmation invalide ou expiré", { + extensions: { code: "INVALID_CODE" }, + }); + } - const newAddress = Address.create({ - street: tempUser.street, - city: tempUser.city, - zipcode: tempUser.zipcode, - country: "France", - }); + const hashed_password = await argon2.hash(password); - await newAddress.save(); + const newAddress = Address.create({ + street: tempUser.street, + city: tempUser.city, + zipcode: tempUser.zipcode, + country: "France", + }); - const userResult = await User.save({ - first_name: tempUser.first_name, - last_name: tempUser.last_name, - email: tempUser.email, - phone_number: tempUser.phone_number, - hashed_password: hashed_password, - created_at: new Date(), - address: newAddress, - role: tempUser.role, - }); - await tempUser.remove(); + await newAddress.save(); - return userResult; + const userResult = await User.save({ + first_name: tempUser.first_name, + last_name: tempUser.last_name, + email: tempUser.email, + phone_number: tempUser.phone_number, + hashed_password: hashed_password, + created_at: new Date(), + address: newAddress, + role: tempUser.role, + }); + await tempUser.remove(); + return userResult; } // Mutation pour enregistrer une adresse de facturation dans les détails du compte diff --git a/backend/src/services/InventoryService.ts b/backend/src/services/InventoryService.ts new file mode 100644 index 0000000..6b49134 --- /dev/null +++ b/backend/src/services/InventoryService.ts @@ -0,0 +1,79 @@ +import { OptionInventory } from "../entities/Inventory"; +import { ProductInOrder } from "../entities/ProductInOrder"; +import { ProductOption } from "../entities/ProductOption"; + +export const getInventoryByOptionsService = async ( + startDate: string, + endDate: string, + productId?: number +)=>{ + const getAllDates = (startDate: string, endDate: string) => { + const allDates = []; + let currentDate = new Date(startDate); + const lastDate = new Date(endDate); + while (currentDate <= lastDate) { + allDates.push(new Date(currentDate)); + currentDate.setDate(currentDate.getDate() + 1); + } + return allDates; + }; + + const allDates = getAllDates(startDate, endDate); + + let productOptions; + let reservations; + + if (productId) { + productOptions = await ProductOption.find({ + where: { id: productId }, + relations: ["product"], + }); + reservations = await ProductInOrder.find({ + where: { productOption: { id: productId } }, + relations: ["productOption.product"], + }); + } else { + productOptions = await ProductOption.find({ relations: ["product"] }); + reservations = await ProductInOrder.find({ + relations: ["productOption.product"], + }); + } + + let inventory = []; + + for (const option of productOptions) { + const optionInventory: OptionInventory = { + id: option.id, + product: option.product.name, + category: option.product.category, + option: option.size, + totalQty: option.total_quantity, + reservations: [], + }; + for (const date of allDates) { + const reservedItems = reservations.filter((item) => { + return ( + item.productOption.id === option.id && + new Date(item.order.rental_start_date) <= date && + new Date(item.order.rental_end_date) >= date + ); + }); + + if (reservedItems.length > 0) { + const reservedQty = reservedItems.reduce( + (sum, item) => sum + item.quantity, + 0 + ); + const availableQty = option.total_quantity - reservedQty; + optionInventory.reservations.push({ + date, + reservedQty, + availableQty, + }); + } + } + inventory.push(optionInventory); + } + + return inventory; +} \ No newline at end of file diff --git a/e2e/Dockerfile b/e2e/Dockerfile index cbe77ee..d2fb65e 100644 --- a/e2e/Dockerfile +++ b/e2e/Dockerfile @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/playwright:v1.55.1-noble +FROM mcr.microsoft.com/playwright:v1.56.1-noble WORKDIR /app diff --git a/e2e/package-lock.json b/e2e/package-lock.json index 6342742..6ae7a24 100644 --- a/e2e/package-lock.json +++ b/e2e/package-lock.json @@ -9,19 +9,19 @@ "dotenv": "^16.4.7" }, "devDependencies": { - "@playwright/test": "^1.55.0", + "@playwright/test": "^1.56.0", "@types/node": "^22.13.10", "cross-env": "^7.0.3" } }, "node_modules/@playwright/test": { - "version": "1.55.0", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.55.0.tgz", - "integrity": "sha512-04IXzPwHrW69XusN/SIdDdKZBzMfOT9UNT/YiJit/xpy2VuAoB8NHc8Aplb96zsWDddLnbkPL3TsmrS04ZU2xQ==", + "version": "1.56.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.56.0.tgz", + "integrity": "sha512-Tzh95Twig7hUwwNe381/K3PggZBZblKUe2wv25oIpzWLr6Z0m4KgV1ZVIjnR6GM9ANEqjZD7XsZEa6JL/7YEgg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright": "1.55.0" + "playwright": "1.56.0" }, "bin": { "playwright": "cli.js" @@ -392,13 +392,13 @@ } }, "node_modules/playwright": { - "version": "1.55.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.55.0.tgz", - "integrity": "sha512-sdCWStblvV1YU909Xqx0DhOjPZE4/5lJsIS84IfN9dAZfcl/CIZ5O8l3o0j7hPMjDvqoTF8ZUcc+i/GL5erstA==", + "version": "1.56.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.56.0.tgz", + "integrity": "sha512-X5Q1b8lOdWIE4KAoHpW3SE8HvUB+ZZsUoN64ZhjnN8dOb1UpujxBtENGiZFE+9F/yhzJwYa+ca3u43FeLbboHA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.55.0" + "playwright-core": "1.56.0" }, "bin": { "playwright": "cli.js" @@ -411,9 +411,9 @@ } }, "node_modules/playwright-core": { - "version": "1.55.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.55.0.tgz", - "integrity": "sha512-GvZs4vU3U5ro2nZpeiwyb0zuFaqb9sUiAJuyrWpcGouD8y9/HLgGbNRjIph7zU9D3hnPaisMl9zG9CgFi/biIg==", + "version": "1.56.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.56.0.tgz", + "integrity": "sha512-1SXl7pMfemAMSDn5rkPeZljxOCYAmQnYLBTExuh6E8USHXGSX3dx6lYZN/xPpTz1vimXmPA9CDnILvmJaB8aSQ==", "dev": true, "license": "Apache-2.0", "bin": { diff --git a/e2e/package.json b/e2e/package.json index f1d51a9..f169ea2 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -9,7 +9,7 @@ "dotenv": "^16.4.7" }, "devDependencies": { - "@playwright/test": "^1.55.1", + "@playwright/test": "^1.56.0", "@types/node": "^22.13.10", "cross-env": "^7.0.3" } diff --git a/frontend/src/components/UserTable.tsx b/frontend/src/components/UserTable.tsx index d8ea895..3a2c04a 100644 --- a/frontend/src/components/UserTable.tsx +++ b/frontend/src/components/UserTable.tsx @@ -52,20 +52,34 @@ export function UserTable({ refetchTempUsers, seeTempUsers, }: UserTableProps) { - const [deleteUserMutation] = useDeleteUserMutation(); - const [deleteTempUserMutation] = useDeleteTempUserMutation(); + const [deleteUserMutation] = useDeleteUserMutation({ + onError: () => { + toast.error( + "Une erreur est survenue, l'utilisateur n'a pas pu être supprimé" + ); + }, + }); + const [deleteTempUserMutation] = useDeleteTempUserMutation({ + onError: () => { + toast.error( + "Une erreur est survenue, l'utilisateur n'a pas pu être supprimé" + ); + }, + }); const deteleUser = async (id: number) => { try { if (!seeTempUsers) { const response = await deleteUserMutation({ - variables: { deleteUserId: id }, + variables: { + data: { + userId: id, + }, + }, }); refetchUsers(); if (response.data?.deleteUser) { toast.success("Utilisateur supprimé avec succès"); - } else { - toast.error("Une erreur est survenue, veuillez réessayer plus tard"); } return response.data; } else { @@ -75,8 +89,6 @@ export function UserTable({ refetchTempUsers(); if (response.data?.deleteTempUser) { toast.success("Utilisateur supprimé avec succès"); - } else { - toast.error("Une erreur est survenue, veuillez réessayer plus tard"); } return response.data; } @@ -113,13 +125,7 @@ export function UserTable({Loading...
; - if (error || errorOptions) returnError loading product
; + if (error) returnError loading product
; + if (errorOptions) returnError loading option
; const mainImage = activeImage || products?.pictures[0].url; return ( -