Compare commits

...

2 Commits

  1. 170
      frontend/edu-connect/src/app/courses/show/[id]/_partials/CourseDiscussion.tsx
  2. 1
      frontend/edu-connect/src/app/courses/show/[id]/_partials/CourseMedia.tsx

@ -8,28 +8,26 @@ import { ScrollArea } from "@/components/ui/scroll-area";
import { MessageCircle, Reply, X } from "lucide-react"; import { MessageCircle, Reply, X } from "lucide-react";
import { APP_BASE_URL } from '@/utils/constants'; import { APP_BASE_URL } from '@/utils/constants';
import { getEduConnectAccessToken } from '@/helpers/token.helper'; import { getEduConnectAccessToken } from '@/helpers/token.helper';
import { usePathname } from 'next/navigation';
// Types import { fetchHeader } from '@/helpers/fetch.helper';
interface Reply {
id: number; // Updated Types
user: string; interface Message {
avatar: string; id: string;
content: string; isSelf: number;
text: string;
timestamp: string; timestamp: string;
userId: string;
username: string;
} }
interface Discussion { interface ApiResponse {
id: number; count: number;
user: string; messages: Message[];
avatar: string;
content: string;
likes: number;
replies: Reply[];
timestamp: string;
} }
interface PageData { interface PageData {
discussions: Discussion[]; messages: Message[];
hasMore: boolean; hasMore: boolean;
} }
@ -39,14 +37,14 @@ interface DiscussionSectionProps {
// Key generator for SWR // Key generator for SWR
const getKey = (courseUuid: string) => (pageIndex: number, previousPageData: PageData | null) => { const getKey = (courseUuid: string) => (pageIndex: number, previousPageData: PageData | null) => {
if (previousPageData && !previousPageData.discussions.length) return null; if (previousPageData && !previousPageData.messages.length) return null;
return `${APP_BASE_URL}/api/chat/get?page=${pageIndex}`; return `${APP_BASE_URL}/api/chat/get`;
}; };
// Fetcher function // Updated fetcher function
const fetcher = async (url: string, courseUuid: string): Promise<PageData> => { const fetcher = async (url: string, courseUuid: string): Promise<PageData> => {
const formData = new FormData(); const formData = new FormData();
formData.append('course_uuid', courseUuid); formData.append('course_id', courseUuid);
const response = await fetch(url, { const response = await fetch(url, {
method: 'POST', method: 'POST',
@ -60,21 +58,22 @@ const fetcher = async (url: string, courseUuid: string): Promise<PageData> => {
throw new Error('Failed to fetch discussions'); throw new Error('Failed to fetch discussions');
} }
const data = await response.json(); const data: ApiResponse = await response.json();
return { return {
discussions: data.discussions.map((discussion: any) => ({ messages: data.messages.map(message => ({
...discussion, ...message,
timestamp: new Date(discussion.timestamp).toLocaleString() timestamp: new Date(message.timestamp).toLocaleString()
})), })),
hasMore: data.hasMore hasMore: data.count > data.messages.length
}; };
}; };
const EnhancedDiscussionSection: React.FC<DiscussionSectionProps> = ({ courseUuid }) => { const EnhancedDiscussionSection: React.FC<DiscussionSectionProps> = ({ courseUuid }) => {
const [comment, setComment] = useState(''); const [comment, setComment] = useState('');
const [replyingTo, setReplyingTo] = useState<{ commentId: number; userName: string } | null>(null); const [replyingTo, setReplyingTo] = useState<any>(null);
const loadingRef = useRef<HTMLDivElement>(null); const loadingRef = useRef<HTMLDivElement>(null);
const containerRef = useRef<HTMLDivElement>(null); const pathname = usePathname();
const id = pathname.split('/')?.at(-1);
const { const {
data, data,
@ -85,7 +84,7 @@ const EnhancedDiscussionSection: React.FC<DiscussionSectionProps> = ({ courseUui
mutate mutate
} = useSWRInfinite( } = useSWRInfinite(
getKey(courseUuid), getKey(courseUuid),
(url) => fetcher(url, courseUuid), (url) => fetcher(url, id!),
{ {
revalidateFirstPage: false, revalidateFirstPage: false,
revalidateOnFocus: false, revalidateOnFocus: false,
@ -93,7 +92,7 @@ const EnhancedDiscussionSection: React.FC<DiscussionSectionProps> = ({ courseUui
} }
); );
const allDiscussions = data ? data.flatMap(page => page.discussions) : []; const allMessages = data ? data.flatMap(page => page.messages) : [];
const hasMore = data ? data[data.length - 1]?.hasMore : true; const hasMore = data ? data[data.length - 1]?.hasMore : true;
const isLoadingMore = !data && !error || (size > 0 && data && typeof data[size - 1] === "undefined"); const isLoadingMore = !data && !error || (size > 0 && data && typeof data[size - 1] === "undefined");
@ -118,54 +117,28 @@ const EnhancedDiscussionSection: React.FC<DiscussionSectionProps> = ({ courseUui
const handleSubmit = async () => { const handleSubmit = async () => {
if (comment.trim()) { if (comment.trim()) {
const formData = new FormData(); const formData = new FormData();
formData.append('course_uuid', courseUuid); formData.append('course_id', courseUuid);
formData.append('content', comment); formData.append('message', comment);
if (replyingTo) { // if (replyingTo) {
formData.append('parent_id', replyingTo.commentId.toString()); // formData.append('parent_id', replyingTo.commentId.toString());
} // }
try { try {
const response = await fetch('/api/chat/post', { const response = await fetch(`${APP_BASE_URL}/api/chat/send`, {
method: 'POST', method: 'POST',
body: formData body: formData,
headers : fetchHeader()
}); });
if (!response.ok) { if (!response.ok) {
throw new Error('Failed to post comment'); throw new Error('Failed to post comment');
} }
const newComment = await response.json();
// Optimistically update the UI // Optimistically update the UI
const updatedData = [...(data || [])];
if (replyingTo) {
// Update the discussion with the new reply
updatedData.forEach(page => {
page.discussions = page.discussions.map(discussion => {
if (discussion.id === replyingTo.commentId) {
return {
...discussion,
replies: [...discussion.replies, {
...newComment,
timestamp: 'Just now'
}]
};
}
return discussion;
});
});
} else {
// Add new top-level comment
if (updatedData[0]) {
updatedData[0].discussions = [{
...newComment,
replies: [],
timestamp: 'Just now'
}, ...updatedData[0].discussions];
}
}
await mutate(updatedData, false);
await mutate();
setComment(''); setComment('');
setReplyingTo(null); setReplyingTo(null);
} catch (error) { } catch (error) {
@ -193,60 +166,28 @@ const EnhancedDiscussionSection: React.FC<DiscussionSectionProps> = ({ courseUui
); );
} }
// Rest of your component implementation remains similar, just update the rendering logic:
return ( return (
<div className="w-full max-w-3xl mx-auto"> <div className="w-full max-w-3xl mx-auto">
<ScrollArea className="h-[600px]"> <ScrollArea className="h-[600px]">
<div className="space-y-6 p-4"> <div className="space-y-6 p-4">
{allDiscussions.map((discussion) => ( {allMessages.map((message) => (
<Card key={discussion.id} className="p-4"> <Card key={message.id} className="p-4">
<div className="space-y-4"> <div className="space-y-4">
<div className="flex gap-4"> <div className="flex gap-4">
<Avatar className="w-8 h-8"> <Avatar className="w-8 h-8">
<AvatarImage src={discussion.avatar} alt={discussion.user} /> <AvatarImage src="/api/placeholder/32/32" alt={message.username} />
<AvatarFallback>{discussion.user[0]}</AvatarFallback> <AvatarFallback>{message.username[0]}</AvatarFallback>
</Avatar> </Avatar>
<div className="flex-1"> <div className="flex-1">
<div className="flex items-center gap-2 mb-1"> <div className="flex items-center gap-2 mb-1">
<span className="font-medium">{discussion.user}</span> <span className="font-medium">{message.username}</span>
<span className="text-sm text-gray-500">{discussion.timestamp}</span> <span className="text-sm text-gray-500">{message.timestamp}</span>
</div>
<p className="text-gray-700 mb-2">{discussion.content}</p>
<div className="flex items-center gap-4">
<button
className="flex items-center gap-1 text-sm text-gray-500 hover:text-gray-700"
onClick={() => handleReply(discussion.id, discussion.user)}
>
<Reply className="w-4 h-4" />
Reply
</button>
<div className="flex items-center gap-1 text-sm text-gray-500">
<MessageCircle className="w-4 h-4" />
{discussion.replies.length} replies
</div> </div>
<p className="text-gray-700 mb-2">{message.text}</p>
</div> </div>
</div> </div>
</div> </div>
{discussion.replies.length > 0 && (
<div className="ml-12 space-y-4">
{discussion.replies.map((reply) => (
<div key={reply.id} className="flex gap-4">
<Avatar className="w-8 h-8">
<AvatarImage src={reply.avatar} alt={reply.user} />
<AvatarFallback>{reply.user[0]}</AvatarFallback>
</Avatar>
<div className="flex-1">
<div className="flex items-center gap-2 mb-1">
<span className="font-medium">{reply.user}</span>
<span className="text-sm text-gray-500">{reply.timestamp}</span>
</div>
<p className="text-gray-700">{reply.content}</p>
</div>
</div>
))}
</div>
)}
</div>
</Card> </Card>
))} ))}
@ -254,8 +195,8 @@ const EnhancedDiscussionSection: React.FC<DiscussionSectionProps> = ({ courseUui
{isLoadingMore && ( {isLoadingMore && (
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-gray-900" /> <div className="animate-spin rounded-full h-6 w-6 border-b-2 border-gray-900" />
)} )}
{!hasMore && allDiscussions.length > 0 && ( {!hasMore && allMessages.length > 0 && (
<div className="text-gray-500">No more discussions</div> <div className="text-gray-500">No more messages</div>
)} )}
</div> </div>
</div> </div>
@ -268,26 +209,15 @@ const EnhancedDiscussionSection: React.FC<DiscussionSectionProps> = ({ courseUui
<AvatarFallback>U</AvatarFallback> <AvatarFallback>U</AvatarFallback>
</Avatar> </Avatar>
<div className="flex-1"> <div className="flex-1">
{replyingTo && (
<div className="flex items-center gap-2 mb-2 text-sm text-gray-500">
<span>Replying to {replyingTo.userName}</span>
<button
onClick={cancelReply}
className="p-1 hover:bg-gray-100 rounded-full"
>
<X className="w-4 h-4" />
</button>
</div>
)}
<Textarea <Textarea
placeholder={replyingTo ? `Reply to ${replyingTo.userName}...` : "Add to the discussion..."} placeholder="Add to the discussion..."
value={comment} value={comment}
onChange={(e) => setComment(e.target.value)} onChange={(e) => setComment(e.target.value)}
className="min-h-24 mb-6" className="min-h-24 mb-6"
/> />
<div className="w-fit ml-auto"> <div className="w-fit ml-auto">
<Button onClick={handleSubmit} className="bg-purple-700"> <Button onClick={handleSubmit} className="bg-purple-700">
{replyingTo ? 'Post Reply' : 'Post Comment'} Post Message
</Button> </Button>
</div> </div>
</div> </div>

@ -10,6 +10,7 @@ import { useState } from "react"
const CourseMedia : React.FC<{courseData : Record<string,any>}> = ({courseData}) => { const CourseMedia : React.FC<{courseData : Record<string,any>}> = ({courseData}) => {
const [currentPage, setCurrentPage] = useState<number>(0); const [currentPage, setCurrentPage] = useState<number>(0);
console.log(courseData)
return( return(
<div className="container mx-auto py-8"> <div className="container mx-auto py-8">

Loading…
Cancel
Save