parent
f801c5b608
commit
6c5ff30ddb
8 changed files with 220 additions and 19 deletions
@ -0,0 +1,116 @@ |
||||
import { useEffect, useState } from 'react'; |
||||
import { httpGet } from '../../lib/http'; |
||||
import { useToast } from '../../hooks/use-toast'; |
||||
import { isLoggedIn } from '../../lib/jwt'; |
||||
import { Loader2, MessageCircle, ServerCrash } from 'lucide-react'; |
||||
import { Rating } from '../Rating/Rating'; |
||||
|
||||
export interface RoadmapRatingDocument { |
||||
_id?: string; |
||||
roadmapId: string; |
||||
userId: string; |
||||
rating: number; |
||||
feedback?: string; |
||||
|
||||
createdAt: Date; |
||||
updatedAt: Date; |
||||
} |
||||
|
||||
type ListRoadmapRatingsResponse = (RoadmapRatingDocument & { |
||||
name: string; |
||||
avatar: string; |
||||
})[]; |
||||
|
||||
type ListRoadmapRatingsProps = { |
||||
roadmapSlug: string; |
||||
}; |
||||
|
||||
export function ListRoadmapRatings(props: ListRoadmapRatingsProps) { |
||||
const { roadmapSlug } = props; |
||||
|
||||
const toast = useToast(); |
||||
const [isLoading, setIsLoading] = useState(true); |
||||
const [error, setError] = useState(''); |
||||
const [ratings, setRatings] = useState<ListRoadmapRatingsResponse>([]); |
||||
|
||||
const listRoadmapRatings = async () => { |
||||
const { response, error } = await httpGet<ListRoadmapRatingsResponse>( |
||||
`${import.meta.env.PUBLIC_API_URL}/v1-list-roadmap-ratings/${roadmapSlug}`, |
||||
); |
||||
|
||||
if (!response || error) { |
||||
setError(error?.message || 'Something went wrong'); |
||||
setIsLoading(false); |
||||
return; |
||||
} |
||||
|
||||
setRatings(response); |
||||
setError(''); |
||||
setIsLoading(false); |
||||
}; |
||||
|
||||
useEffect(() => { |
||||
if (!isLoggedIn()) { |
||||
return; |
||||
} |
||||
|
||||
listRoadmapRatings().then(); |
||||
}, []); |
||||
|
||||
if (error) { |
||||
return ( |
||||
<div className="flex flex-col items-center justify-center py-10"> |
||||
<ServerCrash className="size-12 text-red-500" /> |
||||
<p className="mt-3 text-lg text-red-500">{error}</p> |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
return ( |
||||
<div> |
||||
{isLoading && ( |
||||
<div className="flex items-center justify-center"> |
||||
<Loader2 className="h-6 w-6 animate-spin stroke-[3px]" /> |
||||
</div> |
||||
)} |
||||
|
||||
{!isLoading && ratings.length > 0 && ( |
||||
<div className="flex flex-col gap-2"> |
||||
{ratings.map((rating) => { |
||||
const userAvatar = rating?.avatar |
||||
? `${import.meta.env.PUBLIC_AVATAR_BASE_URL}/${rating.avatar}` |
||||
: '/images/default-avatar.png'; |
||||
|
||||
return ( |
||||
<div key={rating._id} className="rounded-md border p-2"> |
||||
<div className="flex items-center gap-2"> |
||||
<img |
||||
src={userAvatar} |
||||
alt={rating.name} |
||||
className="h-6 w-6 rounded-full" |
||||
/> |
||||
<span className="text-lg font-medium">{rating.name}</span> |
||||
</div> |
||||
|
||||
<div className="mt-2.5"> |
||||
<Rating rating={rating.rating} readOnly /> |
||||
|
||||
{rating.feedback && ( |
||||
<p className="mt-2 text-gray-500">{rating.feedback}</p> |
||||
)} |
||||
</div> |
||||
</div> |
||||
); |
||||
})} |
||||
</div> |
||||
)} |
||||
|
||||
{!isLoading && ratings.length === 0 && ( |
||||
<div className="flex flex-col items-center justify-center py-10"> |
||||
<MessageCircle className="size-12 text-gray-600" /> |
||||
<p className="mt-3 text-lg text-gray-600">No Feedbacks</p> |
||||
</div> |
||||
)} |
||||
</div> |
||||
); |
||||
} |
Loading…
Reference in new issue