diff --git a/code/client/src/ui/pages/document/Document.scss b/code/client/src/ui/pages/document/Document.scss
index 516812a4..b708a158 100644
--- a/code/client/src/ui/pages/document/Document.scss
+++ b/code/client/src/ui/pages/document/Document.scss
@@ -1,2 +1,3 @@
.document {
+ position: relative;
}
diff --git a/code/client/src/ui/pages/document/Document.tsx b/code/client/src/ui/pages/document/Document.tsx
index fa497194..2a45bc59 100644
--- a/code/client/src/ui/pages/document/Document.tsx
+++ b/code/client/src/ui/pages/document/Document.tsx
@@ -8,6 +8,7 @@ import useDocumentService from '@services/resource/useResourcesService';
import useConnectors from '@domain/editor/connectors/useConnectors';
import FloatingButtons from '@ui/pages/document/components/floating-buttons/FloatingButtons';
import { DocumentResource } from '@notespace/shared/src/workspace/types/resource';
+import Collaborators from '@ui/pages/document/components/collaborators/Collaborators';
import './Document.scss';
function Document() {
@@ -43,15 +44,14 @@ function Document() {
setLoaded(true);
}
fetchDocument();
- return () => {
- socket.emit('leaveDocument');
- };
+ return () => socket.emit('leaveDocument');
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [id]);
if (!loaded) return null;
return (
+
diff --git a/code/client/src/ui/pages/document/components/collaborators/Collaborators.scss b/code/client/src/ui/pages/document/components/collaborators/Collaborators.scss
new file mode 100644
index 00000000..4e554b45
--- /dev/null
+++ b/code/client/src/ui/pages/document/components/collaborators/Collaborators.scss
@@ -0,0 +1,23 @@
+.collaborators {
+ position: absolute;
+ top: 0;
+ right: 0;
+
+ > div {
+ width: 4vh;
+ height: 4vh;
+ border-radius: 50%;
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
+ padding: 1vh;
+ margin: 1vh;
+
+ display: flex;
+ justify-content: center;
+ align-items: center;
+
+ a {
+ text-decoration: none !important;
+ color: white;
+ }
+ }
+}
diff --git a/code/client/src/ui/pages/document/components/collaborators/Collaborators.tsx b/code/client/src/ui/pages/document/components/collaborators/Collaborators.tsx
new file mode 100644
index 00000000..a19f9249
--- /dev/null
+++ b/code/client/src/ui/pages/document/components/collaborators/Collaborators.tsx
@@ -0,0 +1,23 @@
+import useCollaborators from '@ui/pages/document/components/collaborators/useCollaborators';
+import { Link } from 'react-router-dom';
+import './Collaborators.scss';
+
+function Collaborators() {
+ const collaborators = useCollaborators();
+ return (
+
+ {collaborators.map(collaborator => {
+ const nameParts = collaborator.name.split(' ');
+ const initials =
+ nameParts.length > 1 ? `${nameParts[0][0]}${nameParts[nameParts.length - 1][0]}` : nameParts[0][0];
+ return (
+
+ {initials}
+
+ );
+ })}
+
+ );
+}
+
+export default Collaborators;
diff --git a/code/client/src/ui/pages/document/components/collaborators/useCollaborators.ts b/code/client/src/ui/pages/document/components/collaborators/useCollaborators.ts
new file mode 100644
index 00000000..75303838
--- /dev/null
+++ b/code/client/src/ui/pages/document/components/collaborators/useCollaborators.ts
@@ -0,0 +1,26 @@
+import { useState } from 'react';
+import { Collaborator } from '@notespace/shared/src/users/types';
+import useSocketListeners from '@services/communication/socket/useSocketListeners';
+import { useCommunication } from '@/contexts/communication/useCommunication';
+
+function useCollaborators() {
+ const [collaborators, setCollaborators] = useState([]);
+ const { socket } = useCommunication();
+
+ const onCollaboratorsJoined = (users: Collaborator[]) => {
+ setCollaborators(prev => [...prev, ...users]);
+ };
+
+ const onCollaboratorLeft = (id: string) => {
+ setCollaborators(prev => prev.filter(u => u.id !== id));
+ };
+
+ useSocketListeners(socket, {
+ joinedDocument: onCollaboratorsJoined,
+ leftDocument: onCollaboratorLeft,
+ });
+
+ return collaborators;
+}
+
+export default useCollaborators;
diff --git a/code/client/src/ui/pages/workspace/components/DocumentView.tsx b/code/client/src/ui/pages/workspace/components/DocumentView.tsx
index aab14e5a..c22a9849 100644
--- a/code/client/src/ui/pages/workspace/components/DocumentView.tsx
+++ b/code/client/src/ui/pages/workspace/components/DocumentView.tsx
@@ -5,6 +5,7 @@ import { DocumentResource } from '@notespace/shared/src/workspace/types/resource
import { Checkbox } from '@mui/material';
import { formatDate, formatTimePassed } from '@/utils/utils';
import { useEffect, useState } from 'react';
+import useWorkspace from '@/contexts/workspace/useWorkspace';
type DocumentViewProps = {
document: DocumentResource;
@@ -19,6 +20,7 @@ function DocumentView({ document, onSelect, onDelete, onRename, onDuplicate, sel
const { wid } = useParams();
const { component, isEditing, setIsEditing } = useEditing(document.name || 'Untitled', onRename);
const [isSelected, setSelected] = useState(selected);
+ const { isMember } = useWorkspace();
useEffect(() => {
setSelected(selected);
@@ -43,6 +45,7 @@ function DocumentView({ document, onSelect, onDelete, onRename, onDuplicate, sel
onOpenInNewTab={() => window.open(`/workspaces/${wid}/${document.id}`, '_blank')}
onDuplicate={onDuplicate}
onDelete={onDelete}
+ enabled={isMember}
>
{isEditing ? DocumentComponent : {DocumentComponent}}
diff --git a/code/client/src/ui/pages/workspaces/components/WorkspaceContextMenu.tsx b/code/client/src/ui/pages/workspaces/components/WorkspaceContextMenu.tsx
index cc1fe218..53a44a24 100644
--- a/code/client/src/ui/pages/workspaces/components/WorkspaceContextMenu.tsx
+++ b/code/client/src/ui/pages/workspaces/components/WorkspaceContextMenu.tsx
@@ -35,9 +35,8 @@ function WorkspaceContextMenu({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [currentUser]);
- if (!isMember) return null;
return (
-
+