Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"@tanstack/react-query": "^5.59.19",
"@tiptap/extension-collaboration": "^2.9.1",
"@tiptap/extension-collaboration-cursor": "^2.9.1",
"@uiw/react-color": "^2.3.2",
"@xyflow/react": "^12.3.4",
"autoprefixer": "^10.4.20",
"axios": "^1.7.7",
Expand Down
9 changes: 6 additions & 3 deletions apps/frontend/src/components/LogoBtn.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import logo from "/logo.png?url";

export default function LogoBtn() {
interface LogoBtnProps {
onClick?: () => void;
}
export default function LogoBtn({ onClick }: LogoBtnProps) {
return (
<div className="h-8 w-8 overflow-clip rounded-md">
<button className="h-8 w-8 overflow-clip rounded-md" onClick={onClick}>
<img src={logo} />
</div>
</button>
);
}
85 changes: 85 additions & 0 deletions apps/frontend/src/components/sidebar/ProfileModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { useEffect, useState } from "react";
import Button from "../commons/button";
import { Dialog } from "../commons/dialog";
import { Compact } from "@uiw/react-color";
import useUserStore from "@/store/useUserStore";

type RemoveNoteModalProps = {
isOpen: boolean;
onConfirm: () => void;
onCloseModal: () => void;
};

export default function ProfileModal({
isOpen,
onConfirm,
onCloseModal,
}: RemoveNoteModalProps) {
const { currentUser, setCurrentUser, provider } = useUserStore();
const [hex, setHex] = useState(currentUser.color);
const [isColorPickerOpen, setIsColorPickerOpen] = useState(false);
const [nickname, setNickname] = useState(currentUser.clientId);

useEffect(() => {
setNickname(currentUser.clientId);
setHex(currentUser.color);
}, [currentUser]);

return (
<Dialog isOpen={isOpen} onCloseModal={onCloseModal}>
<div className="flex flex-col items-center gap-4">
<div className="flex flex-col items-center gap-1.5">
<Dialog.Title>프로필 수정</Dialog.Title>
</div>
<div className="flex flex-row items-center gap-3">
<button
className="h-12 w-12 rounded-full border-[1px] border-black"
style={{ backgroundColor: hex }}
onClick={() => setIsColorPickerOpen(!isColorPickerOpen)}
/>

<input
className={
"h-10 rounded-md border-[1px] border-[#eaeaea] p-2 text-sm text-[#141414] outline-none"
}
placeholder="닉네임을 입력하세요"
value={nickname}
onChange={(e) => setNickname(e.target.value)}
/>
</div>
<div className="">
{isColorPickerOpen && (
<Compact color={hex} onChange={(color) => setHex(color.hex)} />
)}
</div>

<div className="flex w-full flex-row justify-between gap-2">
<Button
className="w-full rounded-lg bg-[#171717] text-neutral-100 hover:bg-slate-800"
onClick={() => {
provider.awareness.setLocalStateField("color", hex);
provider.awareness.setLocalStateField("clientId", nickname);

setCurrentUser({
...currentUser,
color: hex,
clientId: nickname,
});

onConfirm();
}}
>
확인
</Button>
<Button
className="w-full rounded-lg bg-[#f4f4f5] text-neutral-700 hover:bg-neutral-200"
onClick={onCloseModal}
>
취소
</Button>
</div>
</div>
<Dialog.CloseButton onCloseModal={onCloseModal} />
</Dialog>
);
}
20 changes: 18 additions & 2 deletions apps/frontend/src/components/sidebar/TopNav.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,29 @@
import VerticalDivider from "@/components/commons/divider/VerticalDivider";
import WorkspaceNav from "@/components/WorkspaceNav";
import LogoBtn from "@/components/LogoBtn";

import ProfileModal from "./ProfileModal";
import workspaceLogo from "@/../public/workspace-logo.svg?url";
import { useState } from "react";

export default function TopNav() {
const [isModalOpen, setIsModalOpen] = useState(false);

return (
<div className="flex items-center gap-2">
<LogoBtn />
<LogoBtn
onClick={() => {
setIsModalOpen(true);
}}
/>
<ProfileModal
isOpen={isModalOpen}
onCloseModal={() => {
setIsModalOpen(false);
}}
onConfirm={() => {
setIsModalOpen(false);
}}
/>
<VerticalDivider className="h-3" />
<WorkspaceNav imageUrl={workspaceLogo} workspaceTitle="프로젝트 Web15" />
</div>
Expand Down
19 changes: 11 additions & 8 deletions apps/frontend/src/hooks/useCursor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import useUserStore from "@/store/useUserStore";
export interface AwarenessState {
cursor: XYPosition | null;
color: string;
clientId: number;
clientId: string;
}

interface CollaborativeCursorsProps {
Expand All @@ -25,6 +25,7 @@ export function useCollaborativeCursors({
new Map(),
);
const { currentUser } = useUserStore();
const { color, clientId } = currentUser;

useEffect(() => {
const wsProvider = new SocketIOProvider(
Expand All @@ -46,23 +47,25 @@ export function useCollaborativeCursors({

wsProvider.awareness.setLocalState({
cursor: null,
color: currentUser.color,
clientId: currentUser.clientId,
color,
clientId,
});

wsProvider.awareness.on("change", () => {
const states = new Map(
Array.from(
wsProvider.awareness.getStates() as Map<number, AwarenessState>,
).filter(([, state]) => state.cursor !== null),
).filter(
([, state]) => state.clientId !== clientId && state.cursor !== null,
),
);
setCursors(states);
});

return () => {
wsProvider.destroy();
};
}, [ydoc, roomName]);
}, [ydoc, roomName, color, clientId]);

const updateCursorPosition = useCallback(
(x: number | null, y: number | null) => {
Expand All @@ -75,11 +78,11 @@ export function useCollaborativeCursors({

provider.current.awareness.setLocalState({
cursor,
color: currentUser.color,
clientId: currentUser.clientId,
color,
clientId,
});
},
[flowInstance],
[flowInstance, color, clientId],
);

const handleMouseMove = useCallback(
Expand Down
Loading
Loading