Skip to content

Commit 0e0fa94

Browse files
committed
fix: 유저 권한 변경 API 추가
1 parent 7e774b7 commit 0e0fa94

7 files changed

Lines changed: 189 additions & 23 deletions

File tree

apps/ticket-admin/src/app/users/_clientBoundary/ChangeRoleModal/index.tsx

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,34 @@
11
import { useState } from "react";
22

33
import { Button, Dialog, Flex } from "@permit/design-system";
4+
import { usePutUserRoleMutation } from "@/data/admin/putUserRole/mutation";
45
import { ModalComponentProps } from "@/shared/hooks/useModal/types";
6+
import { isAxiosErrorResponse } from "@/shared/types/axioxError";
57

6-
type Props = {} & ModalComponentProps<{ result: boolean }>;
8+
type Props = { userId: number } & ModalComponentProps<{ result: boolean }>;
79

8-
// TODO: 수정
9-
export const ChangeRoleModal = ({ isOpen, close }: Props) => {
10+
export const ChangeRoleModal = ({ userId, isOpen, close }: Props) => {
1011
const [isSubmitting, setIsSubmitting] = useState(false);
1112

13+
const { mutateAsync } = usePutUserRoleMutation({ userId });
14+
15+
const changeUserRole = async (role: "ADMIN" | "STAFF" | "USER") => {
16+
setIsSubmitting(true);
17+
18+
try {
19+
await mutateAsync({ role });
20+
alert(`${role}으로 권한 변경에 성공하였습니다.`);
21+
} catch (error) {
22+
if (isAxiosErrorResponse(error)) {
23+
alert(error.response?.data.message || "Failed to change user role.");
24+
25+
return;
26+
}
27+
} finally {
28+
setIsSubmitting(false);
29+
}
30+
};
31+
1232
const handleClose = () => {
1333
if (!isSubmitting) {
1434
close();
@@ -22,26 +42,23 @@ export const ChangeRoleModal = ({ isOpen, close }: Props) => {
2242
<Button
2343
variant="primary"
2444
size="md"
25-
onClick={() => {}}
26-
isLoading={isSubmitting}
45+
onClick={() => changeUserRole("ADMIN")}
2746
disabled={isSubmitting}
2847
>
2948
ADMIN
3049
</Button>
3150
<Button
3251
variant="primary"
3352
size="md"
34-
onClick={() => {}}
35-
isLoading={isSubmitting}
53+
onClick={() => changeUserRole("STAFF")}
3654
disabled={isSubmitting}
3755
>
3856
STAFF
3957
</Button>
4058
<Button
4159
variant="primary"
4260
size="md"
43-
onClick={() => {}}
44-
isLoading={isSubmitting}
61+
onClick={() => changeUserRole("USER")}
4562
disabled={isSubmitting}
4663
>
4764
USER

apps/ticket-admin/src/app/users/_clientBoundary/UserManagementClient/index.tsx

Lines changed: 59 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,54 @@
11
"use client";
22

3+
import { useState } from "react";
4+
import { useQueryClient } from "@tanstack/react-query";
35
import classNames from "classnames/bind";
46

57
import { Button, Flex, TextField, Typography } from "@permit/design-system";
8+
import { useTextField } from "@permit/design-system/hooks";
9+
import { userOptions } from "@/data/admin/getUser/queries";
10+
import { UserResponse } from "@/data/admin/getUser/types";
611
import { useModal } from "@/shared/hooks/useModal";
12+
import { isAxiosErrorResponse } from "@/shared/types/axioxError";
713

814
import { ChangeRoleModal } from "../ChangeRoleModal";
915
import styles from "./index.module.scss";
1016

1117
const cx = classNames.bind(styles);
1218

1319
export const UserManagementClient = () => {
14-
const { show } = useModal(ChangeRoleModal);
20+
const qc = useQueryClient();
21+
const { show: changeUserRoleModal } = useModal(ChangeRoleModal);
1522

16-
const searchUser = () => {
17-
alert("hi");
23+
const [userInfo, setUserInfo] = useState<UserResponse | null>(null);
24+
25+
const userEmail = useTextField({
26+
initialValue: "",
27+
validate: (value: string) => {
28+
if (!value.trim()) return "유저 이메일을 입력해주세요.";
29+
30+
return undefined;
31+
},
32+
});
33+
34+
const searchUser = async () => {
35+
const userEmailValid = userEmail.validateValue();
36+
37+
if (!userEmailValid) {
38+
return;
39+
}
40+
41+
try {
42+
const userInfoData = await qc.fetchQuery(userOptions({ email: userEmail.value }));
43+
44+
setUserInfo(userInfoData);
45+
} catch (error) {
46+
if (isAxiosErrorResponse(error)) {
47+
setUserInfo(null);
48+
49+
alert(error.response?.data.message || "유저를 찾을 수 없습니다.");
50+
}
51+
}
1852
};
1953

2054
return (
@@ -27,23 +61,34 @@ export const UserManagementClient = () => {
2761
</header>
2862
<main>
2963
<Flex gap={20}>
30-
<TextField className={cx("input")} placeholder="이메일을 입력해주세요" />
64+
<TextField
65+
className={cx("input")}
66+
placeholder="이메일을 입력해주세요"
67+
value={userEmail.value}
68+
onChange={userEmail.handleChange}
69+
error={userEmail.error}
70+
/>
3171
<Button variant="cta" onClick={searchUser}>
3272
Search
3373
</Button>
3474
</Flex>
3575

36-
<div className={cx("serach_area")}>
37-
<Flex justify="space-between">
38-
<Flex direction="column">
39-
<Typography type="title20">userName: hi</Typography>
40-
<Typography type="title20">current Role: USER</Typography>
76+
{userInfo && (
77+
<div className={cx("serach_area")}>
78+
<Flex justify="space-between">
79+
<Flex direction="column">
80+
<Typography type="title20">userName: {userInfo.userName}</Typography>
81+
<Typography type="title20">current Role: {userInfo.currentUserRole}</Typography>
82+
</Flex>
83+
<Button
84+
variant="primary"
85+
onClick={() => changeUserRoleModal({ userId: userInfo.userId })}
86+
>
87+
Change Role
88+
</Button>
4189
</Flex>
42-
<Button variant="primary" onClick={show}>
43-
Change Role
44-
</Button>
45-
</Flex>
46-
</div>
90+
</div>
91+
)}
4792
</main>
4893
</div>
4994
);
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { useQuery } from "@tanstack/react-query";
2+
import queryString from "query-string";
3+
4+
import { API_URL } from "@/data/constants";
5+
import { instance } from "@/lib/axios";
6+
import { useSuspenseQuery } from "@/shared/hooks/useSuspenseQuery";
7+
import {
8+
OptionsObject,
9+
PermitQueryOptions,
10+
UsePermitQueryOptions,
11+
UsePermitSuspenseQueryOptions,
12+
} from "@/shared/types/queryOptions";
13+
14+
import { ADMIN_QUERY_KEYS } from "../queryKeys";
15+
import { UserParams, UserResponse } from "./types";
16+
17+
/** 유저 권한 정보 조회 API */
18+
export const userOptions = (params: UserParams): PermitQueryOptions<UserResponse> => {
19+
return {
20+
queryKey: [ADMIN_QUERY_KEYS.USER, params.email],
21+
queryFn: () => {
22+
const url = queryString.stringifyUrl({
23+
url: API_URL.ADMIN.USER,
24+
query: {
25+
email: params.email,
26+
},
27+
});
28+
29+
return instance.get<UserResponse>(url).then((res) => res.data);
30+
},
31+
};
32+
};
33+
34+
/** 유저 권한 정보 조회 Query */
35+
export const useUserQuery = ({
36+
email,
37+
options,
38+
}: UserParams & OptionsObject<UsePermitQueryOptions<UserResponse>>) => {
39+
return useQuery({
40+
...userOptions({ email }),
41+
...options,
42+
});
43+
};
44+
45+
/** 행사 티켓 라운드+타입 전체 조회 Suspense Query */
46+
export const useUserSuspenseQuery = ({
47+
email,
48+
options,
49+
}: UserParams & OptionsObject<UsePermitSuspenseQueryOptions<UserResponse>>) => {
50+
return useSuspenseQuery({
51+
...userOptions({ email }),
52+
...options,
53+
});
54+
};
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export type UserParams = {
2+
email: string;
3+
};
4+
5+
export type UserResponse = {
6+
userId: number;
7+
userName: string;
8+
currentUserRole: "ADMIN" | "STAFF" | "USER";
9+
};
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { useMutation, UseMutationOptions } from "@tanstack/react-query";
2+
3+
import { API_URL } from "@/data/constants";
4+
import { instance } from "@/lib/axios";
5+
import { getPathUrl } from "@/shared/helpers/getPathUrl";
6+
7+
export type PutUserRoleRequest = {
8+
role: "ADMIN" | "STAFF" | "USER";
9+
};
10+
11+
type PutUserRoleResponse = {
12+
data: null;
13+
};
14+
15+
export type PutUserRoleMutationOptions<TData> = {
16+
userId: number;
17+
options?: Omit<UseMutationOptions<TData, Error, PutUserRoleRequest>, "mutationFn"> & {
18+
onSuccess?: (data: TData) => void;
19+
};
20+
};
21+
22+
export const usePutUserRoleMutation = ({
23+
userId,
24+
options,
25+
}: PutUserRoleMutationOptions<PutUserRoleResponse>) => {
26+
const url = getPathUrl(API_URL.ADMIN.USER_ROLE, { userId });
27+
28+
return useMutation({
29+
mutationFn: async (params: PutUserRoleRequest) => {
30+
const { data } = await instance.put<PutUserRoleResponse>(url, params);
31+
32+
return data;
33+
},
34+
...options,
35+
});
36+
};

apps/ticket-admin/src/data/admin/queryKeys.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@ export const ADMIN_QUERY_KEYS = {
77
TICEKTS: `${queryKeyPrefix}tickets`,
88
TICEKTS_DETAIL: `${queryKeyPrefix}tickets_detail`,
99
COUPONS: `${queryKeyPrefix}coupons`,
10+
USER: `${queryKeyPrefix}user`,
1011
} as const;

apps/ticket-admin/src/data/constants.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,5 +39,9 @@ export const API_URL = {
3939
COUPONS_MEMO: "/api/admin/coupons/memos",
4040
/** 쿠폰 리스트 조회 API */
4141
COUPONS_LIST: "/api/admin/coupons/:eventId",
42+
/** 유저 권한 정보 조회 API */
43+
USER: "/api/admin/users", // ?email (필수)
44+
/** 유저 권한 변경 API */
45+
USER_ROLE: "/api/admin/users/:userId/role",
4246
},
4347
} as const;

0 commit comments

Comments
 (0)