mirror of https://github.com/usememos/memos
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
169 lines
5.6 KiB
TypeScript
169 lines
5.6 KiB
TypeScript
import { useState } from "react";
|
|
import { toast } from "react-hot-toast";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Label } from "@/components/ui/label";
|
|
import { Textarea } from "@/components/ui/textarea";
|
|
import { workspaceStore } from "@/store";
|
|
import { workspaceSettingNamePrefix } from "@/store/common";
|
|
import { WorkspaceSettingKey } from "@/store/workspace";
|
|
import { WorkspaceCustomProfile } from "@/types/proto/api/v1/workspace_service";
|
|
import { useTranslate } from "@/utils/i18n";
|
|
import AppearanceSelect from "./AppearanceSelect";
|
|
import LocaleSelect from "./LocaleSelect";
|
|
|
|
interface UpdateCustomizedProfileDialogProps {
|
|
open: boolean;
|
|
onOpenChange: (open: boolean) => void;
|
|
onSuccess?: () => void;
|
|
}
|
|
|
|
export function UpdateCustomizedProfileDialog({ open, onOpenChange, onSuccess }: UpdateCustomizedProfileDialogProps) {
|
|
const t = useTranslate();
|
|
const workspaceGeneralSetting = workspaceStore.state.generalSetting;
|
|
const [customProfile, setCustomProfile] = useState<WorkspaceCustomProfile>(
|
|
WorkspaceCustomProfile.fromPartial(workspaceGeneralSetting.customProfile || {}),
|
|
);
|
|
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
|
|
const setPartialState = (partialState: Partial<WorkspaceCustomProfile>) => {
|
|
setCustomProfile((state) => ({
|
|
...state,
|
|
...partialState,
|
|
}));
|
|
};
|
|
|
|
const handleNameChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
setPartialState({
|
|
title: e.target.value as string,
|
|
});
|
|
};
|
|
|
|
const handleLogoUrlChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
setPartialState({
|
|
logoUrl: e.target.value as string,
|
|
});
|
|
};
|
|
|
|
const handleDescriptionChanged = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
|
setPartialState({
|
|
description: e.target.value as string,
|
|
});
|
|
};
|
|
|
|
const handleLocaleSelectChange = (locale: Locale) => {
|
|
setPartialState({
|
|
locale: locale,
|
|
});
|
|
};
|
|
|
|
const handleAppearanceSelectChange = (appearance: Appearance) => {
|
|
setPartialState({
|
|
appearance: appearance,
|
|
});
|
|
};
|
|
|
|
const handleRestoreButtonClick = () => {
|
|
setPartialState({
|
|
title: "Memos",
|
|
logoUrl: "/logo.webp",
|
|
description: "",
|
|
locale: "en",
|
|
appearance: "system",
|
|
});
|
|
};
|
|
|
|
const handleCloseButtonClick = () => {
|
|
onOpenChange(false);
|
|
};
|
|
|
|
const handleSaveButtonClick = async () => {
|
|
if (customProfile.title === "") {
|
|
toast.error("Title cannot be empty.");
|
|
return;
|
|
}
|
|
|
|
setIsLoading(true);
|
|
try {
|
|
await workspaceStore.upsertWorkspaceSetting({
|
|
name: `${workspaceSettingNamePrefix}${WorkspaceSettingKey.GENERAL}`,
|
|
generalSetting: {
|
|
...workspaceGeneralSetting,
|
|
customProfile: customProfile,
|
|
},
|
|
});
|
|
toast.success(t("message.update-succeed"));
|
|
onSuccess?.();
|
|
onOpenChange(false);
|
|
} catch (error) {
|
|
console.error(error);
|
|
toast.error("Failed to update profile");
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
<DialogContent className="max-w-2xl">
|
|
<DialogHeader>
|
|
<DialogTitle>{t("setting.system-section.customize-server.title")}</DialogTitle>
|
|
<DialogDescription>Customize your workspace appearance and settings.</DialogDescription>
|
|
</DialogHeader>
|
|
|
|
<div className="grid gap-4 py-4">
|
|
<div className="grid gap-2">
|
|
<Label htmlFor="server-name">{t("setting.system-section.server-name")}</Label>
|
|
<Input id="server-name" type="text" value={customProfile.title} onChange={handleNameChanged} placeholder="Enter server name" />
|
|
</div>
|
|
|
|
<div className="grid gap-2">
|
|
<Label htmlFor="icon-url">{t("setting.system-section.customize-server.icon-url")}</Label>
|
|
<Input id="icon-url" type="text" value={customProfile.logoUrl} onChange={handleLogoUrlChanged} placeholder="Enter icon URL" />
|
|
</div>
|
|
|
|
<div className="grid gap-2">
|
|
<Label htmlFor="description">{t("setting.system-section.customize-server.description")}</Label>
|
|
<Textarea
|
|
id="description"
|
|
rows={3}
|
|
value={customProfile.description}
|
|
onChange={handleDescriptionChanged}
|
|
placeholder="Enter description"
|
|
/>
|
|
</div>
|
|
|
|
<div className="grid gap-2">
|
|
<Label>{t("setting.system-section.customize-server.locale")}</Label>
|
|
<LocaleSelect value={customProfile.locale} onChange={handleLocaleSelectChange} />
|
|
</div>
|
|
|
|
<div className="grid gap-2">
|
|
<Label>{t("setting.system-section.customize-server.appearance")}</Label>
|
|
<AppearanceSelect value={customProfile.appearance as Appearance} onChange={handleAppearanceSelectChange} />
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex items-center justify-between pt-4">
|
|
<Button variant="outline" onClick={handleRestoreButtonClick} disabled={isLoading}>
|
|
{t("common.restore")}
|
|
</Button>
|
|
|
|
<div className="flex gap-2">
|
|
<Button variant="ghost" onClick={handleCloseButtonClick} disabled={isLoading}>
|
|
{t("common.cancel")}
|
|
</Button>
|
|
<Button onClick={handleSaveButtonClick} disabled={isLoading}>
|
|
{isLoading ? "Saving..." : t("common.save")}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</DialogContent>
|
|
</Dialog>
|
|
);
|
|
}
|
|
|
|
export default UpdateCustomizedProfileDialog;
|