Skip to content

Commit 2f45ef3

Browse files
committed
feat: 어드민에서 비밀번호 초기화 시 서버로부터 초기 비밀번호를 받아서 dialog에 노출되도록 수정
1 parent feac85b commit 2f45ef3

File tree

3 files changed

+79
-12
lines changed

3 files changed

+79
-12
lines changed

apps/pyconkr-admin/src/components/pages/user/editor.tsx

Lines changed: 74 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,18 @@
11
import * as Common from "@frontend/common";
2-
import { KeyOff } from "@mui/icons-material";
3-
import { Button, ButtonProps, CircularProgress, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle } from "@mui/material";
2+
import { ContentCopy, KeyOff } from "@mui/icons-material";
3+
import {
4+
Button,
5+
ButtonProps,
6+
CircularProgress,
7+
Dialog,
8+
DialogActions,
9+
DialogContent,
10+
DialogContentText,
11+
DialogTitle,
12+
IconButton,
13+
InputAdornment,
14+
TextField,
15+
} from "@mui/material";
416
import { ErrorBoundary, Suspense } from "@suspensive/react";
517
import * as React from "react";
618
import { useParams } from "react-router-dom";
@@ -9,53 +21,104 @@ import { addErrorSnackbar, addSnackbar } from "../../../utils/snackbar";
921
import { AdminEditor } from "../../layouts/admin_editor";
1022

1123
type PageStateType = {
12-
isDialogOpen: boolean;
24+
isConfirmDialogOpen: boolean;
25+
isResultDialogOpen: boolean;
26+
newPassword: string | null;
1327
};
1428

1529
export const AdminUserExtEditor: React.FC = ErrorBoundary.with(
1630
{ fallback: Common.Components.ErrorFallback },
1731
Suspense.with({ fallback: <CircularProgress /> }, () => {
1832
const { id } = useParams<{ id?: string }>();
19-
const [pageState, setPageState] = React.useState<PageStateType>({ isDialogOpen: false });
20-
const openDialog = () => setPageState((ps) => ({ ...ps, isDialogOpen: true }));
21-
const closeDialog = () => setPageState((ps) => ({ ...ps, isDialogOpen: false }));
33+
const [pageState, setPageState] = React.useState<PageStateType>({
34+
isConfirmDialogOpen: false,
35+
isResultDialogOpen: false,
36+
newPassword: null,
37+
});
38+
const openConfirmDialog = () => setPageState((ps) => ({ ...ps, isConfirmDialogOpen: true }));
39+
const closeConfirmDialog = () => setPageState((ps) => ({ ...ps, isConfirmDialogOpen: false }));
40+
const closeResultDialog = () => setPageState((ps) => ({ ...ps, isResultDialogOpen: false, newPassword: null }));
2241

2342
const backendAdminClient = Common.Hooks.BackendAdminAPI.useBackendAdminClient();
2443
const useResetPasswordMutation = Common.Hooks.BackendAdminAPI.useResetUserPasswordMutation(backendAdminClient, id || "");
2544

2645
const resetUserPassword = () => {
27-
closeDialog();
46+
closeConfirmDialog();
2847
if (id) {
2948
useResetPasswordMutation.mutate(undefined, {
30-
onSuccess: () => addSnackbar("비밀번호가 초기화되었습니다.", "success"),
49+
onSuccess: (data) => {
50+
setPageState((ps) => ({
51+
...ps,
52+
isResultDialogOpen: true,
53+
newPassword: data.password,
54+
}));
55+
},
3156
onError: addErrorSnackbar,
3257
});
3358
}
3459
};
3560

61+
const copyPasswordToClipboard = () => {
62+
if (pageState.newPassword) {
63+
navigator.clipboard.writeText(pageState.newPassword).then(
64+
() => addSnackbar("비밀번호가 클립보드에 복사되었습니다.", "success"),
65+
() => addSnackbar("클립보드 복사에 실패했습니다.", "error")
66+
);
67+
}
68+
};
69+
3670
const resetUserPasswordButton: ButtonProps = {
3771
variant: "outlined",
3872
color: "error",
3973
size: "small",
4074
startIcon: <KeyOff />,
4175
children: "비밀번호 초기화",
42-
onClick: () => id && openDialog(),
76+
onClick: () => id && openConfirmDialog(),
4377
};
4478

4579
return (
4680
<>
47-
<Dialog open={pageState.isDialogOpen}>
81+
<Dialog open={pageState.isConfirmDialogOpen}>
4882
<DialogTitle>비밀번호 초기화</DialogTitle>
4983
<DialogContent>
5084
<DialogContentText>정말 이 사용자의 비밀번호를 초기화하시겠습니까?</DialogContentText>
5185
</DialogContent>
5286
<DialogActions>
53-
<Button color="error" onClick={closeDialog} autoFocus>
87+
<Button color="error" onClick={closeConfirmDialog} autoFocus>
5488
취소
5589
</Button>
5690
<Button onClick={resetUserPassword}>초기화</Button>
5791
</DialogActions>
5892
</Dialog>
93+
94+
<Dialog open={pageState.isResultDialogOpen} maxWidth="sm" fullWidth>
95+
<DialogTitle>비밀번호가 초기화되었습니다</DialogTitle>
96+
<DialogContent>
97+
<DialogContentText sx={{ mb: 2 }}>
98+
새로운 비밀번호가 생성되었습니다. 이 비밀번호는 다시 확인할 수 없으니 반드시 복사해 두세요.
99+
</DialogContentText>
100+
<TextField
101+
fullWidth
102+
value={pageState.newPassword || ""}
103+
slotProps={{
104+
input: {
105+
readOnly: true,
106+
endAdornment: (
107+
<InputAdornment position="end">
108+
<IconButton onClick={copyPasswordToClipboard} edge="end">
109+
<ContentCopy />
110+
</IconButton>
111+
</InputAdornment>
112+
),
113+
},
114+
}}
115+
/>
116+
</DialogContent>
117+
<DialogActions>
118+
<Button onClick={closeResultDialog}>닫기</Button>
119+
</DialogActions>
120+
</Dialog>
121+
59122
<AdminEditor app="user" resource="userext" id={id} extraActions={[resetUserPasswordButton]} />
60123
</>
61124
);

packages/common/src/apis/admin_api.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ namespace BackendAdminAPIs {
2020
client.post<void, BackendAdminAPISchemas.UserChangePasswordSchema>("v1/admin-api/user/userext/password/", data);
2121

2222
export const resetUserPassword = (client: BackendAPIClient, id: string) => () =>
23-
client.delete<void, void>(`v1/admin-api/user/userext/${id}/password/`);
23+
client.delete<BackendAdminAPISchemas.UserResetPasswordResponseSchema>(`v1/admin-api/user/userext/${id}/password/`);
2424

2525
export const list =
2626
<T>(client: BackendAPIClient, app: string, resource: string, params?: Record<string, string>) =>

packages/common/src/schemas/backendAdminAPI.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ namespace BackendAdminAPISchemas {
4040
new_password_confirm: string;
4141
};
4242

43+
export type UserResetPasswordResponseSchema = {
44+
password: string;
45+
};
46+
4347
export type PublicFileSchema = {
4448
id: string; // UUID
4549
file: string; // URL to the public file

0 commit comments

Comments
 (0)