Skip to content

Commit cc4364c

Browse files
Merge pull request #146 from HackYourFutureProjects/FE-BE/moderation-features
Fe be/moderation features
2 parents d8634dd + dedc9aa commit cc4364c

26 files changed

Lines changed: 576 additions & 42 deletions

File tree

client/src/api/review/review.api.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,7 @@ export async function createReviewApi(
4545
const res = await apiProtected.post<ReviewType>("/api/reviews", data);
4646
return res.data;
4747
}
48+
49+
export async function deleteReviewByModeratorApi(reviewId: string) {
50+
return await apiProtected.delete(`/api/reviews/${reviewId}/moderator`);
51+
}

client/src/api/teacher/teacher.type.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ export type TeacherStatus =
55
| "active"
66
| "rejected"
77
| "blocked";
8+
9+
export type TeacherStatusQuery = TeacherStatus | "all";
10+
811
type EducationItem = {
912
degree: string;
1013
institution: string;
@@ -95,6 +98,7 @@ export type TeachersForModeratorQuery = {
9598
sortDirection?: SortDirection;
9699
pageNumber?: number;
97100
pageSize?: number;
101+
status?: TeacherStatusQuery;
98102
};
99103

100104
export type UpdateTeacherProfileInput = {

client/src/components/cardsList/CardsList.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@ import { useRef } from "react";
55
type CardsListType = {
66
cards: TeacherType[];
77
changeStatus?: (id: string, status: TeacherStatus) => void;
8-
isStatusPending?: boolean;
8+
pendingTeacherId?: string | null;
99
};
1010

1111
export const CardsList = ({
1212
cards,
1313
changeStatus,
14-
isStatusPending,
14+
pendingTeacherId,
1515
}: CardsListType) => {
1616
const reduceMotion = useReducedMotion();
1717

@@ -51,7 +51,7 @@ export const CardsList = ({
5151
{cards.map((teacher) => (
5252
<motion.div key={teacher.id} variants={itemVariants}>
5353
<TeacherCard
54-
isStatusPending={isStatusPending}
54+
isStatusPending={pendingTeacherId === teacher.id}
5555
changeStatus={changeStatus}
5656
teacher={teacher}
5757
/>
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { Button } from "../ui/button/Button.tsx";
2+
import { Loader } from "../loader/Loader.tsx";
3+
import { useDeleteReviewByModeratorMutation } from "../../features/review/mutations/useDeleteReviewByModeratorMutation.ts";
4+
5+
interface Props {
6+
isOpen: boolean;
7+
onClose: () => void;
8+
teacherId: string;
9+
reviewId: string;
10+
}
11+
12+
export const DeleteReviewByModeratorConfirmation = ({
13+
isOpen,
14+
onClose,
15+
teacherId,
16+
reviewId,
17+
}: Props) => {
18+
const { mutateAsync, isPending } =
19+
useDeleteReviewByModeratorMutation(teacherId);
20+
21+
const onConfirm = async () => {
22+
await mutateAsync(reviewId);
23+
onClose();
24+
};
25+
26+
if (!isOpen) {
27+
return null;
28+
}
29+
30+
return (
31+
<div className="bg-white rounded-lg p-6 max-w-md w-full mx-4 shadow-xl">
32+
<div className="mb-6 text-gray-700">
33+
<p>Do you really want to delete review?</p>
34+
</div>
35+
36+
<div className="flex gap-3 justify-end">
37+
<Button variant="secondary" onClick={onClose}>
38+
Cancel
39+
</Button>
40+
<Button onClick={onConfirm} disabled={isPending}>
41+
Confirm
42+
</Button>
43+
</div>
44+
{isPending && <Loader />}
45+
</div>
46+
);
47+
};

client/src/components/icons/Delete.tsx

Lines changed: 191 additions & 0 deletions
Large diffs are not rendered by default.

client/src/components/modalHost/modalHost.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { AlertDialog } from "../alertDialog/AlertDialog.tsx";
99
import { useModalStore } from "../../store/modals.store.ts";
1010
import { cva } from "class-variance-authority";
1111
import { twMerge } from "tailwind-merge";
12+
import { DeleteReviewByModeratorConfirmation } from "../deleteReviewConfirmation/DeleteReviewConfirmation.tsx";
1213

1314
const overlayClass = cva(
1415
"fixed inset-0 z-50 flex items-center justify-center transition-opacity duration-200",
@@ -61,6 +62,17 @@ export const ModalHost = () => {
6162
{activeModal === "signIn" && (
6263
<SignInConfirmation isOpen={opened} onClose={close} />
6364
)}
65+
{activeModal === "deleteReview" &&
66+
payload &&
67+
"teacherId" in payload &&
68+
"reviewId" in payload && (
69+
<DeleteReviewByModeratorConfirmation
70+
isOpen={opened}
71+
onClose={close}
72+
teacherId={payload.teacherId}
73+
reviewId={payload.reviewId}
74+
/>
75+
)}
6476
{activeModal === "confirmDelete" &&
6577
payload &&
6678
"onConfirm" in payload && (

client/src/components/teacherCard/teacherCard.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import DefaultAvatarIcon from "../icons/DefaultAvatarIcon";
77
import { cva, VariantProps } from "class-variance-authority";
88
import { twMerge } from "tailwind-merge";
99
import { useAuthSessionStore } from "../../store/authSession.store.ts";
10-
import { Loader } from "../loader/Loader.tsx";
1110
import { StatusChange } from "../statusChanger/StatusChange.tsx";
1211

1312
type TeacherCardType = {
@@ -62,7 +61,7 @@ export const TeacherCard = ({
6261
const navigate = useNavigate();
6362
const user = useAuthSessionStore((state) => state.user);
6463
const avatarUrl = getAvatarUrl(profileImageUrl || null);
65-
64+
const pulseClass = isStatusPending ? `pulse-border--${status}` : "";
6665
const handleBookClick = () => {
6766
if (user?.role === "moderator") {
6867
navigate(`/moderator/teachers/${id}`);
@@ -76,7 +75,12 @@ export const TeacherCard = ({
7675
};
7776

7877
return (
79-
<div className={teacherCardClassName({ status }, "flex flex-col gap-4")}>
78+
<div
79+
className={teacherCardClassName(
80+
{ status },
81+
twMerge("flex flex-col gap-4", pulseClass),
82+
)}
83+
>
8084
{Boolean(changeStatus) && (
8185
<StatusChange changeStatus={onChangeStatus} id={id} status={status} />
8286
)}
@@ -171,7 +175,6 @@ export const TeacherCard = ({
171175
<span className="text-[14px] text-dark-400">First lesson - free</span>
172176
</div>
173177
</div>
174-
{isStatusPending && <Loader />}
175178
</div>
176179
);
177180
};

client/src/components/teacherSection/Reviews/ReviewCardTeacher.tsx

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,19 @@ import ReviewsIcon from "../../icons/Reviews";
22
import { Rating } from "../../rating/Rating";
33
import UsersIcon from "../../icons/UsersIcon";
44
import { ReviewType } from "../../../api/review/review.type";
5+
import { Button } from "../../ui/button/Button.tsx";
6+
import Delete from "../../icons/Delete.tsx";
7+
import { useModalStore } from "../../../store/modals.store.ts";
8+
import { useAuthSessionStore } from "../../../store/authSession.store.ts";
59

610
interface ReviewCardProps {
711
reviewData: ReviewType;
812
}
913

1014
export const ReviewCardTeacher = ({
1115
reviewData: {
16+
_id,
17+
teacherId,
1218
studentName,
1319
studentAvatar,
1420
rating,
@@ -17,9 +23,24 @@ export const ReviewCardTeacher = ({
1723
createdAt,
1824
},
1925
}: ReviewCardProps) => {
26+
const openModal = useModalStore((s) => s.open);
27+
const user = useAuthSessionStore((state) => state.user);
28+
2029
return (
2130
<div className="w-full h-full">
2231
<div className="flex flex-col bg-[#15141D] p-[16px] md:p-[25px] rounded-2xl h-full">
32+
{user?.role === "moderator" && (
33+
<Button
34+
variant="link"
35+
className="self-end hover:shadow-[0_0_18px_rgba(239,68,68,0.55)] mb-5"
36+
onClick={() =>
37+
openModal("deleteReview", { teacherId, reviewId: _id })
38+
}
39+
>
40+
<Delete />
41+
</Button>
42+
)}
43+
2344
{/* Header Part (Avatar , Name, Icon ) */}
2445
<div className="flex items-center gap-[10px] md:gap-[16px] mb-[12px] md:mb-[20px]">
2546
{studentAvatar ? (
@@ -43,7 +64,6 @@ export const ReviewCardTeacher = ({
4364

4465
<ReviewsIcon className="opacity-20 w-3.5 md:w-auto h-3.5 md:h-auto shrink-0" />
4566
</div>
46-
4767
{/* Rieview Section */}
4868
<div className="flex flex-col gap-[12px]">
4969
<div className="flex justify-between items-center">

client/src/components/teacherSection/Reviews/ReviewsManager.tsx

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -41,27 +41,35 @@ export const ReviewsManager = () => {
4141

4242
return (
4343
<>
44-
<div className="text-center">
45-
<h2 className="mb-6 sm:mb-8 lg:mb-10 font-bold text-white xl:text-[56px] text-3xl sm:text-4xl lg:text-5xl">
46-
What our clients say
47-
</h2>
48-
<p className="mx-auto mb-12 sm:mb-16 lg:mb-20 px-4 max-w-3xl text-white/70 text-sm sm:text-base lg:text-lg text-justify leading-relaxed">
49-
Students appreciate the practical approach, supportive instructors,
50-
and clear learning structure across all courses. Many international
51-
students highlight fast progress, increased confidence, and a
52-
comfortable learning environment.
53-
</p>
54-
</div>
5544
<AddReview
5645
teacherId={teacherId ?? ""}
5746
accumulatedReviews={accumulatedReviews}
5847
/>
59-
<ReviewsTeacher
60-
reviews={accumulatedReviews}
61-
isLoading={isLoading}
62-
onLoadMore={handleLoadMore}
63-
hasMore={hasMore}
64-
/>
48+
{reviewsData?.reviews.length ? (
49+
<>
50+
<div className="text-center">
51+
<h2 className="mb-6 sm:mb-8 lg:mb-10 font-bold text-white xl:text-[56px] text-3xl sm:text-4xl lg:text-5xl">
52+
What our clients say
53+
</h2>
54+
<p className="mx-auto mb-12 sm:mb-16 lg:mb-20 px-4 max-w-3xl text-white/70 text-sm sm:text-base lg:text-lg text-justify leading-relaxed">
55+
Students appreciate the practical approach, supportive
56+
instructors, and clear learning structure across all courses. Many
57+
international students highlight fast progress, increased
58+
confidence, and a comfortable learning environment.
59+
</p>
60+
</div>
61+
<ReviewsTeacher
62+
reviews={accumulatedReviews}
63+
isLoading={isLoading}
64+
onLoadMore={handleLoadMore}
65+
hasMore={hasMore}
66+
/>
67+
</>
68+
) : (
69+
<div className="flex items-center justify-center text-light-100 min-h-50">
70+
No reviews
71+
</div>
72+
)}
6573
</>
6674
);
6775
};
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { Option } from "../components/ui/select/Select.tsx";
2+
import {
3+
SortByTeachersForModerator,
4+
TeacherStatusQuery,
5+
} from "../api/teacher/teacher.type.ts";
6+
7+
export const sortByOptions: Option<SortByTeachersForModerator>[] = [
8+
{ label: "CreatedAt", value: "createdAt" },
9+
{ label: "Status", value: "status" },
10+
];
11+
12+
export const sortByStatus: Option<TeacherStatusQuery>[] = [
13+
{ label: "All", value: "all" },
14+
{ label: "Draft", value: "draft" },
15+
{ label: "Pending", value: "pending" },
16+
{ label: "Active", value: "active" },
17+
{ label: "Rejected", value: "rejected" },
18+
{ label: "Blocked", value: "blocked" },
19+
];

0 commit comments

Comments
 (0)