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

View File

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

View File

@ -31,6 +31,7 @@ export function Favorites() {
icon={
<Avatar
key={id}
colorId={person.id}
avatarUrl={person.avatarUrl ?? ''}
type="rounded"
placeholder={`${person.firstName} ${person.lastName}`}

View File

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

View File

@ -123,7 +123,7 @@ export const peopleViewFields: ViewFieldDefinition<ViewFieldMetadata>[] = [
} satisfies ViewFieldDefinition<ViewFieldURLMetadata>,
{
id: 'x',
columnLabel: 'X',
columnLabel: 'Twitter',
columnIcon: <IconBrandX />,
columnSize: 150,
columnOrder: 9,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -21,6 +21,7 @@ export function GenericEditableRelationCell({
}: OwnProps) {
return (
<EditableCell
maxContentWidth={160}
editModeHorizontalAlign={editModeHorizontalAlign}
editHotkeyScope={{ scope: RelationPickerHotkeyScope.RelationPicker }}
editModeContent={

View File

@ -32,6 +32,7 @@ export function GenericEditableURLCell({
return (
<EditableCell
useEditButton
editModeHorizontalAlign={editModeHorizontalAlign}
editModeContent={<GenericEditableURLCellEditMode viewField={viewField} />}
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`, () => {
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';
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);
}