From cf6cfe87b46a2a3eb2913209092ab4c5639e75c3 Mon Sep 17 00:00:00 2001
From: crupest <crupest@outlook.com>
Date: Thu, 11 Jun 2020 17:27:15 +0800
Subject: feat(front): Service worker is coming!

---
 Timeline/ClientApp/src/app/settings/Settings.tsx | 253 +++++++++++++++++++++++
 1 file changed, 253 insertions(+)
 create mode 100644 Timeline/ClientApp/src/app/settings/Settings.tsx

(limited to 'Timeline/ClientApp/src/app/settings/Settings.tsx')

diff --git a/Timeline/ClientApp/src/app/settings/Settings.tsx b/Timeline/ClientApp/src/app/settings/Settings.tsx
new file mode 100644
index 00000000..96a3fab4
--- /dev/null
+++ b/Timeline/ClientApp/src/app/settings/Settings.tsx
@@ -0,0 +1,253 @@
+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,
+  Modal,
+  ModalHeader,
+  ModalBody,
+  ModalFooter,
+  Button,
+} from 'reactstrap';
+
+import { apiBaseUrl } from '../config';
+
+import { useUser, userLogout, useUserLoggedIn } from '../data/user';
+
+import AppBar from '../common/AppBar';
+import OperationDialog, {
+  OperationInputErrorInfo,
+} from '../common/OperationDialog';
+import { CommonErrorResponse } from '../data/common';
+
+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<CommonErrorResponse>;
+    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 = useUserLoggedIn();
+  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 ConfirmLogoutDialog: React.FC<{
+  toggle: () => void;
+  onConfirm: () => void;
+}> = ({ toggle, onConfirm }) => {
+  const { t } = useTranslation();
+
+  return (
+    <Modal isOpen centered>
+      <ModalHeader className="text-danger">
+        {t('settings.dialogConfirmLogout.title')}
+      </ModalHeader>
+      <ModalBody>{t('settings.dialogConfirmLogout.prompt')}</ModalBody>
+      <ModalFooter>
+        <Button color="secondary" onClick={toggle}>
+          {t('operationDialog.cancel')}
+        </Button>
+        <Button color="danger" onClick={onConfirm}>
+          {t('operationDialog.confirm')}
+        </Button>
+      </ModalFooter>
+    </Modal>
+  );
+};
+
+const Settings: React.FC = (_) => {
+  const { i18n, t } = useTranslation();
+  const user = useUser();
+  const history = useHistory();
+
+  const [dialog, setDialog] = useState<null | 'changepassword' | 'logout'>(
+    null
+  );
+
+  const language = i18n.language.slice(0, 2);
+
+  return (
+    <>
+      <AppBar />
+      <Container fluid className="mt-appbar">
+        {user ? (
+          <>
+            <Row className="border-bottom p-3 cursor-pointer">
+              <Col xs="12">
+                <h5
+                  onClick={() => {
+                    history.push(`/users/${user.username}`);
+                  }}
+                >
+                  {t('settings.gotoSelf')}
+                </h5>
+              </Col>
+            </Row>
+            <Row className="border-bottom p-3 cursor-pointer">
+              <Col xs="12">
+                <h5
+                  className="text-danger"
+                  onClick={() => setDialog('changepassword')}
+                >
+                  {t('settings.changePassword')}
+                </h5>
+              </Col>
+            </Row>
+            <Row className="border-bottom p-3 cursor-pointer">
+              <Col xs="12">
+                <h5
+                  className="text-danger"
+                  onClick={() => {
+                    setDialog('logout');
+                  }}
+                >
+                  {t('settings.logout')}
+                </h5>
+              </Col>
+            </Row>
+          </>
+        ) : null}
+        <Row className="align-items-center border-bottom p-3">
+          <Col xs="12" sm="auto">
+            <h5>{t('settings.languagePrimary')}</h5>
+            <p>{t('settings.languageSecondary')}</p>
+          </Col>
+          <Col xs="auto" className="ml-auto">
+            <Input
+              type="select"
+              value={language}
+              onChange={(e) => {
+                void i18n.changeLanguage(e.target.value);
+              }}
+            >
+              <option value="zh">中文</option>
+              <option value="en">English</option>
+            </Input>
+          </Col>
+        </Row>
+        {(() => {
+          switch (dialog) {
+            case 'changepassword':
+              return (
+                <ChangePasswordDialog
+                  open
+                  close={() => {
+                    setDialog(null);
+                  }}
+                />
+              );
+            case 'logout':
+              return (
+                <ConfirmLogoutDialog
+                  toggle={() => setDialog(null)}
+                  onConfirm={() => {
+                    userLogout();
+                    history.push('/');
+                  }}
+                />
+              );
+            default:
+              return null;
+          }
+        })()}
+      </Container>
+    </>
+  );
+};
+
+export default Settings;
-- 
cgit v1.2.3

