diff options
Diffstat (limited to 'Timeline/ClientApp/src/settings/Settings.tsx')
-rw-r--r-- | Timeline/ClientApp/src/settings/Settings.tsx | 200 |
1 files changed, 200 insertions, 0 deletions
diff --git a/Timeline/ClientApp/src/settings/Settings.tsx b/Timeline/ClientApp/src/settings/Settings.tsx new file mode 100644 index 00000000..b3996fdc --- /dev/null +++ b/Timeline/ClientApp/src/settings/Settings.tsx @@ -0,0 +1,200 @@ +import React, { useState } from 'react';
+import { useHistory } from 'react-router';
+import { useTranslation } from 'react-i18next';
+import axios, { AxiosError } from 'axios';
+import { Container, Row, Col, Input } from 'reactstrap';
+
+import { apiBaseUrl } from '../config';
+
+import { useUser, userLogout } from '../data/user';
+
+import AppBar from '../common/AppBar';
+import OperationDialog, {
+ OperationInputErrorInfo,
+} from '../common/OperationDialog';
+
+interface ChangePasswordDialogProps {
+ open: boolean;
+ close: () => void;
+}
+
+async function changePassword(
+ oldPassword: string,
+ newPassword: string,
+ token: string
+): Promise<void> {
+ const url = `${apiBaseUrl}/userop/changepassword?token=${token}`;
+ try {
+ await axios.post(url, {
+ oldPassword,
+ newPassword,
+ });
+ } catch (e) {
+ const error = e as AxiosError;
+ if (
+ error.response &&
+ error.response.status === 400 &&
+ error.response.data &&
+ error.response.data.message
+ ) {
+ throw error.response.data.message;
+ }
+ throw e;
+ }
+}
+
+const ChangePasswordDialog: React.FC<ChangePasswordDialogProps> = (props) => {
+ const user = useUser()!;
+ const history = useHistory();
+ const { t } = useTranslation();
+
+ const [redirect, setRedirect] = useState<boolean>(false);
+
+ return (
+ <OperationDialog
+ open={props.open}
+ title={t('settings.dialogChangePassword.title')}
+ titleColor="dangerous"
+ inputPrompt={t('settings.dialogChangePassword.prompt')}
+ inputScheme={[
+ {
+ type: 'text',
+ label: t('settings.dialogChangePassword.inputOldPassword'),
+ password: true,
+ validator: (v) =>
+ v === ''
+ ? 'settings.dialogChangePassword.errorEmptyOldPassword'
+ : null,
+ },
+ {
+ type: 'text',
+ label: t('settings.dialogChangePassword.inputNewPassword'),
+ password: true,
+ validator: (v, values) => {
+ const error: OperationInputErrorInfo = {};
+ error[1] =
+ v === ''
+ ? 'settings.dialogChangePassword.errorEmptyNewPassword'
+ : null;
+ if (v === values[2]) {
+ error[2] = null;
+ } else {
+ if (values[2] !== '') {
+ error[2] = 'settings.dialogChangePassword.errorRetypeNotMatch';
+ }
+ }
+ return error;
+ },
+ },
+ {
+ type: 'text',
+ label: t('settings.dialogChangePassword.inputRetypeNewPassword'),
+ password: true,
+ validator: (v, values) =>
+ v !== values[1]
+ ? 'settings.dialogChangePassword.errorRetypeNotMatch'
+ : null,
+ },
+ ]}
+ onProcess={async ([oldPassword, newPassword]) => {
+ await changePassword(
+ oldPassword as string,
+ newPassword as string,
+ user.token
+ );
+ userLogout();
+ setRedirect(true);
+ }}
+ close={() => {
+ props.close();
+ if (redirect) {
+ history.push('/login');
+ }
+ }}
+ />
+ );
+};
+
+const Settings: React.FC = (_) => {
+ const { i18n, t } = useTranslation();
+ const user = useUser();
+ const history = useHistory();
+
+ const [dialog, setDialog] = useState<null | 'changepassword'>(null);
+
+ const language = i18n.language.slice(0, 2);
+
+ return (
+ <>
+ <AppBar />
+ <Container fluid style={{ marginTop: '56px' }}>
+ {user ? (
+ <>
+ <Row className="border-bottom p-3">
+ <Col className="col-12">
+ <h5
+ onClick={() => {
+ history.push(`/users/${user.username}`);
+ }}
+ >
+ {t('settings.gotoSelf')}
+ </h5>
+ </Col>
+ </Row>
+ <Row className="border-bottom p-3">
+ <Col className="col-12">
+ <h5
+ className="text-danger"
+ onClick={() => setDialog('changepassword')}
+ >
+ {t('settings.changePassword')}
+ </h5>
+ </Col>
+ </Row>
+ <Row className="border-bottom p-3">
+ <Col className="col-12">
+ <h5
+ className="text-danger"
+ onClick={() => {
+ userLogout();
+ history.push('/');
+ }}
+ >
+ {t('settings.logout')}
+ </h5>
+ </Col>
+ </Row>
+ </>
+ ) : null}
+ <Row className="align-items-center border-bottom p-3">
+ <Col className="col-12 col-sm">
+ <h5>{t('settings.languagePrimary')}</h5>
+ <p>{t('settings.languageSecondary')}</p>
+ </Col>
+ <Col className="col-auto ml-auto">
+ <Input
+ type="select"
+ value={language}
+ onChange={(e) => {
+ i18n.changeLanguage(e.target.value);
+ }}
+ >
+ <option value="zh">中文</option>
+ <option value="en">English</option>
+ </Input>
+ </Col>
+ </Row>
+ {dialog === 'changepassword' ? (
+ <ChangePasswordDialog
+ open
+ close={() => {
+ setDialog(null);
+ }}
+ />
+ ) : null}
+ </Container>
+ </>
+ );
+};
+
+export default Settings;
|