visuddhinanda 2 سال پیش
والد
کامیت
1f8f26b448

+ 83 - 0
dashboard/src/components/invite/InviteCreate.tsx

@@ -0,0 +1,83 @@
+import { useIntl } from "react-intl";
+import {
+  ProForm,
+  ProFormInstance,
+  ProFormText,
+} from "@ant-design/pro-components";
+import { message } from "antd";
+import { post } from "../../request";
+import { useRef } from "react";
+import { IInviteData } from "../../pages/studio/invite/list";
+import LangSelect from "../general/LangSelect";
+
+interface IInviteRequest {
+  email: string;
+  lang: string;
+  studio: string;
+}
+interface IInviteResponse {
+  ok: boolean;
+  message: string;
+  data: IInviteData;
+}
+interface IFormData {
+  email: string;
+  lang: string;
+}
+
+interface IWidget {
+  studio?: string;
+  onCreate?: Function;
+}
+const InviteCreateWidget = ({ studio, onCreate }: IWidget) => {
+  const intl = useIntl();
+  const formRef = useRef<ProFormInstance>();
+
+  return (
+    <ProForm<IFormData>
+      formRef={formRef}
+      onFinish={async (values: IFormData) => {
+        // TODO
+        if (typeof studio === "undefined") {
+          return;
+        }
+        console.log(values);
+        const res = await post<IInviteRequest, IInviteResponse>(`/v2/invite`, {
+          email: values.email,
+          lang: values.lang,
+          studio: studio,
+        });
+        console.log(res);
+        if (res.ok) {
+          message.success(intl.formatMessage({ id: "flashes.success" }));
+          if (typeof onCreate !== "undefined") {
+            onCreate();
+            formRef.current?.resetFields();
+          }
+        } else {
+          message.error(res.message);
+        }
+      }}
+    >
+      <ProForm.Group>
+        <ProFormText
+          width="md"
+          name="email"
+          required
+          label={intl.formatMessage({ id: "forms.fields.email.label" })}
+          rules={[
+            {
+              required: true,
+              type: "email",
+            },
+          ]}
+        />
+      </ProForm.Group>
+      <ProForm.Group>
+        <LangSelect />
+      </ProForm.Group>
+    </ProForm>
+  );
+};
+
+export default InviteCreateWidget;

+ 189 - 0
dashboard/src/components/nut/users/SignUp.tsx

