Skip to content

Commit b5565dd

Browse files
committed
fix: 워크스페이스 기능 복구
1 parent 9973305 commit b5565dd

File tree

8 files changed

+200
-16
lines changed

8 files changed

+200
-16
lines changed

apps/frontend/src/features/workspace/api/workspaceApi.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Delete, Get, Post } from "@/shared/api";
22
import {
33
CreateWorkSpaceResponse,
44
CreateWorkSpaceResquest,
5+
GetCurrentUserWorkspaceResponse,
56
GetUserWorkspaceResponse,
67
RemoveWorkSpaceResponse,
78
} from "../model/workspaceTypes";
@@ -30,3 +31,14 @@ export const getUserWorkspaces = async () => {
3031
const res = await Get<GetUserWorkspaceResponse>(url);
3132
return res.data;
3233
};
34+
35+
export const getCurrentWorkspace = async (
36+
workspaceId: string,
37+
userId: string,
38+
) => {
39+
const url = `${BASE_URL}/${workspaceId}/${userId}`;
40+
41+
// Response type 바꾸기
42+
const res = await Get<GetCurrentUserWorkspaceResponse>(url);
43+
return res.data;
44+
};

apps/frontend/src/features/workspace/model/useWorkspace.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
22

33
import {
44
createWorkspace,
5+
getCurrentWorkspace,
56
getUserWorkspaces,
67
removeWorkspace,
78
} from "../api/workspaceApi";
9+
import { useGetUser } from "@/features/auth";
10+
import { useWorkspace } from "@/shared/lib/useWorkspace";
811

912
export const useUserWorkspace = () => {
1013
return useQuery({
@@ -36,3 +39,22 @@ export const useRemoveWorkspace = () => {
3639
},
3740
});
3841
};
42+
43+
export const useCurrentWorkspace = () => {
44+
const workspaceId = useWorkspace();
45+
const { data: user, isError } = useGetUser();
46+
47+
return useQuery({
48+
queryKey: [
49+
"currentWorkspace",
50+
workspaceId,
51+
isError ? "null" : (user?.snowflakeId ?? "null"),
52+
],
53+
queryFn: () =>
54+
getCurrentWorkspace(
55+
workspaceId,
56+
isError ? "null" : (user?.snowflakeId ?? "null"),
57+
),
58+
enabled: Boolean(workspaceId),
59+
});
60+
};

apps/frontend/src/features/workspace/model/useWorkspaceStatus.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export const useToggleWorkspaceStatus = (
3737
},
3838
onSuccess: () => {
3939
queryClient.invalidateQueries({ queryKey: ["userWorkspace"] });
40+
queryClient.invalidateQueries({ queryKey: ["currentWorkspace"] });
4041
},
4142
});
4243
};

apps/frontend/src/features/workspace/model/workspaceTypes.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,8 @@ export interface GetUserWorkspaceResponse {
2727
message: string;
2828
workspaces: Workspace[];
2929
}
30+
31+
export interface GetCurrentUserWorkspaceResponse {
32+
message: string;
33+
workspace: Workspace;
34+
}

apps/frontend/src/features/workspace/ui/ShareTool/SharePanel.tsx

Lines changed: 60 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,67 @@
1+
import { useState } from "react";
12
import { Switch } from "@/shared/ui/Switch";
23
import { Globe2, Lock, Copy, CheckCheck } from "lucide-react";
3-
import { useState } from "react";
4+
import { useCreateWorkspaceInviteLink } from "../../model/useWorkspaceInvite";
5+
import { useCurrentWorkspace } from "../../model/useWorkspace";
6+
import { useToggleWorkspaceStatus } from "../../model/useWorkspaceStatus";
7+
import { useWorkspace } from "@/shared/lib/useWorkspace";
8+
9+
const createFrontendUrl = (apiUrl: string, currentWorkspaceId: string) => {
10+
const searchParams = new URLSearchParams();
11+
searchParams.set("workspaceId", currentWorkspaceId);
12+
searchParams.set("token", new URL(apiUrl).searchParams.get("token") || "");
13+
return `${window.location.origin}/join?${searchParams.toString()}`;
14+
};
415

