현재 완성도를 올리기 위해 디버깅과 자잘한 기능을 추가중에 있습니다.

워크스페이스 선택창 구현

먼저 유저에게 워크스페이스를 어떻게 선택하게 할까? 에대한 고민입니다.

간단하게 저는 팀원들에게 설문을 통해 진행했습니다.

유저 아이디를 클릭하면 아래에 선택하는 모달창이 나오고 그 모달창으로 접속하는 것이지요.

처음에는 Hover 시에 나오게할까 해서 적용했습니다만, 다른 slack 이나 discord 등의 앱이 워크스페이스를 클릭 으로 바꾸는 걸 생각하면 클릭이 더 좋겠다 라고 생각해서 변경하였습니다.

게스트 상태일때는 워크스페이스 기능 이용 불가!

로그인/비로그인 상태를 파악해서 modal에 표시되는 텍스트를 설정했습니다.

🍿 내가 접속중인 workspace 표시

로그인을 하게 되면 위 모달창 처럼 내가 속해있는 워크스페이스가 표시되게 구현했습니다.

고민했던것은 현재 내가 접속중인 워크스페이스를 어떻게 표시하지? 였습니다.

현재 접속중 으로 표시할지, 아이콘이나 상태로 표시해야하나? 많은 고민이 있었습니다.

가장 위 모달에서 내가 접속중인 상태를 표시해주는 파란색 기둥을 하나 넣어주고, 접속중인 워크스페이스에는 그림자를 넣어 현재 선택중인 느낌이 들도록 표시해 주었습니다.

마우스를 Hover한 모습

또한 선택된 워크스페이스에서 나의 권한(editor, owner)을 설정해 주었습니다.

owner : 소유자(이름 변경, 워크스페이스 초대, 페이지 편집 가능)
editor : 편집자(페이지 편집 가능)

owner 인 유저는 사용자를 이메일로 초대할 수 있습니다.

워크스페이스 초대하기 버튼을 누르면

위와 같은 모달이 나오며 유저를 초대할 수 있습니다.

추후에 필요하다면 viewer 권한도 추가할 예정입니다.
이는 초대 모달에서 권한을 선택하게 하면 좋지않을까 생각하고있습니다.


현재 접속중인 유저 수 반영

하나 유저 UI/UX의 완성도를 위해 고려한 것으로 현재 접속중인 유저 수와 초대한 유저 수를 실시간으로 확인하도록 기획했습니다.

그 이유는 추후 이 워크스페이스에 접속중인 유저를 표시하거나 실시간으로 동시편집을 진행할때 접속상태를 능동적으로 알아야 한다고 판단했습니다.

이를 작업하기 전에 서버에서 접근 가능한 workspace정보를 받아야하는데

기존스키마로써는 부족한점이 많다고 생각하여 몇개 수정이 필요했습니다.

interface UserWorkspaceAccess {
  userId: string;
  workspaces: {
    id: string;
    role: 'owner' | 'editor' | 'viewer';
    joinedAt: Date;
  }[];
}

interface Workspace {
  id: string;
  name: string;
  ownerId: string;
  // ... 다른 워크스페이스 정보들
}

하나 워크스페이스 정보를 누가 가지고 있게 할지가 고민이였는데요.

  1. workspace 별로 접속가능한 유저를 저장
    1. a유저가 접근가능한 workspace를 전부 순회해서 찾아야함.
  2. 유저가 접근가능한 workspace를 저장
    1. a유저가 접근가능한 workspace는 O(1) 바로 조회가 가능함.

위와 같은 고려사항이 있었는데, 그냥 1번 2번 전부다 반영하기로 했습니다.

workspace도 바로 유저를 조회할 수 있고, 유저도 접속가능한 workspace를 바로 알 수 있기 때문이죠.

그리고 초대, 삭제, 등록 등의 workspace 관련 CRUD 가 일어날때마다 각 유저들에게 workspace/list 를 송신해서 실시간으로 변경되게 해주었습니다.

좀더 서버 부하를 줄이기 위해서 모달을 끄고 킬때마다 상태를 서버에서 받아올 수도 있을 것 같습니다.
하지만 모달을 킨상태로 실시간으로 변하는 것이 UX적으로 더 좋다고 판단하여 위와같이 서버에서 클라이언트로 emit 하는 방향으로 판단했습니다.


