Skip to content

Commit 2585a90

Browse files
authored
Merge pull request #33 from pythonkr/feature/add-password-change-on-admin
feat: 비밀번호 변경 페이지 추가
2 parents e74c6ae + d48e4fd commit 2585a90

File tree

7 files changed

+175
-59
lines changed

7 files changed

+175
-59
lines changed

apps/pyconkr-admin/src/components/pages/account/account.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,6 @@ export const AccountRedirectPage: React.FC = ErrorBoundary.with(
1010
const backendAdminAPIClient = Common.Hooks.BackendAdminAPI.useBackendAdminClient();
1111
const { data } = Common.Hooks.BackendAdminAPI.useSignedInUserQuery(backendAdminAPIClient);
1212

13-
return data ? <Navigate to="/account/sign-out" replace /> : <Navigate to="/account/sign-in" replace />;
13+
return data ? <Navigate to="/account/manage" replace /> : <Navigate to="/account/sign-in" replace />;
1414
})
1515
);
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import * as Common from "@frontend/common";
2+
import { Logout } from "@mui/icons-material";
3+
import { Button, Stack, Tab, Tabs, TextField, Typography } from "@mui/material";
4+
import * as React from "react";
5+
import { useNavigate } from "react-router-dom";
6+
7+
import { addErrorSnackbar, addSnackbar } from "../../../utils/snackbar";
8+
9+
type ChangePasswordFormType = {
10+
old_password: string;
11+
new_password: string;
12+
new_password_confirm: string;
13+
};
14+
15+
export const AccountManagementPage: React.FC = () => {
16+
const changePasswordFormRef = React.useRef<HTMLFormElement>(null);
17+
const [pageState, setPageState] = React.useState<{ tab: number }>({ tab: 0 });
18+
const navigate = useNavigate();
19+
const backendAdminAPIClient = Common.Hooks.BackendAdminAPI.useBackendAdminClient();
20+
const signOutMutation = Common.Hooks.BackendAdminAPI.useSignOutMutation(backendAdminAPIClient);
21+
const changePasswordMutation = Common.Hooks.BackendAdminAPI.useChangePasswordMutation(backendAdminAPIClient);
22+
23+
const setTab = (_: React.SyntheticEvent, tab: number) => setPageState((ps) => ({ ...ps, tab }));
24+
25+
const handleSignOut = () => {
26+
signOutMutation.mutate(undefined, {
27+
onSuccess: () => {
28+
addSnackbar("로그아웃 되었습니다.", "success");
29+
navigate("/");
30+
},
31+
onError: addErrorSnackbar,
32+
});
33+
};
34+
35+
const handleChangePassword = (event: React.FormEvent<HTMLFormElement>) => {
36+
event.preventDefault();
37+
event.stopPropagation();
38+
39+
const form = changePasswordFormRef.current;
40+
if (!Common.Utils.isFormValid(form)) {
41+
addSnackbar("폼에 오류가 있습니다. 다시 확인해주세요.", "error");
42+
return;
43+
}
44+
45+
const formData = Common.Utils.getFormValue<ChangePasswordFormType>({ form });
46+
if (formData.new_password !== formData.new_password_confirm) {
47+
addSnackbar("새 비밀번호와 확인 비밀번호가 일치하지 않습니다.", "error");
48+
return;
49+
}
50+
51+
changePasswordMutation.mutate(formData, {
52+
onSuccess: () => {
53+
addSnackbar("비밀번호가 변경되었습니다.", "success");
54+
navigate("/");
55+
},
56+
onError: addErrorSnackbar,
57+
});
58+
};
59+
60+
React.useEffect(() => {
61+
(async () => {
62+
const userInfo = await Common.BackendAdminAPIs.me(backendAdminAPIClient)();
63+
if (!userInfo) {
64+
addSnackbar("로그아웃 상태입니다!", "error");
65+
navigate("/");
66+
}
67+
})();
68+
}, [backendAdminAPIClient, navigate]);
69+
70+
const disabled = signOutMutation.isPending || changePasswordMutation.isPending;
71+
72+
return (
73+
<Stack
74+
sx={{
75+
width: "100%",
76+
height: "100%",
77+
minHeight: "100%",
78+
maxHeight: "100%",
79+
flexGrow: 1,
80+
}}
81+
justifyContent="center"
82+
alignItems="center"
83+
spacing={2}
84+
>
85+
<Tabs value={pageState.tab} onChange={setTab}>
86+
<Tab wrapped label="비밀번호 변경" />
87+
<Tab wrapped label="로그아웃" />
88+
</Tabs>
89+
90+
{pageState.tab === 0 && (
91+
<Stack sx={{ width: "100%", maxWidth: "600px", textAlign: "center" }}>
92+
<Typography variant="h5">비밀번호 변경</Typography>
93+
<br />
94+
<form ref={changePasswordFormRef} onSubmit={handleChangePassword}>
95+
<Stack spacing={2}>
96+
<TextField
97+
disabled={disabled}
98+
name="old_password"
99+
type="password"
100+
label="현재 비밀번호"
101+
required
102+
fullWidth
103+
/>
104+
<TextField
105+
disabled={disabled}
106+
name="new_password"
107+
type="password"
108+
label="새 비밀번호"
109+
required
110+
fullWidth
111+
/>
112+
<TextField
113+
disabled={disabled}
114+
name="new_password_confirm"
115+
type="password"
116+
label="새 비밀번호 확인"
117+
required
118+
fullWidth
119+
/>
120+
<Button type="submit" variant="contained" disabled={disabled} fullWidth>
121+
비밀번호 변경
122+
</Button>
123+
</Stack>
124+
</form>
125+
</Stack>
126+
)}
127+
{pageState.tab === 1 && (
128+
<>
129+
<Typography variant="h5">정말 로그아웃하시겠습니까?</Typography>
130+
<br />
131+
<Button
132+
variant="contained"
133+
onClick={handleSignOut}
134+
disabled={signOutMutation.isPending}
135+
startIcon={<Logout />}
136+
>
137+
로그아웃
138+
</Button>
139+
</>
140+
)}
141+
</Stack>
142+
);
143+
};

