Reorganize frontend and install Craco to alias modules (#190)

This commit is contained in:
Charles Bochet
2023-06-04 11:23:09 +02:00
committed by GitHub
parent bbc80cd543
commit 7b858fd7c9
149 changed files with 3441 additions and 1158 deletions

View File

@ -0,0 +1,63 @@
import { useState } from 'react';
import styled from '@emotion/styled';
import { CellCommentChip } from '@/comments/components/comments/CellCommentChip';
import { EditableDoubleText } from '@/ui/components/editable-cell/EditableDoubleText';
import { PersonChip } from './PersonChip';
type OwnProps = {
firstname: string;
lastname: string;
onChange: (firstname: string, lastname: string) => void;
};
const StyledDiv = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
`;
export function EditablePeopleFullName({
firstname,
lastname,
onChange,
}: OwnProps) {
const [firstnameValue, setFirstnameValue] = useState(firstname);
const [lastnameValue, setLastnameValue] = useState(lastname);
function handleDoubleTextChange(
firstValue: string,
secondValue: string,
): void {
setFirstnameValue(firstValue);
setLastnameValue(secondValue);
onChange(firstValue, secondValue);
}
function handleCommentClick(event: React.MouseEvent<HTMLDivElement>) {
event.preventDefault();
event.stopPropagation();
console.log('comment clicked');
}
return (
<EditableDoubleText
firstValue={firstnameValue}
secondValue={lastnameValue}
firstValuePlaceholder="First name"
secondValuePlaceholder="Last name"
onChange={handleDoubleTextChange}
nonEditModeContent={
<>
<StyledDiv>
<PersonChip name={firstname + ' ' + lastname} />
</StyledDiv>
<CellCommentChip count={12} onClick={handleCommentClick} />
</>
}
/>
);
}

View File

@ -0,0 +1,113 @@
import { useState } from 'react';
import { v4 } from 'uuid';
import CompanyChip, {
CompanyChipPropsType,
} from '@/companies/components/CompanyChip';
import {
Company,
mapToCompany,
} from '@/companies/interfaces/company.interface';
import { SearchConfigType } from '@/search/interfaces/interface';
import { SEARCH_COMPANY_QUERY } from '@/search/services/search';
import { EditableRelation } from '@/ui/components/editable-cell/EditableRelation';
import { getLogoUrlFromDomainName } from '@/utils/utils';
import {
QueryMode,
useInsertCompanyMutation,
useUpdatePeopleMutation,
} from '~/generated/graphql';
import { mapToGqlPerson, Person } from '../interfaces/person.interface';
import { PeopleCompanyCreateCell } from './PeopleCompanyCreateCell';
export type OwnProps = {
people: Person;
};
export function PeopleCompanyCell({ people }: OwnProps) {
const [isCreating, setIsCreating] = useState(false);
const [insertCompany] = useInsertCompanyMutation();
const [updatePeople] = useUpdatePeopleMutation();
const [initialCompanyName, setInitialCompanyName] = useState('');
async function handleCompanyCreate(
companyName: string,
companyDomainName: string,
) {
const newCompanyId = v4();
try {
await insertCompany({
variables: {
id: newCompanyId,
name: companyName,
domainName: companyDomainName,
address: '',
createdAt: new Date().toISOString(),
},
});
await updatePeople({
variables: {
...mapToGqlPerson(people),
companyId: newCompanyId,
},
});
} catch (error) {
// TODO: handle error better
console.log(error);
}
setIsCreating(false);
}
// TODO: should be replaced with search context
function handleChangeSearchInput(searchInput: string) {
setInitialCompanyName(searchInput);
}
return isCreating ? (
<PeopleCompanyCreateCell
initialCompanyName={initialCompanyName}
onCreate={handleCompanyCreate}
/>
) : (
<EditableRelation<Company, CompanyChipPropsType>
relation={people.company}
searchPlaceholder="Company"
ChipComponent={CompanyChip}
chipComponentPropsMapper={(company): CompanyChipPropsType => {
return {
name: company.name || '',
picture: getLogoUrlFromDomainName(company.domainName),
};
}}
onChange={async (relation) => {
await updatePeople({
variables: {
...mapToGqlPerson(people),
companyId: relation.id,
},
});
}}
onChangeSearchInput={handleChangeSearchInput}
searchConfig={
{
query: SEARCH_COMPANY_QUERY,
template: (searchInput: string) => ({
name: { contains: `%${searchInput}%`, mode: QueryMode.Insensitive },
}),
resultMapper: (company) => ({
render: (company) => company.name,
value: mapToCompany(company),
}),
} satisfies SearchConfigType<Company>
}
onCreate={() => {
setIsCreating(true);
}}
/>
);
}

View File

@ -0,0 +1,58 @@
import { useRef, useState } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { CellBaseContainer } from '@/ui/components/editable-cell/CellBaseContainer';
import { CellEditModeContainer } from '@/ui/components/editable-cell/CellEditModeContainer';
import { DoubleTextInput } from '@/ui/components/inputs/DoubleTextInput';
import { useListenClickOutsideArrayOfRef } from '@/ui/hooks/useListenClickOutsideArrayOfRef';
type OwnProps = {
initialCompanyName: string;
onCreate: (companyName: string, companyDomainName: string) => void;
};
export function PeopleCompanyCreateCell({
initialCompanyName,
onCreate,
}: OwnProps) {
const [companyName, setCompanyName] = useState(initialCompanyName);
const [companyDomainName, setCompanyDomainName] = useState('');
const containerRef = useRef(null);
useListenClickOutsideArrayOfRef([containerRef], () => {
onCreate(companyName, companyDomainName);
});
useHotkeys(
'enter, escape',
() => {
onCreate(companyName, companyDomainName);
},
{
enableOnFormTags: true,
enableOnContentEditable: true,
preventDefault: true,
},
[containerRef, companyName, companyDomainName, onCreate],
);
function handleDoubleTextChange(leftValue: string, rightValue: string): void {
setCompanyDomainName(leftValue);
setCompanyName(rightValue);
}
return (
<CellBaseContainer ref={containerRef}>
<CellEditModeContainer editModeVerticalPosition="over">
<DoubleTextInput
leftValue={companyDomainName}
rightValue={companyName}
leftValuePlaceholder="URL"
rightValuePlaceholder="Name"
onChange={handleDoubleTextChange}
/>
</CellEditModeContainer>
</CellBaseContainer>
);
}

View File

@ -0,0 +1,46 @@
import * as React from 'react';
import styled from '@emotion/styled';
import PersonPlaceholder from './person-placeholder.png';
export type PersonChipPropsType = {
name: string;
picture?: string;
};
const StyledContainer = styled.span`
background-color: ${(props) => props.theme.tertiaryBackground};
border-radius: ${(props) => props.theme.spacing(1)};
color: ${(props) => props.theme.text80};
display: inline-flex;
align-items: center;
padding: ${(props) => props.theme.spacing(1)};
gap: ${(props) => props.theme.spacing(1)};
overflow: hidden;
white-space: nowrap;
:hover {
filter: brightness(95%);
}
img {
height: 14px;
width: 14px;
border-radius: 100%;
object-fit: cover;
}
`;
export function PersonChip({ name, picture }: PersonChipPropsType) {
return (
<StyledContainer data-testid="person-chip">
<img
data-testid="person-chip-image"
src={picture ? picture.toString() : PersonPlaceholder.toString()}
alt="person"
/>
{name}
</StyledContainer>
);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB