From 7d5c95435765b0326488f007335bf7a6fb123104 Mon Sep 17 00:00:00 2001 From: Radek <104318242+radekm2000@users.noreply.github.com> Date: Tue, 2 Apr 2024 01:01:20 +0200 Subject: [PATCH] implemented feedback controller and service and added mutation hook for sending feedback to backend --- client/ecommerce/src/api/axios.tsx | 12 +++++++ .../src/components/FeedbackDialog.tsx | 34 ++++++++----------- client/ecommerce/src/components/Navbar.tsx | 6 ++-- .../form-component/FormInputText.tsx | 5 +-- .../src/components/pages/Product.tsx | 1 - .../components/ratingSystem/ReviewForm.tsx | 3 +- client/ecommerce/src/hooks/useAddFeedback.ts | 19 +++++++++++ .../ecommerce/src/hooks/useFetchFeedbacks.ts | 9 +++++ client/ecommerce/src/types/types.ts | 2 ++ server/ecommerce/src/app.module.ts | 2 ++ .../src/feedbacks/feedbacks.controller.ts | 20 +++++++++++ .../src/feedbacks/feedbacks.module.ts | 12 +++++++ .../src/feedbacks/feedbacks.service.ts | 22 ++++++++++++ .../src/feedbacks/feedbacksInterface.ts | 7 ++++ .../ecommerce/src/utils/dtos/feedback.dto.ts | 15 ++++++++ server/ecommerce/src/utils/dtos/types.ts | 3 +- .../src/utils/entities/feedback.entity.ts | 27 +++++++++++++++ 17 files changed, 171 insertions(+), 28 deletions(-) create mode 100644 client/ecommerce/src/hooks/useAddFeedback.ts create mode 100644 client/ecommerce/src/hooks/useFetchFeedbacks.ts create mode 100644 server/ecommerce/src/feedbacks/feedbacks.controller.ts create mode 100644 server/ecommerce/src/feedbacks/feedbacks.module.ts create mode 100644 server/ecommerce/src/feedbacks/feedbacks.service.ts create mode 100644 server/ecommerce/src/feedbacks/feedbacksInterface.ts create mode 100644 server/ecommerce/src/utils/dtos/feedback.dto.ts create mode 100644 server/ecommerce/src/utils/entities/feedback.entity.ts diff --git a/client/ecommerce/src/api/axios.tsx b/client/ecommerce/src/api/axios.tsx index ce56acf..6a96e28 100644 --- a/client/ecommerce/src/api/axios.tsx +++ b/client/ecommerce/src/api/axios.tsx @@ -4,6 +4,7 @@ import { Brand, Conversation, ExtendedUserWithProfileAndReviews, + Feedback, FetchedNotifications, LoginInput, LoginResponseData, @@ -21,6 +22,7 @@ import { } from "../types/types"; import { RequestAccessTokenInterceptor } from "./request-access-token.interceptor"; import { ResponseOAuthInterceptor } from "./response-auth.interceptor"; +import { FeedbackFormData } from "../components/FeedbackDialog"; const LIMIT = 5; let baseUrl; if (import.meta.env.VITE_PRODU == "false") { @@ -290,3 +292,13 @@ export const getAdminNotifications = async () => { const response = await axiosApi.get("admin-notifications"); return response.data as AdminNotification[]; }; + +export const addFeedback = async (dto: FeedbackFormData) => { + const response = await axiosApi.post("feedbacks", dto); + return response.data; +}; + +export const getFeedbacks = async () => { + const response = await axiosApi.get("feedbacks"); + return response.data as Feedback[]; +}; diff --git a/client/ecommerce/src/components/FeedbackDialog.tsx b/client/ecommerce/src/components/FeedbackDialog.tsx index 04a6693..d667887 100644 --- a/client/ecommerce/src/components/FeedbackDialog.tsx +++ b/client/ecommerce/src/components/FeedbackDialog.tsx @@ -4,19 +4,17 @@ import { Dialog, DialogContent, DialogTitle, - MenuItem, Stack, SxProps, - TextField, Typography, } from "@mui/material"; import { zodResolver } from "@hookform/resolvers/zod"; -import { useState } from "react"; import { SubmitHandler, useForm } from "react-hook-form"; import { z } from "zod"; import { FormInputText } from "./form-component/FormInputText"; import { FormInputDropdown } from "./form-component/FormInputDropdown"; +import { useAddFeedback } from "../hooks/useAddFeedback"; const featureTypes = ["other", "enhancement", "bug", "new feature"] as const; export type featureType = (typeof featureTypes)[number]; @@ -46,24 +44,22 @@ export const FeedbackDialog = ({ alignItems: "flex-start", }, }; - const { - handleSubmit, - control, - setValue, - clearErrors, - formState: { errors }, - } = useForm({ - defaultValues: { - contactName: "", - description: "", - email: "", - featureType: "other", - }, - resolver: zodResolver(FeedbackSchema), - }); + const feedbackMutation = useAddFeedback(); + const { mutate } = feedbackMutation; + const { handleSubmit, control, setValue, clearErrors } = + useForm({ + defaultValues: { + contactName: "", + description: "", + email: "", + featureType: "other", + }, + resolver: zodResolver(FeedbackSchema), + }); const onSubmit: SubmitHandler = (data) => { - console.log(data); + mutate(data); + onClose(); }; const handleClose = () => { setValue("contactName", ""); diff --git a/client/ecommerce/src/components/Navbar.tsx b/client/ecommerce/src/components/Navbar.tsx index 93de9f9..b0d1b54 100644 --- a/client/ecommerce/src/components/Navbar.tsx +++ b/client/ecommerce/src/components/Navbar.tsx @@ -24,7 +24,6 @@ import { CardActionArea, CardContent, Container, - Dialog, Tabs, } from "@mui/material"; import { useMediaQuery } from "../hooks/useMediaQuery"; @@ -427,7 +426,10 @@ export const Navbar = () => { - + { const stripe = useStripe(); diff --git a/client/ecommerce/src/components/ratingSystem/ReviewForm.tsx b/client/ecommerce/src/components/ratingSystem/ReviewForm.tsx index 3efb8c7..0804aa1 100644 --- a/client/ecommerce/src/components/ratingSystem/ReviewForm.tsx +++ b/client/ecommerce/src/components/ratingSystem/ReviewForm.tsx @@ -11,10 +11,9 @@ import { import { UserWithAvatar } from "../../types/types"; import { AccountCircle } from "@mui/icons-material"; import { Controller, SubmitHandler, useForm } from "react-hook-form"; -import { useEffect, useState } from "react"; +import { useState } from "react"; import { useAddReview } from "../../hooks/useAddReview"; import { useMediaQuery } from "../../hooks/useMediaQuery"; -import { z } from "zod"; type FormFields = { rating: number; diff --git a/client/ecommerce/src/hooks/useAddFeedback.ts b/client/ecommerce/src/hooks/useAddFeedback.ts new file mode 100644 index 0000000..d8fcf54 --- /dev/null +++ b/client/ecommerce/src/hooks/useAddFeedback.ts @@ -0,0 +1,19 @@ +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { addFeedback } from "../api/axios"; +import toast from "react-hot-toast"; +import { Feedback } from "../types/types"; + +export const useAddFeedback = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationKey: ["feedback", "add"], + mutationFn: addFeedback, + onSuccess: (data: Feedback) => { + queryClient.setQueryData(["feedback"], data); + toast.success("Feedback sent!"); + }, + onError: (err) => { + console.log(err); + }, + }); +}; diff --git a/client/ecommerce/src/hooks/useFetchFeedbacks.ts b/client/ecommerce/src/hooks/useFetchFeedbacks.ts new file mode 100644 index 0000000..9f651c2 --- /dev/null +++ b/client/ecommerce/src/hooks/useFetchFeedbacks.ts @@ -0,0 +1,9 @@ +import { useQuery } from "@tanstack/react-query"; +import { getFeedbacks } from "../api/axios"; + +export const useFetchFeedbacks = () => { + return useQuery({ + queryKey: ["feedbacks"], + queryFn: getFeedbacks, + }); +}; diff --git a/client/ecommerce/src/types/types.ts b/client/ecommerce/src/types/types.ts index 6c18654..8a2b0a6 100644 --- a/client/ecommerce/src/types/types.ts +++ b/client/ecommerce/src/types/types.ts @@ -1,5 +1,6 @@ import { Message } from "@mui/icons-material"; import { z } from "zod"; +import { FeedbackFormData } from "../components/FeedbackDialog"; export type RegisterInput = { username: string; @@ -207,3 +208,4 @@ export type AdminNotification = { userId?: number; }; +export type Feedback = FeedbackFormData & { createdAt: string }; diff --git a/server/ecommerce/src/app.module.ts b/server/ecommerce/src/app.module.ts index a84be66..62b2906 100644 --- a/server/ecommerce/src/app.module.ts +++ b/server/ecommerce/src/app.module.ts @@ -17,6 +17,7 @@ import { NodemailerModule } from './nodemailer/nodemailer.module'; import { StripeModule } from './stripe/stripe.module'; import { ReviewsModule } from './reviews/reviews.module'; import { AdminNotificationsModule } from './admin-notifications/admin-notifications.module'; +import { FeedbacksModule } from './feedbacks/feedbacks.module'; @Module({ imports: [ @@ -35,6 +36,7 @@ import { AdminNotificationsModule } from './admin-notifications/admin-notificati StripeModule, ReviewsModule, AdminNotificationsModule, + FeedbacksModule, ], controllers: [AppController], providers: [AppService], diff --git a/server/ecommerce/src/feedbacks/feedbacks.controller.ts b/server/ecommerce/src/feedbacks/feedbacks.controller.ts new file mode 100644 index 0000000..7f9655f --- /dev/null +++ b/server/ecommerce/src/feedbacks/feedbacks.controller.ts @@ -0,0 +1,20 @@ +import { Body, Controller, Get, Post, UsePipes } from '@nestjs/common'; +import { FeedbacksService } from './feedbacks.service'; +import { ZodValidationPipe } from 'src/utils/pipes/ZodValidationPipe'; +import { FeedbackDto, FeedbackDtoSchema } from 'src/utils/dtos/feedback.dto'; + +@Controller('feedbacks') +export class FeedbacksController { + constructor(private readonly feedbacksService: FeedbacksService) {} + + @Get() + async getFeedbacks() { + return await this.feedbacksService.getFeedbacks(); + } + + @Post() + @UsePipes(new ZodValidationPipe(FeedbackDtoSchema)) + async addFeedback(@Body() dto: FeedbackDto) { + return await this.feedbacksService.addFeedback(dto); + } +} diff --git a/server/ecommerce/src/feedbacks/feedbacks.module.ts b/server/ecommerce/src/feedbacks/feedbacks.module.ts new file mode 100644 index 0000000..312cec7 --- /dev/null +++ b/server/ecommerce/src/feedbacks/feedbacks.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { FeedbacksController } from './feedbacks.controller'; +import { FeedbacksService } from './feedbacks.service'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { Feedback } from 'src/utils/entities/feedback.entity'; + +@Module({ + imports: [TypeOrmModule.forFeature([Feedback])], + controllers: [FeedbacksController], + providers: [FeedbacksService], +}) +export class FeedbacksModule {} diff --git a/server/ecommerce/src/feedbacks/feedbacks.service.ts b/server/ecommerce/src/feedbacks/feedbacks.service.ts new file mode 100644 index 0000000..a8d35be --- /dev/null +++ b/server/ecommerce/src/feedbacks/feedbacks.service.ts @@ -0,0 +1,22 @@ +import { Injectable } from '@nestjs/common'; +import { feedbacksI } from './feedbacksInterface'; +import { Feedback } from 'src/utils/entities/feedback.entity'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { FeedbackDto } from 'src/utils/dtos/feedback.dto'; + +@Injectable() +export class FeedbacksService implements feedbacksI { + constructor( + @InjectRepository(Feedback) + private feedbackRepository: Repository, + ) {} + async addFeedback(dto: FeedbackDto): Promise { + const newFeedback = this.feedbackRepository.create(dto); + return await this.feedbackRepository.save(newFeedback); + } + + async getFeedbacks(): Promise { + return await this.feedbackRepository.find({}); + } +} diff --git a/server/ecommerce/src/feedbacks/feedbacksInterface.ts b/server/ecommerce/src/feedbacks/feedbacksInterface.ts new file mode 100644 index 0000000..c12b1da --- /dev/null +++ b/server/ecommerce/src/feedbacks/feedbacksInterface.ts @@ -0,0 +1,7 @@ +import { FeedbackDto } from 'src/utils/dtos/feedback.dto'; +import { Feedback } from 'src/utils/entities/feedback.entity'; + +export interface feedbacksI { + getFeedbacks(): Promise; + addFeedback(dto: FeedbackDto): Promise; +} diff --git a/server/ecommerce/src/utils/dtos/feedback.dto.ts b/server/ecommerce/src/utils/dtos/feedback.dto.ts new file mode 100644 index 0000000..8e99229 --- /dev/null +++ b/server/ecommerce/src/utils/dtos/feedback.dto.ts @@ -0,0 +1,15 @@ +import { z } from 'zod'; + +export const FeedbackDtoSchema = z.object({ + featureType: z.union([ + z.literal('other'), + z.literal('bug'), + z.literal('enhancement'), + z.literal('new feature'), + ]), + email: z.string().email(), + contactName: z.string().min(1, 'Contact name is required'), + description: z.string().min(1, 'Description is required'), +}); + +export type FeedbackDto = z.infer; diff --git a/server/ecommerce/src/utils/dtos/types.ts b/server/ecommerce/src/utils/dtos/types.ts index 978887a..a8e7857 100644 --- a/server/ecommerce/src/utils/dtos/types.ts +++ b/server/ecommerce/src/utils/dtos/types.ts @@ -1,5 +1,3 @@ -import { User } from '../entities/user.entity'; - export type Order = 'price_high_to_low' | 'price_low_to_high'; export type Brand = @@ -25,3 +23,4 @@ export type Notification = { senderId: number; receiverId: number; }; +export type FeatureType = 'other' | 'enhancement' | 'bug' | 'new feature'; diff --git a/server/ecommerce/src/utils/entities/feedback.entity.ts b/server/ecommerce/src/utils/entities/feedback.entity.ts new file mode 100644 index 0000000..733a41a --- /dev/null +++ b/server/ecommerce/src/utils/entities/feedback.entity.ts @@ -0,0 +1,27 @@ +import { + Column, + Entity, + PrimaryGeneratedColumn, + UpdateDateColumn, +} from 'typeorm'; + +@Entity() +export class Feedback { + @PrimaryGeneratedColumn() + id: number; + + @Column() + featureType: 'other' | 'enhancement' | 'bug' | 'new feature'; + + @Column() + email: string; + + @Column() + contactName: string; + + @Column() + description: string; + + @UpdateDateColumn() + createdAt: Date; +}