From 240d89fbca80a7b1bc638d07d033d192542678a2 Mon Sep 17 00:00:00 2001 From: Johnny Date: Sun, 6 Jul 2025 22:01:55 +0800 Subject: [PATCH] feat: migrate dialogs --- proto/api/v1/attachment_service.proto | 2 +- proto/gen/api/v1/attachment_service.pb.go | 6 +- web/src/components/AttachmentIcon.tsx | 50 ++- .../components/ChangeMemberPasswordDialog.tsx | 103 +++-- .../components/CreateAccessTokenDialog.tsx | 79 ++-- .../CreateIdentityProviderDialog.tsx | 390 +++++++++--------- web/src/components/CreateShortcutDialog.tsx | 140 ++++--- web/src/components/CreateUserDialog.tsx | 155 ++++--- web/src/components/CreateWebhookDialog.tsx | 94 ++--- web/src/components/Dialog/BaseDialog.tsx | 99 ----- web/src/components/Dialog/index.ts | 1 - .../HomeSidebar/ShortcutsSection.tsx | 30 +- .../components/HomeSidebar/TagsSection.tsx | 26 +- web/src/components/MemoAttachmentListView.tsx | 26 +- web/src/components/MemoView.tsx | 16 +- web/src/components/PreviewImageDialog.tsx | 260 ++++-------- web/src/components/RenameTagDialog.tsx | 78 ++-- .../Settings/AccessTokenSection.tsx | 22 +- web/src/components/Settings/MemberSection.tsx | 141 ++----- .../components/Settings/MyAccountSection.tsx | 27 +- web/src/components/Settings/SSOSection.tsx | 32 +- .../components/Settings/WebhookSection.tsx | 16 +- .../components/Settings/WorkspaceSection.tsx | 15 +- web/src/components/UpdateAccountDialog.tsx | 143 +++---- .../UpdateCustomizedProfileDialog.new.tsx | 0 .../UpdateCustomizedProfileDialog.tsx | 131 +++--- .../examples/WorkspaceSection.example.tsx | 0 web/src/components/ui/dialog.tsx | 171 ++++---- web/src/hooks/useDialog.ts | 118 ++++++ web/src/pages/Attachments.tsx | 2 +- web/src/pages/Inboxes.tsx | 2 +- web/src/pages/Setting.tsx | 2 +- .../types/proto/api/v1/attachment_service.ts | 29 +- 33 files changed, 1201 insertions(+), 1205 deletions(-) delete mode 100644 web/src/components/Dialog/BaseDialog.tsx delete mode 100644 web/src/components/Dialog/index.ts create mode 100644 web/src/components/UpdateCustomizedProfileDialog.new.tsx create mode 100644 web/src/components/examples/WorkspaceSection.example.tsx create mode 100644 web/src/hooks/useDialog.ts diff --git a/proto/api/v1/attachment_service.proto b/proto/api/v1/attachment_service.proto index be7ebf7a2..12fc9c1a3 100644 --- a/proto/api/v1/attachment_service.proto +++ b/proto/api/v1/attachment_service.proto @@ -34,7 +34,7 @@ service AttachmentService { // GetAttachmentBinary returns a attachment binary by name. rpc GetAttachmentBinary(GetAttachmentBinaryRequest) returns (google.api.HttpBody) { option (google.api.http) = {get: "/file/{name=attachments/*}/{filename}"}; - option (google.api.method_signature) = "name,filename"; + option (google.api.method_signature) = "name,filename,thumbnail"; } // UpdateAttachment updates a attachment. rpc UpdateAttachment(UpdateAttachmentRequest) returns (Attachment) { diff --git a/proto/gen/api/v1/attachment_service.pb.go b/proto/gen/api/v1/attachment_service.pb.go index fde8e8079..d86d70e77 100644 --- a/proto/gen/api/v1/attachment_service.pb.go +++ b/proto/gen/api/v1/attachment_service.pb.go @@ -596,14 +596,14 @@ const file_api_v1_attachment_service_proto_rawDesc = "" + "updateMask\"N\n" + "\x17DeleteAttachmentRequest\x123\n" + "\x04name\x18\x01 \x01(\tB\x1f\xe0A\x02\xfaA\x19\n" + - "\x17memos.api.v1/AttachmentR\x04name2\xdb\x06\n" + + "\x17memos.api.v1/AttachmentR\x04name2\xe5\x06\n" + "\x11AttachmentService\x12\x89\x01\n" + "\x10CreateAttachment\x12%.memos.api.v1.CreateAttachmentRequest\x1a\x18.memos.api.v1.Attachment\"4\xdaA\n" + "attachment\x82\xd3\xe4\x93\x02!:\n" + "attachment\"\x13/api/v1/attachments\x12{\n" + "\x0fListAttachments\x12$.memos.api.v1.ListAttachmentsRequest\x1a%.memos.api.v1.ListAttachmentsResponse\"\x1b\x82\xd3\xe4\x93\x02\x15\x12\x13/api/v1/attachments\x12z\n" + - "\rGetAttachment\x12\".memos.api.v1.GetAttachmentRequest\x1a\x18.memos.api.v1.Attachment\"+\xdaA\x04name\x82\xd3\xe4\x93\x02\x1e\x12\x1c/api/v1/{name=attachments/*}\x12\x94\x01\n" + - "\x13GetAttachmentBinary\x12(.memos.api.v1.GetAttachmentBinaryRequest\x1a\x14.google.api.HttpBody\"=\xdaA\rname,filename\x82\xd3\xe4\x93\x02'\x12%/file/{name=attachments/*}/{filename}\x12\xa9\x01\n" + + "\rGetAttachment\x12\".memos.api.v1.GetAttachmentRequest\x1a\x18.memos.api.v1.Attachment\"+\xdaA\x04name\x82\xd3\xe4\x93\x02\x1e\x12\x1c/api/v1/{name=attachments/*}\x12\x9e\x01\n" + + "\x13GetAttachmentBinary\x12(.memos.api.v1.GetAttachmentBinaryRequest\x1a\x14.google.api.HttpBody\"G\xdaA\x17name,filename,thumbnail\x82\xd3\xe4\x93\x02'\x12%/file/{name=attachments/*}/{filename}\x12\xa9\x01\n" + "\x10UpdateAttachment\x12%.memos.api.v1.UpdateAttachmentRequest\x1a\x18.memos.api.v1.Attachment\"T\xdaA\x16attachment,update_mask\x82\xd3\xe4\x93\x025:\n" + "attachment2'/api/v1/{attachment.name=attachments/*}\x12~\n" + "\x10DeleteAttachment\x12%.memos.api.v1.DeleteAttachmentRequest\x1a\x16.google.protobuf.Empty\"+\xdaA\x04name\x82\xd3\xe4\x93\x02\x1e*\x1c/api/v1/{name=attachments/*}B\xae\x01\n" + diff --git a/web/src/components/AttachmentIcon.tsx b/web/src/components/AttachmentIcon.tsx index f06c884e0..1469f643e 100644 --- a/web/src/components/AttachmentIcon.tsx +++ b/web/src/components/AttachmentIcon.tsx @@ -9,11 +9,11 @@ import { FileVideo2Icon, SheetIcon, } from "lucide-react"; -import React from "react"; +import React, { useState } from "react"; import { cn } from "@/lib/utils"; import { Attachment } from "@/types/proto/api/v1/attachment_service"; import { getAttachmentType, getAttachmentUrl } from "@/utils/attachment"; -import showPreviewImageDialog from "./PreviewImageDialog"; +import { PreviewImageDialog } from "./PreviewImageDialog"; import SquareDiv from "./kit/SquareDiv"; interface Props { @@ -24,26 +24,52 @@ interface Props { const AttachmentIcon = (props: Props) => { const { attachment } = props; + const [previewImage, setPreviewImage] = useState<{ open: boolean; urls: string[]; index: number }>({ + open: false, + urls: [], + index: 0, + }); const resourceType = getAttachmentType(attachment); - const resourceUrl = getAttachmentUrl(attachment); + const attachmentUrl = getAttachmentUrl(attachment); const className = cn("w-full h-auto", props.className); const strokeWidth = props.strokeWidth; const previewResource = () => { - window.open(resourceUrl); + window.open(attachmentUrl); + }; + + const handleImageClick = () => { + setPreviewImage({ open: true, urls: [attachmentUrl], index: 0 }); }; if (resourceType === "image/*") { return ( - - showPreviewImageDialog(resourceUrl)} - decoding="async" - loading="lazy" + <> + + { + // Fallback to original image if thumbnail fails + const target = e.target as HTMLImageElement; + if (target.src.includes("?thumbnail=true")) { + console.warn("Thumbnail failed, falling back to original image:", attachmentUrl); + target.src = attachmentUrl; + } + }} + decoding="async" + loading="lazy" + /> + + + setPreviewImage((prev) => ({ ...prev, open }))} + imgUrls={previewImage.urls} + initialIndex={previewImage.index} /> - + ); } diff --git a/web/src/components/ChangeMemberPasswordDialog.tsx b/web/src/components/ChangeMemberPasswordDialog.tsx index 127a57b5e..37e374c86 100644 --- a/web/src/components/ChangeMemberPasswordDialog.tsx +++ b/web/src/components/ChangeMemberPasswordDialog.tsx @@ -1,19 +1,21 @@ -import { XIcon } from "lucide-react"; import { useEffect, useState } from "react"; import { toast } from "react-hot-toast"; import { Button } from "@/components/ui/button"; +import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; import { userStore } from "@/store/v2"; import { User } from "@/types/proto/api/v1/user_service"; import { useTranslate } from "@/utils/i18n"; -import { generateDialog } from "./Dialog"; -interface Props extends DialogProps { - user: User; +interface ChangeMemberPasswordDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + user?: User; + onSuccess?: () => void; } -const ChangeMemberPasswordDialog: React.FC = (props: Props) => { - const { user, destroy } = props; +export function ChangeMemberPasswordDialog({ open, onOpenChange, user, onSuccess }: ChangeMemberPasswordDialogProps) { const t = useTranslate(); const [newPassword, setNewPassword] = useState(""); const [newPasswordAgain, setNewPasswordAgain] = useState(""); @@ -23,7 +25,7 @@ const ChangeMemberPasswordDialog: React.FC = (props: Props) => { }, []); const handleCloseBtnClick = () => { - destroy(); + onOpenChange(false); }; const handleNewPasswordChanged = (e: React.ChangeEvent) => { @@ -37,6 +39,8 @@ const ChangeMemberPasswordDialog: React.FC = (props: Props) => { }; const handleSaveBtnClick = async () => { + if (!user) return; + if (newPassword === "" || newPasswordAgain === "") { toast.error(t("message.fill-all")); return; @@ -57,62 +61,55 @@ const ChangeMemberPasswordDialog: React.FC = (props: Props) => { ["password"], ); toast(t("message.password-changed")); - handleCloseBtnClick(); + onSuccess?.(); + onOpenChange(false); } catch (error: any) { console.error(error); toast.error(error.details); } }; + if (!user) return null; + return ( -
-
-

- {t("setting.account-section.change-password")} ({user.displayName}) -

- -
-
-

{t("auth.new-password")}

- -

{t("auth.repeat-new-password")}

- -
+ + + + + {t("setting.account-section.change-password")} ({user.displayName}) + + +
+
+ + +
+
+ + +
+
+ - -
-
-
- ); -}; - -function showChangeMemberPasswordDialog(user: User) { - generateDialog( - { - className: "change-member-password-dialog", - dialogName: "change-member-password-dialog", - }, - ChangeMemberPasswordDialog, - { user }, + + + + ); } -export default showChangeMemberPasswordDialog; +export default ChangeMemberPasswordDialog; diff --git a/web/src/components/CreateAccessTokenDialog.tsx b/web/src/components/CreateAccessTokenDialog.tsx index 8e8b1c87a..0c139c145 100644 --- a/web/src/components/CreateAccessTokenDialog.tsx +++ b/web/src/components/CreateAccessTokenDialog.tsx @@ -1,7 +1,7 @@ -import { XIcon } from "lucide-react"; import React, { useState } from "react"; import { toast } from "react-hot-toast"; import { Button } from "@/components/ui/button"; +import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; @@ -9,10 +9,11 @@ import { userServiceClient } from "@/grpcweb"; import useCurrentUser from "@/hooks/useCurrentUser"; import useLoading from "@/hooks/useLoading"; import { useTranslate } from "@/utils/i18n"; -import { generateDialog } from "./Dialog"; -interface Props extends DialogProps { - onConfirm: () => void; +interface CreateAccessTokenDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + onSuccess: () => void; } interface State { @@ -20,8 +21,7 @@ interface State { expiration: number; } -const CreateAccessTokenDialog: React.FC = (props: Props) => { - const { destroy, onConfirm } = props; +export function CreateAccessTokenDialog({ open, onOpenChange, onSuccess }: CreateAccessTokenDialogProps) { const t = useTranslate(); const currentUser = useCurrentUser(); const [state, setState] = useState({ @@ -71,6 +71,7 @@ const CreateAccessTokenDialog: React.FC = (props: Props) => { } try { + requestState.setLoading(); await userServiceClient.createUserAccessToken({ parent: currentUser.name, accessToken: { @@ -79,42 +80,39 @@ const CreateAccessTokenDialog: React.FC = (props: Props) => { }, }); - onConfirm(); - destroy(); + requestState.setFinish(); + onSuccess(); + onOpenChange(false); } catch (error: any) { toast.error(error.details); console.error(error); + requestState.setError(); } }; return ( -
-
-

{t("setting.access-token-section.create-dialog.create-access-token")}

- -
-
-
- - {t("setting.access-token-section.create-dialog.description")} * - -
+ + + + {t("setting.access-token-section.create-dialog.create-access-token")} + +
+
+
-
-
- - {t("setting.access-token-section.create-dialog.expiration")} * - -
+
+ {expirationOptions.map((option) => (
@@ -125,30 +123,17 @@ const CreateAccessTokenDialog: React.FC = (props: Props) => {
-
- -
-
-
- ); -}; - -function showCreateAccessTokenDialog(onConfirm: () => void) { - generateDialog( - { - className: "create-access-token-dialog", - dialogName: "create-access-token-dialog", - }, - CreateAccessTokenDialog, - { - onConfirm, - }, + +
+
); } -export default showCreateAccessTokenDialog; +export default CreateAccessTokenDialog; diff --git a/web/src/components/CreateIdentityProviderDialog.tsx b/web/src/components/CreateIdentityProviderDialog.tsx index 8ba21f0c7..2d2b2e14e 100644 --- a/web/src/components/CreateIdentityProviderDialog.tsx +++ b/web/src/components/CreateIdentityProviderDialog.tsx @@ -1,7 +1,7 @@ -import { XIcon } from "lucide-react"; import { useEffect, useState } from "react"; import { toast } from "react-hot-toast"; import { Button } from "@/components/ui/button"; +import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Separator } from "@/components/ui/separator"; @@ -9,7 +9,6 @@ import { identityProviderServiceClient } from "@/grpcweb"; import { absolutifyLink } from "@/helpers/utils"; import { FieldMapping, IdentityProvider, IdentityProvider_Type, OAuth2Config } from "@/types/proto/api/v1/idp_service"; import { useTranslate } from "@/utils/i18n"; -import { generateDialog } from "./Dialog"; const templateList: IdentityProvider[] = [ { @@ -98,15 +97,16 @@ const templateList: IdentityProvider[] = [ }, ]; -interface Props extends DialogProps { +interface CreateIdentityProviderDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; identityProvider?: IdentityProvider; - confirmCallback?: () => void; + onSuccess?: () => void; } -const CreateIdentityProviderDialog: React.FC = (props: Props) => { +export function CreateIdentityProviderDialog({ open, onOpenChange, identityProvider, onSuccess }: CreateIdentityProviderDialogProps) { const t = useTranslate(); const identityProviderTypes = [...new Set(templateList.map((t) => t.type))]; - const { confirmCallback, destroy, identityProvider } = props; const [basicInfo, setBasicInfo] = useState({ title: "", identifierFilter: "", @@ -165,7 +165,7 @@ const CreateIdentityProviderDialog: React.FC = (props: Props) => { }, [selectedTemplate]); const handleCloseBtnClick = () => { - destroy(); + onOpenChange(false); }; const allowConfirmAction = () => { @@ -230,10 +230,8 @@ const CreateIdentityProviderDialog: React.FC = (props: Props) => { toast.error(error.details); console.error(error); } - if (confirmCallback) { - confirmCallback(); - } - destroy(); + onSuccess?.(); + onOpenChange(false); }; const setPartialOAuth2Config = (state: Partial) => { @@ -244,204 +242,192 @@ const CreateIdentityProviderDialog: React.FC = (props: Props) => { }; return ( -
-
-

{t(isCreating ? "setting.sso-section.create-sso" : "setting.sso-section.update-sso")}

- -
-
- {isCreating && ( - <> -

{t("common.type")}

- -

{t("setting.sso-section.template")}

- - - - )} -

- {t("common.name")} - * -

- - setBasicInfo({ - ...basicInfo, - title: e.target.value, - }) - } - /> -

{t("setting.sso-section.identifier-filter")}

- - setBasicInfo({ - ...basicInfo, - identifierFilter: e.target.value, - }) - } - /> - - {type === "OAUTH2" && ( - <> - {isCreating && ( -

- {t("setting.sso-section.redirect-url")}: {absolutifyLink("/auth/callback")} +

+ + + {t(isCreating ? "setting.sso-section.create-sso" : "setting.sso-section.update-sso")} + +
+ {isCreating && ( + <> +

{t("common.type")}

+ +

{t("setting.sso-section.template")}

+ + + + )} +

+ {t("common.name")} + * +

+ + setBasicInfo({ + ...basicInfo, + title: e.target.value, + }) + } + /> +

{t("setting.sso-section.identifier-filter")}

+ + setBasicInfo({ + ...basicInfo, + identifierFilter: e.target.value, + }) + } + /> + + {type === "OAUTH2" && ( + <> + {isCreating && ( +

+ {t("setting.sso-section.redirect-url")}: {absolutifyLink("/auth/callback")} +

+ )} +

+ {t("setting.sso-section.client-id")} + *

- )} -

- {t("setting.sso-section.client-id")} - * -

- setPartialOAuth2Config({ clientId: e.target.value })} - /> -

- {t("setting.sso-section.client-secret")} - * -

- setPartialOAuth2Config({ clientSecret: e.target.value })} - /> -

- {t("setting.sso-section.authorization-endpoint")} - * -

- setPartialOAuth2Config({ authUrl: e.target.value })} - /> -

- {t("setting.sso-section.token-endpoint")} - * -

- setPartialOAuth2Config({ tokenUrl: e.target.value })} - /> -

- {t("setting.sso-section.user-endpoint")} - * -

- setPartialOAuth2Config({ userInfoUrl: e.target.value })} - /> -

- {t("setting.sso-section.scopes")} - * -

- setOAuth2Scopes(e.target.value)} - /> - -

- {t("setting.sso-section.identifier")} - * -

- - setPartialOAuth2Config({ fieldMapping: { ...oauth2Config.fieldMapping, identifier: e.target.value } as FieldMapping }) - } - /> -

{t("setting.sso-section.display-name")}

- - setPartialOAuth2Config({ fieldMapping: { ...oauth2Config.fieldMapping, displayName: e.target.value } as FieldMapping }) - } - /> -

{t("common.email")}

- - setPartialOAuth2Config({ fieldMapping: { ...oauth2Config.fieldMapping, email: e.target.value } as FieldMapping }) - } - /> -

Avatar URL

- - setPartialOAuth2Config({ fieldMapping: { ...oauth2Config.fieldMapping, avatarUrl: e.target.value } as FieldMapping }) - } - /> - - )} -
+ setPartialOAuth2Config({ clientId: e.target.value })} + /> +

