mirror of
https://github.com/twentyhq/twenty.git
synced 2024-12-18 17:12:53 +03:00
Misc fixes
This commit is contained in:
parent
20b641bfe6
commit
e61c263b1a
@ -20,6 +20,7 @@ const StyledContainer = styled.div`
|
|||||||
|
|
||||||
const StyledTitle = styled.h3`
|
const StyledTitle = styled.h3`
|
||||||
color: ${({ theme }) => theme.font.color.primary};
|
color: ${({ theme }) => theme.font.color.primary};
|
||||||
|
font-weight: ${({ theme }) => theme.font.weight.semiBold};
|
||||||
margin-bottom: ${({ theme }) => theme.spacing(4)};
|
margin-bottom: ${({ theme }) => theme.spacing(4)};
|
||||||
margin-top: ${({ theme }) => theme.spacing(4)};
|
margin-top: ${({ theme }) => theme.spacing(4)};
|
||||||
`;
|
`;
|
||||||
|
@ -63,8 +63,12 @@ export function CompanyTeam({ company }: CompanyTeamPropsType) {
|
|||||||
<StyledTitle>Team</StyledTitle>
|
<StyledTitle>Team</StyledTitle>
|
||||||
</StyledTitleContainer>
|
</StyledTitleContainer>
|
||||||
<StyledListContainer>
|
<StyledListContainer>
|
||||||
{data?.people?.map((person) => (
|
{data?.people?.map((person, id) => (
|
||||||
<PeopleCard key={person.id} person={person} />
|
<PeopleCard
|
||||||
|
key={person.id}
|
||||||
|
person={person}
|
||||||
|
hasBottomBorder={id !== data.people.length - 1}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</StyledListContainer>
|
</StyledListContainer>
|
||||||
</StyledContainer>
|
</StyledContainer>
|
||||||
|
@ -31,6 +31,7 @@ export function Favorites() {
|
|||||||
icon={
|
icon={
|
||||||
<Avatar
|
<Avatar
|
||||||
key={id}
|
key={id}
|
||||||
|
colorId={person.id}
|
||||||
avatarUrl={person.avatarUrl ?? ''}
|
avatarUrl={person.avatarUrl ?? ''}
|
||||||
type="rounded"
|
type="rounded"
|
||||||
placeholder={`${person.firstName} ${person.lastName}`}
|
placeholder={`${person.firstName} ${person.lastName}`}
|
||||||
|
@ -4,14 +4,17 @@ import styled from '@emotion/styled';
|
|||||||
import { Avatar } from '@/users/components/Avatar';
|
import { Avatar } from '@/users/components/Avatar';
|
||||||
import { Person } from '~/generated/graphql';
|
import { Person } from '~/generated/graphql';
|
||||||
|
|
||||||
export type PeopleCardPropsType = {
|
export type PeopleCardProps = {
|
||||||
person: Pick<Person, 'id' | 'avatarUrl' | 'displayName' | 'jobTitle'>;
|
person: Pick<Person, 'id' | 'avatarUrl' | 'displayName' | 'jobTitle'>;
|
||||||
|
hasBottomBorder?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const StyledCard = styled.div`
|
const StyledCard = styled.div<{ hasBottomBorder: boolean }>`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
align-self: stretch;
|
align-self: stretch;
|
||||||
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
|
border-bottom: 1px solid
|
||||||
|
${({ theme, hasBottomBorder }) =>
|
||||||
|
hasBottomBorder ? theme.border.color.light : 'transparent'};
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: ${({ theme }) => theme.spacing(2)};
|
gap: ${({ theme }) => theme.spacing(2)};
|
||||||
height: ${({ theme }) => theme.spacing(8)};
|
height: ${({ theme }) => theme.spacing(8)};
|
||||||
@ -49,10 +52,16 @@ const StyledJobTitle = styled.div`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export function PeopleCard({ person }: PeopleCardPropsType) {
|
export function PeopleCard({
|
||||||
|
person,
|
||||||
|
hasBottomBorder = true,
|
||||||
|
}: PeopleCardProps) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
return (
|
return (
|
||||||
<StyledCard onClick={() => navigate(`/person/${person.id}`)}>
|
<StyledCard
|
||||||
|
onClick={() => navigate(`/person/${person.id}`)}
|
||||||
|
hasBottomBorder={hasBottomBorder}
|
||||||
|
>
|
||||||
<Avatar
|
<Avatar
|
||||||
size="lg"
|
size="lg"
|
||||||
type="rounded"
|
type="rounded"
|
||||||
@ -61,7 +70,7 @@ export function PeopleCard({ person }: PeopleCardPropsType) {
|
|||||||
/>
|
/>
|
||||||
<StyledCardInfo>
|
<StyledCardInfo>
|
||||||
<StyledTitle>{person.displayName}</StyledTitle>
|
<StyledTitle>{person.displayName}</StyledTitle>
|
||||||
<StyledJobTitle> {person.jobTitle ?? 'Add job title'}</StyledJobTitle>
|
{person.jobTitle && <StyledJobTitle>{person.jobTitle}</StyledJobTitle>}
|
||||||
</StyledCardInfo>
|
</StyledCardInfo>
|
||||||
</StyledCard>
|
</StyledCard>
|
||||||
);
|
);
|
||||||
|
@ -123,7 +123,7 @@ export const peopleViewFields: ViewFieldDefinition<ViewFieldMetadata>[] = [
|
|||||||
} satisfies ViewFieldDefinition<ViewFieldURLMetadata>,
|
} satisfies ViewFieldDefinition<ViewFieldURLMetadata>,
|
||||||
{
|
{
|
||||||
id: 'x',
|
id: 'x',
|
||||||
columnLabel: 'X',
|
columnLabel: 'Twitter',
|
||||||
columnIcon: <IconBrandX />,
|
columnIcon: <IconBrandX />,
|
||||||
columnSize: 150,
|
columnSize: 150,
|
||||||
columnOrder: 9,
|
columnOrder: 9,
|
||||||
|
@ -48,8 +48,8 @@ export function PeopleFullNameEditableField({ people }: OwnProps) {
|
|||||||
return (
|
return (
|
||||||
<RecoilScope SpecificContext={FieldContext}>
|
<RecoilScope SpecificContext={FieldContext}>
|
||||||
<DoubleTextInputEdit
|
<DoubleTextInputEdit
|
||||||
firstValuePlaceholder={'First name'}
|
firstValuePlaceholder={'First name'} // Hack: Fake character to prevent password-manager from filling the field
|
||||||
secondValuePlaceholder={'Last name'}
|
secondValuePlaceholder={'Last name'} // Hack: Fake character to prevent password-manager from filling the field
|
||||||
firstValue={internalValueFirstName ?? ''}
|
firstValue={internalValueFirstName ?? ''}
|
||||||
secondValue={internalValueLastName ?? ''}
|
secondValue={internalValueLastName ?? ''}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
|
@ -20,13 +20,13 @@ type OwnProps = {
|
|||||||
|
|
||||||
const checkUrlType = (url: string) => {
|
const checkUrlType = (url: string) => {
|
||||||
if (
|
if (
|
||||||
/^(http|https):\/\/(?:www\.)?linkedin.com(\w+:{0,1}\w*@)?(\S+)(:([0-9])+)?(\/|\/([\w#!:.?+=&%@!\-/]))?$/.test(
|
/^(http|https):\/\/(?:www\.)?linkedin.com(\w+:{0,1}\w*@)?(\S+)(:([0-9])+)?(\/|\/([\w#!:.?+=&%@!\-/]))?/.test(
|
||||||
url,
|
url,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
return LinkType.LinkedIn;
|
return LinkType.LinkedIn;
|
||||||
}
|
}
|
||||||
if (url.match(/^((http|https):\/\/)?(?:www\.)?twitter\.com\/(\w+)?$/i)) {
|
if (url.match(/^((http|https):\/\/)?(?:www\.)?twitter\.com\/(\w+)?/i)) {
|
||||||
return LinkType.Twitter;
|
return LinkType.Twitter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ export function RoundedLink({ children, href, onClick }: OwnProps) {
|
|||||||
<Chip
|
<Chip
|
||||||
label={`${children}`}
|
label={`${children}`}
|
||||||
variant={ChipVariant.Rounded}
|
variant={ChipVariant.Rounded}
|
||||||
size={ChipSize.Large}
|
size={ChipSize.Small}
|
||||||
/>
|
/>
|
||||||
</ReactLink>
|
</ReactLink>
|
||||||
</StyledClickable>
|
</StyledClickable>
|
||||||
|
@ -9,7 +9,9 @@ import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
|||||||
import { CellHotkeyScopeContext } from '../../states/CellHotkeyScopeContext';
|
import { CellHotkeyScopeContext } from '../../states/CellHotkeyScopeContext';
|
||||||
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
|
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
|
||||||
import { useCurrentCellEditMode } from '../hooks/useCurrentCellEditMode';
|
import { useCurrentCellEditMode } from '../hooks/useCurrentCellEditMode';
|
||||||
|
import { useEditableCell } from '../hooks/useEditableCell';
|
||||||
import { useIsSoftFocusOnCurrentCell } from '../hooks/useIsSoftFocusOnCurrentCell';
|
import { useIsSoftFocusOnCurrentCell } from '../hooks/useIsSoftFocusOnCurrentCell';
|
||||||
|
import { useSetSoftFocusOnCurrentCell } from '../hooks/useSetSoftFocusOnCurrentCell';
|
||||||
|
|
||||||
import { EditableCellDisplayMode } from './EditableCellDisplayMode';
|
import { EditableCellDisplayMode } from './EditableCellDisplayMode';
|
||||||
import { EditableCellEditMode } from './EditableCellEditMode';
|
import { EditableCellEditMode } from './EditableCellEditMode';
|
||||||
@ -39,6 +41,7 @@ type OwnProps = {
|
|||||||
editHotkeyScope?: HotkeyScope;
|
editHotkeyScope?: HotkeyScope;
|
||||||
transparent?: boolean;
|
transparent?: boolean;
|
||||||
maxContentWidth?: number;
|
maxContentWidth?: number;
|
||||||
|
useEditButton?: boolean;
|
||||||
onSubmit?: () => void;
|
onSubmit?: () => void;
|
||||||
onCancel?: () => void;
|
onCancel?: () => void;
|
||||||
};
|
};
|
||||||
@ -55,27 +58,19 @@ export function EditableCell({
|
|||||||
editHotkeyScope,
|
editHotkeyScope,
|
||||||
transparent = false,
|
transparent = false,
|
||||||
maxContentWidth,
|
maxContentWidth,
|
||||||
|
useEditButton,
|
||||||
}: OwnProps) {
|
}: OwnProps) {
|
||||||
const { isCurrentCellInEditMode, setCurrentCellInEditMode } =
|
const { isCurrentCellInEditMode } = useCurrentCellEditMode();
|
||||||
useCurrentCellEditMode();
|
|
||||||
const [isHovered, setIsHovered] = useState(false);
|
const [isHovered, setIsHovered] = useState(false);
|
||||||
|
|
||||||
function isValidUrl(value: string) {
|
const setSoftFocusOnCurrentCell = useSetSoftFocusOnCurrentCell();
|
||||||
let testUrl = value;
|
|
||||||
if (testUrl && !testUrl.startsWith('http')) {
|
|
||||||
testUrl = 'http://' + testUrl;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
new URL(testUrl);
|
|
||||||
return true;
|
|
||||||
} catch (err) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleClick = () => {
|
const { openEditableCell } = useEditableCell();
|
||||||
setCurrentCellInEditMode();
|
|
||||||
};
|
function handlePenClick() {
|
||||||
|
setSoftFocusOnCurrentCell();
|
||||||
|
openEditableCell();
|
||||||
|
}
|
||||||
|
|
||||||
function handleContainerMouseEnter() {
|
function handleContainerMouseEnter() {
|
||||||
setIsHovered(true);
|
setIsHovered(true);
|
||||||
@ -85,9 +80,7 @@ export function EditableCell({
|
|||||||
setIsHovered(false);
|
setIsHovered(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
const value = nonEditModeContent.props.value;
|
const showEditButton = useEditButton && isHovered && !isCurrentCellInEditMode;
|
||||||
const showEditButton =
|
|
||||||
!isCurrentCellInEditMode && isValidUrl(value) && isHovered;
|
|
||||||
|
|
||||||
const hasSoftFocus = useIsSoftFocusOnCurrentCell();
|
const hasSoftFocus = useIsSoftFocusOnCurrentCell();
|
||||||
|
|
||||||
@ -124,7 +117,7 @@ export function EditableCell({
|
|||||||
<IconButton
|
<IconButton
|
||||||
variant="shadow"
|
variant="shadow"
|
||||||
size="small"
|
size="small"
|
||||||
onClick={handleClick}
|
onClick={handlePenClick}
|
||||||
icon={<IconPencil size={14} />}
|
icon={<IconPencil size={14} />}
|
||||||
/>
|
/>
|
||||||
</StyledEditButtonContainer>
|
</StyledEditButtonContainer>
|
||||||
|
@ -16,9 +16,9 @@ export const EditableCellEditModeContainer = styled.div<OwnProps>`
|
|||||||
margin-top: -1px;
|
margin-top: -1px;
|
||||||
|
|
||||||
max-width: ${({ maxContentWidth }) =>
|
max-width: ${({ maxContentWidth }) =>
|
||||||
maxContentWidth ? `${maxContentWidth}px` : 'auto'};
|
maxContentWidth ? `${maxContentWidth}px` : 'none'};
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
min-width: 100%;
|
min-width: ${({ maxContentWidth }) => (maxContentWidth ? `none` : '100%')};
|
||||||
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: ${(props) =>
|
right: ${(props) =>
|
||||||
|
@ -21,6 +21,7 @@ export function GenericEditableRelationCell({
|
|||||||
}: OwnProps) {
|
}: OwnProps) {
|
||||||
return (
|
return (
|
||||||
<EditableCell
|
<EditableCell
|
||||||
|
maxContentWidth={160}
|
||||||
editModeHorizontalAlign={editModeHorizontalAlign}
|
editModeHorizontalAlign={editModeHorizontalAlign}
|
||||||
editHotkeyScope={{ scope: RelationPickerHotkeyScope.RelationPicker }}
|
editHotkeyScope={{ scope: RelationPickerHotkeyScope.RelationPicker }}
|
||||||
editModeContent={
|
editModeContent={
|
||||||
|
@ -32,6 +32,7 @@ export function GenericEditableURLCell({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<EditableCell
|
<EditableCell
|
||||||
|
useEditButton
|
||||||
editModeHorizontalAlign={editModeHorizontalAlign}
|
editModeHorizontalAlign={editModeHorizontalAlign}
|
||||||
editModeContent={<GenericEditableURLCellEditMode viewField={viewField} />}
|
editModeContent={<GenericEditableURLCellEditMode viewField={viewField} />}
|
||||||
nonEditModeContent={
|
nonEditModeContent={
|
||||||
|
35
front/src/utils/__tests__/is-domain.test.ts
Normal file
35
front/src/utils/__tests__/is-domain.test.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { isDomain } from '~/utils/is-domain';
|
||||||
|
|
||||||
|
describe('isDomain', () => {
|
||||||
|
it(`should return false if null`, () => {
|
||||||
|
expect(isDomain(null)).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should return false if undefined`, () => {
|
||||||
|
expect(isDomain(undefined)).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should return true if string google`, () => {
|
||||||
|
expect(isDomain('google')).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should return true if string google.com`, () => {
|
||||||
|
expect(isDomain('google.com')).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should return true if string bbc.co.uk`, () => {
|
||||||
|
expect(isDomain('bbc.co.uk')).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should return true if string web.io`, () => {
|
||||||
|
expect(isDomain('web.io')).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should return true if string x.com`, () => {
|
||||||
|
expect(isDomain('x.com')).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should return true if string 2.com`, () => {
|
||||||
|
expect(isDomain('2.com')).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -32,4 +32,12 @@ describe('isURL', () => {
|
|||||||
it(`should return true if string 2.com`, () => {
|
it(`should return true if string 2.com`, () => {
|
||||||
expect(isURL('2.com')).toBeTruthy();
|
expect(isURL('2.com')).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it(`should return true if string https://2.com/test/`, () => {
|
||||||
|
expect(isURL('https://2.com/test/')).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should return false if string https://2.com/test/sldkfj!?`, () => {
|
||||||
|
expect(isURL('https://2.com/test/sldkfj!?')).toBeFalsy();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
10
front/src/utils/is-domain.ts
Normal file
10
front/src/utils/is-domain.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { isDefined } from './isDefined';
|
||||||
|
|
||||||
|
export function isDomain(url: string | undefined | null) {
|
||||||
|
return (
|
||||||
|
isDefined(url) &&
|
||||||
|
/^((?!-))(xn--)?[a-z0-9][a-z0-9-_]{0,61}[a-z0-9]{0,1}\.(xn--)?([a-z0-9-]{1,61}|[a-z0-9-]{1,30}\.[a-z]{2,})$/.test(
|
||||||
|
url,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
@ -1,10 +1,14 @@
|
|||||||
import { isDefined } from './isDefined';
|
import { isDefined } from './isDefined';
|
||||||
|
|
||||||
export function isURL(url: string | undefined | null) {
|
export function isURL(url: string | undefined | null) {
|
||||||
return (
|
const pattern = new RegExp(
|
||||||
isDefined(url) &&
|
'^(https?:\\/\\/)?' +
|
||||||
/^((?!-))(xn--)?[a-z0-9][a-z0-9-_]{0,61}[a-z0-9]{0,1}\.(xn--)?([a-z0-9-]{1,61}|[a-z0-9-]{1,30}\.[a-z]{2,})$/.test(
|
'((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' +
|
||||||
url,
|
'((\\d{1,3}\\.){3}\\d{1,3}))' +
|
||||||
)
|
'(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' +
|
||||||
|
'(\\?[;&a-z\\d%_.~+=-]*)?' +
|
||||||
|
'(\\#[-a-z\\d_]*)?$',
|
||||||
|
'i',
|
||||||
);
|
);
|
||||||
|
return isDefined(url) && !!pattern.test(url);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user