@@ -0,0 +1,189 @@
+import { useIntl } from "react-intl";
+import {
+  ProForm,
+  ProFormDependency,
+  ProFormText,
+} from "@ant-design/pro-components";
+import { Button, message, Modal, Result } from "antd";
+import { useNavigate } from "react-router-dom";
+import { EyeInvisibleOutlined, EyeTwoTone } from "@ant-design/icons";
+
+import { get, post } from "../../../request";
+import { IInviteResponse } from "../../../pages/studio/invite/list";
+import LangSelect from "../../general/LangSelect";
+import { useState } from "react";
+
+interface IFormData {
+  email: string;
+  username: string;
+  nickname: string;
+  password: string;
+  password2: string;
+  lang: string;
+}
+interface ISignInResponse {
+  ok: boolean;
+  message: string;
+  data: string;
+}
+
+interface ISignUpRequest {
+  token: string;
+  username: string;
+  nickname: string;
+  email: string;
+  password: string;
+  lang: string;
+}
+
+interface IWidget {
+  token?: string;
+}
+const SignUpWidget = ({ token }: IWidget) => {
+  const intl = useIntl();
+  const navigate = useNavigate();
+  const [success, setSuccess] = useState(false);
+  const [nickname, setNickname] = useState<string>();
+  return success ? (
+    <Result
+      status="success"
+      title="注册成功"
+      subTitle={
+        <Button
+          type="primary"
+          onClick={() => navigate("/anonymous/users/sign-in")}
+        >
+          登录
+        </Button>
+      }
+    />
+  ) : (
+    <ProForm<IFormData>
+      onFinish={async (values: IFormData) => {
+        if (typeof token === "undefined") {
+          return;
+        }
+        if (values.password !== values.password2) {
+          Modal.error({ title: "两次密码不同" });
+          return;
+        }
+        const user = {
+          token: token,
+          username: values.username,
+          nickname: values.nickname,
+          email: values.email,
+          password: values.password,
+          lang: values.lang,
+        };
+        const signUp = await post<ISignUpRequest, ISignInResponse>(
+          "/v2/sign-up",
+          user
+        );
+        if (signUp.ok) {
+          setSuccess(true);
+        } else {
+          message.error(signUp.message);
+        }
+      }}
+      request={async () => {
+        const res = await get<IInviteResponse>(`/v2/invite/${token}`);
+        console.log(res.data);
+        return {
+          id: res.data.id,
+          username: "",
+          nickname: "",
+          password: "",
+          password2: "",
+          email: res.data.email,
+          lang: "zh-Hans",
+        };
+      }}
+    >
+      <ProForm.Group>
+        <ProFormText
+          width="md"
+          name="email"
+          required
+          label={intl.formatMessage({
+            id: "forms.fields.email.label",
+          })}
+          rules={[{ required: true, max: 255, min: 4 }]}
+          disabled
+        />
+      </ProForm.Group>
+      <ProForm.Group>
+        <ProFormText
+          width="md"
+          name="username"
+          required
+          label={intl.formatMessage({
+            id: "forms.fields.username.label",
+          })}
+          rules={[{ required: true, max: 255, min: 4 }]}
+        />
+      </ProForm.Group>
+      <ProForm.Group>
+        <ProFormText.Password
+          width="md"
+          name="password"
+          fieldProps={{
+            type: "password",
+
+            iconRender: (visible) =>
+              visible ? <EyeTwoTone /> : <EyeInvisibleOutlined />,
+          }}
+          required
+          label={intl.formatMessage({
+            id: "forms.fields.password.label",
+          })}
+          rules={[{ required: true, max: 32, min: 4 }]}
+        />
+      </ProForm.Group>
+      <ProForm.Group>
+        <ProFormText.Password
+          width="md"
+          name="password2"
+          fieldProps={{
+            type: "password",
+            iconRender: (visible) =>
+              visible ? <EyeTwoTone /> : <EyeInvisibleOutlined />,
+          }}
+          required
+          label={intl.formatMessage({
+            id: "forms.fields.confirm-password.label",
+          })}
+          rules={[{ required: true, max: 32, min: 4 }]}
+        />
+      </ProForm.Group>
+      <ProForm.Group>
+        <ProFormDependency name={["username"]}>
+          {({ username }) => {
+            return (
+              <ProFormText
+                width="md"
+                fieldProps={{
+                  placeholder: username,
+                  value: nickname ? nickname : username,
+                  onChange: (event) => {
+                    setNickname(event.target.value);
+                  },
+                }}
+                name="nickname"
+                required
+                label={intl.formatMessage({
+                  id: "forms.fields.nickname.label",
+                })}
+                rules={[{ required: true, max: 32, min: 4 }]}
+              />
+            );
+          }}
+        </ProFormDependency>
+      </ProForm.Group>
+      <ProForm.Group>
+        <LangSelect label="常用的译文语言" />
+      </ProForm.Group>
+    </ProForm>
+  );
+};
+
+export default SignUpWidget;

+ 22 - 0
dashboard/src/pages/studio/invite/index.tsx

@@ -0,0 +1,22 @@
+import { Outlet } from "react-router-dom";
+import { Layout } from "antd";
+
+import LeftSider from "../../../components/studio/LeftSider";
+import { styleStudioContent } from "../style";
+
+const { Content } = Layout;
+
+const Widget = () => {
+  return (
+    <Layout>
+      <Layout>
+        <LeftSider selectedKeys="invite" />
+        <Content style={styleStudioContent}>
+          <Outlet />
+        </Content>
+      </Layout>
+    </Layout>
+  );
+};
+
+export default Widget;

+ 161 - 0
dashboard/src/pages/studio/invite/list.tsx

