0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-03-31 22:51:25 -05:00

feat(console): reset user password (#1266)

This commit is contained in:
Wang Sijie 2022-06-28 17:41:06 +08:00 committed by GitHub
parent da4878492f
commit 8c46eada4b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 60 additions and 61 deletions

View file

@ -1,8 +1,8 @@
import { AdminConsoleKey } from '@logto/phrases';
import React, { useState } from 'react';
import { toast } from 'react-hot-toast';
import { useTranslation } from 'react-i18next';
import ReactModal from 'react-modal';
import { useSearchParams } from 'react-router-dom';
import Button from '@/components/Button';
import IconButton from '@/components/IconButton';
@ -14,19 +14,14 @@ import * as styles from './CreateSuccess.module.scss';
type Props = {
username: string;
password: string;
title: AdminConsoleKey;
onClose: () => void;
};
const CreateSuccess = ({ username }: Props) => {
const [searchParameters, setSearchParameters] = useSearchParams();
const [passwordVisible, setPasswordVisible] = useState(false);
const CreateSuccess = ({ username, password, title, onClose }: Props) => {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const passwordEncoded = searchParameters.get('password');
const password = passwordEncoded && atob(passwordEncoded);
const handleClose = () => {
setSearchParameters({});
};
const [passwordVisible, setPasswordVisible] = useState(false);
const handleCopy = async () => {
if (!password) {
@ -45,10 +40,10 @@ const CreateSuccess = ({ username }: Props) => {
return (
<ReactModal isOpen className={modalStyles.content} overlayClassName={modalStyles.overlay}>
<ModalLayout
title="user_details.created_title"
title={title}
footer={
<>
<Button title="admin_console.general.close" onClick={handleClose} />
<Button title="admin_console.general.close" onClick={onClose} />
<Button type="primary" title="admin_console.general.copy" onClick={handleCopy} />
</>
}

View file

@ -1,62 +1,39 @@
import { User } from '@logto/schemas';
import { nanoid } from 'nanoid';
import React from 'react';
import { useForm } from 'react-hook-form';
import { toast } from 'react-hot-toast';
import { useTranslation } from 'react-i18next';
import Button from '@/components/Button';
import FormField from '@/components/FormField';
import ModalLayout from '@/components/ModalLayout';
import TextInput from '@/components/TextInput';
import ConfirmModal from '@/components/ConfirmModal';
import useApi from '@/hooks/use-api';
type FormData = {
password: string;
};
type Props = {
userId: string;
onClose?: () => void;
onClose?: (password?: string) => void;
};
const ResetPasswordForm = ({ onClose, userId }: Props) => {
const { t } = useTranslation(undefined, {
keyPrefix: 'admin_console',
});
const {
handleSubmit,
register,
formState: { isSubmitting },
} = useForm<FormData>();
const api = useApi();
const onSubmit = handleSubmit(async (data) => {
await api.patch(`/api/users/${userId}/password`, { json: data }).json<User>();
onClose?.();
toast.success(t('user_details.reset_password.reset_password_success'));
});
const onSubmit = async () => {
const password = nanoid();
await api.patch(`/api/users/${userId}/password`, { json: { password } }).json<User>();
onClose?.(btoa(password));
};
return (
<ModalLayout
<ConfirmModal
isOpen
title="user_details.reset_password.title"
footer={
<Button
isLoading={isSubmitting}
htmlType="submit"
title="admin_console.user_details.reset_password.reset_password"
size="large"
type="primary"
onClick={onSubmit}
/>
}
onClose={onClose}
onCancel={() => {
onClose?.();
}}
onConfirm={onSubmit}
>
<form>
<FormField isRequired title="admin_console.user_details.reset_password.label">
<TextInput {...register('password', { required: true })} />
</FormField>
</form>
</ModalLayout>
{t('user_details.reset_password.content')}
</ConfirmModal>
);
};

View file

@ -6,7 +6,7 @@ import { useController, useForm } from 'react-hook-form';
import { toast } from 'react-hot-toast';
import { useTranslation } from 'react-i18next';
import ReactModal from 'react-modal';
import { useLocation, useParams } from 'react-router-dom';
import { useLocation, useParams, useSearchParams } from 'react-router-dom';
import useSWR from 'swr';
import ActionMenu, { ActionMenuItem } from '@/components/ActionMenu';
@ -51,9 +51,13 @@ const UserDetails = () => {
const location = useLocation();
const isLogs = location.pathname.endsWith('/logs');
const { userId } = useParams();
const [searchParameters, setSearchParameters] = useSearchParams();
const passwordEncoded = searchParameters.get('password');
const password = passwordEncoded && atob(passwordEncoded);
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const [isDeleteFormOpen, setIsDeleteFormOpen] = useState(false);
const [isResetPasswordFormOpen, setIsResetPasswordFormOpen] = useState(false);
const [resetResult, setResetResult] = useState<string>();
const { data, error, mutate } = useSWR<User, RequestError>(userId && `/api/users/${userId}`);
const isLoading = !data && !error;
@ -170,8 +174,12 @@ const UserDetails = () => {
>
<ResetPasswordForm
userId={data.id}
onClose={() => {
onClose={(password) => {
setIsResetPasswordFormOpen(false);
if (password) {
setResetResult(password);
}
}}
/>
</ReactModal>
@ -280,7 +288,26 @@ const UserDetails = () => {
</Card>
</>
)}
{data && <CreateSuccess username={data.username ?? '-'} />}
{data && password && (
<CreateSuccess
title="user_details.created_title"
username={data.username ?? '-'}
password={password}
onClose={() => {
setSearchParameters({});
}}
/>
)}
{data && resetResult && (
<CreateSuccess
title="user_details.reset_password.congratulations"
username={data.username ?? '-'}
password={resetResult}
onClose={() => {
setResetResult(undefined);
}}
/>
)}
</div>
);
};

View file

@ -250,10 +250,10 @@ const translation = {
delete_description: 'This action cannot be undone. It will permanently delete the user.',
deleted: 'The user has been successfully deleted',
reset_password: {
title: 'Reset password',
label: 'New password:',
reset_password: 'Reset password',
reset_password_success: 'Password has been successfully reset',
title: 'Are you sure you want to reset the password?',
content: "This action cannot be undone. This will reset the user's log in information.",
congratulations: 'This user has been reset',
},
tab_logs: 'User logs',
field_email: 'Primary email',

View file

@ -244,10 +244,10 @@ const translation = {
delete_description: '本操作将永久删除该用户,且无法撤销。',
deleted: '用户已成功删除!',
reset_password: {
title: '重置密码',
label: '新密码:',
reset_password: '重置密码',
reset_password_success: '密码已成功重置',
title: '确定要重置密码?',
content: '本操作不可撤销,将会重置用户的登录信息。',
congratulations: '该用户已被重置',
},
tab_logs: '用户日志',
field_email: '主要邮箱',