-

-
{systemStatus.customizedProfile.name}
+
+

+
{systemStatus.customizedProfile.name}
{!disablePasswordLogin && (
-
+ {systemStatus.allowSignUp && (
+
+ {"Don't have an account yet?"}
+
+ {t("common.sign-up")}
+
+
+ )}
+ >
)}
{identityProviderList.length > 0 && (
<>
@@ -218,4 +189,4 @@ const Auth = () => {
);
};
-export default Auth;
+export default SignIn;
diff --git a/web/src/pages/SignUp.tsx b/web/src/pages/SignUp.tsx
new file mode 100644
index 00000000..1d7dc91d
--- /dev/null
+++ b/web/src/pages/SignUp.tsx
@@ -0,0 +1,135 @@
+import { Button, Input } from "@mui/joy";
+import { useState } from "react";
+import { toast } from "react-hot-toast";
+import { Link } from "react-router-dom";
+import AppearanceSelect from "@/components/AppearanceSelect";
+import LocaleSelect from "@/components/LocaleSelect";
+import * as api from "@/helpers/api";
+import useLoading from "@/hooks/useLoading";
+import { useGlobalStore, useUserStore } from "@/store/module";
+import { useTranslate } from "@/utils/i18n";
+
+const SignUp = () => {
+ const t = useTranslate();
+ const globalStore = useGlobalStore();
+ const userStore = useUserStore();
+ const actionBtnLoadingState = useLoading(false);
+ const { appearance, locale, systemStatus } = globalStore.state;
+ const [username, setUsername] = useState("");
+ const [password, setPassword] = useState("");
+
+ const handleUsernameInputChanged = (e: React.ChangeEvent
) => {
+ const text = e.target.value as string;
+ setUsername(text);
+ };
+
+ const handlePasswordInputChanged = (e: React.ChangeEvent) => {
+ const text = e.target.value as string;
+ setPassword(text);
+ };
+
+ const handleLocaleSelectChange = (locale: Locale) => {
+ globalStore.setLocale(locale);
+ };
+
+ const handleAppearanceSelectChange = (appearance: Appearance) => {
+ globalStore.setAppearance(appearance);
+ };
+
+ const handleFormSubmit = (e: React.FormEvent) => {
+ e.preventDefault();
+ handleSignUpButtonClick();
+ };
+
+ const handleSignUpButtonClick = async () => {
+ if (username === "" || password === "") {
+ return;
+ }
+
+ if (actionBtnLoadingState.isLoading) {
+ return;
+ }
+
+ try {
+ actionBtnLoadingState.setLoading();
+ await api.signup(username, password);
+ const user = await userStore.doSignIn();
+ if (user) {
+ window.location.href = "/";
+ } else {
+ toast.error(t("message.signup-failed"));
+ }
+ } catch (error: any) {
+ console.error(error);
+ toast.error(error.response.data.message || error.message || t("message.signup-failed"));
+ }
+ actionBtnLoadingState.setFinish();
+ };
+
+ return (
+
+
+
+
+

+
{systemStatus.customizedProfile.name}
+
+
Create your account
+
+
+ {"Already has an account?"}
+
+ {t("common.sign-in")}
+
+
+
+
+
+
+ );
+};
+
+export default SignUp;
diff --git a/web/src/router/index.tsx b/web/src/router/index.tsx
index e5114cb3..3ff51f63 100644
--- a/web/src/router/index.tsx
+++ b/web/src/router/index.tsx
@@ -1,20 +1,21 @@
import { lazy } from "react";
import { createBrowserRouter, redirect } from "react-router-dom";
import App from "@/App";
-import Archived from "@/pages/Archived";
-import DailyReview from "@/pages/DailyReview";
-import Resources from "@/pages/Resources";
-import Setting from "@/pages/Setting";
import { initialGlobalState, initialUserState } from "@/store/module";
const Root = lazy(() => import("@/layouts/Root"));
-const Auth = lazy(() => import("@/pages/Auth"));
+const SignIn = lazy(() => import("@/pages/SignIn"));
+const SignUp = lazy(() => import("@/pages/SignUp"));
const AuthCallback = lazy(() => import("@/pages/AuthCallback"));
const Explore = lazy(() => import("@/pages/Explore"));
const Home = lazy(() => import("@/pages/Home"));
const UserProfile = lazy(() => import("@/pages/UserProfile"));
const MemoDetail = lazy(() => import("@/pages/MemoDetail"));
const EmbedMemo = lazy(() => import("@/pages/EmbedMemo"));
+const Archived = lazy(() => import("@/pages/Archived"));
+const DailyReview = lazy(() => import("@/pages/DailyReview"));
+const Resources = lazy(() => import("@/pages/Resources"));
+const Setting = lazy(() => import("@/pages/Setting"));
const NotFound = lazy(() => import("@/pages/NotFound"));
const initialGlobalStateLoader = (() => {
@@ -58,7 +59,11 @@ const router = createBrowserRouter([
children: [
{
path: "/auth",
- element: ,
+ element: ,
+ },
+ {
+ path: "/auth/signup",
+ element: ,
},
{
path: "/auth/callback",