refactor(forms): simplify form handling and button behavior (#10441)
Removed redundant handleSave and handleSubmit props in domain settings. Integrated form submission logic directly into form components, ensuring consistent behavior and reducing complexity. Updated button components to explicitly support the "type" attribute for improved accessibility and functionality. --------- Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
This commit is contained in:
@ -15,6 +15,7 @@ export const SaveButton = ({ onSave, disabled }: SaveButtonProps) => {
|
|||||||
accent="blue"
|
accent="blue"
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onClick={onSave}
|
onClick={onSave}
|
||||||
|
type="submit"
|
||||||
Icon={IconDeviceFloppy}
|
Icon={IconDeviceFloppy}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -34,16 +34,18 @@ const StyledDescription = styled.div`
|
|||||||
|
|
||||||
type SettingsRadioCardProps = {
|
type SettingsRadioCardProps = {
|
||||||
value: string;
|
value: string;
|
||||||
handleClick: (value: string) => void;
|
handleSelect: (value: string) => void;
|
||||||
isSelected: boolean;
|
isSelected: boolean;
|
||||||
title: string;
|
title: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
Icon?: IconComponent;
|
Icon?: IconComponent;
|
||||||
|
role?: string;
|
||||||
|
ariaChecked?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SettingsRadioCard = ({
|
export const SettingsRadioCard = ({
|
||||||
value,
|
value,
|
||||||
handleClick,
|
handleSelect,
|
||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
isSelected,
|
isSelected,
|
||||||
@ -51,8 +53,10 @@ export const SettingsRadioCard = ({
|
|||||||
}: SettingsRadioCardProps) => {
|
}: SettingsRadioCardProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
|
const onClick = () => handleSelect(value);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledRadioCardContent onClick={() => handleClick(value)}>
|
<StyledRadioCardContent tabIndex={0} onClick={onClick}>
|
||||||
{Icon && <Icon size={theme.icon.size.xl} color={theme.color.gray50} />}
|
{Icon && <Icon size={theme.icon.size.xl} color={theme.color.gray50} />}
|
||||||
<span>
|
<span>
|
||||||
{title && <StyledTitle>{title}</StyledTitle>}
|
{title && <StyledTitle>{title}</StyledTitle>}
|
||||||
|
|||||||
@ -25,16 +25,18 @@ export const SettingsRadioCardContainer = ({
|
|||||||
onChange,
|
onChange,
|
||||||
}: SettingsRadioCardContainerProps) => {
|
}: SettingsRadioCardContainerProps) => {
|
||||||
return (
|
return (
|
||||||
<StyledRadioCardContainer>
|
<StyledRadioCardContainer role="radiogroup">
|
||||||
{options.map((option) => (
|
{options.map((option) => (
|
||||||
<SettingsRadioCard
|
<SettingsRadioCard
|
||||||
key={option.value}
|
key={option.value}
|
||||||
|
role="radio"
|
||||||
value={option.value}
|
value={option.value}
|
||||||
isSelected={value === option.value}
|
isSelected={value === option.value}
|
||||||
handleClick={onChange}
|
handleSelect={onChange}
|
||||||
title={option.title}
|
title={option.title}
|
||||||
description={option.description}
|
description={option.description}
|
||||||
Icon={option.Icon}
|
Icon={option.Icon}
|
||||||
|
ariaChecked={value === option.value}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</StyledRadioCardContainer>
|
</StyledRadioCardContainer>
|
||||||
|
|||||||
@ -30,6 +30,7 @@ const StyledLinkContainer = styled.div`
|
|||||||
const StyledButtonCopy = styled.div`
|
const StyledButtonCopy = styled.div`
|
||||||
align-items: end;
|
align-items: end;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
margin-bottom: ${({ theme }) => theme.spacing(1)};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const SettingsSSOOIDCForm = () => {
|
export const SettingsSSOOIDCForm = () => {
|
||||||
@ -70,6 +71,7 @@ export const SettingsSSOOIDCForm = () => {
|
|||||||
});
|
});
|
||||||
navigator.clipboard.writeText(authorizedUrl);
|
navigator.clipboard.writeText(authorizedUrl);
|
||||||
}}
|
}}
|
||||||
|
type="button"
|
||||||
/>
|
/>
|
||||||
</StyledButtonCopy>
|
</StyledButtonCopy>
|
||||||
</StyledContainer>
|
</StyledContainer>
|
||||||
@ -94,6 +96,7 @@ export const SettingsSSOOIDCForm = () => {
|
|||||||
});
|
});
|
||||||
navigator.clipboard.writeText(redirectionUrl);
|
navigator.clipboard.writeText(redirectionUrl);
|
||||||
}}
|
}}
|
||||||
|
type="button"
|
||||||
/>
|
/>
|
||||||
</StyledButtonCopy>
|
</StyledButtonCopy>
|
||||||
</StyledContainer>
|
</StyledContainer>
|
||||||
|
|||||||
@ -52,6 +52,7 @@ const StyledLinkContainer = styled.div`
|
|||||||
const StyledButtonCopy = styled.div`
|
const StyledButtonCopy = styled.div`
|
||||||
align-items: end;
|
align-items: end;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
margin-bottom: ${({ theme }) => theme.spacing(1)};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const SettingsSSOSAMLForm = () => {
|
export const SettingsSSOSAMLForm = () => {
|
||||||
@ -136,6 +137,7 @@ export const SettingsSSOSAMLForm = () => {
|
|||||||
Icon={IconUpload}
|
Icon={IconUpload}
|
||||||
onClick={handleUploadFileClick}
|
onClick={handleUploadFileClick}
|
||||||
title={t`Upload file`}
|
title={t`Upload file`}
|
||||||
|
type="button"
|
||||||
></Button>
|
></Button>
|
||||||
{isXMLMetadataValid() && (
|
{isXMLMetadataValid() && (
|
||||||
<IconCheck
|
<IconCheck
|
||||||
@ -157,7 +159,8 @@ export const SettingsSSOSAMLForm = () => {
|
|||||||
Icon={IconDownload}
|
Icon={IconDownload}
|
||||||
onClick={downloadMetadata}
|
onClick={downloadMetadata}
|
||||||
title={t`Download file`}
|
title={t`Download file`}
|
||||||
></Button>
|
type="button"
|
||||||
|
/>
|
||||||
</StyledContainer>
|
</StyledContainer>
|
||||||
<HorizontalSeparator text={'Or'} />
|
<HorizontalSeparator text={'Or'} />
|
||||||
<StyledContainer>
|
<StyledContainer>
|
||||||
@ -181,6 +184,7 @@ export const SettingsSSOSAMLForm = () => {
|
|||||||
});
|
});
|
||||||
navigator.clipboard.writeText(acsUrl);
|
navigator.clipboard.writeText(acsUrl);
|
||||||
}}
|
}}
|
||||||
|
type="button"
|
||||||
/>
|
/>
|
||||||
</StyledButtonCopy>
|
</StyledButtonCopy>
|
||||||
</StyledContainer>
|
</StyledContainer>
|
||||||
@ -205,6 +209,7 @@ export const SettingsSSOSAMLForm = () => {
|
|||||||
});
|
});
|
||||||
navigator.clipboard.writeText(entityID);
|
navigator.clipboard.writeText(entityID);
|
||||||
}}
|
}}
|
||||||
|
type="button"
|
||||||
/>
|
/>
|
||||||
</StyledButtonCopy>
|
</StyledButtonCopy>
|
||||||
</StyledContainer>
|
</StyledContainer>
|
||||||
|
|||||||
@ -23,7 +23,7 @@ export const SettingsSecurityApprovedAccessDomain = () => {
|
|||||||
|
|
||||||
const [createApprovedAccessDomain] = useCreateApprovedAccessDomainMutation();
|
const [createApprovedAccessDomain] = useCreateApprovedAccessDomainMutation();
|
||||||
|
|
||||||
const formConfig = useForm<{ domain: string; email: string }>({
|
const form = useForm<{ domain: string; email: string }>({
|
||||||
mode: 'onSubmit',
|
mode: 'onSubmit',
|
||||||
resolver: zodResolver(
|
resolver: zodResolver(
|
||||||
z
|
z
|
||||||
@ -49,21 +49,18 @@ export const SettingsSecurityApprovedAccessDomain = () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const domain = formConfig.watch('domain');
|
const domain = form.watch('domain');
|
||||||
|
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
try {
|
try {
|
||||||
if (!formConfig.formState.isValid) {
|
if (!form.formState.isValid || !form.formState.isSubmitting) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
createApprovedAccessDomain({
|
createApprovedAccessDomain({
|
||||||
variables: {
|
variables: {
|
||||||
input: {
|
input: {
|
||||||
domain: formConfig.getValues('domain'),
|
domain: form.getValues('domain'),
|
||||||
email:
|
email: form.getValues('email') + '@' + form.getValues('domain'),
|
||||||
formConfig.getValues('email') +
|
|
||||||
'@' +
|
|
||||||
formConfig.getValues('domain'),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
onCompleted: () => {
|
onCompleted: () => {
|
||||||
@ -86,67 +83,78 @@ export const SettingsSecurityApprovedAccessDomain = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SubMenuTopBarContainer
|
<form onSubmit={form.handleSubmit(handleSave)}>
|
||||||
title="New Approved Access Domain"
|
<SubMenuTopBarContainer
|
||||||
actionButton={
|
title="New Approved Access Domain"
|
||||||
<SaveAndCancelButtons
|
actionButton={
|
||||||
onCancel={() => navigate(SettingsPath.Security)}
|
<SaveAndCancelButtons
|
||||||
onSave={formConfig.handleSubmit(handleSave)}
|
onCancel={() => navigate(SettingsPath.Security)}
|
||||||
/>
|
isSaveDisabled={form.formState.isSubmitting}
|
||||||
}
|
|
||||||
links={[
|
|
||||||
{
|
|
||||||
children: <Trans>Workspace</Trans>,
|
|
||||||
href: getSettingsPath(SettingsPath.Workspace),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
children: <Trans>Security</Trans>,
|
|
||||||
href: getSettingsPath(SettingsPath.Security),
|
|
||||||
},
|
|
||||||
{ children: <Trans>New Approved Access Domain</Trans> },
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<SettingsPageContainer>
|
|
||||||
<Section>
|
|
||||||
<H2Title title={t`Domain`} description={t`The name of your Domain`} />
|
|
||||||
<Controller
|
|
||||||
name="domain"
|
|
||||||
control={formConfig.control}
|
|
||||||
render={({ field: { onChange, value }, fieldState: { error } }) => (
|
|
||||||
<TextInput
|
|
||||||
autoComplete="off"
|
|
||||||
value={value}
|
|
||||||
onChange={(domain: string) => {
|
|
||||||
onChange(domain);
|
|
||||||
}}
|
|
||||||
fullWidth
|
|
||||||
placeholder="yourdomain.com"
|
|
||||||
error={error?.message}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
</Section>
|
}
|
||||||
<Section>
|
links={[
|
||||||
<H2Title
|
{
|
||||||
title={t`Email verification`}
|
children: <Trans>Workspace</Trans>,
|
||||||
description={t`We will send your a link to verify domain ownership`}
|
href: getSettingsPath(SettingsPath.Workspace),
|
||||||
/>
|
},
|
||||||
<Controller
|
{
|
||||||
name="email"
|
children: <Trans>Security</Trans>,
|
||||||
control={formConfig.control}
|
href: getSettingsPath(SettingsPath.Security),
|
||||||
render={({ field: { onChange, value }, fieldState: { error } }) => (
|
},
|
||||||
<TextInput
|
{ children: <Trans>New Approved Access Domain</Trans> },
|
||||||
autoComplete="off"
|
]}
|
||||||
value={value.split('@')[0]}
|
>
|
||||||
onChange={onChange}
|
<SettingsPageContainer>
|
||||||
fullWidth
|
<Section>
|
||||||
error={error?.message}
|
<H2Title
|
||||||
/>
|
title={t`Domain`}
|
||||||
)}
|
description={t`The name of your Domain`}
|
||||||
/>
|
/>
|
||||||
{domain}
|
<Controller
|
||||||
</Section>
|
name="domain"
|
||||||
</SettingsPageContainer>
|
control={form.control}
|
||||||
</SubMenuTopBarContainer>
|
render={({
|
||||||
|
field: { onChange, value },
|
||||||
|
fieldState: { error },
|
||||||
|
}) => (
|
||||||
|
<TextInput
|
||||||
|
autoComplete="off"
|
||||||
|
value={value}
|
||||||
|
onChange={(domain: string) => {
|
||||||
|
onChange(domain);
|
||||||
|
}}
|
||||||
|
fullWidth
|
||||||
|
placeholder="yourdomain.com"
|
||||||
|
error={error?.message}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Section>
|
||||||
|
<Section>
|
||||||
|
<H2Title
|
||||||
|
title={t`Email verification`}
|
||||||
|
description={t`We will send your a link to verify domain ownership`}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
name="email"
|
||||||
|
control={form.control}
|
||||||
|
render={({
|
||||||
|
field: { onChange, value },
|
||||||
|
fieldState: { error },
|
||||||
|
}) => (
|
||||||
|
<TextInput
|
||||||
|
autoComplete="off"
|
||||||
|
value={value.split('@')[0]}
|
||||||
|
onChange={onChange}
|
||||||
|
fullWidth
|
||||||
|
error={error?.message}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{domain}
|
||||||
|
</Section>
|
||||||
|
</SettingsPageContainer>
|
||||||
|
</SubMenuTopBarContainer>
|
||||||
|
</form>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -24,8 +24,8 @@ export const SettingsSecuritySSOIdentifyProvider = () => {
|
|||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueSnackBar } = useSnackBar();
|
||||||
const { createSSOIdentityProvider } = useCreateSSOIdentityProvider();
|
const { createSSOIdentityProvider } = useCreateSSOIdentityProvider();
|
||||||
|
|
||||||
const formConfig = useForm<SettingSecurityNewSSOIdentityFormValues>({
|
const form = useForm<SettingSecurityNewSSOIdentityFormValues>({
|
||||||
mode: 'onChange',
|
mode: 'onSubmit',
|
||||||
resolver: zodResolver(SSOIdentitiesProvidersParamsSchema),
|
resolver: zodResolver(SSOIdentitiesProvidersParamsSchema),
|
||||||
defaultValues: Object.values(sSOIdentityProviderDefaultValues).reduce(
|
defaultValues: Object.values(sSOIdentityProviderDefaultValues).reduce(
|
||||||
(acc, fn) => ({ ...acc, ...fn() }),
|
(acc, fn) => ({ ...acc, ...fn() }),
|
||||||
@ -35,12 +35,12 @@ export const SettingsSecuritySSOIdentifyProvider = () => {
|
|||||||
|
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
try {
|
try {
|
||||||
const type = formConfig.getValues('type');
|
const type = form.getValues('type');
|
||||||
|
|
||||||
await createSSOIdentityProvider(
|
await createSSOIdentityProvider(
|
||||||
SSOIdentitiesProvidersParamsSchema.parse(
|
SSOIdentitiesProvidersParamsSchema.parse(
|
||||||
pick(
|
pick(
|
||||||
formConfig.getValues(),
|
form.getValues(),
|
||||||
Object.keys(sSOIdentityProviderDefaultValues[type]()),
|
Object.keys(sSOIdentityProviderDefaultValues[type]()),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -55,33 +55,34 @@ export const SettingsSecuritySSOIdentifyProvider = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SubMenuTopBarContainer
|
<form onSubmit={form.handleSubmit(handleSave)}>
|
||||||
title={t`New SSO Configuration`}
|
|
||||||
actionButton={
|
|
||||||
<SaveAndCancelButtons
|
|
||||||
isSaveDisabled={!formConfig.formState.isValid}
|
|
||||||
onCancel={() => navigate(SettingsPath.Security)}
|
|
||||||
onSave={handleSave}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
links={[
|
|
||||||
{
|
|
||||||
children: <Trans>Workspace</Trans>,
|
|
||||||
href: getSettingsPath(SettingsPath.Workspace),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
children: <Trans>Security</Trans>,
|
|
||||||
href: getSettingsPath(SettingsPath.Security),
|
|
||||||
},
|
|
||||||
{ children: <Trans>New SSO provider</Trans> },
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<FormProvider
|
<FormProvider
|
||||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
{...formConfig}
|
{...form}
|
||||||
>
|
>
|
||||||
<SettingsSSOIdentitiesProvidersForm />
|
<SubMenuTopBarContainer
|
||||||
|
title={t`New SSO Configuration`}
|
||||||
|
actionButton={
|
||||||
|
<SaveAndCancelButtons
|
||||||
|
onCancel={() => navigate(SettingsPath.Security)}
|
||||||
|
isSaveDisabled={form.formState.isSubmitting}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
links={[
|
||||||
|
{
|
||||||
|
children: <Trans>Workspace</Trans>,
|
||||||
|
href: getSettingsPath(SettingsPath.Workspace),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
children: <Trans>Security</Trans>,
|
||||||
|
href: getSettingsPath(SettingsPath.Security),
|
||||||
|
},
|
||||||
|
{ children: <Trans>New SSO provider</Trans> },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<SettingsSSOIdentitiesProvidersForm />
|
||||||
|
</SubMenuTopBarContainer>
|
||||||
</FormProvider>
|
</FormProvider>
|
||||||
</SubMenuTopBarContainer>
|
</form>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -23,11 +23,7 @@ const StyledRecordsWrapper = styled.div`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const SettingsCustomDomain = ({
|
export const SettingsCustomDomain = () => {
|
||||||
handleSave,
|
|
||||||
}: {
|
|
||||||
handleSave: () => void;
|
|
||||||
}) => {
|
|
||||||
const { customDomainRecords, loading } = useRecoilValue(
|
const { customDomainRecords, loading } = useRecoilValue(
|
||||||
customDomainRecordsState,
|
customDomainRecordsState,
|
||||||
);
|
);
|
||||||
@ -36,7 +32,7 @@ export const SettingsCustomDomain = ({
|
|||||||
|
|
||||||
const { t } = useLingui();
|
const { t } = useLingui();
|
||||||
|
|
||||||
const { control, handleSubmit } = useFormContext<{
|
const { control } = useFormContext<{
|
||||||
customDomain: string;
|
customDomain: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
@ -57,11 +53,6 @@ export const SettingsCustomDomain = ({
|
|||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
placeholder="crm.yourdomain.com"
|
placeholder="crm.yourdomain.com"
|
||||||
error={error?.message}
|
error={error?.message}
|
||||||
onKeyDown={(e) => {
|
|
||||||
if (e.key === 'Enter') {
|
|
||||||
handleSubmit(handleSave);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
loading={!!loading}
|
loading={!!loading}
|
||||||
fullWidth
|
fullWidth
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -7,9 +7,10 @@ import { TableHeader } from '@/ui/layout/table/components/TableHeader';
|
|||||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { useLingui } from '@lingui/react/macro';
|
import { useLingui } from '@lingui/react/macro';
|
||||||
import { Button } from 'twenty-ui';
|
import { Button, IconCopy } from 'twenty-ui';
|
||||||
import { useDebouncedCallback } from 'use-debounce';
|
import { useDebouncedCallback } from 'use-debounce';
|
||||||
import { CustomDomainValidRecords } from '~/generated/graphql';
|
import { CustomDomainValidRecords } from '~/generated/graphql';
|
||||||
|
import { useTheme } from '@emotion/react';
|
||||||
|
|
||||||
const StyledTable = styled(Table)`
|
const StyledTable = styled(Table)`
|
||||||
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
|
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
|
||||||
@ -42,12 +43,15 @@ export const SettingsCustomDomainRecords = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueSnackBar } = useSnackBar();
|
||||||
|
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
const { t } = useLingui();
|
const { t } = useLingui();
|
||||||
|
|
||||||
const copyToClipboard = (value: string) => {
|
const copyToClipboard = (value: string) => {
|
||||||
navigator.clipboard.writeText(value);
|
navigator.clipboard.writeText(value);
|
||||||
enqueueSnackBar(t`Copied to clipboard!`, {
|
enqueueSnackBar(t`Copied to clipboard!`, {
|
||||||
variant: SnackBarVariant.Success,
|
variant: SnackBarVariant.Success,
|
||||||
|
icon: <IconCopy size={theme.icon.size.md} />,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -67,6 +71,7 @@ export const SettingsCustomDomainRecords = ({
|
|||||||
<StyledButton
|
<StyledButton
|
||||||
title={record.key}
|
title={record.key}
|
||||||
onClick={() => copyToClipboardDebounced(record.key)}
|
onClick={() => copyToClipboardDebounced(record.key)}
|
||||||
|
type="button"
|
||||||
/>
|
/>
|
||||||
</StyledTableCell>
|
</StyledTableCell>
|
||||||
<StyledTableCell>
|
<StyledTableCell>
|
||||||
@ -75,12 +80,14 @@ export const SettingsCustomDomainRecords = ({
|
|||||||
onClick={() =>
|
onClick={() =>
|
||||||
copyToClipboardDebounced(record.type.toUpperCase())
|
copyToClipboardDebounced(record.type.toUpperCase())
|
||||||
}
|
}
|
||||||
|
type="button"
|
||||||
/>
|
/>
|
||||||
</StyledTableCell>
|
</StyledTableCell>
|
||||||
<StyledTableCell>
|
<StyledTableCell>
|
||||||
<StyledButton
|
<StyledButton
|
||||||
title={record.value}
|
title={record.value}
|
||||||
onClick={() => copyToClipboardDebounced(record.value)}
|
onClick={() => copyToClipboardDebounced(record.value)}
|
||||||
|
type="button"
|
||||||
/>
|
/>
|
||||||
</StyledTableCell>
|
</StyledTableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
|
|||||||
@ -107,6 +107,9 @@ export const SettingsDomain = () => {
|
|||||||
customDomain:
|
customDomain:
|
||||||
customDomain && customDomain.length > 0 ? customDomain : null,
|
customDomain && customDomain.length > 0 ? customDomain : null,
|
||||||
});
|
});
|
||||||
|
enqueueSnackBar(t`Custom domain updated`, {
|
||||||
|
variant: SnackBarVariant.Success,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
if (
|
if (
|
||||||
@ -161,6 +164,10 @@ export const SettingsDomain = () => {
|
|||||||
subdomain,
|
subdomain,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
enqueueSnackBar(t`Subdomain updated`, {
|
||||||
|
variant: SnackBarVariant.Success,
|
||||||
|
});
|
||||||
|
|
||||||
redirectToWorkspaceDomain(currentUrl.toString());
|
redirectToWorkspaceDomain(currentUrl.toString());
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -169,12 +176,6 @@ export const SettingsDomain = () => {
|
|||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
const values = form.getValues();
|
const values = form.getValues();
|
||||||
|
|
||||||
if (!values || !form.formState.isValid || !currentWorkspace) {
|
|
||||||
return enqueueSnackBar(t`Invalid form values`, {
|
|
||||||
variant: SnackBarVariant.Error,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
subdomainValue === currentWorkspace?.subdomain &&
|
subdomainValue === currentWorkspace?.subdomain &&
|
||||||
customDomainValue === currentWorkspace?.customDomain
|
customDomainValue === currentWorkspace?.customDomain
|
||||||
@ -184,6 +185,12 @@ export const SettingsDomain = () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!values || !currentWorkspace) {
|
||||||
|
return enqueueSnackBar(t`Invalid form values`, {
|
||||||
|
variant: SnackBarVariant.Error,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
isDefined(values.subdomain) &&
|
isDefined(values.subdomain) &&
|
||||||
values.subdomain !== currentWorkspace.subdomain
|
values.subdomain !== currentWorkspace.subdomain
|
||||||
@ -197,38 +204,40 @@ export const SettingsDomain = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SubMenuTopBarContainer
|
<form onSubmit={form.handleSubmit(handleSave)}>
|
||||||
title={t`Domain`}
|
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
|
||||||
links={[
|
<FormProvider {...form}>
|
||||||
{
|
<SubMenuTopBarContainer
|
||||||
children: <Trans>Workspace</Trans>,
|
title={t`Domain`}
|
||||||
href: getSettingsPath(SettingsPath.Workspace),
|
links={[
|
||||||
},
|
{
|
||||||
{
|
children: <Trans>Workspace</Trans>,
|
||||||
children: <Trans>General</Trans>,
|
href: getSettingsPath(SettingsPath.Workspace),
|
||||||
href: getSettingsPath(SettingsPath.Workspace),
|
},
|
||||||
},
|
{
|
||||||
{ children: <Trans>Domain</Trans> },
|
children: <Trans>General</Trans>,
|
||||||
]}
|
href: getSettingsPath(SettingsPath.Workspace),
|
||||||
actionButton={
|
},
|
||||||
<SaveAndCancelButtons
|
{ children: <Trans>Domain</Trans> },
|
||||||
onCancel={() => navigate(SettingsPath.Workspace)}
|
]}
|
||||||
onSave={form.handleSubmit(handleSave)}
|
actionButton={
|
||||||
/>
|
<SaveAndCancelButtons
|
||||||
}
|
onCancel={() => navigate(SettingsPath.Workspace)}
|
||||||
>
|
isSaveDisabled={form.formState.isSubmitting}
|
||||||
<SettingsPageContainer>
|
/>
|
||||||
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
|
}
|
||||||
<FormProvider {...form}>
|
>
|
||||||
<SettingsSubdomain handleSave={handleSave} />
|
<SettingsPageContainer>
|
||||||
{isCustomDomainEnabled && (
|
<SettingsSubdomain />
|
||||||
<>
|
{isCustomDomainEnabled && (
|
||||||
<SettingsCustomDomainEffect />
|
<>
|
||||||
<SettingsCustomDomain handleSave={handleSave} />
|
<SettingsCustomDomainEffect />
|
||||||
</>
|
<SettingsCustomDomain />
|
||||||
)}
|
</>
|
||||||
</FormProvider>
|
)}
|
||||||
</SettingsPageContainer>
|
</SettingsPageContainer>
|
||||||
</SubMenuTopBarContainer>
|
</SubMenuTopBarContainer>
|
||||||
|
</FormProvider>
|
||||||
|
</form>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -23,17 +23,13 @@ const StyledDomain = styled.h2`
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const SettingsSubdomain = ({
|
export const SettingsSubdomain = () => {
|
||||||
handleSave,
|
|
||||||
}: {
|
|
||||||
handleSave: () => void;
|
|
||||||
}) => {
|
|
||||||
const domainConfiguration = useRecoilValue(domainConfigurationState);
|
const domainConfiguration = useRecoilValue(domainConfigurationState);
|
||||||
const { t } = useLingui();
|
const { t } = useLingui();
|
||||||
|
|
||||||
const currentWorkspace = useRecoilValue(currentWorkspaceState);
|
const currentWorkspace = useRecoilValue(currentWorkspaceState);
|
||||||
|
|
||||||
const { control, handleSubmit } = useFormContext<{
|
const { control } = useFormContext<{
|
||||||
subdomain: string;
|
subdomain: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
@ -56,11 +52,6 @@ export const SettingsSubdomain = ({
|
|||||||
error={error?.message}
|
error={error?.message}
|
||||||
disabled={!!currentWorkspace?.customDomain}
|
disabled={!!currentWorkspace?.customDomain}
|
||||||
fullWidth
|
fullWidth
|
||||||
onKeyDown={(e) => {
|
|
||||||
if (e.key === 'Enter') {
|
|
||||||
handleSubmit(handleSave);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
{isDefined(domainConfiguration.frontDomain) && (
|
{isDefined(domainConfiguration.frontDomain) && (
|
||||||
<StyledDomain>
|
<StyledDomain>
|
||||||
|
|||||||
@ -408,18 +408,21 @@ export const Button = ({
|
|||||||
soon = false,
|
soon = false,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
justify = 'flex-start',
|
justify = 'flex-start',
|
||||||
focus = false,
|
focus: propFocus = false,
|
||||||
onClick,
|
onClick,
|
||||||
to,
|
to,
|
||||||
target,
|
target,
|
||||||
dataTestId,
|
dataTestId,
|
||||||
hotkeys,
|
hotkeys,
|
||||||
ariaLabel,
|
ariaLabel,
|
||||||
|
type,
|
||||||
}: ButtonProps) => {
|
}: ButtonProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
|
|
||||||
|
const [isFocused, setIsFocused] = React.useState(propFocus);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledButton
|
<StyledButton
|
||||||
fullWidth={fullWidth}
|
fullWidth={fullWidth}
|
||||||
@ -428,7 +431,7 @@ export const Button = ({
|
|||||||
size={size}
|
size={size}
|
||||||
position={position}
|
position={position}
|
||||||
disabled={soon || disabled}
|
disabled={soon || disabled}
|
||||||
focus={focus}
|
focus={isFocused}
|
||||||
justify={justify}
|
justify={justify}
|
||||||
accent={accent}
|
accent={accent}
|
||||||
className={className}
|
className={className}
|
||||||
@ -438,6 +441,9 @@ export const Button = ({
|
|||||||
target={target}
|
target={target}
|
||||||
data-testid={dataTestId}
|
data-testid={dataTestId}
|
||||||
aria-label={ariaLabel}
|
aria-label={ariaLabel}
|
||||||
|
type={type}
|
||||||
|
onFocus={() => setIsFocused(true)}
|
||||||
|
onBlur={() => setIsFocused(false)}
|
||||||
>
|
>
|
||||||
{Icon && <Icon size={theme.icon.size.sm} />}
|
{Icon && <Icon size={theme.icon.size.sm} />}
|
||||||
{title}
|
{title}
|
||||||
|
|||||||
@ -14,6 +14,7 @@ export type LightButtonProps = {
|
|||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
focus?: boolean;
|
focus?: boolean;
|
||||||
onClick?: (event: MouseEvent<HTMLButtonElement>) => void;
|
onClick?: (event: MouseEvent<HTMLButtonElement>) => void;
|
||||||
|
type?: React.ComponentProps<'button'>['type'];
|
||||||
};
|
};
|
||||||
|
|
||||||
const StyledButton = styled.button<
|
const StyledButton = styled.button<
|
||||||
@ -82,6 +83,7 @@ export const LightButton = ({
|
|||||||
accent = 'secondary',
|
accent = 'secondary',
|
||||||
disabled = false,
|
disabled = false,
|
||||||
focus = false,
|
focus = false,
|
||||||
|
type = 'button',
|
||||||
onClick,
|
onClick,
|
||||||
}: LightButtonProps) => {
|
}: LightButtonProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
@ -91,6 +93,7 @@ export const LightButton = ({
|
|||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
focus={focus && !disabled}
|
focus={focus && !disabled}
|
||||||
|
type={type}
|
||||||
accent={accent}
|
accent={accent}
|
||||||
className={className}
|
className={className}
|
||||||
active={active}
|
active={active}
|
||||||
|
|||||||
@ -141,6 +141,7 @@ export const Radio = ({
|
|||||||
id={optionId}
|
id={optionId}
|
||||||
name={name}
|
name={name}
|
||||||
data-testid="input-radio"
|
data-testid="input-radio"
|
||||||
|
tabIndex={-1}
|
||||||
checked={checked}
|
checked={checked}
|
||||||
value={value || label}
|
value={value || label}
|
||||||
radio-size={size}
|
radio-size={size}
|
||||||
|
|||||||
Reference in New Issue
Block a user