Misc fixes

This commit is contained in:
Charles Bochet 2023-08-10 17:16:27 -07:00
parent 20b641bfe6
commit e61c263b1a
16 changed files with 109 additions and 42 deletions

View File

@ -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)};
`; `;

View File

@ -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>

View File

@ -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}`}

View File

@ -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>
); );

View File

@ -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,

View File

@ -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}

View File

@ -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;
} }

View File

@ -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>

View File

@ -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>

View File

@ -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) =>

View File

@ -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={

View File

@ -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={

View 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();
});
});

View File

@ -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();
});
}); });

View 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,
)
);
}

View File

@ -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);
} }