mirror of
https://github.com/usememos/memos.git
synced 2025-02-14 10:20:49 +01:00
feat: customize system profile (#828)
This commit is contained in:
parent
72daa4e1d6
commit
7efa749c66
@ -3,6 +3,8 @@ package api
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
type SystemSettingName string
|
||||
@ -24,6 +26,12 @@ type CustomizedProfile struct {
|
||||
Name string `json:"name"`
|
||||
// IconURL is the url of icon image.
|
||||
IconURL string `json:"iconUrl"`
|
||||
// Description is the server description.
|
||||
Description string `json:"description"`
|
||||
// Locale is the server default locale.
|
||||
Locale string `json:"locale"`
|
||||
// Appearance is the server default appearance.
|
||||
Appearance string `json:"appearance"`
|
||||
// ExternalURL is the external url of server. e.g. https://usermemos.com
|
||||
ExternalURL string `json:"externalUrl"`
|
||||
}
|
||||
@ -90,15 +98,24 @@ func (upsert SystemSettingUpsert) Validate() error {
|
||||
return fmt.Errorf("failed to unmarshal system setting additional script value")
|
||||
}
|
||||
} else if upsert.Name == SystemSettingCustomizedProfileName {
|
||||
value := CustomizedProfile{
|
||||
customizedProfile := CustomizedProfile{
|
||||
Name: "memos",
|
||||
IconURL: "",
|
||||
Description: "",
|
||||
Locale: "en",
|
||||
Appearance: "system",
|
||||
ExternalURL: "",
|
||||
}
|
||||
err := json.Unmarshal([]byte(upsert.Value), &value)
|
||||
err := json.Unmarshal([]byte(upsert.Value), &customizedProfile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unmarshal system setting customized profile value")
|
||||
}
|
||||
if !slices.Contains(UserSettingLocaleValue, customizedProfile.Locale) {
|
||||
return fmt.Errorf("invalid locale value")
|
||||
}
|
||||
if !slices.Contains(UserSettingAppearanceValue, customizedProfile.Appearance) {
|
||||
return fmt.Errorf("invalid appearance value")
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("invalid system setting name")
|
||||
}
|
||||
|
@ -3,6 +3,8 @@ package api
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
type UserSettingKey string
|
||||
@ -60,32 +62,16 @@ func (upsert UserSettingUpsert) Validate() error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unmarshal user setting locale value")
|
||||
}
|
||||
|
||||
invalid := true
|
||||
for _, value := range UserSettingLocaleValue {
|
||||
if localeValue == value {
|
||||
invalid = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if invalid {
|
||||
if !slices.Contains(UserSettingLocaleValue, localeValue) {
|
||||
return fmt.Errorf("invalid user setting locale value")
|
||||
}
|
||||
} else if upsert.Key == UserSettingAppearanceKey {
|
||||
appearanceValue := "light"
|
||||
appearanceValue := "system"
|
||||
err := json.Unmarshal([]byte(upsert.Value), &appearanceValue)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unmarshal user setting appearance value")
|
||||
}
|
||||
|
||||
invalid := true
|
||||
for _, value := range UserSettingAppearanceValue {
|
||||
if appearanceValue == value {
|
||||
invalid = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if invalid {
|
||||
if !slices.Contains(UserSettingAppearanceValue, appearanceValue) {
|
||||
return fmt.Errorf("invalid user setting appearance value")
|
||||
}
|
||||
} else if upsert.Key == UserSettingMemoVisibilityKey {
|
||||
@ -94,15 +80,7 @@ func (upsert UserSettingUpsert) Validate() error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unmarshal user setting memo visibility value")
|
||||
}
|
||||
|
||||
invalid := true
|
||||
for _, value := range UserSettingMemoVisibilityValue {
|
||||
if memoVisibilityValue == value {
|
||||
invalid = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if invalid {
|
||||
if !slices.Contains(UserSettingMemoVisibilityValue, memoVisibilityValue) {
|
||||
return fmt.Errorf("invalid user setting memo visibility value")
|
||||
}
|
||||
} else if upsert.Key == UserSettingMemoDisplayTsOptionKey {
|
||||
@ -111,15 +89,7 @@ func (upsert UserSettingUpsert) Validate() error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unmarshal user setting memo display ts option")
|
||||
}
|
||||
|
||||
invalid := true
|
||||
for _, value := range UserSettingMemoDisplayTsOptionKeyValue {
|
||||
if memoDisplayTsOption == value {
|
||||
invalid = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if invalid {
|
||||
if !slices.Contains(UserSettingMemoDisplayTsOptionKeyValue, memoDisplayTsOption) {
|
||||
return fmt.Errorf("invalid user setting memo display ts option value")
|
||||
}
|
||||
} else {
|
||||
|
7
go.mod
7
go.mod
@ -38,11 +38,14 @@ require (
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasttemplate v1.2.1 // indirect
|
||||
github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c // indirect
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect
|
||||
golang.org/x/sys v0.1.0 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
require github.com/segmentio/analytics-go v3.1.0+incompatible
|
||||
require (
|
||||
github.com/segmentio/analytics-go v3.1.0+incompatible
|
||||
golang.org/x/exp v0.0.0-20221217163422-3c43f8badb15
|
||||
)
|
||||
|
6
go.sum
6
go.sum
@ -70,14 +70,16 @@ github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c h1:3lbZUMbMiGUW/LMkfsEAB
|
||||
github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c/go.mod h1:UrdRz5enIKZ63MEE3IF9l2/ebyx59GyGgPi+tICQdmM=
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c=
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/exp v0.0.0-20221217163422-3c43f8badb15 h1:5oN1Pz/eDhCpbMbLstvIPa0b/BEQo6g6nwV3pLjfM6w=
|
||||
golang.org/x/exp v0.0.0-20221217163422-3c43f8badb15/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||
golang.org/x/net v0.0.0-20220728030405-41545e8bf201 h1:bvOltf3SADAfG05iRml8lAB3qjoEX5RCyN4K6G5v3N0=
|
||||
golang.org/x/net v0.0.0-20220728030405-41545e8bf201/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220405052023-b1e9470b6e64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg=
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 h1:ftMN5LMiBFjbzleLqtoBZk7KdJwhuybIU+FckUHgoyQ=
|
||||
|
@ -27,6 +27,7 @@ func setUserSession(ctx echo.Context, user *api.User) error {
|
||||
Path: "/",
|
||||
MaxAge: 3600 * 24 * 30,
|
||||
HttpOnly: true,
|
||||
Secure: true,
|
||||
}
|
||||
sess.Values[userIDContextKey] = user.ID
|
||||
err := sess.Save(ctx.Request(), ctx.Response())
|
||||
|
@ -47,7 +47,12 @@ func (s *Server) registerSystemRoutes(g *echo.Group) {
|
||||
AdditionalStyle: "",
|
||||
AdditionalScript: "",
|
||||
CustomizedProfile: api.CustomizedProfile{
|
||||
Name: "memos",
|
||||
Name: "memos",
|
||||
IconURL: "",
|
||||
Description: "",
|
||||
Locale: "en",
|
||||
Appearance: "system",
|
||||
ExternalURL: "",
|
||||
},
|
||||
}
|
||||
|
||||
@ -73,6 +78,9 @@ func (s *Server) registerSystemRoutes(g *echo.Group) {
|
||||
systemStatus.CustomizedProfile = api.CustomizedProfile{
|
||||
Name: valueMap["name"].(string),
|
||||
IconURL: valueMap["iconUrl"].(string),
|
||||
Description: valueMap["description"].(string),
|
||||
Locale: valueMap["locale"].(string),
|
||||
Appearance: valueMap["appearance"].(string),
|
||||
ExternalURL: valueMap["externalUrl"].(string),
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,19 @@
|
||||
import { Option, Select } from "@mui/joy";
|
||||
import { FC } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useGlobalStore, useUserStore } from "../store/module";
|
||||
import Icon from "./Icon";
|
||||
|
||||
interface Props {
|
||||
value: Appearance;
|
||||
onChange: (appearance: Appearance) => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const appearanceList = ["system", "light", "dark"];
|
||||
|
||||
const AppearanceSelect = () => {
|
||||
const AppearanceSelect: FC<Props> = (props: Props) => {
|
||||
const { onChange, value, className } = props;
|
||||
const { t } = useTranslation();
|
||||
const globalStore = useGlobalStore();
|
||||
const userStore = useUserStore();
|
||||
const { appearance } = globalStore.state;
|
||||
const user = userStore.state.user;
|
||||
|
||||
const getPrefixIcon = (apperance: Appearance) => {
|
||||
const className = "w-4 h-auto";
|
||||
@ -24,22 +27,19 @@ const AppearanceSelect = () => {
|
||||
};
|
||||
|
||||
const handleSelectChange = async (appearance: Appearance) => {
|
||||
if (user) {
|
||||
await userStore.upsertUserSetting("appearance", appearance);
|
||||
}
|
||||
globalStore.setAppearance(appearance);
|
||||
onChange(appearance);
|
||||
};
|
||||
|
||||
return (
|
||||
<Select
|
||||
className="!min-w-[10rem] w-auto text-sm"
|
||||
value={appearance}
|
||||
className={`!min-w-[10rem] w-auto whitespace-nowrap ${className ?? ""}`}
|
||||
value={value}
|
||||
onChange={(_, appearance) => {
|
||||
if (appearance) {
|
||||
handleSelectChange(appearance);
|
||||
}
|
||||
}}
|
||||
startDecorator={getPrefixIcon(appearance)}
|
||||
startDecorator={getPrefixIcon(value)}
|
||||
>
|
||||
{appearanceList.map((item) => (
|
||||
<Option key={item} value={item} className="whitespace-nowrap">
|
||||
|
37
web/src/components/LocaleSelect.tsx
Normal file
37
web/src/components/LocaleSelect.tsx
Normal file
@ -0,0 +1,37 @@
|
||||
import { Option, Select } from "@mui/joy";
|
||||
import { FC } from "react";
|
||||
import Icon from "./Icon";
|
||||
|
||||
interface Props {
|
||||
value: Locale;
|
||||
onChange: (locale: Locale) => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const LocaleSelect: FC<Props> = (props: Props) => {
|
||||
const { onChange, value, className } = props;
|
||||
|
||||
const handleSelectChange = async (locale: Locale) => {
|
||||
onChange(locale);
|
||||
};
|
||||
|
||||
return (
|
||||
<Select
|
||||
className={`!min-w-[10rem] w-auto whitespace-nowrap ${className ?? ""}`}
|
||||
startDecorator={<Icon.Globe className="w-4 h-auto" />}
|
||||
value={value}
|
||||
onChange={(_, value) => handleSelectChange(value as Locale)}
|
||||
>
|
||||
<Option value="en">English</Option>
|
||||
<Option value="zh">中文</Option>
|
||||
<Option value="vi">Tiếng Việt</Option>
|
||||
<Option value="fr">French</Option>
|
||||
<Option value="nl">Nederlands</Option>
|
||||
<Option value="sv">Svenska</Option>
|
||||
<Option value="de">German</Option>
|
||||
<Option value="es">Español</Option>
|
||||
</Select>
|
||||
);
|
||||
};
|
||||
|
||||
export default LocaleSelect;
|
@ -2,49 +2,15 @@ import { Select, Switch, Option } from "@mui/joy";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useGlobalStore, useUserStore } from "../../store/module";
|
||||
import { VISIBILITY_SELECTOR_ITEMS, MEMO_DISPLAY_TS_OPTION_SELECTOR_ITEMS } from "../../helpers/consts";
|
||||
import Icon from "../Icon";
|
||||
import AppearanceSelect from "../AppearanceSelect";
|
||||
import LocaleSelect from "../LocaleSelect";
|
||||
import "../../less/settings/preferences-section.less";
|
||||
|
||||
const localeSelectorItems = [
|
||||
{
|
||||
text: "English",
|
||||
value: "en",
|
||||
},
|
||||
{
|
||||
text: "中文",
|
||||
value: "zh",
|
||||
},
|
||||
{
|
||||
text: "Tiếng Việt",
|
||||
value: "vi",
|
||||
},
|
||||
{
|
||||
text: "French",
|
||||
value: "fr",
|
||||
},
|
||||
{
|
||||
text: "Nederlands",
|
||||
value: "nl",
|
||||
},
|
||||
{
|
||||
text: "Svenska",
|
||||
value: "sv",
|
||||
},
|
||||
{
|
||||
text: "German",
|
||||
value: "de",
|
||||
},
|
||||
{
|
||||
text: "Español",
|
||||
value: "es",
|
||||
},
|
||||
];
|
||||
|
||||
const PreferencesSection = () => {
|
||||
const { t } = useTranslation();
|
||||
const globalStore = useGlobalStore();
|
||||
const userStore = useUserStore();
|
||||
const { appearance, locale } = globalStore.state;
|
||||
const { setting, localSetting } = userStore.state.user as User;
|
||||
const visibilitySelectorItems = VISIBILITY_SELECTOR_ITEMS.map((item) => {
|
||||
return {
|
||||
@ -60,9 +26,14 @@ const PreferencesSection = () => {
|
||||
};
|
||||
});
|
||||
|
||||
const handleLocaleChanged = async (value: string) => {
|
||||
await userStore.upsertUserSetting("locale", value);
|
||||
globalStore.setLocale(value as Locale);
|
||||
const handleLocaleSelectChange = async (locale: Locale) => {
|
||||
await userStore.upsertUserSetting("locale", locale);
|
||||
globalStore.setLocale(locale);
|
||||
};
|
||||
|
||||
const handleAppearanceSelectChange = async (appearance: Appearance) => {
|
||||
await userStore.upsertUserSetting("appearance", appearance);
|
||||
globalStore.setAppearance(appearance);
|
||||
};
|
||||
|
||||
const handleDefaultMemoVisibilityChanged = async (value: string) => {
|
||||
@ -82,26 +53,11 @@ const PreferencesSection = () => {
|
||||
<p className="title-text">{t("common.basic")}</p>
|
||||
<div className="form-label selector">
|
||||
<span className="normal-text">{t("common.language")}</span>
|
||||
<Select
|
||||
className="!min-w-[10rem] w-auto text-sm"
|
||||
value={setting.locale}
|
||||
onChange={(_, locale) => {
|
||||
if (locale) {
|
||||
handleLocaleChanged(locale);
|
||||
}
|
||||
}}
|
||||
startDecorator={<Icon.Globe className="w-4 h-auto" />}
|
||||
>
|
||||
{localeSelectorItems.map((item) => (
|
||||
<Option key={item.value} value={item.value} className="whitespace-nowrap">
|
||||
{item.text}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
<LocaleSelect value={locale} onChange={handleLocaleSelectChange} />
|
||||
</div>
|
||||
<div className="form-label selector">
|
||||
<span className="normal-text">{t("setting.preference-section.theme")}</span>
|
||||
<AppearanceSelect />
|
||||
<AppearanceSelect value={appearance} onChange={handleAppearanceSelectChange} />
|
||||
</div>
|
||||
<p className="title-text">{t("setting.preference")}</p>
|
||||
<div className="form-label selector">
|
||||
|
@ -5,6 +5,8 @@ import * as api from "../helpers/api";
|
||||
import Icon from "./Icon";
|
||||
import { generateDialog } from "./Dialog";
|
||||
import toastHelper from "./Toast";
|
||||
import LocaleSelect from "./LocaleSelect";
|
||||
import AppearanceSelect from "./AppearanceSelect";
|
||||
|
||||
type Props = DialogProps;
|
||||
|
||||
@ -39,6 +41,33 @@ const UpdateCustomizedProfileDialog: React.FC<Props> = ({ destroy }: Props) => {
|
||||
});
|
||||
};
|
||||
|
||||
const handleDescriptionChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setState((state) => {
|
||||
return {
|
||||
...state,
|
||||
description: e.target.value as string,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const handleLocaleSelectChange = (locale: Locale) => {
|
||||
setState((state) => {
|
||||
return {
|
||||
...state,
|
||||
locale: locale,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const handleAppearanceSelectChange = (appearance: Appearance) => {
|
||||
setState((state) => {
|
||||
return {
|
||||
...state,
|
||||
appearance: appearance,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const handleSaveBtnClick = async () => {
|
||||
if (state.name === "" || state.iconUrl === "") {
|
||||
toastHelper.error(t("message.fill-all"));
|
||||
@ -61,13 +90,13 @@ const UpdateCustomizedProfileDialog: React.FC<Props> = ({ destroy }: Props) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="dialog-header-container !w-64">
|
||||
<div className="dialog-header-container">
|
||||
<p className="title-text">{t("setting.system-section.customize-server.title")}</p>
|
||||
<button className="btn close-btn" onClick={handleCloseBtnClick}>
|
||||
<Icon.X />
|
||||
</button>
|
||||
</div>
|
||||
<div className="dialog-content-container">
|
||||
<div className="dialog-content-container !w-80">
|
||||
<p className="text-sm mb-1">
|
||||
{t("setting.system-section.server-name")}
|
||||
<span className="text-sm text-gray-400 ml-1">({t("setting.system-section.customize-server.default")})</span>
|
||||
@ -75,6 +104,12 @@ const UpdateCustomizedProfileDialog: React.FC<Props> = ({ destroy }: Props) => {
|
||||
<input type="text" className="input-text" value={state.name} onChange={handleNameChanged} />
|
||||
<p className="text-sm mb-1 mt-2">{t("setting.system-section.customize-server.icon-url")}</p>
|
||||
<input type="text" className="input-text" value={state.iconUrl} onChange={handleIconUrlChanged} />
|
||||
<p className="text-sm mb-1 mt-2">Description</p>
|
||||
<input type="text" className="input-text" value={state.description} onChange={handleDescriptionChanged} />
|
||||
<p className="text-sm mb-1 mt-2">Server locale</p>
|
||||
<LocaleSelect className="w-full" value={state.locale} onChange={handleLocaleSelectChange} />
|
||||
<p className="text-sm mb-1 mt-2">Server appearance</p>
|
||||
<AppearanceSelect className="w-full" value={state.appearance} onChange={handleAppearanceSelectChange} />
|
||||
<div className="mt-4 w-full flex flex-row justify-end items-center space-x-2">
|
||||
<span className="btn-text" onClick={handleCloseBtnClick}>
|
||||
{t("common.cancel")}
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { Option, Select } from "@mui/joy";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
@ -9,6 +8,7 @@ import useLoading from "../hooks/useLoading";
|
||||
import Icon from "../components/Icon";
|
||||
import toastHelper from "../components/Toast";
|
||||
import AppearanceSelect from "../components/AppearanceSelect";
|
||||
import LocaleSelect from "../components/LocaleSelect";
|
||||
import "../less/auth.less";
|
||||
|
||||
const validateConfig: ValidatorConfig = {
|
||||
@ -19,12 +19,12 @@ const validateConfig: ValidatorConfig = {
|
||||
};
|
||||
|
||||
const Auth = () => {
|
||||
const { t, i18n } = useTranslation();
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const globalStore = useGlobalStore();
|
||||
const userStore = useUserStore();
|
||||
const actionBtnLoadingState = useLoading(false);
|
||||
const systemStatus = globalStore.state.systemStatus;
|
||||
const { appearance, locale, systemStatus } = globalStore.state;
|
||||
const mode = systemStatus.profile.mode;
|
||||
const [username, setUsername] = useState(mode === "dev" ? "demohero" : "");
|
||||
const [password, setPassword] = useState(mode === "dev" ? "secret" : "");
|
||||
@ -43,6 +43,14 @@ const Auth = () => {
|
||||
setPassword(text);
|
||||
};
|
||||
|
||||
const handleLocaleSelectChange = (locale: Locale) => {
|
||||
globalStore.setLocale(locale);
|
||||
};
|
||||
|
||||
const handleAppearanceSelectChange = (appearance: Appearance) => {
|
||||
globalStore.setAppearance(appearance);
|
||||
};
|
||||
|
||||
const handleSigninBtnsClick = async () => {
|
||||
if (actionBtnLoadingState.isLoading) {
|
||||
return;
|
||||
@ -109,10 +117,6 @@ const Auth = () => {
|
||||
actionBtnLoadingState.setFinish();
|
||||
};
|
||||
|
||||
const handleLocaleItemClick = (locale: Locale) => {
|
||||
globalStore.setLocale(locale);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="page-wrapper auth">
|
||||
<div className="page-container">
|
||||
@ -122,7 +126,7 @@ const Auth = () => {
|
||||
<img className="logo-img" src={systemStatus.customizedProfile.iconUrl} alt="" />
|
||||
<p className="logo-text">{systemStatus.customizedProfile.name}</p>
|
||||
</div>
|
||||
<p className="slogan-text">{t("slogan")}</p>
|
||||
<p className="slogan-text">{systemStatus.customizedProfile.description || t("slogan")}</p>
|
||||
</div>
|
||||
<div className={`page-content-container ${actionBtnLoadingState.isLoading ? "requesting" : ""}`}>
|
||||
<div className="form-item-container input-form-container">
|
||||
@ -167,22 +171,8 @@ const Auth = () => {
|
||||
{!systemStatus?.host && <p className="tip-text">{t("auth.host-tip")}</p>}
|
||||
</div>
|
||||
<div className="flex flex-row items-center justify-center w-full gap-2">
|
||||
<Select
|
||||
className="!min-w-[9rem] w-auto whitespace-nowrap"
|
||||
startDecorator={<Icon.Globe className="w-4 h-auto" />}
|
||||
value={i18n.language}
|
||||
onChange={(_, value) => handleLocaleItemClick(value as Locale)}
|
||||
>
|
||||
<Option value="en">English</Option>
|
||||
<Option value="zh">中文</Option>
|
||||
<Option value="vi">Tiếng Việt</Option>
|
||||
<Option value="fr">French</Option>
|
||||
<Option value="nl">Nederlands</Option>
|
||||
<Option value="sv">Svenska</Option>
|
||||
<Option value="de">German</Option>
|
||||
<Option value="es">Español</Option>
|
||||
</Select>
|
||||
<AppearanceSelect />
|
||||
<LocaleSelect value={locale} onChange={handleLocaleSelectChange} />
|
||||
<AppearanceSelect value={appearance} onChange={handleAppearanceSelectChange} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -13,7 +13,10 @@ export const initialGlobalState = async () => {
|
||||
additionalScript: "",
|
||||
customizedProfile: {
|
||||
name: "memos",
|
||||
iconUrl: "/logo.webp",
|
||||
iconUrl: "https://usememos.com/logo.webp",
|
||||
description: "",
|
||||
locale: "en",
|
||||
appearance: "system",
|
||||
externalUrl: "",
|
||||
},
|
||||
} as SystemStatus,
|
||||
@ -31,6 +34,8 @@ export const initialGlobalState = async () => {
|
||||
const { data } = (await api.getSystemStatus()).data;
|
||||
if (data) {
|
||||
defaultGlobalState.systemStatus = data;
|
||||
defaultGlobalState.locale = data.customizedProfile.locale;
|
||||
defaultGlobalState.appearance = data.customizedProfile.appearance;
|
||||
}
|
||||
} catch (error) {
|
||||
// do nth
|
||||
|
@ -23,7 +23,10 @@ const globalSlice = createSlice({
|
||||
additionalScript: "",
|
||||
customizedProfile: {
|
||||
name: "memos",
|
||||
iconUrl: "/logo.webp",
|
||||
iconUrl: "https://usememos.com/logo.webp",
|
||||
description: "",
|
||||
locale: "en",
|
||||
appearance: "system",
|
||||
externalUrl: "",
|
||||
},
|
||||
},
|
||||
|
3
web/src/types/modules/system.d.ts
vendored
3
web/src/types/modules/system.d.ts
vendored
@ -6,6 +6,9 @@ interface Profile {
|
||||
interface CustomizedProfile {
|
||||
name: string;
|
||||
iconUrl: string;
|
||||
description: string;
|
||||
locale: Locale;
|
||||
appearance: Appearance;
|
||||
externalUrl: string;
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user