diff --git a/src/constants/request.ts b/src/constants/request.ts index 720d33d9d..e3fadba43 100644 --- a/src/constants/request.ts +++ b/src/constants/request.ts @@ -108,5 +108,8 @@ export const URLs = { }, attachments: { post: '/attachments' + }, + reviews: { + post: '/reviews' } } diff --git a/src/constants/translations/en/cooperations-page.json b/src/constants/translations/en/cooperations-page.json index ce6ffa854..ceb514867 100644 --- a/src/constants/translations/en/cooperations-page.json +++ b/src/constants/translations/en/cooperations-page.json @@ -70,7 +70,15 @@ "noAccess": "No access", "monthAccess": "month access", "yearAccess": "year access", - "permanentAccess": "Permanent access" + "permanentAccess": "Permanent access", + "reviewTitle": "Leave your review", + "studentReviewDescription": "Share your experience about the collaboration with the tutor. Your feedback will help improve the platform and user interactions.", + "tutorReviewDescription": "Share your experience about the collaboration with the student. Your feedback will help improve the platform and user interactions.", + "reviewRating": "Rate your experience:", + "reviewLabel": "Your review", + "cancel": "Cancel", + "submit": "Submit", + "success": "Your review was submitted successfully" }, "notes": { "noteText": "Note text...", diff --git a/src/constants/translations/uk/cooperations-page.json b/src/constants/translations/uk/cooperations-page.json index 542696a12..7d8e22ffa 100644 --- a/src/constants/translations/uk/cooperations-page.json +++ b/src/constants/translations/uk/cooperations-page.json @@ -70,7 +70,15 @@ "noAccess": "Не має доступу", "monthAccess": "місяці доступу", "yearAccess": "рік доступу", - "permanentAccess": "Постійний доступ" + "permanentAccess": "Постійний доступ", + "reviewTitle": "Залиште свій відгук", + "studentReviewDescription": "Поділіться враженнями про співпрацю з викладачем. Ваш відгук допоможе покращити платформу та взаємодію користувачів.", + "tutorReviewDescription": "Поділіться враженнями про співпрацю зі студентом. Ваш відгук допоможе покращити платформу та взаємодію користувачів.", + "reviewRating": "Оцініть свій досвід:", + "reviewLabel": "Ваш відгук", + "cancel": "Скасувати", + "submit": "Надіслати", + "success": "Ваш відгук був успішно надісланий" }, "notes": { "noteText": "Текст нотатки...", diff --git a/src/containers/my-cooperations/add-review-modal/AddReviewModal.constants.ts b/src/containers/my-cooperations/add-review-modal/AddReviewModal.constants.ts new file mode 100644 index 000000000..835bcb3bc --- /dev/null +++ b/src/containers/my-cooperations/add-review-modal/AddReviewModal.constants.ts @@ -0,0 +1,11 @@ +import { UserRoleEnum } from '~/types' + +export const initialValues = { + comment: '', + rating: 0, + targetUserId: '', + targetUserRole: UserRoleEnum.Student, + offer: '' +} + +export const validations = {} diff --git a/src/containers/my-cooperations/add-review-modal/AddReviewModal.styles.ts b/src/containers/my-cooperations/add-review-modal/AddReviewModal.styles.ts new file mode 100644 index 000000000..a8bc26374 --- /dev/null +++ b/src/containers/my-cooperations/add-review-modal/AddReviewModal.styles.ts @@ -0,0 +1,25 @@ +import { TypographyVariantEnum } from '~/types' + +export const styles = { + root: { p: 5 }, + title: { + typography: TypographyVariantEnum.H5, + marginBottom: '3px' + }, + description: { + typography: TypographyVariantEnum.Subtitle1, + color: 'primary.500', + marginBottom: '15px' + }, + formWrapper: { + m: '16px 0 24px 0', + display: 'flex', + flexDirection: 'column', + gap: 2 + }, + buttonGroup: { + display: 'flex', + gap: 2, + justifyContent: 'right' + } +} diff --git a/src/containers/my-cooperations/add-review-modal/AddReviewModal.tsx b/src/containers/my-cooperations/add-review-modal/AddReviewModal.tsx new file mode 100644 index 000000000..d4402677d --- /dev/null +++ b/src/containers/my-cooperations/add-review-modal/AddReviewModal.tsx @@ -0,0 +1,139 @@ +import { useTranslation } from 'react-i18next' + +import Box from '@mui/material/Box' +import Typography from '@mui/material/Typography' +import Rating from '@mui/material/Rating' +import TextField from '@mui/material/TextField' + +import Button from '~/design-system/components/button/Button' + +import { ReviewService } from '~/services/review-service' +import { useAppSelector } from '~/hooks/use-redux' +import useForm from '~/hooks/use-form' +import { useAppDispatch } from '~/hooks/use-redux' +import { openAlert } from '~/redux/features/snackbarSlice' +import { snackbarVariants } from '~/constants' +import { getErrorKey } from '~/utils/get-error-key' +import { useModalContext } from '~/context/modal-context' +import { + initialValues, + validations +} from '~/containers/my-cooperations/add-review-modal/AddReviewModal.constants' + +import { + ComponentEnum, + ReviewDataFromCooperation, + ReviewData, + ErrorResponse, + DataFromCooperation +} from '~/types' +import { styles } from '~/containers/my-cooperations/add-review-modal/AddReviewModal.styles' + +const AddReviewModal: React.FC = ({ data }) => { + const { t } = useTranslation() + const { userRole } = useAppSelector((state) => state.appMain) + const dispatch = useAppDispatch() + const { closeModal } = useModalContext() + + const addReview = (data: { + dataFromCooperation: DataFromCooperation + comment: string + rating: number + }) => { + return ReviewService.submitReview({ + ...data.dataFromCooperation, + comment: data.comment, + rating: data.rating + }) + } + + const handleResponse = () => { + dispatch( + openAlert({ + severity: snackbarVariants.success, + message: 'cooperationsPage.cooperationDetails.success' + }) + ) + closeModal() + } + + const handleResponseError = (error?: ErrorResponse) => { + dispatch( + openAlert({ + severity: snackbarVariants.error, + message: getErrorKey(error) + }) + ) + } + + const handleSubmitReview = async () => { + const res = await addReview({ + dataFromCooperation: data, + comment: reviewData.comment, + rating: reviewData.rating + }) + + if (res.data) { + handleResponse() + } else { + handleResponseError((res.request as { data: ErrorResponse }).data) + } + } + + const { + data: reviewData, + handleInputChange, + handleNonInputValueChange, + handleSubmit + } = useForm({ + initialValues, + validations, + onSubmit: handleSubmitReview, + submitWithData: true + }) + + return ( + + + {t('cooperationsPage.cooperationDetails.reviewTitle')} + + + {t(`cooperationsPage.cooperationDetails.${userRole}ReviewDescription`)} + + + + + {t('cooperationsPage.cooperationDetails.reviewRating')} + + + handleNonInputValueChange('rating', newValue ?? 0) + } + value={reviewData.rating} + /> + + + + + + + + + ) +} + +export default AddReviewModal diff --git a/src/containers/my-cooperations/cooperation-completion/CooperationCompletion.tsx b/src/containers/my-cooperations/cooperation-completion/CooperationCompletion.tsx index 24648218e..5dd93562e 100644 --- a/src/containers/my-cooperations/cooperation-completion/CooperationCompletion.tsx +++ b/src/containers/my-cooperations/cooperation-completion/CooperationCompletion.tsx @@ -1,16 +1,28 @@ import { useTranslation } from 'react-i18next' + import Box from '@mui/material/Box' import Typography from '@mui/material/Typography' import Divider from '@mui/material/Divider' import SettingItem from '~/components/setting-item/SettingItem' import AppButton from '~/components/app-button/AppButton' +import { useModalContext } from '~/context/modal-context' import { styles } from '~/containers/my-cooperations/cooperation-completion/CooperationCompletion.styles' -import { ButtonVariantEnum, SizeEnum } from '~/types' +import { ButtonVariantEnum, SizeEnum, ReviewDataFromCooperation } from '~/types' +import AddReviewModal from '~/containers/my-cooperations/add-review-modal/AddReviewModal' -const CooperationCompletion = () => { +const CooperationCompletion: React.FC = ({ + data +}) => { const { t } = useTranslation() + const { openModal } = useModalContext() + + const openAddReviewModal = () => { + openModal({ + component: + }) + } return ( @@ -25,6 +37,7 @@ const CooperationCompletion = () => { title={t('cooperationsPage.cooperationDetails.closeCooperationTitle')} > { const { offer, price } = detailsResponse + const reviewData = { + targetUserId: displayedUser._id, + targetUserRole: displayedUser.role[0], + offer: detailsResponse.offer._id + } + const CategoryIcon = getCategoryIcon(offer.category.appearance.icon) const categoryColor = getValidatedHexColor(offer.category.appearance.color) @@ -106,7 +112,7 @@ const MyCooperationsDetails = () => { createUrlPath(import.meta.env.VITE_APP_IMG_USER_URL, displayedUser.photo) const cooperationCompletion = userRole === UserRoleEnum.Tutor && ( - + ) return ( diff --git a/src/services/review-service.ts b/src/services/review-service.ts new file mode 100644 index 000000000..5c51103a6 --- /dev/null +++ b/src/services/review-service.ts @@ -0,0 +1,11 @@ +import { AxiosResponse } from 'axios' + +import { axiosClient } from '~/plugins/axiosClient' +import { URLs } from '~/constants/request' +import { ReviewData, ReviewResponse } from '~/types' + +export const ReviewService = { + submitReview: (data: ReviewData): Promise> => { + return axiosClient.post(URLs.reviews.post, data) + } +} diff --git a/src/types/index.ts b/src/types/index.ts index 49d74a55c..f50287c90 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -30,3 +30,4 @@ export * from '~/types/edit-user-profile/editUserProfile.index' export * from '~/types/edit-profile/editProfile.index' export * from '~/types/user-profile/userProfile.index' export * from '~/types/bookmarked-offers/bookmarkedOffers.index' +export * from '~/types/reviews/reviews.index' diff --git a/src/types/reviews/interfaces/reviews.interface.ts b/src/types/reviews/interfaces/reviews.interface.ts new file mode 100644 index 000000000..f9e3aa536 --- /dev/null +++ b/src/types/reviews/interfaces/reviews.interface.ts @@ -0,0 +1,31 @@ +import { UserRoleEnum } from '~/types/user/user.index' + +export interface ReviewData { + comment: string + rating: number + targetUserId: string + targetUserRole: UserRoleEnum + offer: string +} + +export interface ReviewDataFromCooperation { + data: Pick +} + +export interface DataFromCooperation { + targetUserId: string + targetUserRole: UserRoleEnum + offer: string +} + +export interface ReviewResponse { + _id: string + comment: string + rating: number + author: string + targetUserId: string + targetUserRole: UserRoleEnum + offer: string + createdAt: Date + updatedAt: Date +} diff --git a/src/types/reviews/reviews.index.ts b/src/types/reviews/reviews.index.ts new file mode 100644 index 000000000..d011ab635 --- /dev/null +++ b/src/types/reviews/reviews.index.ts @@ -0,0 +1 @@ +export * from '~/types/reviews/interfaces/reviews.interface'