Migrate to a monorepo structure (#2909)

This commit is contained in:
Charles Bochet
2023-12-10 18:10:54 +01:00
committed by GitHub
parent a70a9281eb
commit 5bdca9de6c
2304 changed files with 37152 additions and 25869 deletions

View File

@ -0,0 +1,64 @@
import { IconDotsVertical, IconDownload, IconTrash } from '@/ui/display/icon';
import { LightIconButton } from '@/ui/input/button/components/LightIconButton';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope';
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
type AttachmentDropdownProps = {
onDownload: () => void;
onDelete: () => void;
scopeKey: string;
};
export const AttachmentDropdown = ({
onDownload,
onDelete,
scopeKey,
}: AttachmentDropdownProps) => {
const dropdownScopeId = `${scopeKey}-settings-field-active-action-dropdown`;
const { closeDropdown } = useDropdown({ dropdownScopeId });
const handleDownload = () => {
onDownload();
closeDropdown();
};
const handleDelete = () => {
onDelete();
closeDropdown();
};
return (
<DropdownScope dropdownScopeId={dropdownScopeId}>
<Dropdown
clickableComponent={
<LightIconButton Icon={IconDotsVertical} accent="tertiary" />
}
dropdownComponents={
<DropdownMenu width="160px">
<DropdownMenuItemsContainer>
<MenuItem
text="Download"
LeftIcon={IconDownload}
onClick={handleDownload}
/>
<MenuItem
text="Delete"
accent="danger"
LeftIcon={IconTrash}
onClick={handleDelete}
/>
</DropdownMenuItemsContainer>
</DropdownMenu>
}
dropdownHotkeyScope={{
scope: dropdownScopeId,
}}
/>
</DropdownScope>
);
};

View File

@ -0,0 +1,64 @@
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import {
Attachment,
AttachmentType,
} from '@/activities/files/types/Attachment';
import { IconComponent } from '@/ui/display/icon/types/IconComponent';
import {
IconFile,
IconFileText,
IconFileZip,
IconHeadphones,
IconPhoto,
IconPresentation,
IconTable,
IconVideo,
} from '@/ui/input/constants/icons';
const StyledIconContainer = styled.div<{ background: string }>`
align-items: center;
background: ${({ background }) => background};
border-radius: ${({ theme }) => theme.border.radius.sm};
color: ${({ theme }) => theme.grayScale.gray0};
display: flex;
flex-shrink: 0;
height: 20px;
justify-content: center;
width: 20px;
`;
const IconMapping: { [key in AttachmentType]: IconComponent } = {
Archive: IconFileZip,
Audio: IconHeadphones,
Image: IconPhoto,
Presentation: IconPresentation,
Spreadsheet: IconTable,
TextDocument: IconFileText,
Video: IconVideo,
Other: IconFile,
};
export const AttachmentIcon = ({ attachment }: { attachment: Attachment }) => {
const theme = useTheme();
const IconColors: { [key in AttachmentType]: string } = {
Archive: theme.color.gray,
Audio: theme.color.pink,
Image: theme.color.yellow,
Presentation: theme.color.orange,
Spreadsheet: theme.color.turquoise,
TextDocument: theme.color.blue,
Video: theme.color.purple,
Other: theme.color.gray,
};
const Icon = IconMapping[attachment.type];
return (
<StyledIconContainer background={IconColors[attachment.type]}>
{Icon && <Icon size={theme.icon.size.sm} />}
</StyledIconContainer>
);
};

View File

@ -0,0 +1,76 @@
import { ReactElement } from 'react';
import styled from '@emotion/styled';
import { Attachment } from '@/activities/files/types/Attachment';
import { AttachmentRow } from './AttachmentRow';
type AttachmentListProps = {
title: string;
attachments: Attachment[];
button?: ReactElement | false;
};
const StyledContainer = styled.div`
align-items: flex-start;
align-self: stretch;
display: flex;
flex-direction: column;
justify-content: center;
padding: 8px 24px;
`;
const StyledTitleBar = styled.h3`
display: flex;
justify-content: space-between;
margin-bottom: ${({ theme }) => theme.spacing(4)};
margin-top: ${({ theme }) => theme.spacing(4)};
place-items: center;
width: 100%;
`;
const StyledTitle = styled.h3`
color: ${({ theme }) => theme.font.color.primary};
font-weight: ${({ theme }) => theme.font.weight.semiBold};
`;
const StyledCount = styled.span`
color: ${({ theme }) => theme.font.color.light};
margin-left: ${({ theme }) => theme.spacing(2)};
`;
const StyledAttachmentContainer = styled.div`
align-items: flex-start;
align-self: stretch;
background: ${({ theme }) => theme.background.secondary};
border: 1px solid ${({ theme }) => theme.border.color.medium};
border-radius: ${({ theme }) => theme.border.radius.md};
disply: flex;
flex-flow: column nowrap;
justify-content: center;
width: 100%;
`;
export const AttachmentList = ({
title,
attachments,
button,
}: AttachmentListProps) => (
<>
{attachments && attachments.length > 0 && (
<StyledContainer>
<StyledTitleBar>
<StyledTitle>
{title} <StyledCount>{attachments.length}</StyledCount>
</StyledTitle>
{button}
</StyledTitleBar>
<StyledAttachmentContainer>
{attachments.map((attachment) => (
<AttachmentRow key={attachment.id} attachment={attachment} />
))}
</StyledAttachmentContainer>
</StyledContainer>
)}
</>
);

View File

@ -0,0 +1,104 @@
import { useMemo } from 'react';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { AttachmentDropdown } from '@/activities/files/components/AttachmentDropdown';
import { AttachmentIcon } from '@/activities/files/components/AttachmentIcon';
import { Attachment } from '@/activities/files/types/Attachment';
import { downloadFile } from '@/activities/files/utils/downloadFile';
import {
FieldContext,
GenericFieldContextType,
} from '@/object-record/field/contexts/FieldContext';
import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
import { IconCalendar } from '@/ui/display/icon';
import { formatToHumanReadableDate } from '~/utils';
const StyledRow = styled.div`
align-items: center;
align-self: stretch;
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
color: ${({ theme }) => theme.font.color.primary};
display: flex;
justify-content: space-between;
padding: ${({ theme }) => theme.spacing(2)};
`;
const StyledLeftContent = styled.div`
align-items: center;
display: flex;
gap: ${({ theme }) => theme.spacing(3)};
`;
const StyledRightContent = styled.div`
align-items: center;
display: flex;
gap: ${({ theme }) => theme.spacing(0.5)};
`;
const StyledCalendarIconContainer = styled.div`
align-items: center;
color: ${({ theme }) => theme.font.color.light};
display: flex;
`;
const StyledLink = styled.a`
align-items: center;
color: ${({ theme }) => theme.font.color.primary};
display: flex;
text-decoration: none;
:hover {
color: ${({ theme }) => theme.font.color.secondary};
}
`;
export const AttachmentRow = ({ attachment }: { attachment: Attachment }) => {
const theme = useTheme();
const fieldContext = useMemo(
() => ({ recoilScopeId: attachment?.id ?? '' }),
[attachment?.id],
);
const { deleteOneRecord: deleteOneAttachment } =
useDeleteOneRecord<Attachment>({
objectNameSingular: 'attachment',
});
const handleDelete = () => {
deleteOneAttachment(attachment.id);
};
return (
<FieldContext.Provider value={fieldContext as GenericFieldContextType}>
<StyledRow>
<StyledLeftContent>
<AttachmentIcon attachment={attachment} />
<StyledLink
href={
process.env.REACT_APP_SERVER_BASE_URL +
'/files/' +
attachment.fullPath
}
target="__blank"
>
{attachment.name}
</StyledLink>
</StyledLeftContent>
<StyledRightContent>
<StyledCalendarIconContainer>
<IconCalendar size={theme.icon.size.md} />
</StyledCalendarIconContainer>
{formatToHumanReadableDate(attachment.createdAt)}
<AttachmentDropdown
scopeKey={attachment.id}
onDelete={handleDelete}
onDownload={() => {
downloadFile(attachment.fullPath, attachment.name);
}}
/>
</StyledRightContent>
</StyledRow>
</FieldContext.Provider>
);
};

View File

@ -0,0 +1,152 @@
import { ChangeEvent, useRef } from 'react';
import styled from '@emotion/styled';
import { useRecoilValue } from 'recoil';
import { AttachmentList } from '@/activities/files/components/AttachmentList';
import { useAttachments } from '@/activities/files/hooks/useAttachments';
import { Attachment } from '@/activities/files/types/Attachment';
import { getFileType } from '@/activities/files/utils/getFileType';
import { ActivityTargetableEntity } from '@/activities/types/ActivityTargetableEntity';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
import { IconPlus } from '@/ui/display/icon';
import { Button } from '@/ui/input/button/components/Button';
import { FileFolder, useUploadFileMutation } from '~/generated/graphql';
const StyledTaskGroupEmptyContainer = styled.div`
align-items: center;
align-self: stretch;
display: flex;
flex: 1 0 0;
flex-direction: column;
gap: ${({ theme }) => theme.spacing(2)};
justify-content: center;
padding-bottom: ${({ theme }) => theme.spacing(16)};
padding-left: ${({ theme }) => theme.spacing(4)};
padding-right: ${({ theme }) => theme.spacing(4)};
padding-top: ${({ theme }) => theme.spacing(3)};
`;
const StyledEmptyTaskGroupTitle = styled.div`
color: ${({ theme }) => theme.font.color.secondary};
font-size: ${({ theme }) => theme.font.size.xxl};
font-weight: ${({ theme }) => theme.font.weight.semiBold};
line-height: ${({ theme }) => theme.text.lineHeight.md};
`;
const StyledEmptyTaskGroupSubTitle = styled.div`
color: ${({ theme }) => theme.font.color.extraLight};
font-size: ${({ theme }) => theme.font.size.xxl};
font-weight: ${({ theme }) => theme.font.weight.semiBold};
line-height: ${({ theme }) => theme.text.lineHeight.md};
margin-bottom: ${({ theme }) => theme.spacing(2)};
`;
const StyledAttachmentsContainer = styled.div`
display: flex;
flex: 1;
flex-direction: column;
height: 100%;
overflow: auto;
`;
const StyledFileInput = styled.input`
display: none;
`;
export const Attachments = ({
targetableEntity,
}: {
targetableEntity: ActivityTargetableEntity;
}) => {
const inputFileRef = useRef<HTMLInputElement>(null);
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
const { attachments } = useAttachments(targetableEntity);
const [uploadFile] = useUploadFileMutation();
const { createOneRecord: createOneAttachment } =
useCreateOneRecord<Attachment>({
objectNameSingular: 'attachment',
});
const handleFileChange = (e: ChangeEvent<HTMLInputElement>) => {
if (e.target.files) onUploadFile?.(e.target.files[0]);
};
const handleUploadFileClick = () => {
inputFileRef?.current?.click?.();
};
const onUploadFile = async (file: File) => {
const result = await uploadFile({
variables: {
file,
fileFolder: FileFolder.Attachment,
},
});
const attachmentUrl = result?.data?.uploadFile;
if (!attachmentUrl) {
return;
}
if (!createOneAttachment) {
return;
}
await createOneAttachment({
authorId: currentWorkspaceMember?.id,
name: file.name,
fullPath: attachmentUrl,
type: getFileType(file.name),
companyId:
targetableEntity.type == 'Company' ? targetableEntity.id : null,
personId: targetableEntity.type == 'Person' ? targetableEntity.id : null,
});
};
if (attachments?.length === 0 && targetableEntity.type !== 'Custom') {
return (
<StyledTaskGroupEmptyContainer>
<StyledFileInput
ref={inputFileRef}
onChange={handleFileChange}
type="file"
/>
<StyledEmptyTaskGroupTitle>No files yet</StyledEmptyTaskGroupTitle>
<StyledEmptyTaskGroupSubTitle>Upload one:</StyledEmptyTaskGroupSubTitle>
<Button
Icon={IconPlus}
title="Add file"
variant="secondary"
onClick={handleUploadFileClick}
/>
</StyledTaskGroupEmptyContainer>
);
}
return (
<StyledAttachmentsContainer>
<StyledFileInput
ref={inputFileRef}
onChange={handleFileChange}
type="file"
/>
<AttachmentList
title="All"
attachments={attachments ?? []}
button={
<Button
Icon={IconPlus}
size="small"
variant="secondary"
title="Add file"
onClick={handleUploadFileClick}
></Button>
}
/>
</StyledAttachmentsContainer>
);
};