feat: SMTP Driver Integration (#12993)
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> Co-authored-by: Félix Malfait <felix.malfait@gmail.com> Co-authored-by: Félix Malfait <felix@twenty.com>
This commit is contained in:
+3
-13
@@ -4,21 +4,11 @@ import { SettingsPath } from '@/types/SettingsPath';
|
||||
|
||||
import { SettingsAccountsConnectedAccountsRowRightContainer } from '@/settings/accounts/components/SettingsAccountsConnectedAccountsRowRightContainer';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import {
|
||||
IconComponent,
|
||||
IconGoogle,
|
||||
IconMail,
|
||||
IconMicrosoft,
|
||||
} from 'twenty-ui/display';
|
||||
|
||||
import { SettingsConnectedAccountIcon } from '@/settings/accounts/components/SettingsConnectedAccountIcon';
|
||||
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||
import { SettingsListCard } from '../../components/SettingsListCard';
|
||||
|
||||
const ProviderIcons: { [k: string]: IconComponent } = {
|
||||
google: IconGoogle,
|
||||
microsoft: IconMicrosoft,
|
||||
imap: IconMail,
|
||||
};
|
||||
|
||||
export const SettingsAccountsConnectedAccountsListCard = ({
|
||||
accounts,
|
||||
loading,
|
||||
@@ -38,7 +28,7 @@ export const SettingsAccountsConnectedAccountsListCard = ({
|
||||
items={accounts}
|
||||
getItemLabel={(account) => account.handle}
|
||||
isLoading={loading}
|
||||
RowIconFn={(row) => ProviderIcons[row.provider]}
|
||||
RowIconFn={(row) => SettingsConnectedAccountIcon({ account: row })}
|
||||
RowRightComponent={({ item: account }) => (
|
||||
<SettingsAccountsConnectedAccountsRowRightContainer account={account} />
|
||||
)}
|
||||
|
||||
+354
@@ -0,0 +1,354 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { Control, Controller } from 'react-hook-form';
|
||||
|
||||
import { Select } from '@/ui/input/components/Select';
|
||||
import { TextInput } from '@/ui/input/components/TextInput';
|
||||
|
||||
import { ConnectionFormData } from '@/settings/accounts/hooks/useImapSmtpCaldavConnectionForm';
|
||||
import { H2Title } from 'twenty-ui/display';
|
||||
import { Section } from 'twenty-ui/layout';
|
||||
import { MOBILE_VIEWPORT } from 'twenty-ui/theme';
|
||||
|
||||
const StyledFormContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: ${({ theme }) => theme.spacing(6)};
|
||||
`;
|
||||
|
||||
const StyledConnectionSection = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledSectionHeader = styled.div`
|
||||
margin-bottom: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledSectionTitle = styled.h3`
|
||||
color: ${({ theme }) => theme.font.color.primary};
|
||||
font-size: ${({ theme }) => theme.font.size.md};
|
||||
font-weight: ${({ theme }) => theme.font.weight.medium};
|
||||
margin: 0;
|
||||
margin-bottom: ${({ theme }) => theme.spacing(1)};
|
||||
`;
|
||||
|
||||
const StyledSectionDescription = styled.p`
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
font-size: ${({ theme }) => theme.font.size.sm};
|
||||
margin: 0;
|
||||
`;
|
||||
|
||||
const StyledFieldRow = styled.div`
|
||||
display: flex;
|
||||
gap: ${({ theme }) => theme.spacing(3)};
|
||||
|
||||
@media (max-width: ${MOBILE_VIEWPORT}px) {
|
||||
flex-direction: column;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledFieldGroup = styled.div`
|
||||
flex: 1;
|
||||
|
||||
& > * {
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
|
||||
type SettingsAccountsConnectionFormProps = {
|
||||
control: Control<ConnectionFormData>;
|
||||
isEditing: boolean;
|
||||
};
|
||||
|
||||
export const SettingsAccountsConnectionForm = ({
|
||||
control,
|
||||
isEditing,
|
||||
}: SettingsAccountsConnectionFormProps) => {
|
||||
const { t } = useLingui();
|
||||
|
||||
const getTitle = () => {
|
||||
return isEditing ? t`Edit Email Account` : t`New Email Account`;
|
||||
};
|
||||
|
||||
const getDescription = () => {
|
||||
if (isEditing) {
|
||||
return t`Update your email account configuration. Configure any combination of IMAP, SMTP, and CalDAV as needed.`;
|
||||
}
|
||||
return t`You can set up any combination of IMAP (receiving emails), SMTP (sending emails), and CalDAV (calendar sync).`;
|
||||
};
|
||||
|
||||
const handlePortChange = (value: string) => Number(value);
|
||||
|
||||
return (
|
||||
<Section>
|
||||
<H2Title title={getTitle()} description={getDescription()} />
|
||||
<StyledFormContainer>
|
||||
<Controller
|
||||
name="handle"
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<TextInput
|
||||
instanceId="email-address-connection-form"
|
||||
label={t`Email Address`}
|
||||
placeholder={t`john.doe@example.com`}
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
error={fieldState.error?.message}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
<StyledConnectionSection>
|
||||
<StyledSectionHeader>
|
||||
<StyledSectionTitle>{t`IMAP Configuration`}</StyledSectionTitle>
|
||||
<StyledSectionDescription>
|
||||
{t`Configure IMAP settings to receive and sync your emails.`}
|
||||
<br />
|
||||
{t`Leave blank if you don't need to import emails.`}
|
||||
</StyledSectionDescription>
|
||||
</StyledSectionHeader>
|
||||
|
||||
<Controller
|
||||
name="IMAP.host"
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<TextInput
|
||||
instanceId="imap-host-connection-form"
|
||||
label={t`IMAP Server`}
|
||||
placeholder={t`imap.example.com`}
|
||||
value={field.value || ''}
|
||||
onChange={field.onChange}
|
||||
error={fieldState.error?.message}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
name="IMAP.password"
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<TextInput
|
||||
instanceId="imap-password-connection-form"
|
||||
label={t`IMAP Password`}
|
||||
placeholder={t`••••••••`}
|
||||
type="password"
|
||||
value={field.value || ''}
|
||||
onChange={field.onChange}
|
||||
error={fieldState.error?.message}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
<StyledFieldRow>
|
||||
<StyledFieldGroup>
|
||||
<Controller
|
||||
name="IMAP.port"
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<TextInput
|
||||
instanceId="imap-port-connection-form"
|
||||
label={t`IMAP Port`}
|
||||
type="number"
|
||||
placeholder="993"
|
||||
value={field?.value ? field.value : 993}
|
||||
onChange={(value) =>
|
||||
field.onChange(handlePortChange(value))
|
||||
}
|
||||
error={fieldState.error?.message}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</StyledFieldGroup>
|
||||
|
||||
<StyledFieldGroup>
|
||||
<Controller
|
||||
name="IMAP.secure"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<Select
|
||||
label={t`IMAP Encryption`}
|
||||
options={[
|
||||
{ label: 'SSL/TLS', value: true },
|
||||
{ label: 'None', value: false },
|
||||
]}
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
dropdownId="imap-secure-dropdown"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</StyledFieldGroup>
|
||||
</StyledFieldRow>
|
||||
</StyledConnectionSection>
|
||||
|
||||
<StyledConnectionSection>
|
||||
<StyledSectionHeader>
|
||||
<StyledSectionTitle>{t`SMTP Configuration`}</StyledSectionTitle>
|
||||
<StyledSectionDescription>
|
||||
{t`Configure SMTP settings to send emails from your account.`}
|
||||
<br />
|
||||
{t`Leave blank if you don't need to send emails.`}
|
||||
</StyledSectionDescription>
|
||||
</StyledSectionHeader>
|
||||
|
||||
<Controller
|
||||
name="SMTP.host"
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<TextInput
|
||||
instanceId="smtp-host-connection-form"
|
||||
label={t`SMTP Server`}
|
||||
placeholder={t`smtp.example.com`}
|
||||
value={field.value || ''}
|
||||
onChange={field.onChange}
|
||||
error={fieldState.error?.message}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
name="SMTP.password"
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<TextInput
|
||||
instanceId="smtp-password-connection-form"
|
||||
label={t`SMTP Password`}
|
||||
placeholder={t`••••••••`}
|
||||
type="password"
|
||||
value={field.value || ''}
|
||||
onChange={field.onChange}
|
||||
error={fieldState.error?.message}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
<StyledFieldRow>
|
||||
<StyledFieldGroup>
|
||||
<Controller
|
||||
name="SMTP.port"
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<TextInput
|
||||
instanceId="smtp-port-connection-form"
|
||||
label={t`SMTP Port`}
|
||||
type="number"
|
||||
placeholder="587"
|
||||
value={field?.value ? field.value : 587}
|
||||
onChange={(value) =>
|
||||
field.onChange(handlePortChange(value))
|
||||
}
|
||||
error={fieldState.error?.message}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</StyledFieldGroup>
|
||||
|
||||
<StyledFieldGroup>
|
||||
<Controller
|
||||
name="SMTP.secure"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<Select
|
||||
label={t`SMTP Encryption`}
|
||||
options={[
|
||||
{ label: 'SSL/TLS', value: true },
|
||||
{ label: 'STARTTLS', value: false },
|
||||
]}
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
dropdownId="smtp-secure-dropdown"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</StyledFieldGroup>
|
||||
</StyledFieldRow>
|
||||
</StyledConnectionSection>
|
||||
|
||||
<StyledConnectionSection>
|
||||
<StyledSectionHeader>
|
||||
<StyledSectionTitle>{t`CalDAV Configuration`}</StyledSectionTitle>
|
||||
<StyledSectionDescription>
|
||||
{t`Configure CalDAV settings to sync your calendar events.`}
|
||||
<br />
|
||||
{t`Leave blank if you don't need calendar sync.`}
|
||||
</StyledSectionDescription>
|
||||
</StyledSectionHeader>
|
||||
|
||||
<Controller
|
||||
name="CALDAV.host"
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<TextInput
|
||||
instanceId="caldav-host-connection-form"
|
||||
label={t`CalDAV Server`}
|
||||
placeholder={t`caldav.example.com`}
|
||||
value={field.value || ''}
|
||||
onChange={field.onChange}
|
||||
error={fieldState.error?.message}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
name="CALDAV.password"
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<TextInput
|
||||
instanceId="caldav-password-connection-form"
|
||||
label={t`CalDAV Password`}
|
||||
placeholder={t`••••••••`}
|
||||
type="password"
|
||||
value={field.value || ''}
|
||||
onChange={field.onChange}
|
||||
error={fieldState.error?.message}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
<StyledFieldRow>
|
||||
<StyledFieldGroup>
|
||||
<Controller
|
||||
name="CALDAV.port"
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<TextInput
|
||||
instanceId="caldav-port-connection-form"
|
||||
label={t`CalDAV Port`}
|
||||
type="number"
|
||||
placeholder="443"
|
||||
value={field?.value ? field.value : 443}
|
||||
onChange={(value) =>
|
||||
field.onChange(handlePortChange(value))
|
||||
}
|
||||
error={fieldState.error?.message}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</StyledFieldGroup>
|
||||
|
||||
<StyledFieldGroup>
|
||||
<Controller
|
||||
name="CALDAV.secure"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<Select
|
||||
label={t`CalDAV Encryption`}
|
||||
options={[
|
||||
{ label: 'SSL/TLS', value: true },
|
||||
{ label: 'None', value: false },
|
||||
]}
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
dropdownId="caldav-secure-dropdown"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</StyledFieldGroup>
|
||||
</StyledFieldRow>
|
||||
</StyledConnectionSection>
|
||||
</StyledFormContainer>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
+39
-39
@@ -1,93 +1,93 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { FormProvider } from 'react-hook-form';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import { SetttingsAccountsImapConnectionForm } from '@/settings/accounts/components/SetttingsAccountsImapConnectionForm';
|
||||
import { useConnectedImapSmtpCaldavAccount } from '@/settings/accounts/hooks/useConnectedImapSmtpCaldavAccount';
|
||||
import { useImapConnectionForm } from '@/settings/accounts/hooks/useImapConnectionForm';
|
||||
import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons';
|
||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
|
||||
import styled from '@emotion/styled';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
|
||||
import { Loader } from 'twenty-ui/feedback';
|
||||
|
||||
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
|
||||
|
||||
import { NotFound } from '~/pages/not-found/NotFound';
|
||||
import { useImapSmtpCaldavConnectionForm } from '../hooks/useImapSmtpCaldavConnectionForm';
|
||||
import { SettingsAccountsConnectionForm } from './SettingsAccountsConnectionForm';
|
||||
|
||||
const StyledLoadingContainer = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
height: 200px;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export const SettingsAccountsEditImapConnection = () => {
|
||||
export const SettingsAccountsEditImapSmtpCaldavConnection = () => {
|
||||
const { t } = useLingui();
|
||||
const navigate = useNavigateSettings();
|
||||
const { connectedAccountId } = useParams<{ connectedAccountId: string }>();
|
||||
|
||||
const { connectedAccount, loading: accountLoading } =
|
||||
useConnectedImapSmtpCaldavAccount(connectedAccountId);
|
||||
|
||||
const initialData = {
|
||||
handle: connectedAccount?.handle || '',
|
||||
host: connectedAccount?.connectionParameters?.IMAP?.host || '',
|
||||
port: connectedAccount?.connectionParameters?.IMAP?.port || 993,
|
||||
secure: connectedAccount?.connectionParameters?.IMAP?.secure ?? true,
|
||||
password: connectedAccount?.connectionParameters?.IMAP?.password || '',
|
||||
};
|
||||
|
||||
const { formMethods, handleSave, handleSubmit, canSave, isSubmitting } =
|
||||
useImapConnectionForm({
|
||||
initialData,
|
||||
isEditing: true,
|
||||
connectedAccountId,
|
||||
});
|
||||
const {
|
||||
formMethods,
|
||||
handleSave,
|
||||
handleSubmit,
|
||||
canSave,
|
||||
isSubmitting,
|
||||
loading,
|
||||
connectedAccount,
|
||||
} = useImapSmtpCaldavConnectionForm({
|
||||
isEditing: true,
|
||||
connectedAccountId,
|
||||
});
|
||||
|
||||
const { control } = formMethods;
|
||||
|
||||
const renderLoadingState = () => (
|
||||
<StyledLoadingContainer>
|
||||
<Loader />
|
||||
</StyledLoadingContainer>
|
||||
);
|
||||
if (loading && !connectedAccount) {
|
||||
return (
|
||||
<StyledLoadingContainer>
|
||||
<Loader />
|
||||
</StyledLoadingContainer>
|
||||
);
|
||||
}
|
||||
|
||||
if (!connectedAccount && !loading) {
|
||||
return <NotFound />;
|
||||
}
|
||||
|
||||
const renderForm = () => (
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
<FormProvider {...formMethods}>
|
||||
<SubMenuTopBarContainer
|
||||
title={t`Edit IMAP Connection`}
|
||||
title={t`Edit Email Account`}
|
||||
links={[
|
||||
{
|
||||
children: t`Settings`,
|
||||
children: t`Workspace`,
|
||||
href: getSettingsPath(SettingsPath.Workspace),
|
||||
},
|
||||
{
|
||||
children: t`Email Connections`,
|
||||
children: t`Accounts`,
|
||||
href: getSettingsPath(SettingsPath.Accounts),
|
||||
},
|
||||
{ children: t`Edit IMAP Connection` },
|
||||
{ children: t`Edit Email Account` },
|
||||
]}
|
||||
actionButton={
|
||||
<SaveAndCancelButtons
|
||||
isSaveDisabled={!canSave}
|
||||
isCancelDisabled={isSubmitting}
|
||||
isLoading={loading}
|
||||
onCancel={() => navigate(SettingsPath.Accounts)}
|
||||
onSave={handleSubmit(handleSave)}
|
||||
onSave={handleSubmit((data) => handleSave(data))}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<SettingsPageContainer>
|
||||
<SetttingsAccountsImapConnectionForm control={control} isEditing />
|
||||
<SettingsAccountsConnectionForm control={control} isEditing />
|
||||
</SettingsPageContainer>
|
||||
</SubMenuTopBarContainer>
|
||||
</FormProvider>
|
||||
);
|
||||
|
||||
if (accountLoading === true) {
|
||||
return renderLoadingState();
|
||||
}
|
||||
|
||||
return renderForm();
|
||||
};
|
||||
+8
-6
@@ -9,7 +9,7 @@ import styled from '@emotion/styled';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { ConnectedAccountProvider } from 'twenty-shared/types';
|
||||
import { IconGoogle, IconMail, IconMicrosoft } from 'twenty-ui/display';
|
||||
import { IconAt, IconGoogle, IconMicrosoft } from 'twenty-ui/display';
|
||||
import { Button } from 'twenty-ui/input';
|
||||
import { Card, CardContent, CardHeader } from 'twenty-ui/layout';
|
||||
import { FeatureFlagKey } from '~/generated-metadata/graphql';
|
||||
@@ -51,7 +51,9 @@ export const SettingsAccountsListEmptyStateCard = ({
|
||||
isMicrosoftCalendarEnabledState,
|
||||
);
|
||||
|
||||
const isImapEnabled = useIsFeatureEnabled(FeatureFlagKey.IS_IMAP_ENABLED);
|
||||
const isImapSmtpCaldavFeatureFlagEnabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IS_IMAP_SMTP_CALDAV_ENABLED,
|
||||
);
|
||||
|
||||
return (
|
||||
<Card>
|
||||
@@ -75,12 +77,12 @@ export const SettingsAccountsListEmptyStateCard = ({
|
||||
/>
|
||||
)}
|
||||
|
||||
{isImapEnabled && (
|
||||
{isImapSmtpCaldavFeatureFlagEnabled && (
|
||||
<Button
|
||||
Icon={IconMail}
|
||||
title={t`Connect with IMAP`}
|
||||
Icon={IconAt}
|
||||
title={t`Connect Email Account`}
|
||||
variant="secondary"
|
||||
to={getSettingsPath(SettingsPath.NewImapConnection)}
|
||||
to={getSettingsPath(SettingsPath.NewImapSmtpCaldavConnection)}
|
||||
/>
|
||||
)}
|
||||
</StyledBody>
|
||||
|
||||
+3
@@ -44,6 +44,9 @@ export const SettingsAccountsMessageChannelsContainer = () => {
|
||||
connectedAccountId: {
|
||||
in: accounts.map((account) => account.id),
|
||||
},
|
||||
isSyncEnabled: {
|
||||
eq: true,
|
||||
},
|
||||
},
|
||||
skip: !accounts.length,
|
||||
});
|
||||
|
||||
+21
-15
@@ -1,21 +1,29 @@
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { FormProvider } from 'react-hook-form';
|
||||
|
||||
import { SetttingsAccountsImapConnectionForm } from '@/settings/accounts/components/SetttingsAccountsImapConnectionForm';
|
||||
import { useImapConnectionForm } from '@/settings/accounts/hooks/useImapConnectionForm';
|
||||
import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons';
|
||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
|
||||
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
|
||||
|
||||
export const SettingsAccountsNewImapConnection = () => {
|
||||
import { SettingsAccountsConnectionForm } from '@/settings/accounts/components/SettingsAccountsConnectionForm';
|
||||
import { useImapSmtpCaldavConnectionForm } from '../hooks/useImapSmtpCaldavConnectionForm';
|
||||
|
||||
export const SettingsAccountsNewImapSmtpCaldavConnection = () => {
|
||||
const { t } = useLingui();
|
||||
const navigate = useNavigateSettings();
|
||||
|
||||
const { formMethods, handleSave, handleSubmit, canSave, isSubmitting } =
|
||||
useImapConnectionForm();
|
||||
const {
|
||||
formMethods,
|
||||
handleSave,
|
||||
handleSubmit,
|
||||
canSave,
|
||||
isSubmitting,
|
||||
loading,
|
||||
} = useImapSmtpCaldavConnectionForm({});
|
||||
|
||||
const { control } = formMethods;
|
||||
|
||||
@@ -23,32 +31,30 @@ export const SettingsAccountsNewImapConnection = () => {
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
<FormProvider {...formMethods}>
|
||||
<SubMenuTopBarContainer
|
||||
title={t`New IMAP Connection`}
|
||||
title={t`New Email Account`}
|
||||
links={[
|
||||
{
|
||||
children: t`Settings`,
|
||||
children: t`Workspace`,
|
||||
href: getSettingsPath(SettingsPath.Workspace),
|
||||
},
|
||||
{
|
||||
children: t`Email Connections`,
|
||||
children: t`Accounts`,
|
||||
href: getSettingsPath(SettingsPath.Accounts),
|
||||
},
|
||||
{ children: t`New IMAP Connection` },
|
||||
{ children: t`New Email Account` },
|
||||
]}
|
||||
actionButton={
|
||||
<SaveAndCancelButtons
|
||||
isSaveDisabled={!canSave}
|
||||
isCancelDisabled={isSubmitting}
|
||||
isLoading={loading}
|
||||
onCancel={() => navigate(SettingsPath.Accounts)}
|
||||
onSave={handleSubmit(handleSave)}
|
||||
onSave={handleSubmit((data) => handleSave(data))}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<SettingsPageContainer>
|
||||
<SetttingsAccountsImapConnectionForm
|
||||
control={control}
|
||||
isEditing={false}
|
||||
/>
|
||||
<SettingsAccountsConnectionForm control={control} isEditing={false} />
|
||||
</SettingsPageContainer>
|
||||
</SubMenuTopBarContainer>
|
||||
</FormProvider>
|
||||
+15
@@ -10,11 +10,13 @@ import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
|
||||
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
|
||||
import { useModal } from '@/ui/layout/modal/hooks/useModal';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import { ConnectedAccountProvider } from 'twenty-shared/types';
|
||||
import {
|
||||
IconCalendarEvent,
|
||||
IconDotsVertical,
|
||||
IconMail,
|
||||
IconRefresh,
|
||||
IconSettings,
|
||||
IconTrash,
|
||||
} from 'twenty-ui/display';
|
||||
import { LightIconButton } from 'twenty-ui/input';
|
||||
@@ -57,6 +59,19 @@ export const SettingsAccountsRowDropdownMenu = ({
|
||||
dropdownComponents={
|
||||
<DropdownContent>
|
||||
<DropdownMenuItemsContainer>
|
||||
{account.provider ===
|
||||
ConnectedAccountProvider.IMAP_SMTP_CALDAV && (
|
||||
<MenuItem
|
||||
text={t`Connection settings`}
|
||||
LeftIcon={IconSettings}
|
||||
onClick={() => {
|
||||
navigate(SettingsPath.EditImapSmtpCaldavConnection, {
|
||||
connectedAccountId: account.id,
|
||||
});
|
||||
closeDropdown(dropdownId);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<MenuItem
|
||||
LeftIcon={IconMail}
|
||||
text={t`Emails settings`}
|
||||
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
import { ConnectedAccount } from '@/accounts/types/ConnectedAccount';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import { ConnectedAccountProvider } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
IconAt,
|
||||
IconCalendarEvent,
|
||||
IconComponent,
|
||||
IconComponentProps,
|
||||
IconGoogle,
|
||||
IconMail,
|
||||
IconMicrosoft,
|
||||
IconSend,
|
||||
} from 'twenty-ui/display';
|
||||
|
||||
const ImapSmtpCaldavIcon = (
|
||||
props: IconComponentProps & { account: ConnectedAccount },
|
||||
) => {
|
||||
const theme = useTheme();
|
||||
const { account } = props;
|
||||
|
||||
const hasImap = isDefined(account.connectionParameters?.IMAP);
|
||||
const hasSmtp = isDefined(account.connectionParameters?.SMTP);
|
||||
const hasCaldav = isDefined(account.connectionParameters?.CALDAV);
|
||||
|
||||
let IconToShow: IconComponent;
|
||||
|
||||
if (hasImap && hasSmtp && hasCaldav) {
|
||||
IconToShow = IconAt;
|
||||
} else if (hasImap && hasCaldav) {
|
||||
IconToShow = IconAt;
|
||||
} else if (hasImap && hasSmtp) {
|
||||
IconToShow = IconMail;
|
||||
} else if (hasImap) {
|
||||
IconToShow = IconMail;
|
||||
} else if (hasSmtp) {
|
||||
IconToShow = IconSend;
|
||||
} else if (hasCaldav) {
|
||||
IconToShow = IconCalendarEvent;
|
||||
} else {
|
||||
IconToShow = IconMail;
|
||||
}
|
||||
|
||||
return (
|
||||
<IconToShow
|
||||
className={props.className}
|
||||
style={props.style}
|
||||
size={props.size}
|
||||
stroke={props.stroke}
|
||||
color={props.color || theme.font.color.primary}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const getIconForProvider = (account: ConnectedAccount): IconComponent => {
|
||||
switch (account.provider) {
|
||||
case ConnectedAccountProvider.IMAP_SMTP_CALDAV:
|
||||
return (props) => (
|
||||
<ImapSmtpCaldavIcon
|
||||
account={account}
|
||||
className={props.className}
|
||||
style={props.style}
|
||||
size={props.size}
|
||||
stroke={props.stroke}
|
||||
color={props.color}
|
||||
/>
|
||||
);
|
||||
case ConnectedAccountProvider.GOOGLE:
|
||||
return IconGoogle;
|
||||
case ConnectedAccountProvider.MICROSOFT:
|
||||
return IconMicrosoft;
|
||||
default:
|
||||
return IconMail;
|
||||
}
|
||||
};
|
||||
|
||||
export const SettingsConnectedAccountIcon = ({
|
||||
account,
|
||||
}: {
|
||||
account: ConnectedAccount;
|
||||
}): IconComponent => {
|
||||
return getIconForProvider(account);
|
||||
};
|
||||
-123
@@ -1,123 +0,0 @@
|
||||
import { Control, Controller } from 'react-hook-form';
|
||||
|
||||
import { Select } from '@/ui/input/components/Select';
|
||||
import { TextInput } from '@/ui/input/components/TextInput';
|
||||
import styled from '@emotion/styled';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { H2Title } from 'twenty-ui/display';
|
||||
import { Section } from 'twenty-ui/layout';
|
||||
import { ConnectionParameters } from '~/generated/graphql';
|
||||
|
||||
const StyledFormContainer = styled.form`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: ${({ theme }) => theme.spacing(4)};
|
||||
`;
|
||||
|
||||
type SetttingsAccountsImapConnectionFormProps = {
|
||||
control: Control<ConnectionParameters & { handle: string }>;
|
||||
isEditing: boolean;
|
||||
defaultValues?: Partial<ConnectionParameters & { handle: string }>;
|
||||
};
|
||||
|
||||
export const SetttingsAccountsImapConnectionForm = ({
|
||||
control,
|
||||
isEditing,
|
||||
defaultValues,
|
||||
}: SetttingsAccountsImapConnectionFormProps) => {
|
||||
const { t } = useLingui();
|
||||
|
||||
return (
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`IMAP Connection Details`}
|
||||
description={
|
||||
isEditing
|
||||
? t`Update your IMAP email account configuration`
|
||||
: t`Configure your IMAP email account`
|
||||
}
|
||||
/>
|
||||
<StyledFormContainer>
|
||||
<Controller
|
||||
name="handle"
|
||||
control={control}
|
||||
defaultValue={defaultValues?.handle}
|
||||
render={({ field, fieldState }) => (
|
||||
<TextInput
|
||||
instanceId="email-address-imap-connection-form"
|
||||
label={t`Email Address`}
|
||||
placeholder={t`john.doe@example.com`}
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
error={fieldState.error?.message}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="host"
|
||||
control={control}
|
||||
defaultValue={defaultValues?.host}
|
||||
render={({ field, fieldState }) => (
|
||||
<TextInput
|
||||
instanceId="host-imap-connection-form"
|
||||
label={t`IMAP Server`}
|
||||
placeholder={t`imap.example.com`}
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
error={fieldState.error?.message}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="port"
|
||||
control={control}
|
||||
defaultValue={defaultValues?.port ?? 993}
|
||||
render={({ field, fieldState }) => (
|
||||
<TextInput
|
||||
instanceId="port-imap-connection-form"
|
||||
label={t`IMAP Port`}
|
||||
type="number"
|
||||
placeholder={t`993`}
|
||||
value={field.value.toString()}
|
||||
onChange={(value) => field.onChange(Number(value))}
|
||||
error={fieldState.error?.message}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="secure"
|
||||
control={control}
|
||||
defaultValue={defaultValues?.secure}
|
||||
render={({ field }) => (
|
||||
<Select
|
||||
label={t`Encryption`}
|
||||
options={[
|
||||
{ label: 'SSL/TLS', value: true },
|
||||
{ label: 'None', value: false },
|
||||
]}
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
dropdownId="secure-dropdown"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="password"
|
||||
control={control}
|
||||
defaultValue={defaultValues?.password}
|
||||
render={({ field, fieldState }) => (
|
||||
<TextInput
|
||||
instanceId="password-imap-connection-form"
|
||||
label={t`Password`}
|
||||
placeholder={t`••••••••`}
|
||||
type="password"
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
error={fieldState.error?.message}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</StyledFormContainer>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user