Skip to content

Commit f3e07c5

Browse files
chore(member-invite): early return for pending mutations while copying invite link (calcom#28753)
* chore(member-invite): early return for pending mutations while copying invite link * typo fix * make rabbit happy --------- Co-authored-by: Romit <85230081+romitg2@users.noreply.github.com>
1 parent dcbb417 commit f3e07c5

1 file changed

Lines changed: 47 additions & 33 deletions

File tree

apps/web/modules/ee/teams/components/MemberInvitationModal.tsx

Lines changed: 47 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useSession } from "next-auth/react";
22
import posthog from "posthog-js";
33
import type { FormEvent } from "react";
4-
import { useMemo, useRef, useState } from "react";
4+
import { useCallback, useMemo, useRef, useState } from "react";
55
import { Controller, useForm } from "react-hook-form";
66

77
import TeamInviteFromOrg from "~/ee/organizations/components/TeamInviteFromOrg";
@@ -89,6 +89,7 @@ export default function MemberInvitationModal(props: MemberInvitationModalProps)
8989
const [modalImportMode, setModalInputMode] = useState<ModalMode>(
9090
canSeeOrganization ? "ORGANIZATION" : "INDIVIDUAL"
9191
);
92+
const [isCopying, setIsCopying] = useState(false);
9293

9394
const createInviteMutation = trpc.viewer.teams.createInvite.useMutation({
9495
async onSuccess() {
@@ -193,6 +194,48 @@ export default function MemberInvitationModal(props: MemberInvitationModalProps)
193194

194195
const importRef = useRef<HTMLInputElement | null>(null);
195196

197+
const handleCopyInviteLink = useCallback(async () => {
198+
if (isCopying || createInviteMutation.isPending) return;
199+
200+
setIsCopying(true);
201+
try {
202+
// Required for Safari but also works on Chrome
203+
// Credits to https://wolfgangrittner.dev/how-to-use-clipboard-api-in-firefox/
204+
if (typeof ClipboardItem !== "undefined") {
205+
const inviteLinkClipboardItem = new ClipboardItem({
206+
//eslint-disable-next-line no-async-promise-executor
207+
"text/plain": new Promise((resolve, reject) => {
208+
// Instead of doing async work and then writing to clipboard, do async work in clipboard API itself
209+
createInviteMutation.mutateAsync({
210+
teamId: props.teamId,
211+
token: props.token,
212+
}).then(({ inviteLink }) => {
213+
resolve(new Blob([inviteLink], { type: "text/plain" }));
214+
})
215+
.catch((err) => {
216+
reject(err);
217+
});
218+
}),
219+
});
220+
await navigator.clipboard.write([inviteLinkClipboardItem]);
221+
showToast(t("invite_link_copied"), "success");
222+
} else {
223+
// Fallback for browsers that don't support ClipboardItem e.g. Firefox
224+
const { inviteLink } = await createInviteMutation.mutateAsync({
225+
teamId: props.teamId,
226+
token: props.token,
227+
});
228+
await navigator.clipboard.writeText(inviteLink);
229+
showToast(t("invite_link_copied"), "success");
230+
}
231+
} catch (e) {
232+
showToast(t("something_went_wrong_on_our_end"), "error");
233+
console.error(e);
234+
} finally {
235+
setIsCopying(false);
236+
}
237+
}, [isCopying, createInviteMutation, props.teamId, props.token, t]);
238+
196239
return (
197240
<Dialog
198241
name="inviteModal"
@@ -412,38 +455,9 @@ export default function MemberInvitationModal(props: MemberInvitationModalProps)
412455
type="button"
413456
color="minimal"
414457
variant="icon"
415-
onClick={async function () {
416-
try {
417-
// Required for Safari but also works on Chrome
418-
// Credits to https://wolfgangrittner.dev/how-to-use-clipboard-api-in-firefox/
419-
if (typeof ClipboardItem !== "undefined") {
420-
const inviteLinkClipbardItem = new ClipboardItem({
421-
//eslint-disable-next-line no-async-promise-executor
422-
"text/plain": new Promise(async (resolve) => {
423-
// Instead of doing async work and then writing to clipboard, do async work in clipboard API itself
424-
const { inviteLink } = await createInviteMutation.mutateAsync({
425-
teamId: props.teamId,
426-
token: props.token,
427-
});
428-
showToast(t("invite_link_copied"), "success");
429-
resolve(new Blob([inviteLink], { type: "text/plain" }));
430-
}),
431-
});
432-
await navigator.clipboard.write([inviteLinkClipbardItem]);
433-
} else {
434-
// Fallback for browsers that don't support ClipboardItem e.g. Firefox
435-
const { inviteLink } = await createInviteMutation.mutateAsync({
436-
teamId: props.teamId,
437-
token: props.token,
438-
});
439-
await navigator.clipboard.writeText(inviteLink);
440-
showToast(t("invite_link_copied"), "success");
441-
}
442-
} catch (e) {
443-
showToast(t("something_went_wrong_on_our_end"), "error");
444-
console.error(e);
445-
}
446-
}}
458+
onClick={handleCopyInviteLink}
459+
loading={isCopying || createInviteMutation.isPending}
460+
disabled={isCopying || createInviteMutation.isPending}
447461
className={classNames("gap-2", props.token && "opacity-50")}
448462
StartIcon="link"
449463
data-testid="copy-invite-link-button">

0 commit comments

Comments
 (0)