From cf59359ffd0fa122706d3dbfd1dbadd17cb43134 Mon Sep 17 00:00:00 2001 From: galalqassas0 Date: Mon, 15 Sep 2025 22:17:19 +0300 Subject: [PATCH 1/2] update(ui): redesign lesson card component --- .../components/UpcomingLessons/Content.tsx | 7 +- .../components/Lessons/LessonCard.stories.tsx | 25 +++ .../ui/src/components/Lessons/LessonCard.tsx | 149 ++++++++++++++---- packages/ui/src/locales/ar-eg.json | 1 + 4 files changed, 150 insertions(+), 32 deletions(-) diff --git a/apps/web/src/components/UpcomingLessons/Content.tsx b/apps/web/src/components/UpcomingLessons/Content.tsx index 0e7d48e50..aea9529e9 100644 --- a/apps/web/src/components/UpcomingLessons/Content.tsx +++ b/apps/web/src/components/UpcomingLessons/Content.tsx @@ -28,6 +28,9 @@ import { Button } from "@litespace/ui/Button"; import { UNCANCELLABLE_LESSON_HOURS } from "@litespace/utils"; import CloseCircle from "@litespace/assets/CloseCircle"; import { ConfirmationDialog } from "@litespace/ui/ConfirmationDialog"; +// import { range } from "lodash"; +// import { faker } from "@faker-js/faker/locale/ar"; + type Lessons = ILesson.FindUserLessonsApiResponse["list"]; export const Content: React.FC<{ @@ -140,7 +143,7 @@ export const Content: React.FC<{ return (
-
+
{list.map((item) => { const tutor = item.members.find( (member) => @@ -209,6 +212,8 @@ export const Content: React.FC<{ otherMember.role === IUser.Role.Student ? "student" : "tutor", + // topics: range(7).map(() => faker.lorem.words(1)), + // level: "C1", }} sendingMessage={sendingMessageLessonId === item.lesson.id} disabled={!!sendingMessageLessonId} diff --git a/packages/ui/src/components/Lessons/LessonCard.stories.tsx b/packages/ui/src/components/Lessons/LessonCard.stories.tsx index b0774e2b3..06f723233 100644 --- a/packages/ui/src/components/Lessons/LessonCard.stories.tsx +++ b/packages/ui/src/components/Lessons/LessonCard.stories.tsx @@ -2,6 +2,7 @@ import { Meta, StoryObj } from "@storybook/react"; import LessonCard, { Props } from "@/components/Lessons/LessonCard"; import dayjs from "@/lib/dayjs"; import { faker } from "@faker-js/faker/locale/ar"; +import { range } from "lodash"; const meta: Meta = { title: "Lessons/LessonCard", @@ -30,6 +31,8 @@ export const BeforeJoinForStudent: Story = { name: faker.person.fullName(), image: url, role: "tutor", + topics: range(7).map(() => faker.lorem.words(1)), + level: "C1", }, ...actions, }, @@ -45,6 +48,8 @@ export const BeforeJoinForTutor: Story = { name: faker.person.fullName(), image: url, role: "student", + topics: range(7).map(() => faker.lorem.words(1)), + level: "C1", }, ...actions, }, @@ -60,6 +65,8 @@ export const CanJoinLesson: Story = { name: faker.person.fullName(), image: url, role: "tutor", + topics: range(7).map(() => faker.lorem.words(1)), + level: "C1", }, ...actions, }, @@ -75,6 +82,8 @@ export const CanJoinLessonNow: Story = { name: faker.person.fullName(), image: url, role: "tutor", + topics: range(7).map(() => faker.lorem.words(1)), + level: "C1", }, ...actions, }, @@ -90,6 +99,8 @@ export const AfterLessonStarted: Story = { name: faker.person.fullName(), image: url, role: "tutor", + topics: range(7).map(() => faker.lorem.words(1)), + level: "C1", }, ...actions, }, @@ -105,6 +116,8 @@ export const LessonAboutToEnd: Story = { name: faker.person.fullName(), image: url, role: "tutor", + topics: range(7).map(() => faker.lorem.words(1)), + level: "C1", }, ...actions, }, @@ -120,6 +133,8 @@ export const AfterLessonFinishForStudent: Story = { name: faker.person.fullName(), image: url, role: "tutor", + topics: range(7).map(() => faker.lorem.words(1)), + level: "C1", }, ...actions, }, @@ -135,6 +150,8 @@ export const AfterLessonFinishForTutor: Story = { name: faker.person.fullName(), image: url, role: "student", + topics: range(7).map(() => faker.lorem.words(1)), + level: "C1", }, ...actions, }, @@ -150,6 +167,8 @@ export const CanceledByTutor: Story = { name: faker.person.fullName(), image: url, role: "tutor", + topics: range(7).map(() => faker.lorem.words(1)), + level: "C1", }, ...actions, }, @@ -165,6 +184,8 @@ export const CanceledByStudent: Story = { name: faker.person.fullName(), image: url, role: "student", + topics: range(7).map(() => faker.lorem.words(1)), + level: "C1", }, ...actions, }, @@ -180,6 +201,8 @@ export const CanceledByCurrentTutor: Story = { name: faker.person.fullName(), image: url, role: "student", + topics: range(7).map(() => faker.lorem.words(1)), + level: "C1", }, ...actions, }, @@ -195,6 +218,8 @@ export const CanceledByCurrentStudent: Story = { name: faker.person.fullName(), image: url, role: "tutor", + topics: range(7).map(() => faker.lorem.words(1)), + level: "C1", }, ...actions, }, diff --git a/packages/ui/src/components/Lessons/LessonCard.tsx b/packages/ui/src/components/Lessons/LessonCard.tsx index bad9732f6..c2fae9996 100644 --- a/packages/ui/src/components/Lessons/LessonCard.tsx +++ b/packages/ui/src/components/Lessons/LessonCard.tsx @@ -11,6 +11,9 @@ import { Menu, type MenuAction } from "@/components/Menu"; import CalendarEdit from "@litespace/assets/CalendarEdit"; import CalendarRemove from "@litespace/assets/CalendarRemove"; import CheckCircle from "@litespace/assets/CheckCircle"; +import Calendar from "@litespace/assets/Calendar"; +import AllMessages from "@litespace/assets/AllMessages"; +import Clock from "@litespace/assets/Clock"; export type Props = { start: string; @@ -25,7 +28,10 @@ export type Props = { * @note role shall be `student` when the current user is tutor and vice versa */ role: "tutor" | "student"; + topics: Array; + level: string; }; + sendingMessage: boolean; disabled: boolean; onRebook: Void; @@ -131,6 +137,16 @@ export const LessonCard: React.FC = ({ return () => clearInterval(interval); }, [getTitle]); + const isEnded = useMemo(() => end.isBefore(dayjs()), [end]); + const isOngoing = useMemo( + () => !canceled && dayjs().isBetween(dayjs(start), end), + [canceled, start, end] + ); + const isFuture = useMemo( + () => !canceled && dayjs().isBefore(dayjs(start)), + [canceled, start] + ); + const actions: MenuAction[] = useMemo( () => currentUserRole === "student" @@ -159,25 +175,41 @@ export const LessonCard: React.FC = ({ ); const action = useMemo(() => { - const ended = end.isBefore(dayjs()); - if (currentUserRole === "student" && (ended || canceled)) - return { - label: intl("lessons.button.rebook"), - onClick: onRebook, - }; - if (currentUserRole === "tutor" && (ended || canceled)) - return { - label: intl("lessons.button.send-message"), - onClick: onSendMsg, - }; + if (currentUserRole === "student") { + if (isEnded || canceled) { + return { + label: intl("lessons.button.rebook"), + onClick: onRebook, + }; + } + } else if (currentUserRole === "tutor") { + if (isEnded) { + return { + label: intl("lessons.button.send-message"), + onClick: onSendMsg, + }; + } + if (canceled) { + return { + label: intl("lessons.button.contact-student"), + onClick: onSendMsg, + }; + } + } return { label: intl("lessons.button.join"), onClick: onJoin, }; - }, [canceled, currentUserRole, end, intl, onJoin, onRebook, onSendMsg]); + }, [canceled, currentUserRole, isEnded, intl, onJoin, onRebook, onSendMsg]); + + const buttonType = useMemo( + () => (currentUserRole === "tutor" && isOngoing ? "main" : "natural"), + [currentUserRole, isOngoing] + ); const button = (
-
- +
+
+ + {member.level} + +
+
+ +
-
+
{member.name} - - {dayjs(start).format("dddd، D MMMM")} - - - {dayjs(start).format("h:mm a")} - {" - "} - {dayjs(start).add(duration, "minutes").format("h:mm a")} - + +
+
+ + + {dayjs(start).format("h:mm a")} + {" - "} + {dayjs(start).add(duration, "minutes").format("h:mm a")} + +
+ +
+ + + {dayjs(start).format("dddd، D MMMM")} + +
+
+ +
+ {member.topics.slice(0, 4).map((topic) => ( + + {topic} + + ))} + {member.topics.length > 4 && ( + + {member.topics.length - 4}+ + + )} +
- {button} +
+ {button} + {currentUserRole === "tutor" && isFuture && ( +
); diff --git a/packages/ui/src/locales/ar-eg.json b/packages/ui/src/locales/ar-eg.json index b3fc64cd4..cc5cba2da 100644 --- a/packages/ui/src/locales/ar-eg.json +++ b/packages/ui/src/locales/ar-eg.json @@ -831,6 +831,7 @@ "lessons.button.rebook": "إعادة الحجز", "lessons.button.join": "دخول الجلسة", "lessons.button.send-message": "إرسال رسالة", + "lessons.button.contact-student": "تواصل مع الطالب", "lessons.button.find-tutors": "تصفح المعلمين", "lessons.canceled-by-tutor": "لقد تم إلغاء الجلسة من قبل المعلم", "lessons.canceled-by-student": "لقد قام الطالب بإلغاء الجلسة", From 1715b9373342f54d3a5b5199be1b827e9c406a82 Mon Sep 17 00:00:00 2001 From: devmooo3 Date: Thu, 23 Oct 2025 12:22:57 +0300 Subject: [PATCH 2/2] feat(ui): new lesson card for tutors --- .../components/UpcomingLessons/Content.tsx | 18 ++- .../components/Lessons/LessonCard.stories.tsx | 4 +- .../ui/src/components/Lessons/LessonCard.tsx | 120 +++++++++++------- 3 files changed, 85 insertions(+), 57 deletions(-) diff --git a/apps/web/src/components/UpcomingLessons/Content.tsx b/apps/web/src/components/UpcomingLessons/Content.tsx index aea9529e9..2fb1412c8 100644 --- a/apps/web/src/components/UpcomingLessons/Content.tsx +++ b/apps/web/src/components/UpcomingLessons/Content.tsx @@ -28,8 +28,7 @@ import { Button } from "@litespace/ui/Button"; import { UNCANCELLABLE_LESSON_HOURS } from "@litespace/utils"; import CloseCircle from "@litespace/assets/CloseCircle"; import { ConfirmationDialog } from "@litespace/ui/ConfirmationDialog"; -// import { range } from "lodash"; -// import { faker } from "@faker-js/faker/locale/ar"; +import { useUserTopics } from "@litespace/headless/topic"; type Lessons = ILesson.FindUserLessonsApiResponse["list"]; @@ -49,7 +48,8 @@ export const Content: React.FC<{ const toast = useToast(); const { user } = useUser(); const navigate = useNavigate(); - + const { query } = useUserTopics(); + const userTopics = query.data; const [cancelLessonData, setCancelLessonData] = useState<{ id: number | null; start: string | null; @@ -68,6 +68,8 @@ export const Content: React.FC<{ const [manageLessonData, setManageLessonData] = useState(null); + console.log(userTopics); + const onCancelSuccess = useCallback(() => { toast.success({ title: intl("cancel-lesson.success") }); subscription.refetch(); @@ -145,6 +147,7 @@ export const Content: React.FC<{
{list.map((item) => { + // console.log("list", item.lesson); const tutor = item.members.find( (member) => member.role === IUser.Role.Tutor || @@ -156,6 +159,11 @@ export const Content: React.FC<{ ); if (!tutor || !otherMember) return null; + + const userOfStudent = userTopics + ?.filter((t) => t.userId === otherMember.userId) + .map((t) => t.name.ar); + console.log(userOfStudent); return ( faker.lorem.words(1)), - // level: "C1", + topics: userOfStudent, + // level: "A1", }} sendingMessage={sendingMessageLessonId === item.lesson.id} disabled={!!sendingMessageLessonId} diff --git a/packages/ui/src/components/Lessons/LessonCard.stories.tsx b/packages/ui/src/components/Lessons/LessonCard.stories.tsx index 06f723233..fe6c83dcf 100644 --- a/packages/ui/src/components/Lessons/LessonCard.stories.tsx +++ b/packages/ui/src/components/Lessons/LessonCard.stories.tsx @@ -32,7 +32,6 @@ export const BeforeJoinForStudent: Story = { image: url, role: "tutor", topics: range(7).map(() => faker.lorem.words(1)), - level: "C1", }, ...actions, }, @@ -133,8 +132,7 @@ export const AfterLessonFinishForStudent: Story = { name: faker.person.fullName(), image: url, role: "tutor", - topics: range(7).map(() => faker.lorem.words(1)), - level: "C1", + topics: range(9).map(() => faker.lorem.words(1)), }, ...actions, }, diff --git a/packages/ui/src/components/Lessons/LessonCard.tsx b/packages/ui/src/components/Lessons/LessonCard.tsx index c2fae9996..5c2b5a8cc 100644 --- a/packages/ui/src/components/Lessons/LessonCard.tsx +++ b/packages/ui/src/components/Lessons/LessonCard.tsx @@ -28,8 +28,8 @@ export type Props = { * @note role shall be `student` when the current user is tutor and vice versa */ role: "tutor" | "student"; - topics: Array; - level: string; + topics?: string[]; + level?: string; }; sendingMessage: boolean; @@ -138,10 +138,12 @@ export const LessonCard: React.FC = ({ }, [getTitle]); const isEnded = useMemo(() => end.isBefore(dayjs()), [end]); + const isOngoing = useMemo( () => !canceled && dayjs().isBetween(dayjs(start), end), [canceled, start, end] ); + const isFuture = useMemo( () => !canceled && dayjs().isBefore(dayjs(start)), [canceled, start] @@ -203,8 +205,8 @@ export const LessonCard: React.FC = ({ }, [canceled, currentUserRole, isEnded, intl, onJoin, onRebook, onSendMsg]); const buttonType = useMemo( - () => (currentUserRole === "tutor" && isOngoing ? "main" : "natural"), - [currentUserRole, isOngoing] + () => (isOngoing ? "main" : "natural"), + [isOngoing] ); const button = ( @@ -216,15 +218,21 @@ export const LessonCard: React.FC = ({ onClick={action.onClick} loading={sendingMessage} > - - {action.label} - + {isOngoing ? ( + + {action.label} + + ) : ( + + {action.label} + + )} ); @@ -261,28 +269,35 @@ export const LessonCard: React.FC = ({ > {title} + {!canceled ? ( - - - +
+ + + +
) : null} )}
+
-
- - {member.level} - -
-
+ {currentUserRole === "tutor" ? ( +
+ + {member.level} + +
+ ) : null} + +
@@ -299,10 +314,10 @@ export const LessonCard: React.FC = ({ {dayjs(start).format("h:mm a")} - {" - "} + {" الي "} {dayjs(start).add(duration, "minutes").format("h:mm a")}
@@ -318,26 +333,31 @@ export const LessonCard: React.FC = ({
-
- {member.topics.slice(0, 4).map((topic) => ( - - {topic} - - ))} - {member.topics.length > 4 && ( - - {member.topics.length - 4}+ - - )} -
+ {currentUserRole === "tutor" && + member.topics && + member.topics.length > 0 ? ( +
+ {member.topics.slice(0, 4).map((topic) => ( + + {topic} + + ))} + + {member.topics.length > 4 && ( + + {member.topics.length - 4}+ + + )} +
+ ) : null}
@@ -346,7 +366,9 @@ export const LessonCard: React.FC = ({