UNPKG

@ieltsrealtest/ui

Version:

Reusable UI components for IELTS Real Test platform, built with React and TypeScript.

117 lines (116 loc) 6.72 kB
'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" }) }))] })] })); }