+ {t("setting.sso-section.client-secret")} + * +

+ setPartialOAuth2Config({ clientSecret: e.target.value })} + /> +

+ {t("setting.sso-section.authorization-endpoint")} + * +

+ setPartialOAuth2Config({ authUrl: e.target.value })} + /> +

+ {t("setting.sso-section.token-endpoint")} + * +

+ setPartialOAuth2Config({ tokenUrl: e.target.value })} + /> +

+ {t("setting.sso-section.user-endpoint")} + * +

+ setPartialOAuth2Config({ userInfoUrl: e.target.value })} + /> +

+ {t("setting.sso-section.scopes")} + * +

+ setOAuth2Scopes(e.target.value)} + /> + +

+ {t("setting.sso-section.identifier")} + * +

+ + setPartialOAuth2Config({ fieldMapping: { ...oauth2Config.fieldMapping, identifier: e.target.value } as FieldMapping }) + } + /> +

{t("setting.sso-section.display-name")}

+ + setPartialOAuth2Config({ fieldMapping: { ...oauth2Config.fieldMapping, displayName: e.target.value } as FieldMapping }) + } + /> +

{t("common.email")}

+ + setPartialOAuth2Config({ fieldMapping: { ...oauth2Config.fieldMapping, email: e.target.value } as FieldMapping }) + } + /> +

