Skip to content

Commit

Permalink
Merge branch 'dev'
Browse files Browse the repository at this point in the history
  • Loading branch information
Lee-Dongwook committed Oct 19, 2024
2 parents 6dd9a00 + b50d566 commit a75e673
Show file tree
Hide file tree
Showing 6 changed files with 200 additions and 197 deletions.
167 changes: 0 additions & 167 deletions client/src/app/[id]/page.tsx

This file was deleted.

53 changes: 53 additions & 0 deletions client/src/components/Chat/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
"use client";

import { useEffect, useState } from "react";
import toast from "react-hot-toast";
import socket from "@/lib/socket";

interface ChatProps {
id: string;
}

export default function Chat({ id }: ChatProps) {
const [message, setMessage] = useState<string>("");
const [messages, setMessages] = useState<string[]>([]);

const sendMessage = () => {
socket.emit("send-message", message);
setMessages((prevMessages) => [...prevMessages, message]);
setMessage("");
};

useEffect(() => {
socket.on("receive-message", (message: string) => {
toast("New message received");
setMessages((prevMessages) => [...prevMessages, message]);
});

return () => {
socket.off("receive-message");
};
}, [id]);

return (
<div>
<h2 className="text-lg font-bold">Chat</h2>
<div className="bg-gray-100 p-4 h-64 overflow-y-scroll">
{messages.map((msg, index) => (
<div key={index} className="text-gray-800">
{msg}
</div>
))}
</div>
<input
value={message}
onChange={(e) => setMessage(e.target.value)}
className="border p-2 w-full mt-2"
placeholder="Type a message..."
/>
<button onClick={sendMessage} className="bg-blue-500 text-white p-2 mt-2">
Send
</button>
</div>
);
}
94 changes: 75 additions & 19 deletions client/src/components/DocumentEditor/index.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,86 @@
/* eslint-disable react-hooks/exhaustive-deps */
"use client";

import dynamic from "next/dynamic";
import "react-quill/dist/quill.snow.css";

const QuillNoSSRWrapper = dynamic(() => import("react-quill"), {
ssr: false,
loading: () => <p>Loading...</p>,
});
import { useEffect, useState } from "react";
import QuillEditor from "@/components/QuillEditor";
import useDebounce from "@/hook/useDebounce";
import API from "@/lib/api";
import socket from "@/lib/socket";
import {
saveDocumentLocally,
getLocalDocument,
deleteLocalDocument,
} from "@/lib/db";

interface DocumentEditorProps {
content: string;
onChange: (value: string) => void;
id: string;
}

export default function DocumentEditor({
content,
onChange,
}: DocumentEditorProps) {
export default function DocumentEditor({ id }: DocumentEditorProps) {
const [content, setContent] = useState<string>("");
const [isOffline, setIsOffline] = useState<boolean>(false);
const debouncedContent = useDebounce(content, 500);

const handleOnline = () => setIsOffline(false);
const handleOffline = () => setIsOffline(true);

const fetchDocument = async () => {
try {
const localData = await getLocalDocument(id);
if (localData) {
setContent(localData.content);
setIsOffline(true);
} else {
const { data } = await API.get(`/api/document/${id}`);
setContent(data.content);
}
} catch (error) {
console.error("Failed to fetch document:", error);
}
};

const handleChangeContent = (value: string) => {
setContent(value);
socket.emit("edit-document", id, value);
};

useEffect(() => {
fetchDocument();

window.addEventListener("online", handleOnline);
window.addEventListener("offline", handleOffline);

return () => {
window.removeEventListener("online", handleOnline);
window.removeEventListener("offline", handleOffline);
};
}, [id]);

useEffect(() => {
if (debouncedContent) {
if (isOffline) {
saveDocumentLocally(id, debouncedContent);
} else {
API.put(`/api/document/${id}`, { content: debouncedContent })
.then(() => {
deleteLocalDocument(id);
})
.catch((error) => {
console.error("Failed to save document: ", error);
saveDocumentLocally(id, debouncedContent);
});
}
}
}, [debouncedContent, isOffline, id]);

return (
<div className="mt-4">
<QuillNoSSRWrapper
theme="snow"
value={content}
onChange={onChange}
className="bg-white p-2"
/>
{isOffline && (
<div className="bg-yellow-300 text-yellow-900 p-2 mb-2 rounded">
You are offline. Changes will be synced when you are back online.
</div>
)}
<QuillEditor value={content} onChange={handleChangeContent} />
</div>
);
}
37 changes: 37 additions & 0 deletions client/src/components/ParticipantsList/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"use client";

import { useState, useEffect } from "react";
import socket from "@/lib/socket";

interface ParticipantsListProps {
id: string;
}

export default function ParticipantsList({ id }: ParticipantsListProps) {
const [participants, setParticipants] = useState<string[]>([]);

useEffect(() => {
socket.emit("join-document", id);

socket.on("update-participants", (participants: string[]) => {
setParticipants(participants);
});

return () => {
socket.off("update-participants");
};
}, [id]);

return (
<div>
<h2 className="text-lg font-bold">Participants</h2>
<ul>
{participants.map((participant, index) => (
<li key={index} className="text-gray-700">
{participant}
</li>
))}
</ul>
</div>
);
}
25 changes: 25 additions & 0 deletions client/src/components/QuillEditor/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"use client";

import dynamic from "next/dynamic";
import "react-quill/dist/quill.snow.css";

const QuillNoSSRWrapper = dynamic(() => import("react-quill"), {
ssr: false,
loading: () => <p>Loading...</p>,
});

interface QuillEditorProps {
value: string;
onChange: (value: string) => void;
}

export default function QuillEditor({ value, onChange }: QuillEditorProps) {
return (
<QuillNoSSRWrapper
theme="snow"
value={value}
onChange={onChange}
className="bg-white p-2"
/>
);
}
Loading

0 comments on commit a75e673

Please sign in to comment.