From 93be66d003184658dc31e6e448f4426fd9d824da Mon Sep 17 00:00:00 2001
From: Radek <104318242+radekm2000@users.noreply.github.com>
Date: Fri, 24 May 2024 19:54:24 +0200
Subject: [PATCH] add review command and make product photo larger using
inventory command
---
.../src/components/DisplayUserProducts.tsx | 22 ++--
.../ProductPage/DisplayUserInfo.tsx | 14 +--
.../components/ratingSystem/ReviewForm.tsx | 15 +--
.../src/discord-bot/discord-bot.service.ts | 2 +
.../src/discord-bot/src/commands/inventory.ts | 2 +-
.../src/discord-bot/src/commands/reviews.ts | 105 ++++++++++++++++++
server/ecommerce/src/users/users.service.ts | 19 ++++
.../ecommerce/src/utils/dtos/product.dto.ts | 5 +-
8 files changed, 145 insertions(+), 39 deletions(-)
create mode 100644 server/ecommerce/src/discord-bot/src/commands/reviews.ts
diff --git a/client/ecommerce/src/components/DisplayUserProducts.tsx b/client/ecommerce/src/components/DisplayUserProducts.tsx
index 8dfcba3..e45a624 100644
--- a/client/ecommerce/src/components/DisplayUserProducts.tsx
+++ b/client/ecommerce/src/components/DisplayUserProducts.tsx
@@ -13,6 +13,7 @@ import { ProductWithImageAndUser } from "../types/types";
import { useMediaQuery } from "../hooks/useMediaQuery";
import { Link, useLocation } from "wouter";
import { calculateGridWidth } from "../utils/calculateGridWidth";
+import { RenderAvatar } from "./RenderAvatar";
export const DisplayUserProducts = ({
products,
@@ -92,21 +93,12 @@ export const DisplayUserProducts = ({
cursor: "pointer",
}}
>
- {product.user.avatar ? (
-
- ) : (
-
- )}
+
+
- {user?.avatar ? (
-
- ) : (
-
- )}
+
+
{user?.username}
diff --git a/client/ecommerce/src/components/ratingSystem/ReviewForm.tsx b/client/ecommerce/src/components/ratingSystem/ReviewForm.tsx
index 339dfe2..5b5db9a 100644
--- a/client/ecommerce/src/components/ratingSystem/ReviewForm.tsx
+++ b/client/ecommerce/src/components/ratingSystem/ReviewForm.tsx
@@ -11,9 +11,10 @@ import {
import { UserWithAvatar } from "../../types/types";
import { AccountCircle } from "@mui/icons-material";
import { Controller, SubmitHandler, useForm } from "react-hook-form";
-import { useState } from "react";
+import { useState } from "react";
import { useAddReview } from "../../hooks/useAddReview";
import { useMediaQuery } from "../../hooks/useMediaQuery";
+import { RenderAvatar } from "../RenderAvatar";
type FormFields = {
rating: number;
@@ -74,16 +75,8 @@ export const ReviewForm = ({ user }: { user: UserWithAvatar }) => {
alignItems: "center",
}}
>
- {user.avatar ? (
-
- ) : (
-
- )}
+
+
{user.username}
diff --git a/server/ecommerce/src/discord-bot/discord-bot.service.ts b/server/ecommerce/src/discord-bot/discord-bot.service.ts
index 6b5c2b9..b2cb8db 100644
--- a/server/ecommerce/src/discord-bot/discord-bot.service.ts
+++ b/server/ecommerce/src/discord-bot/discord-bot.service.ts
@@ -7,6 +7,7 @@ import { ProfileCommand } from './src/commands/profile';
import { UsersService } from 'src/users/users.service';
import { InventoryCommand } from './src/commands/inventory';
import { ProductsService } from 'src/products/products.service';
+import { ReviewsCommand } from './src/commands/reviews';
@Injectable()
export class DiscordBotService implements OnModuleInit {
@@ -42,6 +43,7 @@ export class DiscordBotService implements OnModuleInit {
productsService: this.productsService,
usersService: this.userService,
}),
+ new ReviewsCommand({ usersService: this.userService }),
],
});
}
diff --git a/server/ecommerce/src/discord-bot/src/commands/inventory.ts b/server/ecommerce/src/discord-bot/src/commands/inventory.ts
index e1474c2..d43e6f2 100644
--- a/server/ecommerce/src/discord-bot/src/commands/inventory.ts
+++ b/server/ecommerce/src/discord-bot/src/commands/inventory.ts
@@ -60,7 +60,7 @@ export class InventoryCommand implements SlashCommand {
{ name: 'Brand', value: product.brand, inline: true },
{ name: 'Category', value: product.category, inline: true },
])
- .setThumbnail(productImage);
+ .setImage(productImage);
});
return [headerEmbed, ...productEmbeds];
diff --git a/server/ecommerce/src/discord-bot/src/commands/reviews.ts b/server/ecommerce/src/discord-bot/src/commands/reviews.ts
new file mode 100644
index 0000000..74ed46e
--- /dev/null
+++ b/server/ecommerce/src/discord-bot/src/commands/reviews.ts
@@ -0,0 +1,105 @@
+import {
+ ChatInputCommandInteraction,
+ EmbedBuilder,
+ SlashCommandBuilder,
+} from 'discord.js';
+import { SlashCommand } from './slash-command';
+import { UsersService } from 'src/users/users.service';
+import { Review } from 'src/utils/entities/review.entity';
+import { DiscordEmbedColors } from '../discord-embeds/colors';
+
+type Config = {
+ usersService: UsersService;
+};
+
+export class ReviewsCommand implements SlashCommand {
+ private readonly usersService: UsersService;
+
+ public readonly config = new SlashCommandBuilder()
+ .setName('reviews')
+ .setDescription('Displays reviews that are listed on your profile');
+
+ constructor(config: Config) {
+ this.usersService = config.usersService;
+ }
+
+ public execute = async (interaction: ChatInputCommandInteraction) => {
+ const userId = interaction.user.id;
+ const userAvatar = interaction.user.avatarURL();
+
+ await interaction.deferReply({ ephemeral: true });
+
+ const user = await this.usersService.getUserWithReviews(userId);
+
+ const totalReviews = this.calculateTotalReviews(user.reviews);
+ const averageRating = this.calculateAverageReviewsRating(user.reviews);
+
+ if (totalReviews === 0) {
+ await interaction.editReply('You have no reviews yet');
+ }
+
+ const embed = await this.createEmbed(
+ totalReviews,
+ averageRating,
+ user.reviews,
+ userAvatar,
+ );
+
+ await interaction.editReply({ embeds: [embed] });
+ };
+
+ private createEmbed = async (
+ totalReviews: number,
+ averageRating: number,
+ reviews: Review[],
+ userAvatar: string,
+ ) => {
+ const embed = new EmbedBuilder()
+ .setTitle(
+ `You have a total of ${totalReviews} reviews with an average rating of ⭐ ${averageRating.toFixed(
+ 2,
+ )}`,
+ )
+ .setColor(DiscordEmbedColors.default)
+ .setThumbnail(userAvatar);
+
+ reviews.forEach((review) => {
+ embed.addFields([
+ {
+ name: 'Reviewer',
+ value: review.reviewCreator.username,
+ inline: true,
+ },
+ {
+ name: 'Rating',
+ value: `⭐ ${review.rating.toString()}`,
+ inline: true,
+ },
+ { name: 'Comment', value: review.comment },
+ ]);
+ });
+
+ return embed;
+ };
+
+ private calculateTotalReviews = (reviews: Review[]) => {
+ return reviews.length;
+ };
+
+ private calculateAverageReviewsRating = (reviews: Review[]): number => {
+ if (reviews.length === 0) {
+ return 0;
+ }
+
+ const ratings = reviews.map((review) => review.rating);
+ const sortedRatings = ratings.sort((a, b) => a - b);
+
+ const middleIndex = Math.floor(sortedRatings.length / 2);
+
+ if (sortedRatings.length % 2 === 0) {
+ return (sortedRatings[middleIndex - 1] + sortedRatings[middleIndex]) / 2;
+ } else {
+ return sortedRatings[middleIndex];
+ }
+ };
+}
diff --git a/server/ecommerce/src/users/users.service.ts b/server/ecommerce/src/users/users.service.ts
index 2fa3b58..ab5cddf 100644
--- a/server/ecommerce/src/users/users.service.ts
+++ b/server/ecommerce/src/users/users.service.ts
@@ -137,6 +137,25 @@ export class UsersService {
return user;
}
+ public getUserWithReviews = async (discordId: string) => {
+ const user = await this.usersRepository.findOne({
+ where: {
+ discordId,
+ },
+ relations: {
+ reviews: {
+ reviewCreator: true,
+ },
+ avatarEntity: true,
+ },
+ });
+ if (!user) {
+ throw new HttpException('User not found', HttpStatus.NOT_FOUND);
+ }
+
+ return user;
+ };
+
public findUserByDiscordId = async (discordId: string) => {
const user = await this.usersRepository.findOne({
where: {
diff --git a/server/ecommerce/src/utils/dtos/product.dto.ts b/server/ecommerce/src/utils/dtos/product.dto.ts
index 1bc06cf..3a4437f 100644
--- a/server/ecommerce/src/utils/dtos/product.dto.ts
+++ b/server/ecommerce/src/utils/dtos/product.dto.ts
@@ -41,9 +41,10 @@ const User = z.object({
id: z.number(),
username: z.string(),
googleId: z.string().or(z.null()),
- email: z.string(),
- role: z.enum(['user', 'admin']),
+ email: z.string().or(z.null()),
+ role: z.enum(['user', 'admin', 'discordUser']),
avatar: z.string().or(z.null()),
+ discordId: z.string().or(z.null()),
});
export const ProductWithImageAndUserSchema = z.object({