memos/web/src/components/Settings/WorkspaceSection.tsx
2025-07-01 19:29:48 +08:00

173 lines
7.4 KiB
TypeScript

import { isEqual } from "lodash-es";
import { ExternalLinkIcon } from "lucide-react";
import { observer } from "mobx-react-lite";
import { useEffect, useState } from "react";
import { toast } from "react-hot-toast";
import { Link } from "react-router-dom";
import { Button } from "@/components/ui/button";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Separator } from "@/components/ui/separator";
import { Switch } from "@/components/ui/switch";
import { Textarea } from "@/components/ui/textarea";
import { identityProviderServiceClient } from "@/grpcweb";
import { workspaceSettingNamePrefix } from "@/store/common";
import { workspaceStore } from "@/store/v2";
import { WorkspaceSettingKey } from "@/store/v2/workspace";
import { IdentityProvider } from "@/types/proto/api/v1/idp_service";
import { WorkspaceGeneralSetting } from "@/types/proto/api/v1/workspace_service";
import { useTranslate } from "@/utils/i18n";
import showUpdateCustomizedProfileDialog from "../UpdateCustomizedProfileDialog";
const WorkspaceSection = observer(() => {
const t = useTranslate();
const originalSetting = WorkspaceGeneralSetting.fromPartial(
workspaceStore.getWorkspaceSettingByKey(WorkspaceSettingKey.GENERAL)?.generalSetting || {},
);
const [workspaceGeneralSetting, setWorkspaceGeneralSetting] = useState<WorkspaceGeneralSetting>(originalSetting);
const [identityProviderList, setIdentityProviderList] = useState<IdentityProvider[]>([]);
useEffect(() => {
setWorkspaceGeneralSetting({ ...workspaceGeneralSetting, customProfile: originalSetting.customProfile });
}, [workspaceStore.getWorkspaceSettingByKey(WorkspaceSettingKey.GENERAL)]);
const handleUpdateCustomizedProfileButtonClick = () => {
showUpdateCustomizedProfileDialog();
};
const updatePartialSetting = (partial: Partial<WorkspaceGeneralSetting>) => {
setWorkspaceGeneralSetting(
WorkspaceGeneralSetting.fromPartial({
...workspaceGeneralSetting,
...partial,
}),
);
};
const handleSaveGeneralSetting = async () => {
try {
await workspaceStore.upsertWorkspaceSetting({
name: `${workspaceSettingNamePrefix}${WorkspaceSettingKey.GENERAL}`,
generalSetting: workspaceGeneralSetting,
});
} catch (error: any) {
toast.error(error.details);
console.error(error);
return;
}
toast.success(t("message.update-succeed"));
};
useEffect(() => {
fetchIdentityProviderList();
}, []);
const fetchIdentityProviderList = async () => {
const { identityProviders } = await identityProviderServiceClient.listIdentityProviders({});
setIdentityProviderList(identityProviders);
};
return (
<div className="w-full flex flex-col gap-2 pt-2 pb-4">
<p className="font-medium text-gray-700 dark:text-gray-500">{t("common.basic")}</p>
<div className="w-full flex flex-row justify-between items-center">
<div>
{t("setting.system-section.server-name")}:{" "}
<span className="font-mono font-bold">{workspaceGeneralSetting.customProfile?.title || "Memos"}</span>
</div>
<Button variant="outline" onClick={handleUpdateCustomizedProfileButtonClick}>
{t("common.edit")}
</Button>
</div>
<Separator />
<p className="font-medium text-gray-700 dark:text-gray-500">{t("setting.system-section.title")}</p>
<div className="w-full flex flex-row justify-between items-center">
<span>{t("setting.system-section.additional-style")}</span>
</div>
<Textarea
className="font-mono w-full"
rows={3}
placeholder={t("setting.system-section.additional-style-placeholder")}
value={workspaceGeneralSetting.additionalStyle}
onChange={(event) => updatePartialSetting({ additionalStyle: event.target.value })}
/>
<div className="w-full flex flex-row justify-between items-center">
<span>{t("setting.system-section.additional-script")}</span>
</div>
<Textarea
className="font-mono w-full"
rows={3}
placeholder={t("setting.system-section.additional-script-placeholder")}
value={workspaceGeneralSetting.additionalScript}
onChange={(event) => updatePartialSetting({ additionalScript: event.target.value })}
/>
<div className="w-full">
<Link
className="text-gray-500 text-sm flex flex-row justify-start items-center hover:underline hover:text-blue-600"
to="https://usememos.com/docs/advanced-settings/custom-style-and-script"
target="_blank"
>
{t("common.learn-more")}
<ExternalLinkIcon className="inline w-4 h-auto ml-1" />
</Link>
</div>
<div className="w-full flex flex-row justify-between items-center">
<span>{t("setting.workspace-section.disallow-user-registration")}</span>
<Switch
disabled={workspaceStore.state.profile.mode === "demo"}
checked={workspaceGeneralSetting.disallowUserRegistration}
onCheckedChange={(checked) => updatePartialSetting({ disallowUserRegistration: checked })}
/>
</div>
<div className="w-full flex flex-row justify-between items-center">
<span>{t("setting.workspace-section.disallow-password-auth")}</span>
<Switch
disabled={
workspaceStore.state.profile.mode === "demo" ||
(identityProviderList.length === 0 && !workspaceGeneralSetting.disallowPasswordAuth)
}
checked={workspaceGeneralSetting.disallowPasswordAuth}
onCheckedChange={(checked) => updatePartialSetting({ disallowPasswordAuth: checked })}
/>
</div>
<div className="w-full flex flex-row justify-between items-center">
<span>{t("setting.workspace-section.disallow-change-username")}</span>
<Switch
checked={workspaceGeneralSetting.disallowChangeUsername}
onCheckedChange={(checked) => updatePartialSetting({ disallowChangeUsername: checked })}
/>
</div>
<div className="w-full flex flex-row justify-between items-center">
<span>{t("setting.workspace-section.disallow-change-nickname")}</span>
<Switch
checked={workspaceGeneralSetting.disallowChangeNickname}
onCheckedChange={(checked) => updatePartialSetting({ disallowChangeNickname: checked })}
/>
</div>
<div className="w-full flex flex-row justify-between items-center">
<span className="truncate">{t("setting.workspace-section.week-start-day")}</span>
<Select
value={workspaceGeneralSetting.weekStartDayOffset.toString()}
onValueChange={(value) => {
updatePartialSetting({ weekStartDayOffset: parseInt(value) || 0 });
}}
>
<SelectTrigger className="min-w-fit">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="-1">{t("setting.workspace-section.saturday")}</SelectItem>
<SelectItem value="0">{t("setting.workspace-section.sunday")}</SelectItem>
<SelectItem value="1">{t("setting.workspace-section.monday")}</SelectItem>
</SelectContent>
</Select>
</div>
<div className="mt-2 w-full flex justify-end">
<Button disabled={isEqual(workspaceGeneralSetting, originalSetting)} onClick={handleSaveGeneralSetting}>
{t("common.save")}
</Button>
</div>
</div>
);
});
export default WorkspaceSection;