apps/pyconkr-admin/src/components/pages/account/sign_out.tsx

Lines changed: 0 additions & 54 deletions
This file was deleted.

apps/pyconkr-admin/src/routes.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import { AdminEditorCreateRoutePage, AdminEditorModifyRoutePage } from "./compon
44
import { AdminList } from "./components/layouts/admin_list";
55
import { RouteDef } from "./components/layouts/global";
66
import { AccountRedirectPage } from "./components/pages/account/account";
7+
import { AccountManagementPage } from "./components/pages/account/manage";
78
import { SignInPage } from "./components/pages/account/sign_in";
8-
import { SignOutPage } from "./components/pages/account/sign_out";
99
import { PublicFileUploadPage } from "./components/pages/file/upload";
1010
import { AdminCMSPageEditor } from "./components/pages/page/editor";
1111

@@ -50,7 +50,7 @@ export const RouteDefinitions: RouteDef[] = [
5050
icon: AccountCircle,
5151
title: "로그인 / 로그아웃",
5252
app: "user",
53-
resource: "userext",
53+
resource: "account",
5454
route: "/account",
5555
placeOnBottom: true,
5656
},
@@ -80,5 +80,5 @@ export const RegisteredRoutes = {
8080
"/file/publicfile/:id": <AdminEditorModifyRoutePage app="file" resource="publicfile" notModifiable notDeletable />,
8181
"/account": <AccountRedirectPage />,
8282
"/account/sign-in": <SignInPage />,
83-
"/account/sign-out": <SignOutPage />,
83+
"/account/manage": <AccountManagementPage />,
8484
};

packages/common/src/apis/admin_api.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ namespace BackendAdminAPIs {
1919

2020
export const signOut = (client: BackendAPIClient) => () => client.delete<void>("v1/admin-api/user/userext/signout/");
2121

22+
export const changePassword = (client: BackendAPIClient) => (data: BackendAdminAPISchemas.UserChangePasswordSchema) =>
23+
client.post<void, BackendAdminAPISchemas.UserChangePasswordSchema>("v1/admin-api/user/userext/password/", data);
24+
25+
export const resetUserPassword = (client: BackendAPIClient, id: string) => () =>
26+
client.delete<void, void>(`v1/admin-api/user/userext/${id}/password/`);
27+
2228
export const list =
2329
<T>(client: BackendAPIClient, app: string, resource: string) =>
2430
() =>

packages/common/src/hooks/useAdminAPI.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ const QUERY_KEYS = {
1313

1414
const MUTATION_KEYS = {
1515
ADMIN_SIGN_IN: ["mutation", "admin", "sign-in"],
16+
ADMIN_SIGN_OUT: ["mutation", "admin", "sign-out"],
17+
ADMIN_CHANGE_PASSWORD: ["mutation", "admin", "change-password"],
18+
ADMIN_RESET_PASSWORD: ["mutation", "admin", "reset-password"],
1619
ADMIN_CREATE: ["mutation", "admin", "create"],
1720
ADMIN_UPDATE: ["mutation", "admin", "update"],
1821
ADMIN_REMOVE: ["mutation", "admin", "remove"],
@@ -38,10 +41,22 @@ namespace BackendAdminAPIHooks {
3841

3942
export const useSignOutMutation = (client: BackendAPIClient) =>
4043
useMutation({
41-
mutationKey: [...MUTATION_KEYS.ADMIN_SIGN_IN, "sign-out"],
44+
mutationKey: [...MUTATION_KEYS.ADMIN_SIGN_OUT],
4245
mutationFn: BackendAdminAPIs.signOut(client),
4346
});
4447

48+
export const useChangePasswordMutation = (client: BackendAPIClient) =>
49+
useMutation({
50+
mutationKey: [...MUTATION_KEYS.ADMIN_CHANGE_PASSWORD],
51+
mutationFn: BackendAdminAPIs.changePassword(client),
52+
});
53+
54+
export const useResetUserPasswordMutation = (client: BackendAPIClient, id: string) =>
55+
useMutation({
56+
mutationKey: [...MUTATION_KEYS.ADMIN_RESET_PASSWORD, id],
57+
mutationFn: BackendAdminAPIs.resetUserPassword(client, id),
58+
});
59+
4560
export const useSchemaQuery = (client: BackendAPIClient, app: string, resource: string) =>
4661
useSuspenseQuery({
4762
queryKey: [...QUERY_KEYS.ADMIN_SCHEMA, app, resource],

packages/common/src/schemas/backendAdminAPI.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@ namespace BackendAdminAPISchemas {
3434
password: string;
3535
};
3636

37+
export type UserChangePasswordSchema = {
38+
old_password: string;
39+
new_password: string;
40+
new_password_confirm: string;
41+
};
42+
3743
export type PublicFileSchema = {
3844
id: string; // UUID
3945
file: string; // URL to the public file

0 commit comments

Comments
 (0)