@@ -0,0 +1,161 @@
+import { useParams } from "react-router-dom";
+import { useIntl } from "react-intl";
+import { Button, Popover } from "antd";
+import { ActionType, ProTable } from "@ant-design/pro-components";
+import { UserAddOutlined } from "@ant-design/icons";
+
+import { get } from "../../../request";
+import { RoleValueEnum } from "../../../components/studio/table";
+
+import { useRef, useState } from "react";
+import InviteCreate from "../../../components/invite/InviteCreate";
+
+export interface IInviteData {
+  id: string;
+  user_uid: string;
+  email: string;
+  status: string;
+  created_at: string;
+  updated_at: string;
+}
+interface IInviteListResponse {
+  ok: boolean;
+  message: string;
+  data: {
+    rows: IInviteData[];
+    count: number;
+  };
+}
+export interface IInviteResponse {
+  ok: boolean;
+  message: string;
+  data: IInviteData;
+}
+
+interface DataItem {
+  sn: number;
+  id: string;
+  email: string;
+  status: string;
+  createdAt: number;
+}
+
+const Widget = () => {
+  const intl = useIntl(); //i18n
+  const { studioname } = useParams(); //url 参数
+  const [openCreate, setOpenCreate] = useState(false);
+
+  const ref = useRef<ActionType>();
+
+  return (
+    <>
+      <ProTable<DataItem>
+        actionRef={ref}
+        columns={[
+          {
+            title: intl.formatMessage({
+              id: "dict.fields.sn.label",
+            }),
+            dataIndex: "sn",
+            key: "sn",
+            width: 50,
+            search: false,
+          },
+          {
+            title: intl.formatMessage({
+              id: "forms.fields.email.label",
+            }),
+            dataIndex: "email",
+            key: "email",
+          },
+          {
+            title: intl.formatMessage({
+              id: "forms.fields.status.label",
+            }),
+            dataIndex: "status",
+            key: "status",
+            width: 100,
+            search: false,
+            filters: true,
+            onFilter: true,
+            valueEnum: RoleValueEnum(),
+          },
+          {
+            title: intl.formatMessage({
+              id: "forms.fields.created-at.label",
+            }),
+            key: "created-at",
+            width: 100,
+            search: false,
+            dataIndex: "createdAt",
+            valueType: "date",
+            sorter: (a, b) => a.createdAt - b.createdAt,
+          },
+        ]}
+        request={async (params = {}, sorter, filter) => {
+          // TODO
+          console.log(params, sorter, filter);
+          let url = `/v2/invite?view=studio&studio=${studioname}`;
+          const offset =
+            ((params.current ? params.current : 1) - 1) *
+            (params.pageSize ? params.pageSize : 20);
+          url += `&limit=${params.pageSize}&offset=${offset}`;
+          url += params.keyword ? "&search=" + params.keyword : "";
+          console.log(url);
+          const res = await get<IInviteListResponse>(url);
+          const items: DataItem[] = res.data.rows.map((item, id) => {
+            const date = new Date(item.created_at);
+            return {
+              sn: id + 1,
+              id: item.id,
+              email: item.email,
+              status: item.status,
+              createdAt: date.getTime(),
+            };
+          });
+          console.log(items);
+          return {
+            total: res.data.count,
+            succcess: true,
+            data: items,
+          };
+        }}
+        rowKey="id"
+        bordered
+        pagination={{
+          showQuickJumper: true,
+          showSizeChanger: true,
+        }}
+        search={false}
+        options={{
+          search: true,
+        }}
+        toolBarRender={() => [
+          <Popover
+            content={
+              <InviteCreate
+                studio={studioname}
+                onCreate={() => {
+                  setOpenCreate(false);
+                  ref.current?.reload();
+                }}
+              />
+            }
+            placement="bottomRight"
+            trigger="click"
+            open={openCreate}
+            onOpenChange={(open: boolean) => {
+              setOpenCreate(open);
+            }}
+          >
+            <Button key="button" icon={<UserAddOutlined />} type="primary">
+              {intl.formatMessage({ id: "buttons.invite" })}
+            </Button>
+          </Popover>,
+        ]}
+      />
+    </>
+  );
+};
+
+export default Widget;