From 4bb2155cfb0c2b7fdaa86ef867e7ff229762129f Mon Sep 17 00:00:00 2001
From: JAESEON PARK
Date: Sun, 1 Mar 2026 16:32:40 +0900
Subject: [PATCH 01/14] =?UTF-8?q?feat:=20=EC=9B=8C=ED=81=AC=EC=8A=A4?=
=?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=8A=A4=20=EC=A1=B0=ED=9A=8C/=EC=83=9D?=
=?UTF-8?q?=EC=84=B1=20api=EC=97=B0=EB=8F=99?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/api/workspace/org.ts | 32 +++++++
src/pages/workspace/Workspace.tsx | 148 +++++++++++++++++++-----------
src/types/workspace/mapper.ts | 9 ++
src/types/workspace/workspace.ts | 29 ++++++
4 files changed, 166 insertions(+), 52 deletions(-)
create mode 100644 src/api/workspace/org.ts
create mode 100644 src/types/workspace/mapper.ts
diff --git a/src/api/workspace/org.ts b/src/api/workspace/org.ts
new file mode 100644
index 00000000..3f2049ac
--- /dev/null
+++ b/src/api/workspace/org.ts
@@ -0,0 +1,32 @@
+import { toWorkspace } from "@/types/workspace/mapper";
+import type {
+ TApiResult,
+ TCreateOrgRequest,
+ TCreateOrgResponse,
+ TMyOrgsData,
+ TWorkspace,
+} from "@/types/workspace/workspace";
+
+import { authInstance } from "@/lib/axiosInstance";
+
+export const getMyWorkspaces = async (): Promise => {
+ const { data } =
+ await authInstance.get>("/api/org/my");
+ if (data.status !== "200") {
+ throw new Error("워크스페이스 조회에 실패했습니다");
+ }
+ return data.data.organizations.map(toWorkspace);
+};
+
+export const createWorkspace = async (
+ body: TCreateOrgRequest,
+): Promise => {
+ const { data } = await authInstance.post>(
+ "/api/org/create",
+ body,
+ );
+ if (data.status !== "200") {
+ throw new Error("워크스페이스 생성에 실패했습니다");
+ }
+ return data.data;
+};
diff --git a/src/pages/workspace/Workspace.tsx b/src/pages/workspace/Workspace.tsx
index f32f74ff..7ac927f1 100644
--- a/src/pages/workspace/Workspace.tsx
+++ b/src/pages/workspace/Workspace.tsx
@@ -10,6 +10,7 @@ import Modal from "@/components/common/modal/Modal";
import TextareaField from "@/components/common/textarea/TextareaField";
import WorkspaceCard from "@/components/workspace/WorkspaceCard";
+import { createWorkspace, getMyWorkspaces } from "@/api/workspace/org";
import EditContainIcon from "@/assets/icon/workspace/edit-contained.svg?react";
import PlusIcon from "@/assets/icon/workspace/plus.svg?react";
import SearchIcon from "@/assets/icon/workspace/search.svg?react";
@@ -24,6 +25,12 @@ export default function WorkspacePage() {
const [newName, setNewName] = useState("");
const [newDesc, setNewDesc] = useState("");
+
+ const [workspaces, setWorkspaces] = useState([]);
+ const [loading, setLoading] = useState(false);
+ const [listErrorMsg, setListErrorMsg] = useState(null);
+ const [creating, setCreating] = useState(false);
+ const [createErrorMsg, setCreateErrorMsg] = useState(null);
const fileRef = useRef(null);
const openFile = () => {
if (!fileRef.current) return;
@@ -32,29 +39,7 @@ export default function WorkspacePage() {
};
const [logoFile, setLogoFile] = useState(null);
const [logoPreview, setLogoPreview] = useState(null);
- const workspaces: TWorkspace[] = useMemo(
- () => [
- {
- id: "1",
- name: "광고회사1",
- description: "광고회사1입니다.",
- myRole: "admin",
- },
- {
- id: "2",
- name: "광고회사2",
- description: "광고회사2입니다.",
- myRole: "admin",
- },
- {
- id: "3",
- name: "광고회사3",
- description: "광고회사3입니다.",
- myRole: "admin",
- },
- ],
- [],
- );
+
const filtered = useMemo(() => {
const q = query.trim().toLowerCase();
if (!q) return workspaces;
@@ -78,6 +63,10 @@ export default function WorkspacePage() {
const onOpenCreate = () => {
setNewName("");
setNewDesc("");
+ setLogoFile(null);
+ setLogoPreview(null);
+ setCreateErrorMsg(null);
+ setListErrorMsg(null);
setCreateOpen(true);
};
@@ -95,19 +84,52 @@ export default function WorkspacePage() {
};
}, [logoPreview]);
- // TODO: API 연동 후에 생성 동작 연결하기
- const onSubmitCreate = () => {
- const form = new FormData();
- form.append("name", newName.trim());
- form.append("description", newDesc.trim());
- if (logoFile) form.append("logo", logoFile);
-
- console.log("create payload", {
- name: newName.trim(),
- description: newDesc.trim(),
- logoFile,
- });
- alert("API 연동 후에 생성 기능 연결예정");
+ // 워크스페이스 목록 조회 함수 (길어지면, util함수로 빼기)
+ const fetchWorkspaces = async () => {
+ setLoading(true);
+ setListErrorMsg(null);
+ try {
+ const list = await getMyWorkspaces();
+ setWorkspaces(list);
+ } catch (e) {
+ const message =
+ e instanceof Error
+ ? e.message
+ : "워크스페이스 목록 조회중 오류가 발생했습니다";
+ setListErrorMsg(message);
+ setWorkspaces([]);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ useEffect(() => {
+ void fetchWorkspaces();
+ }, []);
+
+ const onSubmitCreate = async () => {
+ const name = newName.trim();
+ const description = newDesc.trim();
+ if (!name) return;
+ setCreating(true);
+ setCreateErrorMsg(null);
+ try {
+ await createWorkspace({
+ name,
+ description,
+ logoUrl: null,
+ });
+ setCreateOpen(false);
+ await fetchWorkspaces();
+ } catch (e) {
+ let message = "워크스페이스 생성 중 오류가 발생했습니다";
+ if (e instanceof Error) {
+ message = e.message;
+ }
+ setCreateErrorMsg(message);
+ } finally {
+ setCreating(false);
+ }
};
return (
@@ -139,20 +161,39 @@ export default function WorkspacePage() {
워크스페이스 생성하기
-
- {filtered.map((w) => (
-
- ))}
- {filtered.length === 0 && (
- -
-
워크스페이스가 없습니다.
-
- )}
-
+
+ {loading && (
+
+ )}
+ {!loading && listErrorMsg && (
+
+
{listErrorMsg}
+
+
+ )}
+ {!loading && !listErrorMsg && (
+
+ {filtered.map((w) => (
+
+ ))}
+ {filtered.length === 0 && (
+ -
+
+ 워크스페이스가 없습니다.
+
+
+ )}
+
+ )}
+
setCreateOpen(false)}
@@ -212,15 +253,18 @@ export default function WorkspacePage() {
value={newDesc}
onChange={(e) => setNewDesc(e.target.value)}
/>
+ {createErrorMsg && (
+ {createErrorMsg}
+ )}
diff --git a/src/types/workspace/mapper.ts b/src/types/workspace/mapper.ts
new file mode 100644
index 00000000..c2084bcb
--- /dev/null
+++ b/src/types/workspace/mapper.ts
@@ -0,0 +1,9 @@
+import type { TWorkspace, TWorkspaceDTO } from "./workspace";
+
+export const toWorkspace = (dto: TWorkspaceDTO): TWorkspace => ({
+ id: String(dto.orgId),
+ name: dto.name,
+ description: dto.description,
+ logoUrl: dto.logoUrl,
+ myRole: dto.myRole,
+});
diff --git a/src/types/workspace/workspace.ts b/src/types/workspace/workspace.ts
index f66b16bb..99571212 100644
--- a/src/types/workspace/workspace.ts
+++ b/src/types/workspace/workspace.ts
@@ -7,3 +7,32 @@ export type TWorkspace = {
logoUrl?: string | null;
myRole: string;
};
+
+// UI구현할때, id로 해놓았는데, DTO코드작성하지 말고, swagger에 맞게 코드 작성해볼까?
+export type TWorkspaceDTO = {
+ orgId: number;
+ name: string;
+ description: string;
+ logoUrl: string | null;
+ myRole: string;
+};
+export type TApiResult = {
+ status: string;
+ data: T;
+};
+
+export type TMyOrgsData = {
+ organizations: TWorkspaceDTO[];
+};
+
+export type TCreateOrgRequest = {
+ name: string;
+ description: string;
+ logoUrl?: string | null;
+};
+
+export type TCreateOrgResponse = {
+ name: string;
+ description: string;
+ logoUrl: string | null;
+};
From 80721444f21422c6720c8d27b15b004aba32e57e Mon Sep 17 00:00:00 2001
From: JAESEON PARK
Date: Sun, 1 Mar 2026 23:46:17 +0900
Subject: [PATCH 02/14] =?UTF-8?q?feat:=20=EC=9B=8C=ED=81=AC=EC=8A=A4?=
=?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=8A=A4=20=EA=B0=9C=EB=B3=84=EC=A1=B0?=
=?UTF-8?q?=ED=9A=8C/=EC=88=98=EC=A0=95/=EC=82=AD=EC=A0=9C=20API=EC=97=B0?=
=?UTF-8?q?=EB=8F=99?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/api/workspace/org.ts | 50 +++-
src/pages/workspace/Workspace.tsx | 2 +-
src/pages/workspace/WorkspaceSetting.tsx | 363 +++++++++++++++--------
src/types/workspace/mapper.ts | 15 +-
src/types/workspace/workspace.ts | 22 ++
5 files changed, 331 insertions(+), 121 deletions(-)
diff --git a/src/api/workspace/org.ts b/src/api/workspace/org.ts
index 3f2049ac..30847d20 100644
--- a/src/api/workspace/org.ts
+++ b/src/api/workspace/org.ts
@@ -1,10 +1,13 @@
-import { toWorkspace } from "@/types/workspace/mapper";
+import { toWorkspace, toWorkspaceDetail } from "@/types/workspace/mapper";
import type {
TApiResult,
TCreateOrgRequest,
TCreateOrgResponse,
+ TGetOrgResponse,
TMyOrgsData,
+ TUpdateWorkspaceRequest,
TWorkspace,
+ TWorkspaceDetail,
} from "@/types/workspace/workspace";
import { authInstance } from "@/lib/axiosInstance";
@@ -30,3 +33,48 @@ export const createWorkspace = async (
}
return data.data;
};
+
+export const getWorkspace = async (
+ orgId: number,
+): Promise => {
+ const { data } = await authInstance.get>(
+ `/api/org/${orgId}`,
+ );
+ if (data.status !== "200") {
+ if (data.status === "404_1")
+ throw new Error("해당 조직은 존재하지 않습니다");
+ if (data.status === "410_1") throw new Error("해당 조직은 삭제되었습니다");
+ throw new Error("워크스페이스 정보를 불러오지 못했습니다");
+ }
+ return toWorkspaceDetail(data.data);
+};
+
+export const updateWorkspace = async (
+ orgId: number,
+ body: TUpdateWorkspaceRequest,
+): Promise => {
+ const { data } = await authInstance.patch>(
+ `/api/org/${orgId}`,
+ body,
+ );
+ if (data.status !== "200") {
+ if (data.status === "404_1")
+ throw new Error("해당 조직은 존재하지 않습니다");
+ if (data.status === "403_1")
+ throw new Error("해당 조직에 대한 권한이 없습니다.");
+ throw new Error("워크스페이스 수정에 실패했습니다");
+ }
+};
+
+export const deleteWorkspace = async (orgId: number): Promise => {
+ const { data } = await authInstance.delete>(
+ `/api/org/${orgId}`,
+ );
+ if (data.status !== "200") {
+ if (data.status === "404_1")
+ throw new Error("해당 조직은 존재하지 않습니다");
+ if (data.status === "403_1")
+ throw new Error("해당 조직에 대한 권한이 없습니다.");
+ throw new Error("워크스페이스 삭제에 실패했습니다");
+ }
+};
diff --git a/src/pages/workspace/Workspace.tsx b/src/pages/workspace/Workspace.tsx
index 7ac927f1..beee9d11 100644
--- a/src/pages/workspace/Workspace.tsx
+++ b/src/pages/workspace/Workspace.tsx
@@ -31,6 +31,7 @@ export default function WorkspacePage() {
const [listErrorMsg, setListErrorMsg] = useState(null);
const [creating, setCreating] = useState(false);
const [createErrorMsg, setCreateErrorMsg] = useState(null);
+
const fileRef = useRef(null);
const openFile = () => {
if (!fileRef.current) return;
@@ -66,7 +67,6 @@ export default function WorkspacePage() {
setLogoFile(null);
setLogoPreview(null);
setCreateErrorMsg(null);
- setListErrorMsg(null);
setCreateOpen(true);
};
diff --git a/src/pages/workspace/WorkspaceSetting.tsx b/src/pages/workspace/WorkspaceSetting.tsx
index 9eca1860..58910d8d 100644
--- a/src/pages/workspace/WorkspaceSetting.tsx
+++ b/src/pages/workspace/WorkspaceSetting.tsx
@@ -1,10 +1,15 @@
-import { useState } from "react";
+import { useEffect, useMemo, useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import Button from "@/components/common/button/Button";
import Input from "@/components/common/input/Input";
import Modal from "@/components/common/modal/Modal";
+import {
+ deleteWorkspace,
+ getWorkspace,
+ updateWorkspace,
+} from "@/api/workspace/org";
import MessageCircleWarningIcon from "@/assets/icon/workspace/message-circle-warning.svg?react";
import UpLoadImgIcon from "@/assets/icon/workspace/uploadImg.svg?react";
import WarningIcon from "@/assets/icon/workspace/warning.svg?react";
@@ -12,16 +17,101 @@ import WarningIcon from "@/assets/icon/workspace/warning.svg?react";
export default function WorkspaceSetting() {
const navigate = useNavigate();
const { workspaceId } = useParams();
+
+ const orgId = useMemo(() => {
+ const n = Number(workspaceId);
+ return Number.isFinite(n) ? n : null;
+ }, [workspaceId]);
const [name, setName] = useState("");
const [desc, setDesc] = useState("");
+ const [loading, setLoading] = useState(false);
+ const [errorMsg, setErrorMsg] = useState(null);
+
+ const [saving, setSaving] = useState(false);
+ const [saveErrorMsg, setSaveErrorMsg] = useState(null);
const [deleteOpen, setDeleteOpen] = useState(false);
- const onSave = () => {
- alert(`API 연동후 저장예정 (workspaceId=${workspaceId})`);
+ const [deleting, setDeleting] = useState(false);
+ const [deleteErrorMsg, setDeleteErrorMsg] = useState(null);
+
+ const fetchWorkspaceDetail = async () => {
+ if (orgId === null) {
+ setErrorMsg("잘못된 워크스페이스ID 입니다");
+ return;
+ }
+ setLoading(true);
+ setErrorMsg(null);
+ try {
+ const detail = await getWorkspace(orgId);
+ setName(detail.name);
+ setDesc(detail.description ?? "");
+ } catch (e) {
+ const message =
+ e instanceof Error
+ ? e.message
+ : "워크스페이스 정보를 불러오지 못했습니다.";
+ setErrorMsg(message);
+ } finally {
+ setLoading(false);
+ }
+ };
+ useEffect(() => {
+ void fetchWorkspaceDetail();
+ }, [orgId]);
+
+ const onSave = async () => {
+ if (orgId === null) {
+ setSaveErrorMsg("잘못된 워크스페이스ID 입니다");
+ return;
+ }
+ const nextName = name.trim();
+ const nextDesc = desc.trim();
+ if (!nextName) return;
+ setSaving(true);
+ setSaveErrorMsg(null);
+
+ try {
+ await updateWorkspace(orgId, {
+ name: nextName,
+ description: nextDesc,
+ logoUrl: null,
+ });
+ await fetchWorkspaceDetail();
+ setSaveErrorMsg(null);
+ } catch (e) {
+ const message =
+ e instanceof Error
+ ? e.message
+ : "워크스페이스 수정 중 오류가 발생했습니다";
+ setSaveErrorMsg(message);
+ } finally {
+ setSaving(false);
+ }
+ };
+ const onDelete = async () => {
+ if (orgId === null) {
+ setDeleteErrorMsg("잘못된 워크스페이스ID 입니다");
+ return;
+ }
+ setDeleting(true);
+ setDeleteErrorMsg(null);
+ try {
+ await deleteWorkspace(orgId);
+ setDeleteOpen(false);
+ navigate("/workspace", { replace: true });
+ } catch (e) {
+ const message =
+ e instanceof Error
+ ? e.message
+ : "워크스페이스 삭제 중 오류가 발생했습니다";
+ setDeleteErrorMsg(message);
+ } finally {
+ setDeleting(false);
+ }
};
- const onDelete = () => {
- alert(`API 연동후 삭제예정 (workspaceId=${workspaceId})`);
- setDeleteOpen(false);
- navigate("/workspace", { replace: true });
+
+ const openDeleteModal = () => {
+ setDeleteErrorMsg(null);
+ setDeleteOpen(true);
};
return (
@@ -32,126 +122,163 @@ export default function WorkspaceSetting() {
워크스페이스 정보를 확인하고 관리하세요.
-
-
기본 정보
-
- 워크스페이스의 대표적인 정보를 설정합니다.
-
-
-
-
로고 이미지
-
-
-
-
-
-
-
-
-
-
setName(e.target.value)}
- />
-
-
-
{" "}
-
+
+ {loading && (
+
-
-
-
-
-
-
-
-
-
- 워크스페이스 삭제
-
-
- 워크스페이스를 삭제하면 모든 데이터가 영구적으로 삭제됩니다.
-
이 작업은 되돌릴 수 없습니다.
+ )}
+ {!loading && errorMsg && (
+
+
{errorMsg}
+
+
+ )}
+ {!loading && !errorMsg && (
+ <>
+
+
기본 정보
+
+ 워크스페이스의 대표적인 정보를 설정합니다.
+
+
+
로고 이미지
+
+
+
+
+
+
+
+
+
+
setName(e.target.value)}
+ disabled={saving || deleting}
+ />
+
+
+
+ {saveErrorMsg && (
+
{saveErrorMsg}
+ )}
+
+
-
-
-
-
setDeleteOpen(false)}
- size="lg"
- padding="lg"
- title="워크스페이스 삭제 확인"
- >
-
-
-
+
+
-
- 정말 삭제하시겠습니까?
-
-
- 삭제된 워크스페이스와 관련된 모든 데이터는 영구 삭제되며
절대
- 되돌릴 수 없습니다.
-
-
+
+
+
+
+
+ 워크스페이스 삭제
+
+
+ 워크스페이스를 삭제하면 모든 데이터가 영구적으로 삭제됩니다.
+
이 작업은 되돌릴 수 없습니다.
+
+
+
-
-
+
setDeleteOpen(false)}
+ size="lg"
+ padding="lg"
+ title="워크스페이스 삭제 확인"
+ >
+
+
+
+
+
+ 정말 삭제하시겠습니까?
+
+
+ 삭제된 워크스페이스와 관련된 모든 데이터는 영구 삭제되며
{" "}
+ 절대 되돌릴 수 없습니다.
+
+ {deleteErrorMsg && (
+
+ {deleteErrorMsg}
+
+ )}
+
+
+
+
+
+ >
+ )}
);
}
diff --git a/src/types/workspace/mapper.ts b/src/types/workspace/mapper.ts
index c2084bcb..843860ad 100644
--- a/src/types/workspace/mapper.ts
+++ b/src/types/workspace/mapper.ts
@@ -1,4 +1,9 @@
-import type { TWorkspace, TWorkspaceDTO } from "./workspace";
+import type {
+ TGetOrgResponse,
+ TWorkspace,
+ TWorkspaceDetail,
+ TWorkspaceDTO,
+} from "./workspace";
export const toWorkspace = (dto: TWorkspaceDTO): TWorkspace => ({
id: String(dto.orgId),
@@ -7,3 +12,11 @@ export const toWorkspace = (dto: TWorkspaceDTO): TWorkspace => ({
logoUrl: dto.logoUrl,
myRole: dto.myRole,
});
+
+export const toWorkspaceDetail = (dto: TGetOrgResponse): TWorkspaceDetail => ({
+ id: String(dto.orgId),
+ name: dto.name,
+ description: dto.description,
+ logoUrl: dto.logoUrl,
+ createdAt: dto.createdAt,
+});
diff --git a/src/types/workspace/workspace.ts b/src/types/workspace/workspace.ts
index 99571212..0b99bcba 100644
--- a/src/types/workspace/workspace.ts
+++ b/src/types/workspace/workspace.ts
@@ -36,3 +36,25 @@ export type TCreateOrgResponse = {
description: string;
logoUrl: string | null;
};
+
+export type TGetOrgResponse = {
+ orgId: number;
+ name: string;
+ description: string;
+ logoUrl: string | null;
+ createdAt: string;
+};
+
+export type TWorkspaceDetail = {
+ id: TWorkSpaceId;
+ name: string;
+ description: string;
+ logoUrl: string | null;
+ createdAt: string;
+};
+
+export type TUpdateWorkspaceRequest = {
+ name: string;
+ description: string;
+ logoUrl: string | null;
+};
From 81e627c497b26add7391e8b217789489eb62dae5 Mon Sep 17 00:00:00 2001
From: JAESEON PARK
Date: Mon, 2 Mar 2026 01:49:07 +0900
Subject: [PATCH 03/14] =?UTF-8?q?fix:=20=EC=9B=8C=ED=81=AC=EC=8A=A4?=
=?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=8A=A4=20API=20status=20=EB=AC=B8=EC=9E=90?=
=?UTF-8?q?=EC=97=B4=20=EC=B2=B4=ED=81=AC=20=EB=A1=9C=EC=A7=81=20=EC=A0=9C?=
=?UTF-8?q?=EA=B1=B0=20=EB=B0=8F=20axios=20=EA=B8=B0=EB=B3=B8=20=EC=97=90?=
=?UTF-8?q?=EB=9F=AC=20=EC=B2=98=EB=A6=AC=EB=A1=9C=20=EB=8B=A8=EC=88=9C?=
=?UTF-8?q?=ED=99=94?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/api/workspace/org.ts | 39 +++++++--------------------------------
1 file changed, 7 insertions(+), 32 deletions(-)
diff --git a/src/api/workspace/org.ts b/src/api/workspace/org.ts
index 30847d20..4ebe2e94 100644
--- a/src/api/workspace/org.ts
+++ b/src/api/workspace/org.ts
@@ -10,42 +10,31 @@ import type {
TWorkspaceDetail,
} from "@/types/workspace/workspace";
-import { authInstance } from "@/lib/axiosInstance";
+import { axiosInstance } from "@/lib/axiosInstance";
export const getMyWorkspaces = async (): Promise => {
const { data } =
- await authInstance.get>("/api/org/my");
- if (data.status !== "200") {
- throw new Error("워크스페이스 조회에 실패했습니다");
- }
+ await axiosInstance.get>("/api/org/my");
return data.data.organizations.map(toWorkspace);
};
export const createWorkspace = async (
body: TCreateOrgRequest,
): Promise => {
- const { data } = await authInstance.post>(
+ const { data } = await axiosInstance.post>(
"/api/org/create",
body,
);
- if (data.status !== "200") {
- throw new Error("워크스페이스 생성에 실패했습니다");
- }
return data.data;
};
export const getWorkspace = async (
orgId: number,
): Promise => {
- const { data } = await authInstance.get>(
+ const { data } = await axiosInstance.get>(
`/api/org/${orgId}`,
);
- if (data.status !== "200") {
- if (data.status === "404_1")
- throw new Error("해당 조직은 존재하지 않습니다");
- if (data.status === "410_1") throw new Error("해당 조직은 삭제되었습니다");
- throw new Error("워크스페이스 정보를 불러오지 못했습니다");
- }
+
return toWorkspaceDetail(data.data);
};
@@ -53,28 +42,14 @@ export const updateWorkspace = async (
orgId: number,
body: TUpdateWorkspaceRequest,
): Promise => {
- const { data } = await authInstance.patch>(
+ const { data } = await axiosInstance.patch>(
`/api/org/${orgId}`,
body,
);
- if (data.status !== "200") {
- if (data.status === "404_1")
- throw new Error("해당 조직은 존재하지 않습니다");
- if (data.status === "403_1")
- throw new Error("해당 조직에 대한 권한이 없습니다.");
- throw new Error("워크스페이스 수정에 실패했습니다");
- }
};
export const deleteWorkspace = async (orgId: number): Promise => {
- const { data } = await authInstance.delete>(
+ const { data } = await axiosInstance.delete>(
`/api/org/${orgId}`,
);
- if (data.status !== "200") {
- if (data.status === "404_1")
- throw new Error("해당 조직은 존재하지 않습니다");
- if (data.status === "403_1")
- throw new Error("해당 조직에 대한 권한이 없습니다.");
- throw new Error("워크스페이스 삭제에 실패했습니다");
- }
};
From 25824ccdb646a31899e163018a9ad22c062752d6 Mon Sep 17 00:00:00 2001
From: JAESEON PARK
Date: Mon, 2 Mar 2026 02:03:13 +0900
Subject: [PATCH 04/14] =?UTF-8?q?feat:=20=EC=9B=8C=ED=81=AC=EC=8A=A4?=
=?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=8A=A4=20=EC=84=A4=EC=A0=95=20=EC=A0=80?=
=?UTF-8?q?=EC=9E=A5/=EC=82=AD=EC=A0=9C=20=EA=B2=B0=EA=B3=BC=20=ED=86=A0?=
=?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=95=8C=EB=A6=BC=20=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/pages/workspace/WorkspaceSetting.tsx | 19 ++++++++-----------
1 file changed, 8 insertions(+), 11 deletions(-)
diff --git a/src/pages/workspace/WorkspaceSetting.tsx b/src/pages/workspace/WorkspaceSetting.tsx
index 58910d8d..ef4fffb6 100644
--- a/src/pages/workspace/WorkspaceSetting.tsx
+++ b/src/pages/workspace/WorkspaceSetting.tsx
@@ -1,5 +1,6 @@
import { useEffect, useMemo, useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
+import { toast } from "sonner";
import Button from "@/components/common/button/Button";
import Input from "@/components/common/input/Input";
@@ -60,14 +61,13 @@ export default function WorkspaceSetting() {
const onSave = async () => {
if (orgId === null) {
- setSaveErrorMsg("잘못된 워크스페이스ID 입니다");
+ toast.error("잘못된 워크스페이스ID 입니다");
return;
}
const nextName = name.trim();
const nextDesc = desc.trim();
if (!nextName) return;
setSaving(true);
- setSaveErrorMsg(null);
try {
await updateWorkspace(orgId, {
@@ -75,14 +75,12 @@ export default function WorkspaceSetting() {
description: nextDesc,
logoUrl: null,
});
+ toast.success("변경사항이 저장되었습니다");
await fetchWorkspaceDetail();
- setSaveErrorMsg(null);
} catch (e) {
const message =
- e instanceof Error
- ? e.message
- : "워크스페이스 수정 중 오류가 발생했습니다";
- setSaveErrorMsg(message);
+ e instanceof Error ? e.message : "변경사항 저장에 실패했습니다";
+ toast.error(message);
} finally {
setSaving(false);
}
@@ -96,14 +94,13 @@ export default function WorkspaceSetting() {
setDeleteErrorMsg(null);
try {
await deleteWorkspace(orgId);
+ toast.success("워크스페이스가 삭제되었습니다");
setDeleteOpen(false);
navigate("/workspace", { replace: true });
} catch (e) {
const message =
- e instanceof Error
- ? e.message
- : "워크스페이스 삭제 중 오류가 발생했습니다";
- setDeleteErrorMsg(message);
+ e instanceof Error ? e.message : "워크스페이스 삭제에 실패했습니다";
+ toast.error(message);
} finally {
setDeleting(false);
}
From ef057eb9b42450dc27418b571d50965065299af1 Mon Sep 17 00:00:00 2001
From: JAESEON PARK
Date: Mon, 2 Mar 2026 02:24:10 +0900
Subject: [PATCH 05/14] =?UTF-8?q?feat:=20=EC=9B=8C=ED=81=AC=EC=8A=A4?=
=?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=8A=A4=20=EC=84=A4=EC=A0=95=20=EC=A0=80?=
=?UTF-8?q?=EC=9E=A5/=EC=82=AD=EC=A0=9C=20=ED=86=A0=EC=8A=A4=ED=8A=B8=20?=
=?UTF-8?q?=EC=95=8C=EB=A6=BC=20=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/api/workspace/org.ts | 9 ++----
src/pages/workspace/Workspace.tsx | 2 --
src/pages/workspace/WorkspaceSetting.tsx | 39 ++++++++----------------
3 files changed, 14 insertions(+), 36 deletions(-)
diff --git a/src/api/workspace/org.ts b/src/api/workspace/org.ts
index 4ebe2e94..92e7dd3e 100644
--- a/src/api/workspace/org.ts
+++ b/src/api/workspace/org.ts
@@ -42,14 +42,9 @@ export const updateWorkspace = async (
orgId: number,
body: TUpdateWorkspaceRequest,
): Promise => {
- const { data } = await axiosInstance.patch>(
- `/api/org/${orgId}`,
- body,
- );
+ await axiosInstance.patch>(`/api/org/${orgId}`, body);
};
export const deleteWorkspace = async (orgId: number): Promise => {
- const { data } = await axiosInstance.delete>(
- `/api/org/${orgId}`,
- );
+ await axiosInstance.delete>(`/api/org/${orgId}`);
};
diff --git a/src/pages/workspace/Workspace.tsx b/src/pages/workspace/Workspace.tsx
index beee9d11..0800774d 100644
--- a/src/pages/workspace/Workspace.tsx
+++ b/src/pages/workspace/Workspace.tsx
@@ -77,14 +77,12 @@ export default function WorkspacePage() {
setLogoPreview(URL.createObjectURL(file));
};
- // TODO: 모달 닫힐때 preview URL 해제필요
useEffect(() => {
return () => {
if (logoPreview) URL.revokeObjectURL(logoPreview);
};
}, [logoPreview]);
- // 워크스페이스 목록 조회 함수 (길어지면, util함수로 빼기)
const fetchWorkspaces = async () => {
setLoading(true);
setListErrorMsg(null);
diff --git a/src/pages/workspace/WorkspaceSetting.tsx b/src/pages/workspace/WorkspaceSetting.tsx
index ef4fffb6..d3105718 100644
--- a/src/pages/workspace/WorkspaceSetting.tsx
+++ b/src/pages/workspace/WorkspaceSetting.tsx
@@ -1,5 +1,6 @@
import { useEffect, useMemo, useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
+import axios from "axios";
import { toast } from "sonner";
import Button from "@/components/common/button/Button";
@@ -15,6 +16,9 @@ import MessageCircleWarningIcon from "@/assets/icon/workspace/message-circle-war
import UpLoadImgIcon from "@/assets/icon/workspace/uploadImg.svg?react";
import WarningIcon from "@/assets/icon/workspace/warning.svg?react";
+const getAxiosMessage = (e: unknown, fallback: string) =>
+ axios.isAxiosError(e) ? (e.response?.data?.message ?? fallback) : fallback;
+
export default function WorkspaceSetting() {
const navigate = useNavigate();
const { workspaceId } = useParams();
@@ -26,13 +30,11 @@ export default function WorkspaceSetting() {
const [name, setName] = useState("");
const [desc, setDesc] = useState("");
const [loading, setLoading] = useState(false);
- const [errorMsg, setErrorMsg] = useState(null);
const [saving, setSaving] = useState(false);
- const [saveErrorMsg, setSaveErrorMsg] = useState(null);
+ const [errorMsg, setErrorMsg] = useState(null);
const [deleteOpen, setDeleteOpen] = useState(false);
const [deleting, setDeleting] = useState(false);
- const [deleteErrorMsg, setDeleteErrorMsg] = useState(null);
const fetchWorkspaceDetail = async () => {
if (orgId === null) {
@@ -40,17 +42,14 @@ export default function WorkspaceSetting() {
return;
}
setLoading(true);
- setErrorMsg(null);
try {
const detail = await getWorkspace(orgId);
setName(detail.name);
setDesc(detail.description ?? "");
} catch (e) {
- const message =
- e instanceof Error
- ? e.message
- : "워크스페이스 정보를 불러오지 못했습니다.";
- setErrorMsg(message);
+ toast.error(
+ getAxiosMessage(e, "워크스페이스 정보를 불러오지 못했습니다"),
+ );
} finally {
setLoading(false);
}
@@ -61,7 +60,7 @@ export default function WorkspaceSetting() {
const onSave = async () => {
if (orgId === null) {
- toast.error("잘못된 워크스페이스ID 입니다");
+ setErrorMsg("잘못된 워크스페이스ID 입니다");
return;
}
const nextName = name.trim();
@@ -78,36 +77,30 @@ export default function WorkspaceSetting() {
toast.success("변경사항이 저장되었습니다");
await fetchWorkspaceDetail();
} catch (e) {
- const message =
- e instanceof Error ? e.message : "변경사항 저장에 실패했습니다";
- toast.error(message);
+ toast.error(getAxiosMessage(e, "변경사항 저장에 실패했습니다"));
} finally {
setSaving(false);
}
};
const onDelete = async () => {
if (orgId === null) {
- setDeleteErrorMsg("잘못된 워크스페이스ID 입니다");
+ setErrorMsg("잘못된 워크스페이스ID 입니다");
return;
}
setDeleting(true);
- setDeleteErrorMsg(null);
try {
await deleteWorkspace(orgId);
toast.success("워크스페이스가 삭제되었습니다");
setDeleteOpen(false);
navigate("/workspace", { replace: true });
} catch (e) {
- const message =
- e instanceof Error ? e.message : "워크스페이스 삭제에 실패했습니다";
- toast.error(message);
+ toast.error(getAxiosMessage(e, "워크스페이스 삭제에 실패했습니다"));
} finally {
setDeleting(false);
}
};
const openDeleteModal = () => {
- setDeleteErrorMsg(null);
setDeleteOpen(true);
};
@@ -195,9 +188,6 @@ export default function WorkspaceSetting() {
disabled={saving || deleting}
/>
- {saveErrorMsg && (
-
{saveErrorMsg}
- )}
@@ -254,11 +244,6 @@ export default function WorkspaceSetting() {
삭제된 워크스페이스와 관련된 모든 데이터는 영구 삭제되며
{" "}
절대 되돌릴 수 없습니다.
- {deleteErrorMsg && (
-
- {deleteErrorMsg}
-
- )}
);
diff --git a/src/pages/workspace/Workspace.tsx b/src/pages/workspace/Workspace.tsx
index 150fdd87..1bfbfcbc 100644
--- a/src/pages/workspace/Workspace.tsx
+++ b/src/pages/workspace/Workspace.tsx
@@ -240,6 +240,7 @@ export default function WorkspacePage() {
placeholder="조직의 이름을 입력하세요."
value={newName}
onChange={(e) => setNewName(e.target.value)}
+ disabled={creating}
/>
setNewDesc(e.target.value)}
+ disabled={creating}
/>
{createErrorMsg && (
{createErrorMsg}
diff --git a/src/pages/workspace/WorkspaceSetting.tsx b/src/pages/workspace/WorkspaceSetting.tsx
index ff55da77..400810ff 100644
--- a/src/pages/workspace/WorkspaceSetting.tsx
+++ b/src/pages/workspace/WorkspaceSetting.tsx
@@ -5,6 +5,7 @@ import { toast } from "sonner";
import Button from "@/components/common/button/Button";
import Input from "@/components/common/input/Input";
import Modal from "@/components/common/modal/Modal";
+import TextareaField from "@/components/common/textarea/TextareaField";
import {
deleteWorkspace,
@@ -169,22 +170,16 @@ export default function WorkspaceSetting() {
onChange={(e) => setName(e.target.value)}
disabled={saving || deleting}
/>
-
-
-
+ setDesc(e.target.value)}
+ minRows={4}
+ className="min-h-55"
+ disabled={saving || deleting}
+ />
From 2d9c2829450af536fc30bbd9d5b5ed8e5f3a6a11 Mon Sep 17 00:00:00 2001
From: JAESEON PARK
Date: Mon, 2 Mar 2026 17:59:31 +0900
Subject: [PATCH 09/14] =?UTF-8?q?fix:=20WorkspaceSetting=20=EB=A1=9C?=
=?UTF-8?q?=EA=B3=A0=20URL=20=ED=95=98=EB=93=9C=EC=BD=94=EB=94=A9=20?=
=?UTF-8?q?=EC=A0=9C=EA=B1=B0=20=EB=B0=8F=20=EC=83=81=ED=83=9C=EA=B0=92=20?=
=?UTF-8?q?=EC=A0=81=EC=9A=A9?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/pages/workspace/WorkspaceSetting.tsx | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/src/pages/workspace/WorkspaceSetting.tsx b/src/pages/workspace/WorkspaceSetting.tsx
index 400810ff..ace0199b 100644
--- a/src/pages/workspace/WorkspaceSetting.tsx
+++ b/src/pages/workspace/WorkspaceSetting.tsx
@@ -34,6 +34,8 @@ export default function WorkspaceSetting() {
const [deleteOpen, setDeleteOpen] = useState(false);
const [deleting, setDeleting] = useState(false);
+ const [logoUrl, setLogoUrl] = useState(null);
+
const fetchWorkspaceDetail = async () => {
if (orgId === null) {
setErrorMsg("잘못된 워크스페이스ID 입니다");
@@ -44,6 +46,7 @@ export default function WorkspaceSetting() {
const detail = await getWorkspace(orgId);
setName(detail.name);
setDesc(detail.description ?? "");
+ setLogoUrl(detail.logoUrl ?? null);
} catch (e) {
toast.error(
getAxiosMessage(e, "워크스페이스 정보를 불러오지 못했습니다"),
@@ -70,7 +73,7 @@ export default function WorkspaceSetting() {
await updateWorkspace(orgId, {
name: nextName,
description: nextDesc,
- logoUrl: null,
+ logoUrl,
});
toast.success("변경사항이 저장되었습니다");
await fetchWorkspaceDetail();
@@ -154,7 +157,7 @@ export default function WorkspaceSetting() {