Skip to content

Commit bfa57e2

Browse files
authored
Merge pull request #324 from boostcampwm-2024/feature-fe-#315
워크스페이스 공유 기능 추가
2 parents 5920c66 + b193161 commit bfa57e2

File tree

16 files changed

+524
-42
lines changed

16 files changed

+524
-42
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { Delete, Get, Post } from "@/shared/api";
2+
import {
3+
CreateWorkSpaceResponse,
4+
CreateWorkSpaceResquest,
5+
GetUserWorkspaceResponse,
6+
RemoveWorkSpaceResponse,
7+
} from "../model/workspaceTypes";
8+
9+
const BASE_URL = "/api/workspace";
10+
11+
export const createWorkspace = async (payload: CreateWorkSpaceResquest) => {
12+
const res = await Post<CreateWorkSpaceResponse, CreateWorkSpaceResquest>(
13+
BASE_URL,
14+
payload,
15+
);
16+
return res.data;
17+
};
18+
19+
export const removeWorkspace = async (workspaceId: string) => {
20+
const url = `${BASE_URL}/${workspaceId}`;
21+
22+
const res = await Delete<RemoveWorkSpaceResponse>(url);
23+
return res.data;
24+
};
25+
26+
// TODO: /entities/user vs workspace 위치 고민해봐야할듯?
27+
export const getUserWorkspaces = async () => {
28+
const url = `${BASE_URL}/user`;
29+
30+
const res = await Get<GetUserWorkspaceResponse>(url);
31+
return res.data;
32+
};
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { Post } from "@/shared/api";
2+
import { SetWorkspaceStatusResponse } from "../model/workspaceInviteTypes";
3+
4+
export const setWorkspaceStatusToPrivate = async (id: string) => {
5+
// TODO: URL 맞게 고치기.
6+
const url = `/api/workspace/${id}/private`;
7+
await Post<SetWorkspaceStatusResponse, null>(url);
8+
};
9+
10+
export const setWorkspaceStatusToPublic = async (id: string) => {
11+
// TODO: URL 맞게 고치기.
12+
const url = `/api/workspace/${id}/public`;
13+
await Post<SetWorkspaceStatusResponse, null>(url);
14+
};
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { Get, Post } from "@/shared/api";
2+
import {
3+
WorkspaceInviteLinkRequest,
4+
WorkspaceInviteLinkResponse,
5+
ValidateWorkspaceLinkResponse,
6+
} from "@/features/workspace/model/workspaceInviteTypes";
7+
8+
export const createWorkspaceInviteLink = async (id: string) => {
9+
const url = `/api/workspace/${id}/invite`;
10+
11+
const res = await Post<
12+
WorkspaceInviteLinkResponse,
13+
WorkspaceInviteLinkRequest
14+
>(url, { id });
15+
16+
return res.data.inviteUrl;
17+
};
18+
19+
export const validateWorkspaceInviteLink = async (token: string) => {
20+
const url = `/api/workspace/join?token=${token}`;
21+
const res = await Get<ValidateWorkspaceLinkResponse>(url);
22+
return res.data;
23+
};
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { create } from "zustand";
2+
3+
type InviteLinkStore = {
4+
inviteLink: string | null;
5+
setInviteLink: (link: string) => void;
6+
};
7+
8+
export const useInviteLinkStore = create<InviteLinkStore>((set) => ({
9+
inviteLink: null,
10+
setInviteLink: (link) => set({ inviteLink: link }),
11+
}));
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
2+
3+
import {
4+
createWorkspace,
5+
getUserWorkspaces,
6+
removeWorkspace,
7+
} from "../api/workspaceApi";
8+
9+
export const useUserWorkspace = () => {
10+
return useQuery({
11+
queryKey: ["userWorkspace"],
12+
queryFn: getUserWorkspaces,
13+
});
14+
};
15+
16+
// response로 workspaceId가 오는데 userWorkspace를 어떻게 invalidate 할까?
17+
// login state에 있는 userId로?
18+
export const useCreateWorkspace = () => {
19+
const queryClient = useQueryClient();
20+
21+
return useMutation({
22+
mutationFn: createWorkspace,
23+
onSuccess: () => {
24+
queryClient.invalidateQueries({ queryKey: ["userWorkspace"] });
25+
},
26+
});
27+
};
28+
29+
export const useRemoveWorkspace = () => {
30+
const queryClient = useQueryClient();
31+
32+
return useMutation({
33+
mutationFn: removeWorkspace,
34+
onSuccess: () => {
35+
queryClient.invalidateQueries({ queryKey: ["userWorkspace"] });
36+
},
37+
});
38+
};
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { useMutation } from "@tanstack/react-query";
2+
import {
3+
createWorkspaceInviteLink,
4+
validateWorkspaceInviteLink,
5+
} from "../api/worskspaceInviteApi";
6+
7+
export const useCreateWorkspaceInviteLink = () => {
8+
return useMutation({
9+
mutationFn: (id: string) => createWorkspaceInviteLink(id),
10+
});
11+
};
12+
13+
export const useValidateWorkspaceInviteLink = () => {
14+
return useMutation({
15+
mutationFn: (token: string) => validateWorkspaceInviteLink(token),
16+
});
17+
};
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { useMutation, useQueryClient } from "@tanstack/react-query";
2+
import { useUserWorkspace } from "@/features/workspace/model/useWorkspace";
3+
import { useWorkspace } from "@/shared/lib/useWorkspace";
4+
import {
5+
setWorkspaceStatusToPrivate,
6+
setWorkspaceStatusToPublic,
7+
} from "../api/workspaceStatusApi";
8+
9+
export const useWorkspaceStatus = () => {
10+
const { data } = useUserWorkspace();
11+
const currentWorkspaceId = useWorkspace();
12+
13+
const workspaces = data?.workspaces;
14+
return workspaces?.find(
15+
(workspace) => workspace.workspaceId === currentWorkspaceId,
16+
)?.visibility;
17+
};
18+
19+
export const useToggleWorkspaceStatus = (
20+
currentStatus: "public" | "private" | undefined,
21+
) => {
22+
const queryClient = useQueryClient();
23+
const currentWorkspaceId = useWorkspace();
24+
25+
return useMutation({
26+
mutationFn: () => {
27+
if (currentStatus === undefined) {
28+
throw new Error("Workspace status is undefined");
29+
}
30+
31+
const toggleFn =
32+
currentStatus === "public"
33+
? setWorkspaceStatusToPrivate
34+
: setWorkspaceStatusToPublic;
35+
36+
return toggleFn(currentWorkspaceId);
37+
},
38+
onSuccess: () => {
39+
queryClient.invalidateQueries({ queryKey: ["userWorkspace"] });
40+
},
41+
});
42+
};
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
export interface WorkspaceInviteLinkRequest {
2+
id: string;
3+
}
4+
5+
export interface WorkspaceInviteLinkResponse {
6+
message: string;
7+
inviteUrl: string;
8+
}
9+
10+
export interface ValidateWorkspaceLinkResponse {
11+
message: string;
12+
}
13+
14+
export interface SetWorkspaceStatusResponse {
15+
message: string;
16+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
export interface Workspace {
2+
workspaceId: string;
3+
title: string;
4+
description: string;
5+
thumbnailUrl: string;
6+
role: "owner" | "guest";
7+
visibility: "private" | "public";
8+
}
9+
10+
export interface CreateWorkSpaceResquest {
11+
title: string;
12+
description?: string;
13+
visibility?: "private" | "public";
14+
thumbnailUrl?: string;
15+
}
16+
17+
export interface CreateWorkSpaceResponse {
18+
message: string;
19+
workspaceId: string;
20+
}
21+
22+
export interface RemoveWorkSpaceResponse {
23+
message: string;
24+
}
25+
26+
export interface GetUserWorkspaceResponse {
27+
message: string;
28+
workspaces: Workspace[];
29+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// TODO: admin이 아니라면 tooltip 추가 "권한이 없습니다."
2+
3+
import { useWorkspace } from "@/shared/lib/useWorkspace";
4+
import { useUserWorkspace } from "../../model/useWorkspace";
5+
6+
export function Sharebutton() {
7+
const currentWorkspaceId = useWorkspace();
8+
const { data } = useUserWorkspace();
9+
const workspaces = data?.workspaces;
10+
const workspace = workspaces?.find(
11+
(workspace) => workspace.workspaceId === currentWorkspaceId,
12+
);
13+
const isGuest = workspace?.role === "guest";
14+
15+
return (
16+
<div className="flex h-9 items-center justify-center">
17+
<button
18+
disabled={isGuest}
19+
className="rounded-md bg-blue-400 px-2 py-1 text-sm text-white hover:bg-blue-500"
20+
>
21+
공유
22+
</button>
23+
</div>
24+
);
25+
}

0 commit comments

Comments
 (0)