516
export function SharePanel() {
17+
const workspaceId = useWorkspace();
18+
const { data: currentWorkspace, isPending: isWorkspaceLoading } =
19+
useCurrentWorkspace();
620
const [copied, setCopied] = useState(false);
7-
const [isPublic, setIsPublic] = useState(false);
21+
const createInviteLinkMutation = useCreateWorkspaceInviteLink();
22+
const toggleStatusMutation = useToggleWorkspaceStatus(
23+
currentWorkspace?.workspace?.visibility,
24+
);
25+
26+
const PUBLIC_URL = window.location.href;
27+
28+
const isPending =
29+
isWorkspaceLoading ||
30+
createInviteLinkMutation.isPending ||
31+
toggleStatusMutation.isPending;
32+
const isPublic = currentWorkspace?.workspace?.visibility === "public";
33+
const isGuest = currentWorkspace?.workspace?.role === "guest";
834

9-
const url = "https://octodocs.com";
35+
const handlePublicToggle = async () => {
36+
if (isGuest) return;
37+
await toggleStatusMutation.mutateAsync();
38+
39+
if (isPublic && !createInviteLinkMutation.data) {
40+
await createInviteLinkMutation.mutateAsync(workspaceId);
41+
}
42+
};
43+
44+
const getCurrentUrl = () => {
45+
if (isWorkspaceLoading) return "워크스페이스를 불러오는 중...";
46+
if (isPending) return "처리 중...";
47+
if (isPublic) return PUBLIC_URL;
48+
if (createInviteLinkMutation.data) {
49+
return createFrontendUrl(createInviteLinkMutation.data, workspaceId);
50+
}
51+
return "링크 생성 중...";
52+
};
1053

1154
const handleCopy = async () => {
12-
await navigator.clipboard.writeText(url);
13-
setCopied(true);
14-
setTimeout(() => setCopied(false), 2000);
55+
const urlToCopy = getCurrentUrl();
56+
if (!isPending) {
57+
await navigator.clipboard.writeText(urlToCopy);
58+
setCopied(true);
59+
setTimeout(() => setCopied(false), 2000);
60+
}
1561
};
1662

63+
const isDisabled = !currentWorkspace?.workspace || isGuest || isPending;
64+
1765
return (
1866
<div className="w-full">
1967
<div className="flex w-full flex-row justify-between p-1">
@@ -22,29 +70,30 @@ export function SharePanel() {
2270
<div className="flex items-center space-x-2">
2371
<Switch
2472
checked={isPublic}
25-
onChange={setIsPublic}
73+
onChange={handlePublicToggle}
2674
CheckedIcon={Globe2}
2775
UncheckedIcon={Lock}
76+
disabled={!currentWorkspace?.workspace || isPending || isGuest}
2877
/>
2978
</div>
3079
</div>
3180
<div className="select-none flex-row text-sm text-slate-400">
32-
🚧 수정 중입니다.
81+
{isGuest ? "변경 권한이 없습니다." : ""}
3382
</div>
3483
</div>
3584
<div
3685
className={`flex w-full items-center justify-between gap-2 py-2 ${
37-
!isPublic ? "opacity-50" : ""
86+
isGuest ? "opacity-50" : ""
3887
}`}
3988
>
4089
<div className="w-48 flex-1 truncate rounded-md bg-gray-100 px-3 py-2 text-sm text-gray-600">
41-
{isPublic ? url : "비공개 모드입니다."}
90+
{getCurrentUrl()}
4291
</div>
4392
<button
4493
onClick={handleCopy}
4594
className="inline-flex h-8 w-8 items-center justify-center rounded-md hover:bg-gray-100 disabled:cursor-not-allowed disabled:hover:bg-transparent"
4695
aria-label="Copy URL"
47-
disabled={!isPublic}
96+
disabled={isDisabled}
4897
>
4998
{copied ? (
5099
<CheckCheck className="h-4 w-4 text-green-500" />

apps/frontend/src/routeTree.gen.ts

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
import { Route as rootRoute } from './routes/__root'
1414
import { Route as IndexImport } from './routes/index'
15+
import { Route as JoinIndexImport } from './routes/join/index'
1516
import { Route as WorkspaceWorkspaceIdImport } from './routes/workspace/$workspaceId'
1617

1718
// Create/Update Routes
@@ -22,6 +23,12 @@ const IndexRoute = IndexImport.update({
2223
getParentRoute: () => rootRoute,
2324
} as any)
2425

26+
const JoinIndexRoute = JoinIndexImport.update({
27+
id: '/join/',
28+
path: '/join/',
29+
getParentRoute: () => rootRoute,
30+
} as any)
31+
2532
const WorkspaceWorkspaceIdRoute = WorkspaceWorkspaceIdImport.update({
2633
id: '/workspace/$workspaceId',
2734
path: '/workspace/$workspaceId',
@@ -46,6 +53,13 @@ declare module '@tanstack/react-router' {
4653
preLoaderRoute: typeof WorkspaceWorkspaceIdImport
4754
parentRoute: typeof rootRoute
4855
}
56+
'/join/': {
57+
id: '/join/'
58+
path: '/join'
59+
fullPath: '/join'
60+
preLoaderRoute: typeof JoinIndexImport
61+
parentRoute: typeof rootRoute
62+
}
4963
}
5064
}
5165

@@ -54,36 +68,41 @@ declare module '@tanstack/react-router' {
5468
export interface FileRoutesByFullPath {
5569
'/': typeof IndexRoute
5670
'/workspace/$workspaceId': typeof WorkspaceWorkspaceIdRoute
71+
'/join': typeof JoinIndexRoute
5772
}
5873

5974
export interface FileRoutesByTo {
6075
'/': typeof IndexRoute
6176
'/workspace/$workspaceId': typeof WorkspaceWorkspaceIdRoute
77+
'/join': typeof JoinIndexRoute
6278
}
6379

6480
export interface FileRoutesById {
6581
__root__: typeof rootRoute
6682
'/': typeof IndexRoute
6783
'/workspace/$workspaceId': typeof WorkspaceWorkspaceIdRoute
84+
'/join/': typeof JoinIndexRoute
6885
}
6986

7087
export interface FileRouteTypes {
7188
fileRoutesByFullPath: FileRoutesByFullPath
72-
fullPaths: '/' | '/workspace/$workspaceId'
89+
fullPaths: '/' | '/workspace/$workspaceId' | '/join'
7390
fileRoutesByTo: FileRoutesByTo
74-
to: '/' | '/workspace/$workspaceId'
75-
id: '__root__' | '/' | '/workspace/$workspaceId'
91+
to: '/' | '/workspace/$workspaceId' | '/join'
92+
id: '__root__' | '/' | '/workspace/$workspaceId' | '/join/'
7693
fileRoutesById: FileRoutesById
7794
}
7895

7996
export interface RootRouteChildren {
8097
IndexRoute: typeof IndexRoute
8198
WorkspaceWorkspaceIdRoute: typeof WorkspaceWorkspaceIdRoute
99+
JoinIndexRoute: typeof JoinIndexRoute
82100
}
83101

84102
const rootRouteChildren: RootRouteChildren = {
85103
IndexRoute: IndexRoute,
86104
WorkspaceWorkspaceIdRoute: WorkspaceWorkspaceIdRoute,
105+
JoinIndexRoute: JoinIndexRoute,
87106
}
88107

89108
export const routeTree = rootRoute
@@ -97,14 +116,18 @@ export const routeTree = rootRoute
97116
"filePath": "__root.tsx",
98117
"children": [
99118
"/",
100-
"/workspace/$workspaceId"
119+
"/workspace/$workspaceId",
120+
"/join/"
101121
]
102122
},
103123
"/": {
104124
"filePath": "index.tsx"
105125
},
106126
"/workspace/$workspaceId": {
107127
"filePath": "workspace/$workspaceId.tsx"
128+
},
129+
"/join/": {
130+
"filePath": "join/index.tsx"
108131
}
109132
}
110133
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { useEffect } from "react";
2+
import { createFileRoute, useNavigate } from "@tanstack/react-router";
3+
import { useValidateWorkspaceInviteLink } from "@/features/workspace/model/useWorkspaceInvite";
4+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
5+
6+
const joinQueryClient = new QueryClient();
7+
8+
function JoinWrapper() {
9+
return (
10+
<QueryClientProvider client={joinQueryClient}>
11+
<JoinComponent />
12+
</QueryClientProvider>
13+
);
14+
}
15+
16+
export const Route = createFileRoute("/join/")({
17+
validateSearch: (search: Record<string, unknown>) => {
18+
const workspaceId = search.workspaceId as string;
19+
const token = search.token as string;
20+
21+
if (!workspaceId || !token) {
22+
throw new Error("유효한 링크가 아닙니다.");
23+
}
24+
25+
return { workspaceId, token };
26+
},
27+
component: JoinWrapper,
28+
});
29+
30+
function JoinComponent() {
31+
const { workspaceId, token } = Route.useSearch();
32+
const navigate = useNavigate();
33+
const { mutate: validateInvite, isPending } =
34+
useValidateWorkspaceInviteLink();
35+
36+
useEffect(() => {
37+
if (!token || !workspaceId) {
38+
navigate({ to: "/" });
39+
return;
40+
}
41+
42+
validateInvite(token, {
43+
onSuccess: () => {
44+
navigate({
45+
to: "/workspace/$workspaceId",
46+
params: { workspaceId },
47+
replace: true,
48+
});
49+
},
50+
onError: () => {
51+
navigate({ to: "/" });
52+
},
53+
});
54+
}, [token, workspaceId, validateInvite, navigate]);
55+
56+
return (
57+
<div className="flex h-screen items-center justify-center">
58+
<div className="text-center">
59+
<div className="mb-2 text-lg font-medium">
60+
워크스페이스 초대 확인 중
61+
</div>
62+
{isPending && (
63+
<div className="text-sm text-gray-500">
64+
워크스페이스 {workspaceId} 입장 중...
65+
</div>
66+
)}
67+
</div>
68+
</div>
69+
);
70+
}

apps/frontend/src/shared/lib/useWorkspace.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,7 @@ export function useWorkspace(): string {
55
const workspaceMatch = matches.find(
66
(match) => match.routeId === "/workspace/$workspaceId",
77
);
8-
return workspaceMatch?.params.workspaceId ?? "main";
8+
const workspaceId = workspaceMatch?.params.workspaceId ?? "main";
9+
console.log(workspaceId);
10+
return workspaceId;
911
}

0 commit comments

Comments
 (0)