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`
|
||||
color: ${({ theme }) => theme.font.color.primary};
|
||||
font-weight: ${({ theme }) => theme.font.weight.semiBold};
|
||||
margin-bottom: ${({ theme }) => theme.spacing(4)};
|
||||
margin-top: ${({ theme }) => theme.spacing(4)};
|
||||
`;
|
||||
|
@ -63,8 +63,12 @@ export function CompanyTeam({ company }: CompanyTeamPropsType) {
|
||||
<StyledTitle>Team</StyledTitle>
|
||||
</StyledTitleContainer>
|
||||
<StyledListContainer>
|
||||
{data?.people?.map((person) => (
|
||||
<PeopleCard key={person.id} person={person} />
|
||||
{data?.people?.map((person, id) => (
|
||||
<PeopleCard
|
||||
key={person.id}
|
||||
person={person}
|
||||
hasBottomBorder={id !== data.people.length - 1}
|
||||
/>
|
||||
))}
|
||||
</StyledListContainer>
|
||||
</StyledContainer>
|
||||
|
@ -31,6 +31,7 @@ export function Favorites() {
|
||||
icon={
|
||||
<Avatar
|
||||
key={id}
|
||||
colorId={person.id}
|
||||
avatarUrl={person.avatarUrl ?? ''}
|
||||
type="rounded"
|
||||
placeholder={`${person.firstName} ${person.lastName}`}
|
||||
|
@ -4,14 +4,17 @@ import styled from '@emotion/styled';
|
||||
import { Avatar } from '@/users/components/Avatar';
|
||||
import { Person } from '~/generated/graphql';
|
||||
|
||||
export type PeopleCardPropsType = {
|
||||
export type PeopleCardProps = {
|
||||
person: Pick<Person, 'id' | 'avatarUrl' | 'displayName' | 'jobTitle'>;
|
||||
hasBottomBorder?: boolean;
|
||||
};
|
||||
|
||||
const StyledCard = styled.div`
|
||||
const StyledCard = styled.div<{ hasBottomBorder: boolean }>`
|
||||
align-items: center;
|
||||
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;
|
||||
gap: ${({ theme }) => theme.spacing(2)};
|
||||
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();
|
||||
return (
|
||||
<StyledCard onClick={() => navigate(`/person/${person.id}`)}>
|
||||
<StyledCard
|
||||
onClick={() => navigate(`/person/${person.id}`)}
|
||||
hasBottomBorder={hasBottomBorder}
|
||||
>
|
||||
<Avatar
|
||||
size="lg"
|
||||
type="rounded"
|
||||
@ -61,7 +70,7 @@ export function PeopleCard({ person }: PeopleCardPropsType) {
|
||||
/>
|
||||
<StyledCardInfo>
|
||||
<StyledTitle>{person.displayName}</StyledTitle>
|
||||
<StyledJobTitle> {person.jobTitle ?? 'Add job title'}</StyledJobTitle>
|
||||
{person.jobTitle && <StyledJobTitle>{person.jobTitle}</StyledJobTitle>}
|
||||
</StyledCardInfo>
|
||||
</StyledCard>
|
||||
);
|
||||
|
@ -123,7 +123,7 @@ export const peopleViewFields: ViewFieldDefinition<ViewFieldMetadata>[] = [
|
||||
} satisfies ViewFieldDefinition<ViewFieldURLMetadata>,
|
||||
{
|
||||
id: 'x',
|
||||
columnLabel: 'X',
|
||||
columnLabel: 'Twitter',
|
||||
columnIcon: <IconBrandX />,
|
||||
columnSize: 150,
|
||||
columnOrder: 9,
|
||||
|
@ -48,8 +48,8 @@ export function PeopleFullNameEditableField({ people }: OwnProps) {
|
||||
return (
|
||||
<RecoilScope SpecificContext={FieldContext}>
|
||||
<DoubleTextInputEdit
|
||||
firstValuePlaceholder={'First name'}
|
||||
secondValuePlaceholder={'Last name'}
|
||||
firstValuePlaceholder={'First name'} // Hack: Fake character to prevent password-manager from filling the field
|
||||
secondValuePlaceholder={'Last name'} // Hack: Fake character to prevent password-manager from filling the field
|
||||
firstValue={internalValueFirstName ?? ''}
|
||||
secondValue={internalValueLastName ?? ''}
|
||||
onChange={handleChange}
|
||||
|
@ -20,13 +20,13 @@ type OwnProps = {
|
||||
|
||||
const checkUrlType = (url: string) => {
|
||||
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,
|
||||
)
|
||||
) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -30,7 +30,7 @@ export function RoundedLink({ children, href, onClick }: OwnProps) {
|
||||
<Chip
|
||||
label={`${children}`}
|
||||
variant={ChipVariant.Rounded}
|
||||
size={ChipSize.Large}
|
||||
size={ChipSize.Small}
|
||||
/>
|
||||
</ReactLink>
|
||||
</StyledClickable>
|
||||
|
@ -9,7 +9,9 @@ import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
||||
import { CellHotkeyScopeContext } from '../../states/CellHotkeyScopeContext';
|
||||
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
|
||||
import { useCurrentCellEditMode } from '../hooks/useCurrentCellEditMode';
|
||||
import { useEditableCell } from '../hooks/useEditableCell';
|
||||
import { useIsSoftFocusOnCurrentCell } from '../hooks/useIsSoftFocusOnCurrentCell';
|
||||
import { useSetSoftFocusOnCurrentCell } from '../hooks/useSetSoftFocusOnCurrentCell';
|
||||
|
||||
import { EditableCellDisplayMode } from './EditableCellDisplayMode';
|
||||
import { EditableCellEditMode } from './EditableCellEditMode';
|
||||
@ -39,6 +41,7 @@ type OwnProps = {
|
||||
editHotkeyScope?: HotkeyScope;
|
||||
transparent?: boolean;
|
||||
maxContentWidth?: number;
|
||||
useEditButton?: boolean;
|
||||
onSubmit?: () => void;
|
||||
onCancel?: () => void;
|
||||
};
|
||||
@ -55,27 +58,19 @@ export function EditableCell({
|
||||
editHotkeyScope,
|
||||
transparent = false,
|
||||
maxContentWidth,
|
||||
useEditButton,
|
||||
}: OwnProps) {
|
||||
const { isCurrentCellInEditMode, setCurrentCellInEditMode } =
|
||||
useCurrentCellEditMode();
|
||||
const { isCurrentCellInEditMode } = useCurrentCellEditMode();
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
|
||||
function isValidUrl(value: string) {
|
||||
let testUrl = value;
|
||||
if (testUrl && !testUrl.startsWith('http')) {
|
||||
testUrl = 'http://' + testUrl;
|
||||
}
|
||||
try {
|
||||
new URL(testUrl);
|
||||
return true;
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
const setSoftFocusOnCurrentCell = useSetSoftFocusOnCurrentCell();
|
||||
|
||||
const handleClick = () => {
|
||||
setCurrentCellInEditMode();
|
||||
};
|
||||
const { openEditableCell } = useEditableCell();
|
||||
|
||||
function handlePenClick() {
|
||||
setSoftFocusOnCurrentCell();
|
||||
openEditableCell();
|
||||
}
|
||||
|
||||
function handleContainerMouseEnter() {
|
||||
setIsHovered(true);
|
||||
@ -85,9 +80,7 @@ export function EditableCell({
|
||||
setIsHovered(false);
|
||||
}
|
||||
|
||||
const value = nonEditModeContent.props.value;
|
||||
const showEditButton =
|
||||
!isCurrentCellInEditMode && isValidUrl(value) && isHovered;
|
||||
const showEditButton = useEditButton && isHovered && !isCurrentCellInEditMode;
|
||||
|
||||
const hasSoftFocus = useIsSoftFocusOnCurrentCell();
|
||||
|
||||
@ -124,7 +117,7 @@ export function EditableCell({
|
||||
<IconButton
|
||||
variant="shadow"
|
||||
size="small"
|
||||
onClick={handleClick}
|
||||
onClick={handlePenClick}
|
||||
icon={<IconPencil size={14} />}
|
||||
/>
|
||||
</StyledEditButtonContainer>
|
||||
|
@ -16,9 +16,9 @@ export const EditableCellEditModeContainer = styled.div<OwnProps>`
|
||||
margin-top: -1px;
|
||||
|
||||
max-width: ${({ maxContentWidth }) =>
|
||||
maxContentWidth ? `${maxContentWidth}px` : 'auto'};
|
||||
maxContentWidth ? `${maxContentWidth}px` : 'none'};
|
||||
min-height: 100%;
|
||||
min-width: 100%;
|
||||
min-width: ${({ maxContentWidth }) => (maxContentWidth ? `none` : '100%')};
|
||||
|
||||
position: absolute;
|
||||
right: ${(props) =>
|
||||
|
@ -21,6 +21,7 @@ export function GenericEditableRelationCell({
|
||||
}: OwnProps) {
|
||||
return (
|
||||
<EditableCell
|
||||
maxContentWidth={160}
|
||||
editModeHorizontalAlign={editModeHorizontalAlign}
|
||||
editHotkeyScope={{ scope: RelationPickerHotkeyScope.RelationPicker }}
|
||||
editModeContent={
|
||||
|
@ -32,6 +32,7 @@ export function GenericEditableURLCell({
|
||||
|
||||
return (
|
||||
<EditableCell
|
||||
useEditButton
|
||||
editModeHorizontalAlign={editModeHorizontalAlign}
|
||||
editModeContent={<GenericEditableURLCellEditMode viewField={viewField} />}
|
||||
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`, () => {
|
||||
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';
|
||||
|
||||
export function isURL(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,
|
||||
)
|
||||
const pattern = new RegExp(
|
||||
'^(https?:\\/\\/)?' +
|
||||
'((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' +
|
||||
'((\\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