👮 실시간으로 유저 리스트는 어떻게 주고받지?

  • 처음 팀원들과 간단히 이야기한 내용은 HTTP 통신을 통해 workspace와 계정정보를 주고받으려고했습니다.
  • 하지만 응답을 받지않은 client에게 초대, 알림 등을 송신을 하려면 socket을 보내는 방법으로 하는게 좋을듯 해 보였습니다.

그래서 workspaceId, userId, userName, userEmail

이렇게 네가지 정보를 확실하게 나눠주었습니다.

이 네가지 정보를 서버에서 저장하고 관리합니다.


이제 이 정보를 기반으로 워크스페이스 변경 스토리보드를 작성해보았습니다.

워크스페이스 변경 유저 스토리보드

  1. 회원가입을 합니다.
  2. 서버는 workspace를 만듭니다.
  - id : workspace 고유의 id
  - authList : [{userId : "editor"},]
  - 그외 나머지..
  1. 저가 서버에 workspace/list를 요청합니다.
  2. 서버는 workspace/list를 보내줍니다.
export interface WorkspaceListItem {
  id: string;
  name: string;
  role: string;
  memberCount?: number;
}
  • 위와같은 정보가 담긴 배열이 도착합니다.
  1. 받은 배열을 기반으로 selectModal내부 요소를 렌더링합니다.
  2. 유저가 selectModal에서 workspace를 선택합니다.
  3. switchSocket(userId,workspaceId) 메소드가 실행됩니다.
switchWorkspace: (userId: string | null, workspaceId: string | null) => {
    const { socket, workspace, init } = get();
    // 기존 연결 정리
    if (socket) {
      if (workspace?.id) {
        socket.emit("leave/workspace", { workspaceId: workspace.id, userId });
      }
      socket.disconnect();
    }
    sessionStorage.removeItem("currentWorkspace");
    set({ workspace: null }); // 상태도 초기화
    init(userId, workspaceId);
  },

  fetchWorkspaceData: () => get().workspace,

  setWorkspace: (workspace: WorkSpaceSerializedProps) => {
    sessionStorage.setItem("currentWorkspace", JSON.stringify(workspace));
    set({ workspace });
  },
  • 기존 연결을 leave/workspacedisconnect() 로 끊어주고 새 workspaceIdInit() 합니다.
  1. userId로, workspaceId에 해당하는 workspace 정보로 socket을 재연결합니다.

이제 이 유저시나리오에서 얻은 정보를 서버가 저장한 상태로 invite/workspace를 실행하게 되면, auth로부터 가입한 유저의 email을 조회한 다음, 해당정보를 클라이언트에게 보내주며 성공한 메세지를 보여줍니다.


  @SubscribeMessage("invite/workspace")
  async handleWorkspaceInvite(
    @MessageBody() data: WorkspaceInviteData,
    @ConnectedSocket() client: Socket,
  ): Promise<void> {
    const clientInfo = this.clientMap.get(client.id);
    if (!clientInfo) {
      throw new WsException("Client information not found");
    }

    try {
      // 1. 초대받을 사용자 확인
      // 2. 이미 워크스페이스 멤버인지 확인
      // 3. 초대한 사용자에게 성공 메시지
      // 4. 워크 스페이스에 사용자 추가
      // 5. 초대한 사용자의 UI에 최신 workspace/list 반영
      client.emit("workspace/list", sentUserUpdatedWorkspaces);
    } catch (error) {
      this.logger.error(
        `Workspace Invite 처리 중 오류 발생 - Client ID: ${clientInfo.clientId}`,
        error.stack,
      );
      throw new WsException(`Invite 실패: ${error.message}`);
    }
  }

이제 workspace의 초대, 이동등이 구현됐으니 이를 알려주는 toast Toggle 창을 구현해보겠습니다.


📕 toast Toggle창 구현

  • 페이지 삭제를 안내하는 창을 토글창을 만들었습니다.
  • useToastStore를 통해 추후 다른 alarm에서 재사용성을 고려하여 hook 으로 분리해주었습니다.
addToast(`${message}를 표시합니다.`);
  • 위와같은 형태로 사용할 수 있고, 내부에 setTimeout을 활용하여 duration을 지정할 수 있습니다.

마무리

이제 workspace 관련된 UI측 완성도는 거의 마무리 되었습니다.

다음에는 workspace의 이름 변경 구현과 workpsace를 mongoDB에 어떤 식으로 저장하게 할지에 대해 알아보겠습니다 !!

profile
비전공자 + 반도체 경력2년의 IT 개발자 도전기~

0개의 댓글

Powered by GraphCDN, the GraphQL CDN