@ieltsrealtest/ui
Version:
Reusable UI components for IELTS Real Test platform, built with React and TypeScript.
117 lines (116 loc) • 6.72 kB
JavaScript
'use client';
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import Image from 'next/image';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faThumbsUp as solidThumbsUp } from '@fortawesome/free-solid-svg-icons';
import { faThumbsUp as regularThumbsUp } from '@fortawesome/free-regular-svg-icons';
import { useMemo, useState } from 'react';
import CommentInput from './commentInput';
import { faThumbtack } from '@fortawesome/free-solid-svg-icons';
// import avt from './image/avt.png';
import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc';
import timezone from 'dayjs/plugin/timezone';
import relativeTime from 'dayjs/plugin/relativeTime';
dayjs.extend(utc);
dayjs.extend(timezone);
dayjs.extend(relativeTime);
function formatTimeAgo(date, userTimezone) {
const tz = userTimezone || Intl.DateTimeFormat().resolvedOptions().timeZone;
return dayjs.utc(date).tz(tz).fromNow();
}
export default function CommentCard({ id, avatar, name, comment, threadId, page, userId, isReplying = false, onReplyClick, onRefresh, role, replied_user, time, likes, pin, variant = 'main', isDev = false, }) {
const [liked, setLiked] = useState(() => (likes || []).includes(userId));
const [likeCount, setLikeCount] = useState(likes?.length || 0);
const [pinned, setPinned] = useState(pin);
const { BASE_URL } = useMemo(() => {
return {
BASE_URL: isDev ? 'https://devapi.youready.net/ielts/comment/api' : 'https://api.youready.net/ielts/comment/api',
};
}, [isDev]);
const isReply = variant === 'reply';
const avatarSize = isReply ? 40 : 48;
const avt = "https://ieltsrealtest.s3.us-east-1.amazonaws.com/public/user/avatar/avatar.jpg";
const displayAvatar = useMemo(() => {
return avatar && avatar.trim() ? avatar : avt;
}, [avatar]);
const handleToggleLike = async () => {
try {
if (!liked) {
const res = await fetch(`${BASE_URL}/discussion/likes/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
discussion_id: id,
user_id: userId,
}),
});
if (!res.ok)
throw new Error('Failed to like');
setLiked(true);
setLikeCount((prev) => prev + 1);
}
else {
const res = await fetch(`${BASE_URL}/discussion/likes/`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
discussion_id: id,
user_id: userId,
}),
});
if (!res.ok)
throw new Error('Failed to unlike');
setLiked(false);
setLikeCount((prev) => Math.max(0, prev - 1));
}
}
catch (err) {
// console.error('Error toggling like:', err);
}
};
const handleTogglePin = async () => {
try {
if (!pinned) {
const res = await fetch(`${BASE_URL}/discussion/pins/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
discussion_id: id,
}),
});
if (!res.ok)
throw new Error('Failed to pin');
setPinned(true);
}
else {
const res = await fetch(`${BASE_URL}/discussion/pins/`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
discussion_id: id,
}),
});
if (!res.ok)
throw new Error('Failed to unpin');
setPinned(false);
}
onRefresh?.();
}
catch (err) {
// console.error('Error toggling pin:', err);
}
};
return (_jsxs("div", { className: "flex items-start gap-3", children: [_jsx("div", { className: `flex-shrink-0 overflow-hidden rounded-full bg-gray-200 ${isReply ? 'mt-1' : ''}`, style: { width: avatarSize, height: avatarSize }, children: _jsx(Image, { src: displayAvatar, alt: `${name}'s avatar`, width: avatarSize, height: avatarSize, className: "h-full w-full object-cover" }) }), _jsxs("div", { className: "flex-1 text-sm text-gray-700", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx("span", { className: "font-semibold text-gray-900", children: name }), role && (_jsx("span", { className: "rounded-full border border-[#F8D29B] bg-[#FEEBCB] px-2 py-0.5 text-xs font-medium text-[#7A4C0F]", children: role })), _jsx("span", { className: "text-xs text-gray-500", children: formatTimeAgo(time) }), pinned && (_jsxs("span", { className: "flex items-center gap-1 rounded-full border border-[#FECACA] bg-[#FFF5F5] px-2 py-0.5 text-[11px] font-medium text-[#DC2626]", children: [_jsx(FontAwesomeIcon, { icon: faThumbtack, className: "h-3 w-3 rotate-45" }), "Pinned"] }))] }), _jsxs("p", { className: "mt-2 whitespace-pre-line text-sm leading-relaxed text-gray-700", children: [replied_user && (_jsxs("span", { className: "font-medium text-[#A11D33]", children: ["@", replied_user, " "] })), comment] }), userId && (_jsxs("div", { className: "mt-3 flex items-center gap-5 text-sm text-gray-600", children: [_jsxs("button", { onClick: handleToggleLike, className: "flex items-center gap-1 font-medium text-gray-600 transition-colors hover:text-[#DA1E37]", children: [_jsx(FontAwesomeIcon, { icon: liked ? solidThumbsUp : regularThumbsUp, className: `text-lg ${liked ? 'text-[#DA1E37]' : 'text-gray-400'}` }), _jsx("span", { children: likeCount })] }), onReplyClick && (_jsx("button", { onClick: onReplyClick, className: "font-medium text-gray-600 transition-colors hover:text-[#DA1E37]", children: "Reply" }))] })), isReplying && (_jsx("div", { className: "mt-4", children: _jsx(CommentInput, { userId: userId, threadId: threadId, parentId: id, page: page, onSuccess: () => {
onReplyClick?.();
onRefresh();
}, variant: "reply" }) }))] })] }));
}