refactor: to useCopyToClipboard to catch errors - when user has disable copy clipboard permission in browser (#13330)

as discussed here
https://github.com/twentyhq/twenty/issues/13292#issuecomment-3092215050
with @prastoin

- [x] introduce optional message param to `copyToClipboard` method in
`useCopyToClipboard` and refactor it across app, as in case if user has
disallowed clipboard permission in BROWSER unprotected clipboard access
breaks without catch.

- [x] Email copied to clipboard

- [x] run lingui extract
This commit is contained in:
Nabhag Motivaras
2025-07-30 14:43:55 +05:30
committed by GitHub
parent 00f0c68478
commit 316f2ec38c
13 changed files with 61 additions and 172 deletions
@@ -1,9 +1,7 @@
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { useLingui } from '@lingui/react/macro';
import { IconCopy, OverflowingTextWithTooltip } from 'twenty-ui/display';
import { OverflowingTextWithTooltip } from 'twenty-ui/display';
import { useDebouncedCallback } from 'use-debounce';
import { useCopyToClipboard } from '~/hooks/useCopyToClipboard';
type SettingsAdminConfigCopyableTextProps = {
text: string;
@@ -32,18 +30,10 @@ export const SettingsAdminConfigCopyableText = ({
multiline = false,
maxRows,
}: SettingsAdminConfigCopyableTextProps) => {
const { enqueueSuccessSnackBar } = useSnackBar();
const theme = useTheme();
const { t } = useLingui();
const { copyToClipboard } = useCopyToClipboard();
const copyToClipboardDebounced = useDebouncedCallback((value: string) => {
navigator.clipboard.writeText(value);
enqueueSuccessSnackBar({
message: t`Copied to clipboard!`,
options: {
icon: <IconCopy size={theme.icon.size.md} />,
},
});
copyToClipboard(value);
}, 200);
return (
@@ -1,12 +1,9 @@
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { TextInput } from '@/ui/input/components/TextInput';
import { useLingui } from '@lingui/react/macro';
import { IconCopy } from 'twenty-ui/display';
import { Button } from 'twenty-ui/input';
import { useCopyToClipboard } from '~/hooks/useCopyToClipboard';
const StyledContainer = styled.div`
display: flex;
@@ -21,10 +18,8 @@ const StyledLinkContainer = styled.div`
type ApiKeyInputProps = { apiKey: string };
export const ApiKeyInput = ({ apiKey }: ApiKeyInputProps) => {
const theme = useTheme();
const { t } = useLingui();
const { enqueueSuccessSnackBar } = useSnackBar();
const { copyToClipboard } = useCopyToClipboard();
return (
<StyledContainer>
<StyledLinkContainer>
@@ -34,14 +29,7 @@ export const ApiKeyInput = ({ apiKey }: ApiKeyInputProps) => {
Icon={IconCopy}
title={t`Copy`}
onClick={() => {
enqueueSuccessSnackBar({
message: t`API Key copied to clipboard`,
options: {
icon: <IconCopy size={theme.icon.size.md} />,
duration: 2000,
},
});
navigator.clipboard.writeText(apiKey);
copyToClipboard(apiKey, t`API Key copied to clipboard`);
}}
/>
</StyledContainer>
@@ -1,4 +1,3 @@
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { Select } from '@/ui/input/components/Select';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
@@ -7,6 +6,7 @@ import { useState } from 'react';
import { IconCopy, IconDatabase, IconSitemap } from 'twenty-ui/display';
import { Button, CodeEditor } from 'twenty-ui/input';
import { REACT_APP_SERVER_BASE_URL } from '~/config';
import { useCopyToClipboard } from '~/hooks/useCopyToClipboard';
const StyledWrapper = styled.div`
background-color: ${({ theme }) => theme.background.secondary};
@@ -57,8 +57,8 @@ const StyledEditorContainer = styled.div`
export const SettingsIntegrationMCP = () => {
const theme = useTheme();
const { enqueueSuccessSnackBar } = useSnackBar();
const { t } = useLingui();
const { copyToClipboard } = useCopyToClipboard();
const generateMcpContent = (pathSuffix: string, serverName: string) => {
return JSON.stringify(
@@ -125,14 +125,10 @@ export const SettingsIntegrationMCP = () => {
<Button
Icon={IconCopy}
onClick={() => {
enqueueSuccessSnackBar({
message: t`MCP Configuration copied to clipboard`,
options: {
icon: <IconCopy size={theme.icon.size.md} />,
duration: 2000,
},
});
navigator.clipboard.writeText(selectedOption.content);
copyToClipboard(
selectedOption.content,
t`MCP Configuration copied to clipboard`,
);
}}
type="button"
/>
@@ -1,8 +1,6 @@
/* @license Enterprise */
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { TextInput } from '@/ui/input/components/TextInput';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { useLingui } from '@lingui/react/macro';
import { Controller, useFormContext } from 'react-hook-form';
@@ -10,6 +8,7 @@ import { H2Title, IconCopy } from 'twenty-ui/display';
import { Button } from 'twenty-ui/input';
import { Section } from 'twenty-ui/layout';
import { REACT_APP_SERVER_BASE_URL } from '~/config';
import { useCopyToClipboard } from '~/hooks/useCopyToClipboard';
const StyledInputsContainer = styled.div`
display: flex;
@@ -36,8 +35,7 @@ const StyledButtonCopy = styled.div`
export const SettingsSSOOIDCForm = () => {
const { control } = useFormContext();
const { enqueueSuccessSnackBar } = useSnackBar();
const theme = useTheme();
const { copyToClipboard } = useCopyToClipboard();
const { t } = useLingui();
const authorizedUrl = window.location.origin;
@@ -66,14 +64,10 @@ export const SettingsSSOOIDCForm = () => {
Icon={IconCopy}
title={t`Copy`}
onClick={() => {
enqueueSuccessSnackBar({
message: t`Authorized URL copied to clipboard`,
options: {
icon: <IconCopy size={theme.icon.size.md} />,
duration: 2000,
},
});
navigator.clipboard.writeText(authorizedUrl);
copyToClipboard(
authorizedUrl,
t`Authorized URL copied to clipboard`,
);
}}
type="button"
/>
@@ -94,14 +88,10 @@ export const SettingsSSOOIDCForm = () => {
Icon={IconCopy}
title={t`Copy`}
onClick={() => {
enqueueSuccessSnackBar({
message: t`Redirect Url copied to clipboard`,
options: {
icon: <IconCopy size={theme.icon.size.md} />,
duration: 2000,
},
});
navigator.clipboard.writeText(redirectionUrl);
copyToClipboard(
redirectionUrl,
t`Redirect Url copied to clipboard`,
);
}}
type="button"
/>
@@ -20,6 +20,7 @@ import {
import { Button } from 'twenty-ui/input';
import { Section } from 'twenty-ui/layout';
import { REACT_APP_SERVER_BASE_URL } from '~/config';
import { useCopyToClipboard } from '~/hooks/useCopyToClipboard';
const StyledUploadFileContainer = styled.div`
align-items: center;
@@ -55,10 +56,11 @@ const StyledButtonCopy = styled.div`
`;
export const SettingsSSOSAMLForm = () => {
const { enqueueErrorSnackBar, enqueueSuccessSnackBar } = useSnackBar();
const { enqueueErrorSnackBar } = useSnackBar();
const theme = useTheme();
const { setValue, getValues, watch, trigger } = useFormContext();
const { t } = useLingui();
const { copyToClipboard } = useCopyToClipboard();
const handleFileChange = async (e: ChangeEvent<HTMLInputElement>) => {
if (isDefined(e.target.files)) {
@@ -179,16 +181,9 @@ export const SettingsSSOSAMLForm = () => {
<StyledButtonCopy>
<Button
Icon={IconCopy}
title="Copy"
title={t`Copy`}
onClick={() => {
enqueueSuccessSnackBar({
message: t`ACS Url copied to clipboard`,
options: {
icon: <IconCopy size={theme.icon.size.md} />,
duration: 2000,
},
});
navigator.clipboard.writeText(acsUrl);
copyToClipboard(acsUrl, t`ACS Url copied to clipboard`);
}}
type="button"
/>
@@ -209,14 +204,7 @@ export const SettingsSSOSAMLForm = () => {
Icon={IconCopy}
title={t`Copy`}
onClick={() => {
enqueueSuccessSnackBar({
message: t`Entity ID copied to clipboard`,
options: {
icon: <IconCopy size={theme.icon.size.md} />,
duration: 2000,
},
});
navigator.clipboard.writeText(entityID);
copyToClipboard(entityID, t`Entity ID copied to clipboard`);
}}
type="button"
/>