Skip to content

Commit

Permalink
Merge pull request #241 from litespace-org/mk/rating-algorithms
Browse files Browse the repository at this point in the history
fix(ui): fixed review card layout bug
  • Loading branch information
neuodev authored Dec 26, 2024
2 parents 2ba19a3 + 29c4418 commit abb481b
Show file tree
Hide file tree
Showing 11 changed files with 210 additions and 201 deletions.
2 changes: 1 addition & 1 deletion apps/nova/src/components/TutorProfile/ProfileInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const ProfileInfo: React.FC<{
}> = ({ about, topics, video }) => {
const intl = useFormatMessage();
return (
<div className="grid grid-cols-2 gap-[88px]">
<div className="grid grid-cols-2 py-8 gap-[88px] p-8 px-10">
<div>
{about ? (
<div>
Expand Down
100 changes: 58 additions & 42 deletions apps/nova/src/components/TutorProfile/Ratings.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useFindTutorRatings } from "@litespace/headless/rating";
import React, { useCallback, useMemo } from "react";
import React, { useMemo } from "react";
import { organizeRatings } from "@/lib/ratings";
import { useUser } from "@litespace/headless/context/user";
import {
Expand All @@ -9,21 +9,23 @@ import {
import { Typography } from "@litespace/luna/Typography";
import { useFormatMessage } from "@litespace/luna/hooks/intl";
import Star from "@litespace/assets/Star";
import NewTutor from "@litespace/assets/NewTutor";
import { Button, ButtonSize } from "@litespace/luna/Button";
import { isEmpty } from "lodash";
import { Loader, LoadingError } from "@litespace/luna/Loading";
import cn from "classnames";
import { asFullAssetUrl } from "@litespace/luna/backend";
import NewTutor from "@litespace/assets/NewTutor";

const NoTutorRatings: React.FC<{ tutorName: string | null }> = ({
tutorName,
}) => {
const intl = useFormatMessage();
return (
<div className="flex gap-[88px] items-center justify-center">
<div className="flex tw-relative items-center justify-center h-[294px] w-full gap-[88px]">
<Typography
element="subtitle-1"
weight="bold"
className="text-natural-950 text-center max-w-[476px]"
className="text-natural-950 text-center -translate-y-7 max-w-[476px]"
>
{intl("tutor.profile.first-rating", { tutor: tutorName })}
</Typography>
Expand All @@ -42,12 +44,8 @@ const Ratings: React.FC<{ id: number; tutorName: string | null }> = ({
const intl = useFormatMessage();
const ratingsQuery = useFindTutorRatings(id, { page: 1, size: 30 });

const refetchRatings = useCallback(() => {
ratingsQuery.refetch();
}, [ratingsQuery]);

const ratings = useMemo(
() => organizeRatings(ratingsQuery.data?.list || [], user?.id),
() => organizeRatings(ratingsQuery.data?.list, user?.id),
[ratingsQuery.data, user]
);

Expand All @@ -63,49 +61,67 @@ const Ratings: React.FC<{ id: number; tutorName: string | null }> = ({
<div className="h-96 flex items-center justify-center">
<LoadingError
error={intl("tutor.profile.error")}
retry={refetchRatings}
retry={ratingsQuery.refetch}
/>
</div>
);

return (
<div className="flex flex-col my-8 mx-10 gap-10 justify-center">
{isEmpty(ratings) ? <NoTutorRatings tutorName={tutorName} /> : null}
<div
className={cn(
"flex flex-col justify-center p-8",
isEmpty(ratings) ? "gap-8" : "gap-10"
)}
>
{isEmpty(ratings) ? (
<NoTutorRatings tutorName={tutorName} />
) : (
<div className="grid gap-4 flex-wrap justify-center grid-cols-[repeat(auto-fill,minmax(256px,1fr))]">
{ratings.map((rating, index) => {
if (
"userId" in rating &&
(rating.feedback || rating.userId === user.id)
)
return (
<TutorRatingCard
key={index}
feedback={rating.feedback}
imageUrl={asFullAssetUrl(rating.image || "")}
rating={rating.value}
studentId={rating.userId}
studentName={rating.name}
tutorName={tutorName}
owner={rating.userId === user.id}
/>
);

<div className="flex gap-4 flex-wrap justify-center">
{ratings.map((rating) => {
if (
"userId" in rating &&
(rating.feedback || rating.userId === user.id)
)
return (
<TutorRatingCard
feedback={rating.feedback}
imageUrl={rating.image}
rating={rating.value}
studentId={rating.userId}
studentName={rating.name}
tutorName={tutorName}
owner={rating.userId === user.id}
/>
);
if ("ratings" in rating)
return (
<TutorRatingCardGroup
key={index}
ratings={rating.ratings.map((rating) => ({
userId: rating.userId,
name: rating.name,
imageUrl: asFullAssetUrl(rating.image || ""),
}))}
tutorName={tutorName}
value={rating.value}
/>
);
})}
</div>
)}

if ("ratings" in rating)
return (
<TutorRatingCardGroup
ratings={rating.ratings}
tutorName={tutorName}
value={rating.value}
/>
);
})}
</div>

<div className="flex gap-10 flex-col items-center justify-center ">
<div
className={cn(
"flex gap-10 flex-col items-center justify-center",
isEmpty(ratings) && "-mt-6"
)}
>
<Typography
element="subtitle-1"
weight="medium"
className="text-natural-950 text-center max-w-[912px]"
className="text-natural-950 text-center max-w-[631px]"
>
{intl("tutor.profile.your-ratings-help")}
</Typography>
Expand Down
38 changes: 17 additions & 21 deletions apps/nova/src/components/TutorProfile/TutorTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export const TutorTabs: React.FC<{
value={tab}
onValueChange={(value: string) => setTab(value as Tab)}
>
<Tabs.List className="border-b border-natural-300 flex gap-[56px] ">
<Tabs.List className="border-b border-natural-300 flex gap-[56px] px-10 ">
{tabs.map(({ value, label }) => (
<Tabs.Trigger
key={value}
Expand Down Expand Up @@ -100,26 +100,22 @@ export const TutorTabs: React.FC<{
))}
</Tabs.List>

<div className="mt-8">
<AnimatePresence initial={false} mode="wait">
{tab === "profile" ? (
<Animate key="profile" tab="profile">
<ProfileInfo
about={tutor.about}
topics={tutor.topics}
video={tutor.video}
/>
</Animate>
) : null}
{tab === "ratings" ? (
<Animate key="ratings" tab="ratings">
<div className="min-h-96">
<Ratings tutorName={tutor.name} id={tutor.id} />
</div>
</Animate>
) : null}
</AnimatePresence>
</div>
<AnimatePresence initial={false} mode="wait">
{tab === "profile" ? (
<Animate key="profile" tab="profile">
<ProfileInfo
about={tutor.about}
topics={tutor.topics}
video={tutor.video}
/>
</Animate>
) : null}
{tab === "ratings" ? (
<Animate key="ratings" tab="ratings">
<Ratings tutorName={tutor.name} id={tutor.id} />
</Animate>
) : null}
</AnimatePresence>
</Tabs.Root>
);
};
55 changes: 23 additions & 32 deletions apps/nova/src/lib/ratings.ts
Original file line number Diff line number Diff line change
@@ -1,50 +1,41 @@
import { IRating } from "@litespace/types";
import { TutorRatingCardGroupProps } from "@litespace/luna/TutorFeedback";
import { concat } from "lodash";

const MAX_RATING_COUNT = 8;

export const organizeRatings = (
ratings: IRating.FindTutorRatingsApiResponse["list"],
currentUserId: number | undefined
ratings?: IRating.FindTutorRatingsApiResponse["list"],
currentUserId?: number
) => {
let currentUserRating: IRating.RateeRatings | undefined;
const ratingsWithFeedback: IRating.RateeRatings[] = [];
const ratingsWithoutFeedback: Omit<TutorRatingCardGroupProps, "tutorName">[] =
[];
if (!ratings) return [];

let currentUserRating: IRating.RateeRating | null = null;
const ratingsWithFeedback: IRating.RateeRating[] = [];
const ratingsWithoutFeedback: {
ratings: IRating.RateeRating[];
value: number;
}[] = [];

ratings.forEach((rating) => {
if (rating.userId === currentUserId) {
currentUserRating = rating;
return;
}
if (rating.userId === currentUserId) return (currentUserRating = rating);

if (rating.feedback) ratingsWithFeedback.push(rating);
if (!rating.feedback) {
const ratingGroup = ratingsWithoutFeedback.find(
(r) => r.value === rating.value
(rating) => rating.value === rating.value
);
if (ratingGroup)
return ratingGroup.ratings.push({
name: rating.name,
imageUrl: rating.image,
userId: rating.userId,
});
if (ratingGroup) return ratingGroup.ratings.push(rating);

return ratingsWithoutFeedback.push({
ratings: [
{
name: rating.name,
imageUrl: rating.image,
userId: rating.userId,
},
],
ratings: [rating],
value: rating.value,
});
}
});

if (currentUserRating)
return [
currentUserRating,
...ratingsWithFeedback,
...ratingsWithoutFeedback,
].slice(0, 8);
return [...ratingsWithFeedback, ...ratingsWithoutFeedback].slice(0, 8);
const otherUsersRatings = [...ratingsWithFeedback, ...ratingsWithoutFeedback];
return concat(currentUserRating || [], otherUsersRatings).slice(
0,
MAX_RATING_COUNT
);
};
2 changes: 1 addition & 1 deletion apps/nova/src/pages/TutorProfile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ const TutorProfile: React.FC = () => {
<span className="underline text-brand-700">{tutor.data.name}</span>
</Typography>
</div>
<div className="bg-natural-50 border border-natural-100 shadow-tutor-profile rounded-2xl p-10 mt-6 flex flex-col gap-12">
<div className="bg-natural-50 border border-natural-100 shadow-tutor-profile rounded-2xl mt-6 flex flex-col gap-12">
<TutorProfileCard {...tutor.data} onBook={openDialog} />
<TutorTabs tutor={tutor.data} />
<BookLesson tutorId={tutor.data.id} close={closeDialog} open={open} />
Expand Down
170 changes: 85 additions & 85 deletions packages/assets/assets/new-tutor.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
26 changes: 16 additions & 10 deletions packages/luna/src/components/Loading/Loader.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,31 @@
import React from "react";
import { Typography } from "@litespace/luna/Typography";
import cn from "classnames";
import { motion } from "framer-motion";

export const Loader: React.FC<{
text?: string;
variant?: "small" | "large";
}> = ({ text, variant = "large" }) => {
return (
<div className="tw-h-full tw-flex tw-flex-col tw-justify-center tw-items-center tw-gap-4">
<div
<motion.div
animate={{
rotate: [360, 0],
}}
transition={{
repeat: Infinity,
duration: 1,
ease: "linear",
}}
style={{
animationDirection: "reverse",
maskImage:
variant === "large"
? "radial-gradient(circle, transparent 30px, white 16px)"
: "radial-gradient(circle, transparent 25px, white 0px)",
}}
className={cn(
"tw-animate-spin tw-flex tw-items-center tw-relative tw-rounded-full tw-justify-center tw-bg-loader",
"tw-flex tw-items-center tw-relative tw-rounded-full tw-justify-center tw-bg-loader",
{
"tw-w-[64px] tw-h-[64px] tw-max-w-16 tw-max-h-16":
variant === "small",
Expand All @@ -22,12 +34,6 @@ export const Loader: React.FC<{
}
)}
>
<div
className={cn("tw-bg-white tw-rounded-full", {
"tw-w-[48px] tw-h-[48px]": variant === "small",
"tw-w-[60px] tw-h-[60px]": variant === "large",
})}
/>
<div
className={cn(
"tw-bg-background-loader-spinner tw-rounded-full tw-absolute tw-bottom-0 tw-left-1/2 -tw-translate-x-1/2",
Expand All @@ -37,7 +43,7 @@ export const Loader: React.FC<{
}
)}
/>
</div>
</motion.div>

{text ? (
<Typography
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export const TutorProfileCard: React.FC<{
);

return (
<div className="tw-flex tw-gap-10 tw-items-center">
<div className="tw-flex tw-gap-10 tw-items-center tw-p-10">
<div className="tw-w-[242px] tw-aspect-square tw-rounded-full tw-overflow-hidden">
<Avatar
src={orUndefined(image)}
Expand Down
6 changes: 3 additions & 3 deletions packages/luna/src/components/VideoPlayer/V2/VideoPlayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { CONTAINER_ID, useVideo } from "@/components/VideoPlayer/V2/video";
import Play from "@litespace/assets/Play";
import Pause from "@litespace/assets/Pause";
import SettingsScrew from "@litespace/assets/SettingsScrew";
import Spinner from "@litespace/assets/Spinner";
import ExclaimationMarkCircle from "@litespace/assets/ExclaimationMarkCircle";
import cn from "classnames";
import SettingsMenu from "@/components/VideoPlayer/V2/SettingsMenu";
Expand All @@ -12,6 +11,7 @@ import { useFormatMessage } from "@/hooks";
import { AudioController } from "@/components/VideoPlayer/V2/AudioController";
import { VideoProgressbar } from "@/components/VideoPlayer/V2/VideoProgressbar";
import { PlayButton } from "@/components/VideoPlayer/V2/PlayButton";
import { Loader } from "@/components/Loading";

export const VideoPlayer: React.FC<{ src?: string }> = ({ src }) => {
const intl = useFormatMessage();
Expand Down Expand Up @@ -58,7 +58,7 @@ export const VideoPlayer: React.FC<{ src?: string }> = ({ src }) => {
src={src}
className="tw-w-full tw-h-full data-[ready=true]:tw-cursor-pointer"
/>
{paused && currentTime === 0 ? (
{readyState > 1 && paused && currentTime === 0 ? (
<div className="tw-absolute tw-top-1/2 tw-left-1/2 -tw-translate-x-1/2 -tw-translate-y-1/2">
<PlayButton togglePlay={togglePlay} />
</div>
Expand Down Expand Up @@ -97,7 +97,7 @@ export const VideoPlayer: React.FC<{ src?: string }> = ({ src }) => {
"tw-absolute tw-z-[8] tw-transition-opacity tw-duration-300 tw-top-1/2 tw-left-1/2 -tw-translate-x-1/2 -tw-translate-y-1/2"
)}
>
<Spinner className="tw-animate-spin" />
<Loader variant="large" />
</span>
) : null}
{status === "error" ? (
Expand Down
Loading

0 comments on commit abb481b

Please sign in to comment.