Avatar URL

+ + setPartialOAuth2Config({ fieldMapping: { ...oauth2Config.fieldMapping, avatarUrl: e.target.value } as FieldMapping }) + } + /> + + )} +
+ -
-
-
- ); -}; - -function showCreateIdentityProviderDialog(identityProvider?: IdentityProvider, confirmCallback?: () => void) { - generateDialog( - { - className: "create-identity-provider-dialog", - dialogName: "create-identity-provider-dialog", - }, - CreateIdentityProviderDialog, - { identityProvider, confirmCallback }, + + + ); } -export default showCreateIdentityProviderDialog; +export default CreateIdentityProviderDialog; diff --git a/web/src/components/CreateShortcutDialog.tsx b/web/src/components/CreateShortcutDialog.tsx index deb48ec48..1b49e593e 100644 --- a/web/src/components/CreateShortcutDialog.tsx +++ b/web/src/components/CreateShortcutDialog.tsx @@ -1,8 +1,9 @@ -import { XIcon } from "lucide-react"; import React, { useState } from "react"; import { toast } from "react-hot-toast"; import { Button } from "@/components/ui/button"; +import { Dialog, DialogContent, DialogFooter, 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 { shortcutServiceClient } from "@/grpcweb"; import useCurrentUser from "@/hooks/useCurrentUser"; @@ -10,23 +11,24 @@ import useLoading from "@/hooks/useLoading"; import { userStore } from "@/store/v2"; import { Shortcut } from "@/types/proto/api/v1/shortcut_service"; import { useTranslate } from "@/utils/i18n"; -import { generateDialog } from "./Dialog"; -interface Props extends DialogProps { +interface CreateShortcutDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; shortcut?: Shortcut; + onSuccess?: () => void; } -const CreateShortcutDialog: React.FC = (props: Props) => { - const { destroy } = props; +export function CreateShortcutDialog({ open, onOpenChange, shortcut: initialShortcut, onSuccess }: CreateShortcutDialogProps) { const t = useTranslate(); const user = useCurrentUser(); const [shortcut, setShortcut] = useState({ - name: props.shortcut?.name || "", - title: props.shortcut?.title || "", - filter: props.shortcut?.filter || "", + name: initialShortcut?.name || "", + title: initialShortcut?.title || "", + filter: initialShortcut?.filter || "", }); const requestState = useLoading(false); - const isCreating = !props.shortcut; + const isCreating = !initialShortcut; const onShortcutTitleChange = (e: React.ChangeEvent) => { setShortcut({ ...shortcut, title: e.target.value }); @@ -43,6 +45,7 @@ const CreateShortcutDialog: React.FC = (props: Props) => { } try { + requestState.setLoading(); if (isCreating) { await shortcutServiceClient.createShortcut({ parent: user.name, @@ -57,7 +60,7 @@ const CreateShortcutDialog: React.FC = (props: Props) => { await shortcutServiceClient.updateShortcut({ shortcut: { ...shortcut, - name: props.shortcut!.name, // Keep the original resource name + name: initialShortcut!.name, // Keep the original resource name }, updateMask: ["title", "filter"], }); @@ -65,79 +68,74 @@ const CreateShortcutDialog: React.FC = (props: Props) => { } // Refresh shortcuts. await userStore.fetchShortcuts(); - destroy(); + requestState.setFinish(); + onSuccess?.(); + onOpenChange(false); } catch (error: any) { console.error(error); toast.error(error.details); + requestState.setError(); } }; return ( -
-
-

{`${isCreating ? t("common.create") : t("common.edit")} ${t("common.shortcuts")}`}

- -
-
-
- {t("common.title")} - - {t("common.filter")} -