Unverified Commit cf750541 authored by boojack's avatar boojack Committed by GitHub

feat: add system setting to allow user signup (#407)

parent 4ed98722
...@@ -5,4 +5,6 @@ import "github.com/usememos/memos/server/profile" ...@@ -5,4 +5,6 @@ import "github.com/usememos/memos/server/profile"
type SystemStatus struct { type SystemStatus struct {
Host *User `json:"host"` Host *User `json:"host"`
Profile *profile.Profile `json:"profile"` Profile *profile.Profile `json:"profile"`
// System settings
AllowSignUp bool `json:"allowSignUp"`
} }
package api
import (
"encoding/json"
"fmt"
)
type SystemSettingName string
const (
// SystemSettingAllowSignUpName is the key type of allow signup setting.
SystemSettingAllowSignUpName SystemSettingName = "allowSignUp"
SystemSettingPlaceholderName SystemSettingName = "placeholder"
)
func (key SystemSettingName) String() string {
switch key {
case SystemSettingAllowSignUpName:
return "allowSignUp"
case SystemSettingPlaceholderName:
return "placeholder"
}
return ""
}
var (
SystemSettingAllowSignUpValue = []bool{true, false}
)
type SystemSetting struct {
Name SystemSettingName
// Value is a JSON string with basic value
Value string
Description string
}
type SystemSettingUpsert struct {
Name SystemSettingName `json:"name"`
Value string `json:"value"`
Description string `json:"description"`
}
func (upsert SystemSettingUpsert) Validate() error {
if upsert.Name == SystemSettingAllowSignUpName {
value := false
err := json.Unmarshal([]byte(upsert.Value), &value)
if err != nil {
return fmt.Errorf("failed to unmarshal system setting allow signup value")
}
invalid := true
for _, v := range SystemSettingAllowSignUpValue {
if value == v {
invalid = false
break
}
}
if invalid {
return fmt.Errorf("invalid system setting allow signup value")
}
} else {
return fmt.Errorf("invalid system setting name")
}
return nil
}
type SystemSettingFind struct {
Name *SystemSettingName `json:"name"`
}
...@@ -45,7 +45,7 @@ func run(profile *profile.Profile) error { ...@@ -45,7 +45,7 @@ func run(profile *profile.Profile) error {
println(greetingBanner) println(greetingBanner)
fmt.Printf("Version %s has started at :%d\n", profile.Version, profile.Port) fmt.Printf("Version %s has started at :%d\n", profile.Version, profile.Port)
metricCollector.Collect(ctx, &metric.Metric{ metricCollector.Collect(ctx, &metric.Metric{
Name: "servive started", Name: "service started",
}) })
return serverInstance.Run() return serverInstance.Run()
......
...@@ -8,7 +8,9 @@ import ( ...@@ -8,7 +8,9 @@ import (
metric "github.com/usememos/memos/plugin/metrics" metric "github.com/usememos/memos/plugin/metrics"
) )
var _ metric.Collector = (*collector)(nil) var (
sessionUUID = uuid.NewString()
)
// collector is the metrics collector https://segment.com/. // collector is the metrics collector https://segment.com/.
type collector struct { type collector struct {
...@@ -33,7 +35,7 @@ func (c *collector) Collect(metric *metric.Metric) error { ...@@ -33,7 +35,7 @@ func (c *collector) Collect(metric *metric.Metric) error {
return c.client.Enqueue(analytics.Track{ return c.client.Enqueue(analytics.Track{
Event: string(metric.Name), Event: string(metric.Name),
AnonymousId: uuid.NewString(), AnonymousId: sessionUUID,
Properties: properties, Properties: properties,
Timestamp: time.Now().UTC(), Timestamp: time.Now().UTC(),
}) })
......
...@@ -70,7 +70,11 @@ func (s *Server) registerAuthRoutes(g *echo.Group) { ...@@ -70,7 +70,11 @@ func (s *Server) registerAuthRoutes(g *echo.Group) {
g.POST("/auth/signup", func(c echo.Context) error { g.POST("/auth/signup", func(c echo.Context) error {
ctx := c.Request().Context() ctx := c.Request().Context()
// Don't allow to signup by this api if site host existed. signup := &api.Signup{}
if err := json.NewDecoder(c.Request().Body).Decode(signup); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Malformatted signup request").SetInternal(err)
}
hostUserType := api.Host hostUserType := api.Host
hostUserFind := api.UserFind{ hostUserFind := api.UserFind{
Role: &hostUserType, Role: &hostUserType,
...@@ -79,13 +83,27 @@ func (s *Server) registerAuthRoutes(g *echo.Group) { ...@@ -79,13 +83,27 @@ func (s *Server) registerAuthRoutes(g *echo.Group) {
if err != nil && common.ErrorCode(err) != common.NotFound { if err != nil && common.ErrorCode(err) != common.NotFound {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find host user").SetInternal(err) return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find host user").SetInternal(err)
} }
if hostUser != nil { if signup.Role == api.Host && hostUser != nil {
return echo.NewHTTPError(http.StatusUnauthorized, "Site Host existed, please contact the site host to signin account firstly.").SetInternal(err) return echo.NewHTTPError(http.StatusUnauthorized, "Site Host existed, please contact the site host to signin account firstly.").SetInternal(err)
} }
signup := &api.Signup{} systemSettingAllowSignUpName := api.SystemSettingAllowSignUpName
if err := json.NewDecoder(c.Request().Body).Decode(signup); err != nil { allowSignUpSetting, err := s.Store.FindSystemSetting(ctx, &api.SystemSettingFind{
return echo.NewHTTPError(http.StatusBadRequest, "Malformatted signup request").SetInternal(err) Name: &systemSettingAllowSignUpName,
})
if err != nil && common.ErrorCode(err) != common.NotFound {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find system setting").SetInternal(err)
}
allowSignUpSettingValue := false
if allowSignUpSetting != nil {
err = json.Unmarshal([]byte(allowSignUpSetting.Value), &allowSignUpSettingValue)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to unmarshal system setting allow signup").SetInternal(err)
}
}
if !allowSignUpSettingValue && hostUser != nil {
return echo.NewHTTPError(http.StatusUnauthorized, "Site Host existed, please contact the site host to signin account firstly.").SetInternal(err)
} }
userCreate := &api.UserCreate{ userCreate := &api.UserCreate{
......
...@@ -38,8 +38,25 @@ func (s *Server) registerSystemRoutes(g *echo.Group) { ...@@ -38,8 +38,25 @@ func (s *Server) registerSystemRoutes(g *echo.Group) {
} }
systemStatus := api.SystemStatus{ systemStatus := api.SystemStatus{
Host: hostUser, Host: hostUser,
Profile: s.Profile, Profile: s.Profile,
AllowSignUp: false,
}
systemSettingList, err := s.Store.FindSystemSettingList(ctx, &api.SystemSettingFind{})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find system setting list").SetInternal(err)
}
for _, systemSetting := range systemSettingList {
var value interface{}
err = json.Unmarshal([]byte(systemSetting.Value), &value)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to unmarshal system setting").SetInternal(err)
}
if systemSetting.Name == api.SystemSettingAllowSignUpName {
systemStatus.AllowSignUp = value.(bool)
}
} }
c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSONCharsetUTF8) c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSONCharsetUTF8)
...@@ -48,4 +65,57 @@ func (s *Server) registerSystemRoutes(g *echo.Group) { ...@@ -48,4 +65,57 @@ func (s *Server) registerSystemRoutes(g *echo.Group) {
} }
return nil return nil
}) })
g.POST("/system/setting", func(c echo.Context) error {
ctx := c.Request().Context()
userID, ok := c.Get(getUserIDContextKey()).(int)
if !ok {
return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
}
user, err := s.Store.FindUser(ctx, &api.UserFind{
ID: &userID,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
}
if user == nil {
return echo.NewHTTPError(http.StatusNotFound, "Current signin user not found")
} else if user.Role != api.Host {
return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
}
systemSettingUpsert := &api.SystemSettingUpsert{}
if err := json.NewDecoder(c.Request().Body).Decode(systemSettingUpsert); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post system setting request").SetInternal(err)
}
if err := systemSettingUpsert.Validate(); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "system setting invalidate").SetInternal(err)
}
systemSetting, err := s.Store.UpsertSystemSetting(ctx, systemSettingUpsert)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to upsert system setting").SetInternal(err)
}
c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSONCharsetUTF8)
if err := json.NewEncoder(c.Response().Writer).Encode(composeResponse(systemSetting)); err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to encode system setting response").SetInternal(err)
}
return nil
})
g.GET("/system/setting", func(c echo.Context) error {
ctx := c.Request().Context()
systemSettingList, err := s.Store.FindSystemSettingList(ctx, &api.SystemSettingFind{})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find system setting list").SetInternal(err)
}
c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSONCharsetUTF8)
if err := json.NewEncoder(c.Response().Writer).Encode(composeResponse(systemSettingList)); err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to encode system setting list response").SetInternal(err)
}
return nil
})
} }
INSERT INTO
system_setting (
`name`,
`value`,
`description`
)
VALUES
(
'allowSignUp',
'true',
''
);
package store
import (
"context"
"database/sql"
"fmt"
"strings"
"github.com/usememos/memos/api"
"github.com/usememos/memos/common"
)
type systemSettingRaw struct {
Name api.SystemSettingName
Value string
Description string
}
func (raw *systemSettingRaw) toSystemSetting() *api.SystemSetting {
return &api.SystemSetting{
Name: raw.Name,
Value: raw.Value,
Description: raw.Description,
}
}
func (s *Store) UpsertSystemSetting(ctx context.Context, upsert *api.SystemSettingUpsert) (*api.SystemSetting, error) {
tx, err := s.db.BeginTx(ctx, nil)
if err != nil {
return nil, FormatError(err)
}
defer tx.Rollback()
systemSettingRaw, err := upsertSystemSetting(ctx, tx, upsert)
if err != nil {
return nil, err
}
if err := tx.Commit(); err != nil {
return nil, err
}
systemSetting := systemSettingRaw.toSystemSetting()
return systemSetting, nil
}
func (s *Store) FindSystemSettingList(ctx context.Context, find *api.SystemSettingFind) ([]*api.SystemSetting, error) {
tx, err := s.db.BeginTx(ctx, nil)
if err != nil {
return nil, FormatError(err)
}
defer tx.Rollback()
systemSettingRawList, err := findSystemSettingList(ctx, tx, find)
if err != nil {
return nil, err
}
if err := tx.Commit(); err != nil {
return nil, err
}
list := []*api.SystemSetting{}
for _, raw := range systemSettingRawList {
list = append(list, raw.toSystemSetting())
}
return list, nil
}
func (s *Store) FindSystemSetting(ctx context.Context, find *api.SystemSettingFind) (*api.SystemSetting, error) {
tx, err := s.db.BeginTx(ctx, nil)
if err != nil {
return nil, FormatError(err)
}
defer tx.Rollback()
systemSettingRawList, err := findSystemSettingList(ctx, tx, find)
if err != nil {
return nil, err
}
if len(systemSettingRawList) == 0 {
return nil, &common.Error{Code: common.NotFound, Err: fmt.Errorf("not found")}
}
return systemSettingRawList[0].toSystemSetting(), nil
}
func upsertSystemSetting(ctx context.Context, tx *sql.Tx, upsert *api.SystemSettingUpsert) (*systemSettingRaw, error) {
query := `
INSERT INTO system_setting (
name, value, description
)
VALUES (?, ?, ?)
ON CONFLICT(name) DO UPDATE
SET
value = EXCLUDED.value,
description = EXCLUDED.description
RETURNING name, value, description
`
var systemSettingRaw systemSettingRaw
if err := tx.QueryRowContext(ctx, query, upsert.Name, upsert.Value, upsert.Description).Scan(
&systemSettingRaw.Name,
&systemSettingRaw.Value,
&systemSettingRaw.Description,
); err != nil {
return nil, FormatError(err)
}
return &systemSettingRaw, nil
}
func findSystemSettingList(ctx context.Context, tx *sql.Tx, find *api.SystemSettingFind) ([]*systemSettingRaw, error) {
where, args := []string{"1 = 1"}, []interface{}{}
if v := find.Name; v != nil {
where, args = append(where, "name = ?"), append(args, v.String())
}
query := `
SELECT
name,
value,
description
FROM system_setting
WHERE ` + strings.Join(where, " AND ")
rows, err := tx.QueryContext(ctx, query, args...)
if err != nil {
return nil, FormatError(err)
}
defer rows.Close()
systemSettingRawList := make([]*systemSettingRaw, 0)
for rows.Next() {
var systemSettingRaw systemSettingRaw
if err := rows.Scan(
&systemSettingRaw.Name,
&systemSettingRaw.Value,
&systemSettingRaw.Description,
); err != nil {
return nil, FormatError(err)
}
systemSettingRawList = append(systemSettingRawList, &systemSettingRaw)
}
if err := rows.Err(); err != nil {
return nil, FormatError(err)
}
return systemSettingRawList, nil
}
...@@ -155,8 +155,6 @@ func (s *Store) FindUser(ctx context.Context, find *api.UserFind) (*api.User, er ...@@ -155,8 +155,6 @@ func (s *Store) FindUser(ctx context.Context, find *api.UserFind) (*api.User, er
if len(list) == 0 { if len(list) == 0 {
return nil, &common.Error{Code: common.NotFound, Err: fmt.Errorf("not found user with filter %+v", find)} return nil, &common.Error{Code: common.NotFound, Err: fmt.Errorf("not found user with filter %+v", find)}
} else if len(list) > 1 {
return nil, &common.Error{Code: common.Conflict, Err: fmt.Errorf("found %d users with filter %+v, expect 1", len(list), find)}
} }
userRaw := list[0] userRaw := list[0]
......
...@@ -8,6 +8,9 @@ ...@@ -8,6 +8,9 @@
"test": "jest --passWithNoTests" "test": "jest --passWithNoTests"
}, },
"dependencies": { "dependencies": {
"@emotion/react": "^11.10.5",
"@emotion/styled": "^11.10.5",
"@mui/joy": "^5.0.0-alpha.52",
"@reduxjs/toolkit": "^1.8.1", "@reduxjs/toolkit": "^1.8.1",
"axios": "^0.27.2", "axios": "^0.27.2",
"copy-to-clipboard": "^3.3.2", "copy-to-clipboard": "^3.3.2",
......
import { CssVarsProvider } from "@mui/joy/styles";
import { useEffect } from "react"; import { useEffect } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { RouterProvider } from "react-router-dom"; import { RouterProvider } from "react-router-dom";
...@@ -26,7 +27,11 @@ function App() { ...@@ -26,7 +27,11 @@ function App() {
}); });
}, [global.locale]); }, [global.locale]);
return <RouterProvider router={router} />; return (
<CssVarsProvider>
<RouterProvider router={router} />
</CssVarsProvider>
);
} }
export default App; export default App;
...@@ -6,11 +6,12 @@ import { generateDialog } from "./Dialog"; ...@@ -6,11 +6,12 @@ import { generateDialog } from "./Dialog";
import MyAccountSection from "./Settings/MyAccountSection"; import MyAccountSection from "./Settings/MyAccountSection";
import PreferencesSection from "./Settings/PreferencesSection"; import PreferencesSection from "./Settings/PreferencesSection";
import MemberSection from "./Settings/MemberSection"; import MemberSection from "./Settings/MemberSection";
import SystemSection from "./Settings/SystemSection";
import "../less/setting-dialog.less"; import "../less/setting-dialog.less";
type Props = DialogProps; type Props = DialogProps;
type SettingSection = "my-account" | "preferences" | "member"; type SettingSection = "my-account" | "preferences" | "member" | "system";
interface State { interface State {
selectedSection: SettingSection; selectedSection: SettingSection;
...@@ -61,6 +62,12 @@ const SettingDialog: React.FC<Props> = (props: Props) => { ...@@ -61,6 +62,12 @@ const SettingDialog: React.FC<Props> = (props: Props) => {
> >
<span className="icon-text">👤</span> {t("setting.member")} <span className="icon-text">👤</span> {t("setting.member")}
</span> </span>
<span
onClick={() => handleSectionSelectorItemClick("system")}
className={`section-item ${state.selectedSection === "system" ? "selected" : ""}`}
>
<span className="icon-text">🧑‍🔧</span> System Setting
</span>
</div> </div>
</> </>
) : null} ) : null}
...@@ -72,6 +79,8 @@ const SettingDialog: React.FC<Props> = (props: Props) => { ...@@ -72,6 +79,8 @@ const SettingDialog: React.FC<Props> = (props: Props) => {
<PreferencesSection /> <PreferencesSection />
) : state.selectedSection === "member" ? ( ) : state.selectedSection === "member" ? (
<MemberSection /> <MemberSection />
) : state.selectedSection === "system" ? (
<SystemSection />
) : null} ) : null}
</div> </div>
</div> </div>
......
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import Switch from "@mui/joy/Switch";
import * as api from "../../helpers/api";
import { globalService, userService } from "../../services";
import Selector from "../common/Selector";
import "../../less/settings/preferences-section.less";
const localeSelectorItems = [
{
text: "English",
value: "en",
},
{
text: "中文",
value: "zh",
},
{
text: "Tiếng Việt",
value: "vi",
},
];
interface State {
allowSignUp: boolean;
}
const SystemSection = () => {
const { t } = useTranslation();
const [state, setState] = useState<State>({
allowSignUp: false,
});
useEffect(() => {
api.getSystemStatus().then(({ data }) => {
const { data: status } = data;
setState({
allowSignUp: status.allowSignUp,
});
});
}, []);
const handleAllowSignUpChanged = async (value: boolean) => {
setState({
...state,
allowSignUp: value,
});
await api.upsertSystemSetting({
name: "allowSignUp",
value: JSON.stringify(value),
});
};
return (
<div className="section-container preferences-section-container">
<p className="title-text">{t("common.basic")}</p>
<label className="form-label selector">
<span className="normal-text">Allow user signUp</span>
<Switch size="sm" checked={state.allowSignUp} onChange={(event) => handleAllowSignUpChanged(event.target.checked)} />
</label>
</div>
);
};
export default SystemSection;
...@@ -10,6 +10,10 @@ export function getSystemStatus() { ...@@ -10,6 +10,10 @@ export function getSystemStatus() {
return axios.get<ResponseObject<SystemStatus>>("/api/status"); return axios.get<ResponseObject<SystemStatus>>("/api/status");
} }
export function upsertSystemSetting(systemSetting: SystemSetting) {
return axios.post<ResponseObject<SystemSetting>>("/api/system/setting", systemSetting);
}
export function signin(email: string, password: string) { export function signin(email: string, password: string) {
return axios.post<ResponseObject<User>>("/api/auth/signin", { return axios.post<ResponseObject<User>>("/api/auth/signin", {
email, email,
......
...@@ -59,6 +59,10 @@ ...@@ -59,6 +59,10 @@
> .btn { > .btn {
@apply flex flex-row justify-center items-center px-1 py-2 text-sm rounded hover:opacity-80; @apply flex flex-row justify-center items-center px-1 py-2 text-sm rounded hover:opacity-80;
&.signup-btn {
@apply px-3;
}
&.signin-btn { &.signin-btn {
@apply bg-green-600 text-white px-3 shadow; @apply bg-green-600 text-white px-3 shadow;
} }
......
...@@ -28,6 +28,7 @@ ...@@ -28,6 +28,7 @@
"admin": "Admin", "admin": "Admin",
"explore": "Explore", "explore": "Explore",
"sign-in": "Sign in", "sign-in": "Sign in",
"sign-up": "Sign up",
"sign-out": "Sign out", "sign-out": "Sign out",
"back-to-home": "Back to Home", "back-to-home": "Back to Home",
"type": "Type", "type": "Type",
......
...@@ -28,6 +28,7 @@ ...@@ -28,6 +28,7 @@
"admin": "Admin", "admin": "Admin",
"explore": "Khám phá", "explore": "Khám phá",
"sign-in": "Đăng nhập", "sign-in": "Đăng nhập",
"sign-up": "Sign up",
"sign-out": "Đăng xuất", "sign-out": "Đăng xuất",
"back-to-home": "Về trang chủ", "back-to-home": "Về trang chủ",
"type": "Kiểu", "type": "Kiểu",
...@@ -173,4 +174,4 @@ ...@@ -173,4 +174,4 @@
"resource-filename-updated": "Tên tệp tài nguyên đã thay đổi.", "resource-filename-updated": "Tên tệp tài nguyên đã thay đổi.",
"invalid-resource-filename": "Tên tệp không hợp lệ." "invalid-resource-filename": "Tên tệp không hợp lệ."
} }
} }
\ No newline at end of file
...@@ -28,6 +28,7 @@ ...@@ -28,6 +28,7 @@
"admin": "管理员", "admin": "管理员",
"explore": "探索", "explore": "探索",
"sign-in": "登录", "sign-in": "登录",
"sign-up": "注册",
"sign-out": "退出登录", "sign-out": "退出登录",
"back-to-home": "回到主页", "back-to-home": "回到主页",
"type": "类型", "type": "类型",
......
...@@ -5,7 +5,6 @@ import * as api from "../helpers/api"; ...@@ -5,7 +5,6 @@ import * as api from "../helpers/api";
import { validate, ValidatorConfig } from "../helpers/validator"; import { validate, ValidatorConfig } from "../helpers/validator";
import useLoading from "../hooks/useLoading"; import useLoading from "../hooks/useLoading";
import { globalService, userService } from "../services"; import { globalService, userService } from "../services";
import Icon from "../components/Icon";
import toastHelper from "../components/Toast"; import toastHelper from "../components/Toast";
import "../less/auth.less"; import "../less/auth.less";
...@@ -20,7 +19,7 @@ const Auth = () => { ...@@ -20,7 +19,7 @@ const Auth = () => {
const { t, i18n } = useTranslation(); const { t, i18n } = useTranslation();
const navigate = useNavigate(); const navigate = useNavigate();
const pageLoadingState = useLoading(true); const pageLoadingState = useLoading(true);
const [siteHost, setSiteHost] = useState<User>(); const [systemStatus, setSystemStatus] = useState<SystemStatus>();
const [email, setEmail] = useState(""); const [email, setEmail] = useState("");
const [password, setPassword] = useState(""); const [password, setPassword] = useState("");
const actionBtnLoadingState = useLoading(false); const actionBtnLoadingState = useLoading(false);
...@@ -28,7 +27,7 @@ const Auth = () => { ...@@ -28,7 +27,7 @@ const Auth = () => {
useEffect(() => { useEffect(() => {
api.getSystemStatus().then(({ data }) => { api.getSystemStatus().then(({ data }) => {
const { data: status } = data; const { data: status } = data;
setSiteHost(status.host); setSystemStatus(status);
if (status.profile.mode === "dev") { if (status.profile.mode === "dev") {
setEmail("demo@usememos.com"); setEmail("demo@usememos.com");
setPassword("secret"); setPassword("secret");
...@@ -47,9 +46,7 @@ const Auth = () => { ...@@ -47,9 +46,7 @@ const Auth = () => {
setPassword(text); setPassword(text);
}; };
const handleSigninBtnsClick = async (e: React.FormEvent<EventTarget>) => { const handleSigninBtnsClick = async () => {
e.preventDefault();
if (actionBtnLoadingState.isLoading) { if (actionBtnLoadingState.isLoading) {
return; return;
} }
...@@ -77,14 +74,12 @@ const Auth = () => { ...@@ -77,14 +74,12 @@ const Auth = () => {
} }
} catch (error: any) { } catch (error: any) {
console.error(error); console.error(error);
toastHelper.error(error.response.data.message); toastHelper.error(error.response.data.error);
} }
actionBtnLoadingState.setFinish(); actionBtnLoadingState.setFinish();
}; };
const handleSignUpAsHostBtnsClick = async (e: React.FormEvent<EventTarget>) => { const handleSignUpBtnsClick = async (role: UserRole) => {
e.preventDefault();
if (actionBtnLoadingState.isLoading) { if (actionBtnLoadingState.isLoading) {
return; return;
} }
...@@ -103,7 +98,7 @@ const Auth = () => { ...@@ -103,7 +98,7 @@ const Auth = () => {
try { try {
actionBtnLoadingState.setLoading(); actionBtnLoadingState.setLoading();
await api.signup(email, password, "HOST"); await api.signup(email, password, role);
const user = await userService.doSignIn(); const user = await userService.doSignIn();
if (user) { if (user) {
navigate("/"); navigate("/");
...@@ -112,7 +107,7 @@ const Auth = () => { ...@@ -112,7 +107,7 @@ const Auth = () => {
} }
} catch (error: any) { } catch (error: any) {
console.error(error); console.error(error);
toastHelper.error(error.response.data.message); toastHelper.error(error.response.data.error);
} }
actionBtnLoadingState.setFinish(); actionBtnLoadingState.setFinish();
}; };
...@@ -124,7 +119,7 @@ const Auth = () => { ...@@ -124,7 +119,7 @@ const Auth = () => {
return ( return (
<div className="page-wrapper auth"> <div className="page-wrapper auth">
<div className="page-container"> <div className="page-container">
<form className="auth-form-wrapper" onSubmit={(e) => (siteHost ? handleSigninBtnsClick(e) : handleSignUpAsHostBtnsClick(e))}> <div className="auth-form-wrapper">
<div className="page-header-container"> <div className="page-header-container">
<div className="title-container"> <div className="title-container">
<img className="logo-img" src="/logo-full.webp" alt="" /> <img className="logo-img" src="/logo-full.webp" alt="" />
...@@ -143,16 +138,39 @@ const Auth = () => { ...@@ -143,16 +138,39 @@ const Auth = () => {
</div> </div>
<div className="action-btns-container"> <div className="action-btns-container">
{!pageLoadingState.isLoading && ( {!pageLoadingState.isLoading && (
<button className={`btn signin-btn ${actionBtnLoadingState.isLoading ? "requesting" : ""}`} type="submit"> <>
{actionBtnLoadingState.isLoading && <Icon.Loader className="img-icon" />} {systemStatus?.host ? (
{siteHost ? t("common.sign-in") : t("auth.signup-as-host")} <>
</button> {systemStatus?.allowSignUp && (
<button
className={`btn signup-btn ${actionBtnLoadingState.isLoading ? "requesting" : ""}`}
onClick={() => handleSignUpBtnsClick("USER")}
>
{t("common.sign-up")}
</button>
)}
<button
className={`btn signin-btn ${actionBtnLoadingState.isLoading ? "requesting" : ""}`}
onClick={handleSigninBtnsClick}
>
{t("common.sign-in")}
</button>
</>
) : (
<>
<button
className={`btn signin-btn ${actionBtnLoadingState.isLoading ? "requesting" : ""}`}
onClick={() => handleSignUpBtnsClick("HOST")}
>
{t("auth.signup-as-host")}
</button>
</>
)}
</>
)} )}
</div> </div>
<p className={`tip-text ${siteHost || pageLoadingState.isLoading ? "" : "host-tip"}`}> {!systemStatus?.host && <p className="tip-text">{t("auth.host-tip")}</p>}
{siteHost || pageLoadingState.isLoading ? t("auth.not-host-tip") : t("auth.host-tip")} </div>
</p>
</form>
<div className="footer-container"> <div className="footer-container">
<div className="language-container"> <div className="language-container">
<span className={`locale-item ${i18n.language === "en" ? "active" : ""}`} onClick={() => handleLocaleItemClick("en")}> <span className={`locale-item ${i18n.language === "en" ? "active" : ""}`} onClick={() => handleLocaleItemClick("en")}>
......
...@@ -6,4 +6,11 @@ interface Profile { ...@@ -6,4 +6,11 @@ interface Profile {
interface SystemStatus { interface SystemStatus {
host: User; host: User;
profile: Profile; profile: Profile;
// System settings
allowSignUp: boolean;
}
interface SystemSetting {
name: string;
value: string;
} }
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment