mirror of
https://github.com/twentyhq/twenty.git
synced 2024-11-23 22:12:24 +03:00
feat: add Settings/Accounts/Emails/Inbox Settings visibility section (#3077)
* feat: add Settings/Accounts/Emails/Inbox Settings page Closes #3013 * feat: add Settings/Accounts/Emails/Inbox Settings synchronization section Closes #3014 * feat: add Settings/Accounts/Emails/Inbox Settings visibility section Closes #3015 --------- Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
parent
5bbd1a7c49
commit
6c30556d00
@ -1 +1,8 @@
|
|||||||
export type Account = { email: string; isSynced?: boolean; uuid: string };
|
import { InboxSettingsVisibilityValue } from '@/settings/accounts/components/SettingsAccountsInboxSettingsVisibilitySection';
|
||||||
|
|
||||||
|
export type Account = {
|
||||||
|
email: string;
|
||||||
|
isSynced?: boolean;
|
||||||
|
uuid: string;
|
||||||
|
visibility: InboxSettingsVisibilityValue;
|
||||||
|
};
|
||||||
|
@ -3,10 +3,11 @@ import styled from '@emotion/styled';
|
|||||||
const StyledCardMedia = styled.div`
|
const StyledCardMedia = styled.div`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border: 2px solid ${({ theme }) => theme.border.color.medium};
|
border: 2px solid ${({ theme }) => theme.border.color.medium};
|
||||||
border-radius: ${({ theme }) => theme.border.radius.xs};
|
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||||
color: ${({ theme }) => theme.font.color.light};
|
color: ${({ theme }) => theme.font.color.light};
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
gap: ${({ theme }) => theme.spacing(0.5)};
|
||||||
height: ${({ theme }) => theme.spacing(8)};
|
height: ${({ theme }) => theme.spacing(8)};
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: ${({ theme }) => theme.spacing(0.5)};
|
padding: ${({ theme }) => theme.spacing(0.5)};
|
||||||
|
@ -0,0 +1,133 @@
|
|||||||
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
|
import { SettingsAccountsInboxSettingsCardMedia } from '@/settings/accounts/components/SettingsAccountsInboxSettingsCardMedia';
|
||||||
|
import { H2Title } from '@/ui/display/typography/components/H2Title';
|
||||||
|
import { Radio } from '@/ui/input/components/Radio';
|
||||||
|
import { Card } from '@/ui/layout/card/components/Card';
|
||||||
|
import { CardContent } from '@/ui/layout/card/components/CardContent';
|
||||||
|
import { Section } from '@/ui/layout/section/components/Section';
|
||||||
|
|
||||||
|
export enum InboxSettingsVisibilityValue {
|
||||||
|
Everything = 'everything',
|
||||||
|
SubjectMetadata = 'subject-metadata',
|
||||||
|
Metadata = 'metadata',
|
||||||
|
}
|
||||||
|
|
||||||
|
type SettingsAccountsInboxSettingsVisibilitySectionProps = {
|
||||||
|
onChange: (nextValue: InboxSettingsVisibilityValue) => void;
|
||||||
|
value?: InboxSettingsVisibilityValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
const StyledCardContent = styled(CardContent)`
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
gap: ${({ theme }) => theme.spacing(4)};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledCardMedia = styled(SettingsAccountsInboxSettingsCardMedia)`
|
||||||
|
align-items: stretch;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledSubjectSkeleton = styled.div<{ isActive?: boolean }>`
|
||||||
|
background-color: ${({ isActive, theme }) =>
|
||||||
|
isActive ? theme.accent.accent4060 : theme.background.quaternary};
|
||||||
|
border-radius: 1px;
|
||||||
|
height: 3px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledMetadataSkeleton = styled(StyledSubjectSkeleton)`
|
||||||
|
margin-right: ${({ theme }) => theme.spacing(2)};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledBodySkeleton = styled(StyledSubjectSkeleton)`
|
||||||
|
border-radius: ${({ theme }) => theme.border.radius.xs};
|
||||||
|
height: 22px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledTitle = styled.div`
|
||||||
|
color: ${({ theme }) => theme.font.color.primary};
|
||||||
|
font-weight: ${({ theme }) => theme.font.weight.medium};
|
||||||
|
margin-bottom: ${({ theme }) => theme.spacing(2)};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledDescription = styled.div`
|
||||||
|
color: ${({ theme }) => theme.font.color.tertiary};
|
||||||
|
font-size: ${({ theme }) => theme.font.size.sm};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledRadio = styled(Radio)`
|
||||||
|
margin-left: auto;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const inboxSettingsVisibilityOptions = [
|
||||||
|
{
|
||||||
|
title: 'Everything',
|
||||||
|
description: 'Subject, body and attachments will be shared with your team.',
|
||||||
|
value: InboxSettingsVisibilityValue.Everything,
|
||||||
|
visibleElements: {
|
||||||
|
metadata: true,
|
||||||
|
subject: true,
|
||||||
|
body: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Subject and metadata',
|
||||||
|
description: 'Subject and metadata will be shared with your team.',
|
||||||
|
value: InboxSettingsVisibilityValue.SubjectMetadata,
|
||||||
|
visibleElements: {
|
||||||
|
metadata: true,
|
||||||
|
subject: true,
|
||||||
|
body: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Metadata',
|
||||||
|
description: 'Timestamp and participants will be shared with your team.',
|
||||||
|
value: InboxSettingsVisibilityValue.Metadata,
|
||||||
|
visibleElements: {
|
||||||
|
metadata: true,
|
||||||
|
subject: false,
|
||||||
|
body: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const SettingsAccountsInboxSettingsVisibilitySection = ({
|
||||||
|
onChange,
|
||||||
|
value = InboxSettingsVisibilityValue.Everything,
|
||||||
|
}: SettingsAccountsInboxSettingsVisibilitySectionProps) => (
|
||||||
|
<Section>
|
||||||
|
<H2Title
|
||||||
|
title="Email visibility"
|
||||||
|
description="Define what will be visible to other users in your workspace"
|
||||||
|
/>
|
||||||
|
<Card>
|
||||||
|
{inboxSettingsVisibilityOptions.map(
|
||||||
|
(
|
||||||
|
{ title, description, value: optionValue, visibleElements },
|
||||||
|
index,
|
||||||
|
) => (
|
||||||
|
<StyledCardContent
|
||||||
|
key={value}
|
||||||
|
divider={index < inboxSettingsVisibilityOptions.length - 1}
|
||||||
|
>
|
||||||
|
<StyledCardMedia>
|
||||||
|
<StyledMetadataSkeleton isActive={visibleElements.metadata} />
|
||||||
|
<StyledSubjectSkeleton isActive={visibleElements.subject} />
|
||||||
|
<StyledBodySkeleton isActive={visibleElements.body} />
|
||||||
|
</StyledCardMedia>
|
||||||
|
<div>
|
||||||
|
<StyledTitle>{title}</StyledTitle>
|
||||||
|
<StyledDescription>{description}</StyledDescription>
|
||||||
|
</div>
|
||||||
|
<StyledRadio
|
||||||
|
value={optionValue}
|
||||||
|
onCheckedChange={() => onChange(optionValue)}
|
||||||
|
checked={value === optionValue}
|
||||||
|
/>
|
||||||
|
</StyledCardContent>
|
||||||
|
),
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
</Section>
|
||||||
|
);
|
@ -38,7 +38,15 @@ const StyledRadioInput = styled(motion.input)<RadioInputProps>`
|
|||||||
appearance: none;
|
appearance: none;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
border: 1px solid ${({ theme }) => theme.font.color.secondary};
|
border: 1px solid ${({ theme }) => theme.font.color.secondary};
|
||||||
border-radius: 50%;
|
border-radius: ${({ theme }) => theme.border.radius.rounded};
|
||||||
|
height: ${({ 'radio-size': radioSize }) =>
|
||||||
|
radioSize === RadioSize.Large ? '18px' : '16px'};
|
||||||
|
margin: 0;
|
||||||
|
margin-left: 3px;
|
||||||
|
position: relative;
|
||||||
|
width: ${({ 'radio-size': radioSize }) =>
|
||||||
|
radioSize === RadioSize.Large ? '18px' : '16px'};
|
||||||
|
|
||||||
:hover {
|
:hover {
|
||||||
background-color: ${({ theme, checked }) => {
|
background-color: ${({ theme, checked }) => {
|
||||||
if (!checked) {
|
if (!checked) {
|
||||||
@ -53,6 +61,7 @@ const StyledRadioInput = styled(motion.input)<RadioInputProps>`
|
|||||||
return rgba(theme.color.blue, 0.12);
|
return rgba(theme.color.blue, 0.12);
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
&:checked {
|
&:checked {
|
||||||
background-color: ${({ theme }) => theme.color.blue};
|
background-color: ${({ theme }) => theme.color.blue};
|
||||||
border: none;
|
border: none;
|
||||||
@ -70,15 +79,11 @@ const StyledRadioInput = styled(motion.input)<RadioInputProps>`
|
|||||||
radioSize === RadioSize.Large ? '8px' : '6px'};
|
radioSize === RadioSize.Large ? '8px' : '6px'};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:disabled {
|
&:disabled {
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
opacity: 0.12;
|
opacity: 0.12;
|
||||||
}
|
}
|
||||||
height: ${({ 'radio-size': radioSize }) =>
|
|
||||||
radioSize === RadioSize.Large ? '18px' : '16px'};
|
|
||||||
position: relative;
|
|
||||||
width: ${({ 'radio-size': radioSize }) =>
|
|
||||||
radioSize === RadioSize.Large ? '18px' : '16px'};
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
type LabelProps = {
|
type LabelProps = {
|
||||||
@ -99,26 +104,28 @@ const StyledLabel = styled.label<LabelProps>`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export type RadioProps = {
|
export type RadioProps = {
|
||||||
style?: React.CSSProperties;
|
|
||||||
className?: string;
|
|
||||||
checked?: boolean;
|
checked?: boolean;
|
||||||
value?: string;
|
className?: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
label?: string;
|
||||||
|
labelPosition?: LabelPosition;
|
||||||
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
||||||
onCheckedChange?: (checked: boolean) => void;
|
onCheckedChange?: (checked: boolean) => void;
|
||||||
size?: RadioSize;
|
size?: RadioSize;
|
||||||
disabled?: boolean;
|
style?: React.CSSProperties;
|
||||||
labelPosition?: LabelPosition;
|
value?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Radio = ({
|
export const Radio = ({
|
||||||
checked,
|
checked,
|
||||||
value,
|
className,
|
||||||
|
disabled = false,
|
||||||
|
label,
|
||||||
|
labelPosition = LabelPosition.Right,
|
||||||
onChange,
|
onChange,
|
||||||
onCheckedChange,
|
onCheckedChange,
|
||||||
size = RadioSize.Small,
|
size = RadioSize.Small,
|
||||||
labelPosition = LabelPosition.Right,
|
value,
|
||||||
disabled = false,
|
|
||||||
className,
|
|
||||||
}: RadioProps) => {
|
}: RadioProps) => {
|
||||||
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
onChange?.(event);
|
onChange?.(event);
|
||||||
@ -133,7 +140,7 @@ export const Radio = ({
|
|||||||
name="input-radio"
|
name="input-radio"
|
||||||
data-testid="input-radio"
|
data-testid="input-radio"
|
||||||
checked={checked}
|
checked={checked}
|
||||||
value={value}
|
value={value || label}
|
||||||
radio-size={size}
|
radio-size={size}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
@ -141,13 +148,13 @@ export const Radio = ({
|
|||||||
animate={{ scale: checked ? 1.05 : 0.95 }}
|
animate={{ scale: checked ? 1.05 : 0.95 }}
|
||||||
transition={{ type: 'spring', stiffness: 300, damping: 20 }}
|
transition={{ type: 'spring', stiffness: 300, damping: 20 }}
|
||||||
/>
|
/>
|
||||||
{value && (
|
{label && (
|
||||||
<StyledLabel
|
<StyledLabel
|
||||||
htmlFor="input-radio"
|
htmlFor="input-radio"
|
||||||
labelPosition={labelPosition}
|
labelPosition={labelPosition}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
>
|
>
|
||||||
{value}
|
{label}
|
||||||
</StyledLabel>
|
</StyledLabel>
|
||||||
)}
|
)}
|
||||||
</StyledContainer>
|
</StyledContainer>
|
||||||
|
@ -16,7 +16,7 @@ type Story = StoryObj<typeof Radio>;
|
|||||||
|
|
||||||
export const Default: Story = {
|
export const Default: Story = {
|
||||||
args: {
|
args: {
|
||||||
value: 'Radio',
|
label: 'Radio',
|
||||||
checked: false,
|
checked: false,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
size: RadioSize.Small,
|
size: RadioSize.Small,
|
||||||
@ -26,10 +26,9 @@ export const Default: Story = {
|
|||||||
|
|
||||||
export const Catalog: CatalogStory<Story, typeof Radio> = {
|
export const Catalog: CatalogStory<Story, typeof Radio> = {
|
||||||
args: {
|
args: {
|
||||||
value: 'Radio',
|
label: 'Radio',
|
||||||
},
|
},
|
||||||
argTypes: {
|
argTypes: {
|
||||||
value: { control: false },
|
|
||||||
size: { control: false },
|
size: { control: false },
|
||||||
},
|
},
|
||||||
parameters: {
|
parameters: {
|
||||||
|
@ -2,6 +2,10 @@ import { useEffect } from 'react';
|
|||||||
import { useNavigate, useParams } from 'react-router-dom';
|
import { useNavigate, useParams } from 'react-router-dom';
|
||||||
|
|
||||||
import { SettingsAccountsInboxSettingsSynchronizationSection } from '@/settings/accounts/components/SettingsAccountsInboxSettingsSynchronizationSection';
|
import { SettingsAccountsInboxSettingsSynchronizationSection } from '@/settings/accounts/components/SettingsAccountsInboxSettingsSynchronizationSection';
|
||||||
|
import {
|
||||||
|
InboxSettingsVisibilityValue,
|
||||||
|
SettingsAccountsInboxSettingsVisibilitySection,
|
||||||
|
} from '@/settings/accounts/components/SettingsAccountsInboxSettingsVisibilitySection';
|
||||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||||
import { AppPath } from '@/types/AppPath';
|
import { AppPath } from '@/types/AppPath';
|
||||||
import { IconSettings } from '@/ui/display/icon';
|
import { IconSettings } from '@/ui/display/icon';
|
||||||
@ -20,9 +24,11 @@ export const SettingsAccountsEmailsInboxSettings = () => {
|
|||||||
if (!account) navigate(AppPath.NotFound);
|
if (!account) navigate(AppPath.NotFound);
|
||||||
}, [account, navigate]);
|
}, [account, navigate]);
|
||||||
|
|
||||||
const handleToggle = (_value: boolean) => {};
|
if (!account) return null;
|
||||||
|
|
||||||
if (!account) return <></>;
|
const handleSynchronizationToggle = (_value: boolean) => {};
|
||||||
|
|
||||||
|
const handleVisibilityChange = (_value: InboxSettingsVisibilityValue) => {};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SubMenuTopBarContainer Icon={IconSettings} title="Settings">
|
<SubMenuTopBarContainer Icon={IconSettings} title="Settings">
|
||||||
@ -36,7 +42,11 @@ export const SettingsAccountsEmailsInboxSettings = () => {
|
|||||||
/>
|
/>
|
||||||
<SettingsAccountsInboxSettingsSynchronizationSection
|
<SettingsAccountsInboxSettingsSynchronizationSection
|
||||||
account={account}
|
account={account}
|
||||||
onToggle={handleToggle}
|
onToggle={handleSynchronizationToggle}
|
||||||
|
/>
|
||||||
|
<SettingsAccountsInboxSettingsVisibilitySection
|
||||||
|
value={account.visibility}
|
||||||
|
onChange={handleVisibilityChange}
|
||||||
/>
|
/>
|
||||||
</SettingsPageContainer>
|
</SettingsPageContainer>
|
||||||
</SubMenuTopBarContainer>
|
</SubMenuTopBarContainer>
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
import { Account } from '@/accounts/types/Account';
|
import { Account } from '@/accounts/types/Account';
|
||||||
|
import { InboxSettingsVisibilityValue } from '@/settings/accounts/components/SettingsAccountsInboxSettingsVisibilitySection';
|
||||||
|
|
||||||
export const mockedAccounts: Account[] = [
|
export const mockedAccounts: Account[] = [
|
||||||
{
|
{
|
||||||
email: 'thomas@twenty.com',
|
email: 'thomas@twenty.com',
|
||||||
isSynced: true,
|
isSynced: true,
|
||||||
uuid: '0794b782-f52e-48c3-977e-b0f57f90de24',
|
uuid: '0794b782-f52e-48c3-977e-b0f57f90de24',
|
||||||
|
visibility: InboxSettingsVisibilityValue.Everything,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
email: 'thomas@twenty.dev',
|
email: 'thomas@twenty.dev',
|
||||||
isSynced: false,
|
isSynced: false,
|
||||||
uuid: 'dc66a7ec-56b2-425b-a8e8-26ff0396c3aa',
|
uuid: 'dc66a7ec-56b2-425b-a8e8-26ff0396c3aa',
|
||||||
|
visibility: InboxSettingsVisibilityValue.Metadata,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
Loading…
Reference in New Issue
Block a user