Removing Prisma and Grapql-nestjs-prisma resolvers (#2574)
* Some cleaning * Fix seeds * Fix all sign in, sign up flow and apiKey optimistic rendering * Fix
This commit is contained in:
@ -9,7 +9,9 @@ import { z } from 'zod';
|
||||
|
||||
import { SubTitle } from '@/auth/components/SubTitle';
|
||||
import { Title } from '@/auth/components/Title';
|
||||
import { useOnboardingStatus } from '@/auth/hooks/useOnboardingStatus';
|
||||
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
||||
import { OnboardingStatus } from '@/auth/utils/getOnboardingStatus';
|
||||
import { useUpdateOneObjectRecord } from '@/object-record/hooks/useUpdateOneObjectRecord';
|
||||
import { ProfilePictureUploader } from '@/settings/profile/components/ProfilePictureUploader';
|
||||
import { PageHotkeyScope } from '@/types/PageHotkeyScope';
|
||||
@ -52,6 +54,7 @@ type Form = z.infer<typeof validationSchema>;
|
||||
|
||||
export const CreateProfile = () => {
|
||||
const navigate = useNavigate();
|
||||
const onboardingStatus = useOnboardingStatus();
|
||||
|
||||
const { enqueueSnackBar } = useSnackBar();
|
||||
|
||||
@ -61,7 +64,7 @@ export const CreateProfile = () => {
|
||||
|
||||
const { updateOneObject, objectNotFoundInMetadata } =
|
||||
useUpdateOneObjectRecord<WorkspaceMember>({
|
||||
objectNameSingular: 'workspaceMemberV2',
|
||||
objectNameSingular: 'workspaceMember',
|
||||
});
|
||||
|
||||
// Form
|
||||
@ -139,6 +142,10 @@ export const CreateProfile = () => {
|
||||
[onSubmit],
|
||||
);
|
||||
|
||||
if (onboardingStatus !== OnboardingStatus.OngoingProfileCreation) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Title>Create profile</Title>
|
||||
|
||||
@ -8,7 +8,9 @@ import { z } from 'zod';
|
||||
|
||||
import { SubTitle } from '@/auth/components/SubTitle';
|
||||
import { Title } from '@/auth/components/Title';
|
||||
import { useOnboardingStatus } from '@/auth/hooks/useOnboardingStatus';
|
||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||
import { OnboardingStatus } from '@/auth/utils/getOnboardingStatus';
|
||||
import { WorkspaceLogoUploader } from '@/settings/workspace/components/WorkspaceLogoUploader';
|
||||
import { PageHotkeyScope } from '@/types/PageHotkeyScope';
|
||||
import { H2Title } from '@/ui/display/typography/components/H2Title';
|
||||
@ -43,6 +45,7 @@ export const CreateWorkspace = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { enqueueSnackBar } = useSnackBar();
|
||||
const onboardingStatus = useOnboardingStatus();
|
||||
const setCurrentWorkspace = useSetRecoilState(currentWorkspaceState);
|
||||
|
||||
const [updateWorkspace] = useUpdateWorkspaceMutation();
|
||||
@ -66,7 +69,7 @@ export const CreateWorkspace = () => {
|
||||
try {
|
||||
const result = await updateWorkspace({
|
||||
variables: {
|
||||
data: {
|
||||
input: {
|
||||
displayName: data.name,
|
||||
},
|
||||
},
|
||||
@ -101,6 +104,10 @@ export const CreateWorkspace = () => {
|
||||
[onSubmit],
|
||||
);
|
||||
|
||||
if (onboardingStatus !== OnboardingStatus.OngoingWorkspaceCreation) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Title>Create your workspace</Title>
|
||||
|
||||
@ -1,75 +0,0 @@
|
||||
import { getOperationName } from '@apollo/client/utilities';
|
||||
import styled from '@emotion/styled';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { useOptimisticEffect } from '@/apollo/optimistic-effect/hooks/useOptimisticEffect';
|
||||
import { CompanyTable } from '@/companies/table/components/CompanyTable';
|
||||
import { SEARCH_COMPANY_QUERY } from '@/search/graphql/queries/searchCompanyQuery';
|
||||
import { SpreadsheetImportProvider } from '@/spreadsheet-import/provider/components/SpreadsheetImportProvider';
|
||||
import { IconBuildingSkyscraper } from '@/ui/display/icon';
|
||||
import { PageAddButton } from '@/ui/layout/page/PageAddButton';
|
||||
import { PageBody } from '@/ui/layout/page/PageBody';
|
||||
import { PageContainer } from '@/ui/layout/page/PageContainer';
|
||||
import { PageHeader } from '@/ui/layout/page/PageHeader';
|
||||
import { PageHotkeysEffect } from '@/ui/layout/page/PageHotkeysEffect';
|
||||
import { RecordTableActionBar } from '@/ui/object/record-table/action-bar/components/RecordTableActionBar';
|
||||
import { RecordTableContextMenu } from '@/ui/object/record-table/context-menu/components/RecordTableContextMenu';
|
||||
import { useRecordTable } from '@/ui/object/record-table/hooks/useRecordTable';
|
||||
import { useUpsertTableRowId } from '@/ui/object/record-table/hooks/useUpsertTableRowId';
|
||||
import { useInsertOneCompanyMutation } from '~/generated/graphql';
|
||||
|
||||
const StyledTableContainer = styled.div`
|
||||
display: flex;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export const Companies = () => {
|
||||
const [insertCompany] = useInsertOneCompanyMutation();
|
||||
const { upsertRecordTableItem } = useRecordTable({
|
||||
recordTableScopeId: 'companies',
|
||||
});
|
||||
const upsertTableRowIds = useUpsertTableRowId();
|
||||
const { triggerOptimisticEffects } = useOptimisticEffect({
|
||||
objectNameSingular: 'company',
|
||||
});
|
||||
|
||||
const handleAddButtonClick = async () => {
|
||||
const newCompanyId: string = v4();
|
||||
await insertCompany({
|
||||
variables: {
|
||||
data: {
|
||||
id: newCompanyId,
|
||||
name: '',
|
||||
domainName: '',
|
||||
address: '',
|
||||
},
|
||||
},
|
||||
update: (_cache, { data }) => {
|
||||
if (data?.createOneCompany) {
|
||||
upsertTableRowIds(data?.createOneCompany.id);
|
||||
upsertRecordTableItem(data?.createOneCompany);
|
||||
triggerOptimisticEffects('Company', [data?.createOneCompany]);
|
||||
}
|
||||
},
|
||||
refetchQueries: [getOperationName(SEARCH_COMPANY_QUERY) ?? ''],
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<SpreadsheetImportProvider>
|
||||
<PageContainer>
|
||||
<PageHeader title="Companies" Icon={IconBuildingSkyscraper}>
|
||||
<PageHotkeysEffect onAddButtonClick={handleAddButtonClick} />
|
||||
<PageAddButton onClick={handleAddButtonClick} />
|
||||
</PageHeader>
|
||||
<PageBody>
|
||||
<StyledTableContainer>
|
||||
<CompanyTable />
|
||||
</StyledTableContainer>
|
||||
<RecordTableActionBar />
|
||||
<RecordTableContextMenu />
|
||||
</PageBody>
|
||||
</PageContainer>
|
||||
</SpreadsheetImportProvider>
|
||||
);
|
||||
};
|
||||
@ -1,6 +1,5 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { CompanyTableMockMode } from '@/companies/table/components/CompanyTableMockMode';
|
||||
import { IconBuildingSkyscraper } from '@/ui/display/icon';
|
||||
import { PageBody } from '@/ui/layout/page/PageBody';
|
||||
import { PageContainer } from '@/ui/layout/page/PageContainer';
|
||||
@ -16,9 +15,7 @@ export const CompaniesMockMode = () => {
|
||||
<PageContainer>
|
||||
<PageHeader title="Companies" Icon={IconBuildingSkyscraper} />
|
||||
<PageBody>
|
||||
<StyledTableContainer>
|
||||
<CompanyTableMockMode />
|
||||
</StyledTableContainer>
|
||||
<StyledTableContainer></StyledTableContainer>
|
||||
</PageBody>
|
||||
</PageContainer>
|
||||
);
|
||||
|
||||
@ -1,129 +0,0 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
|
||||
import { CompanyTeam } from '@/companies/components/CompanyTeam';
|
||||
import { useCompanyQuery } from '@/companies/hooks/useCompanyQuery';
|
||||
import { useFavorites } from '@/favorites/hooks/useFavorites';
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import { IconBuildingSkyscraper } from '@/ui/display/icon';
|
||||
import { PageBody } from '@/ui/layout/page/PageBody';
|
||||
import { PageContainer } from '@/ui/layout/page/PageContainer';
|
||||
import { PageFavoriteButton } from '@/ui/layout/page/PageFavoriteButton';
|
||||
import { PageHeader } from '@/ui/layout/page/PageHeader';
|
||||
import { ShowPageAddButton } from '@/ui/layout/show-page/components/ShowPageAddButton';
|
||||
import { ShowPageLeftContainer } from '@/ui/layout/show-page/components/ShowPageLeftContainer';
|
||||
import { ShowPageRightContainer } from '@/ui/layout/show-page/components/ShowPageRightContainer';
|
||||
import { ShowPageSummaryCard } from '@/ui/layout/show-page/components/ShowPageSummaryCard';
|
||||
import { ShowPageRecoilScopeContext } from '@/ui/layout/states/ShowPageRecoilScopeContext';
|
||||
import { FieldContext } from '@/ui/object/field/contexts/FieldContext';
|
||||
import { RecordInlineCell } from '@/ui/object/record-inline-cell/components/RecordInlineCell';
|
||||
import { PropertyBox } from '@/ui/object/record-inline-cell/property-box/components/PropertyBox';
|
||||
import { InlineCellHotkeyScope } from '@/ui/object/record-inline-cell/types/InlineCellHotkeyScope';
|
||||
import { PageTitle } from '@/ui/utilities/page-title/PageTitle';
|
||||
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
|
||||
import { useUpdateOneCompanyMutation } from '~/generated/graphql';
|
||||
import { getLogoUrlFromDomainName } from '~/utils';
|
||||
|
||||
import { CompanyNameEditableField } from '../../modules/companies/editable-field/components/CompanyNameEditableField';
|
||||
import { ShowPageContainer } from '../../modules/ui/layout/page/ShowPageContainer';
|
||||
|
||||
import { companyShowFieldDefinitions } from './constants/companyShowFieldDefinitions';
|
||||
|
||||
export const CompanyShow = () => {
|
||||
const companyId = useParams().companyId ?? '';
|
||||
const { createFavorite, deleteFavorite } = useFavorites({
|
||||
objectNamePlural: 'companies',
|
||||
});
|
||||
const navigate = useNavigate();
|
||||
const { data, loading } = useCompanyQuery(companyId);
|
||||
const company = data?.findUniqueCompany;
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading && !company) {
|
||||
navigate(AppPath.NotFound);
|
||||
}
|
||||
}, [loading, company, navigate]);
|
||||
|
||||
if (!company) return <></>;
|
||||
|
||||
const isFavorite =
|
||||
company.Favorite && company.Favorite?.length > 0 ? true : false;
|
||||
|
||||
const handleFavoriteButtonClick = async () => {
|
||||
if (isFavorite) deleteFavorite(companyId);
|
||||
else createFavorite('company', companyId);
|
||||
};
|
||||
|
||||
return (
|
||||
<PageContainer>
|
||||
<PageTitle title={company.name || 'No Name'} />
|
||||
<PageHeader
|
||||
title={company.name ?? ''}
|
||||
hasBackButton
|
||||
Icon={IconBuildingSkyscraper}
|
||||
>
|
||||
<PageFavoriteButton
|
||||
isFavorite={isFavorite}
|
||||
onClick={handleFavoriteButtonClick}
|
||||
/>
|
||||
<ShowPageAddButton
|
||||
key="add"
|
||||
entity={{
|
||||
id: company.id,
|
||||
type: 'Company',
|
||||
}}
|
||||
/>
|
||||
</PageHeader>
|
||||
<PageBody>
|
||||
<RecoilScope CustomRecoilScopeContext={ShowPageRecoilScopeContext}>
|
||||
<ShowPageContainer>
|
||||
<ShowPageLeftContainer>
|
||||
<ShowPageSummaryCard
|
||||
id={company.id}
|
||||
logoOrAvatar={getLogoUrlFromDomainName(
|
||||
company.domainName ?? '',
|
||||
)}
|
||||
title={company.name ?? 'No name'}
|
||||
date={company.createdAt ?? ''}
|
||||
renderTitleEditComponent={() => (
|
||||
<CompanyNameEditableField company={company} />
|
||||
)}
|
||||
avatarType="squared"
|
||||
/>
|
||||
<PropertyBox extraPadding={true}>
|
||||
{companyShowFieldDefinitions.map((fieldDefinition) => {
|
||||
return (
|
||||
<FieldContext.Provider
|
||||
key={company.id + fieldDefinition.fieldMetadataId}
|
||||
value={{
|
||||
entityId: company.id,
|
||||
recoilScopeId:
|
||||
company.id + fieldDefinition.fieldMetadataId,
|
||||
fieldDefinition,
|
||||
useUpdateEntityMutation: useUpdateOneCompanyMutation,
|
||||
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
||||
}}
|
||||
>
|
||||
<RecordInlineCell />
|
||||
</FieldContext.Provider>
|
||||
);
|
||||
})}
|
||||
</PropertyBox>
|
||||
<CompanyTeam company={company}></CompanyTeam>
|
||||
</ShowPageLeftContainer>
|
||||
<ShowPageRightContainer
|
||||
entity={{
|
||||
id: company.id,
|
||||
type: 'Company',
|
||||
}}
|
||||
timeline
|
||||
tasks
|
||||
notes
|
||||
emails
|
||||
/>
|
||||
</ShowPageContainer>
|
||||
</RecoilScope>
|
||||
</PageBody>
|
||||
</PageContainer>
|
||||
);
|
||||
};
|
||||
@ -1,62 +0,0 @@
|
||||
import { expect } from '@storybook/jest';
|
||||
import { Meta } from '@storybook/react';
|
||||
import { userEvent, within } from '@storybook/testing-library';
|
||||
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import {
|
||||
PageDecorator,
|
||||
PageDecoratorArgs,
|
||||
} from '~/testing/decorators/PageDecorator';
|
||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||
import { mockedCompaniesData } from '~/testing/mock-data/companies';
|
||||
import { sleep } from '~/testing/sleep';
|
||||
|
||||
import { Companies } from '../Companies';
|
||||
|
||||
import { Story } from './Companies.stories';
|
||||
|
||||
const meta: Meta<PageDecoratorArgs> = {
|
||||
title: 'Pages/Companies/Add',
|
||||
component: Companies,
|
||||
decorators: [PageDecorator],
|
||||
args: { routePath: AppPath.CompaniesPage },
|
||||
parameters: {
|
||||
msw: graphqlMocks,
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
export const AddNewCompany: Story = {
|
||||
play: async ({ canvasElement, step }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
await step('Wait for rows to appear', async () => {
|
||||
await canvas.findByText(
|
||||
mockedCompaniesData[0].name,
|
||||
{},
|
||||
{ timeout: 3000 },
|
||||
);
|
||||
});
|
||||
|
||||
const rowsBeforeAdd = canvas.getAllByRole('row');
|
||||
|
||||
await step('Click on add button', async () => {
|
||||
const addButton = canvas.getByRole('button', { name: 'Add' });
|
||||
|
||||
await userEvent.click(addButton);
|
||||
});
|
||||
|
||||
await sleep(1000);
|
||||
|
||||
await step('Check an empty row has been added', async () => {
|
||||
const rowsAfterAdd = canvas.getAllByRole('row');
|
||||
|
||||
const firstRow = rowsAfterAdd[1];
|
||||
const cells = within(firstRow).getAllByRole('cell');
|
||||
|
||||
expect(cells[1].textContent).toBe('');
|
||||
expect(rowsAfterAdd).toHaveLength(rowsBeforeAdd.length + 1);
|
||||
});
|
||||
},
|
||||
};
|
||||
@ -1,119 +0,0 @@
|
||||
import { expect } from '@storybook/jest';
|
||||
import { Meta } from '@storybook/react';
|
||||
import { userEvent, within } from '@storybook/testing-library';
|
||||
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import {
|
||||
PageDecorator,
|
||||
PageDecoratorArgs,
|
||||
} from '~/testing/decorators/PageDecorator';
|
||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||
import { mockedCompaniesData } from '~/testing/mock-data/companies';
|
||||
import { sleep } from '~/testing/sleep';
|
||||
|
||||
import { Companies } from '../Companies';
|
||||
|
||||
import { Story } from './Companies.stories';
|
||||
|
||||
const meta: Meta<PageDecoratorArgs> = {
|
||||
title: 'Pages/Companies/FilterBy',
|
||||
component: Companies,
|
||||
decorators: [PageDecorator],
|
||||
args: { routePath: AppPath.CompaniesPage },
|
||||
parameters: {
|
||||
msw: graphqlMocks,
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
export const FilterByName: Story = {
|
||||
play: async ({ canvasElement, step }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
await step('Wait for rows to appear', async () => {
|
||||
await canvas.findByText(
|
||||
mockedCompaniesData[0].name,
|
||||
{},
|
||||
{ timeout: 3000 },
|
||||
);
|
||||
});
|
||||
|
||||
await step('Click on filter button', async () => {
|
||||
const filterButton = canvas.getByText('Filter');
|
||||
await userEvent.click(filterButton);
|
||||
});
|
||||
|
||||
await step('Select name filter', async () => {
|
||||
const nameFilterButton = canvas.getByTestId('select-filter-0');
|
||||
await userEvent.click(nameFilterButton);
|
||||
|
||||
const nameInput = canvas.getByPlaceholderText('Name');
|
||||
await userEvent.type(nameInput, 'Air', { delay: 200 });
|
||||
|
||||
const nameFilter = canvas.getAllByText(
|
||||
(_, element) => !!element?.textContent?.includes('Name: Air'),
|
||||
);
|
||||
expect(nameFilter).not.toHaveLength(0);
|
||||
});
|
||||
|
||||
await sleep(1000);
|
||||
|
||||
await step('Check filtered rows', async () => {
|
||||
expect(canvas.getByText('Airbnb')).toBeVisible();
|
||||
expect(canvas.getByText('Aircall')).toBeVisible();
|
||||
expect(canvas.queryByText('Qonto')).toBeNull();
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export const FilterByAccountOwner: Story = {
|
||||
play: async ({ canvasElement, step }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
await step('Wait for rows to appear', async () => {
|
||||
await canvas.findByText(
|
||||
mockedCompaniesData[0].name,
|
||||
{},
|
||||
{ timeout: 3000 },
|
||||
);
|
||||
});
|
||||
|
||||
await step('Click on filter button', async () => {
|
||||
const filterButton = canvas.getByText('Filter');
|
||||
await userEvent.click(filterButton);
|
||||
});
|
||||
|
||||
await step('Select account owner filter', async () => {
|
||||
const accountOwnerFilterButton = canvas.getByTestId('select-filter-5');
|
||||
await userEvent.click(accountOwnerFilterButton);
|
||||
|
||||
const accountOwnerNameInput =
|
||||
canvas.getByPlaceholderText('Account owner');
|
||||
await userEvent.type(accountOwnerNameInput, 'Char', { delay: 200 });
|
||||
|
||||
const charlesChip = await canvas.findByRole(
|
||||
'listitem',
|
||||
{
|
||||
name: (_, element) =>
|
||||
!!element?.textContent?.includes('Charles Test'),
|
||||
},
|
||||
{ timeout: 1000 },
|
||||
);
|
||||
await userEvent.click(charlesChip);
|
||||
|
||||
const accountOwnerFilter = canvas.getAllByText(
|
||||
(_, element) =>
|
||||
!!element?.textContent?.includes('Account owner: Charles Test'),
|
||||
);
|
||||
expect(accountOwnerFilter).not.toHaveLength(0);
|
||||
});
|
||||
|
||||
await sleep(1000);
|
||||
|
||||
await step('Check filtered rows', async () => {
|
||||
expect(canvas.getByText('Airbnb')).toBeVisible();
|
||||
expect(canvas.queryByText('Qonto')).toBeNull();
|
||||
});
|
||||
},
|
||||
};
|
||||
@ -1,76 +0,0 @@
|
||||
import { expect } from '@storybook/jest';
|
||||
import { Meta } from '@storybook/react';
|
||||
import { userEvent, within } from '@storybook/testing-library';
|
||||
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import {
|
||||
PageDecorator,
|
||||
PageDecoratorArgs,
|
||||
} from '~/testing/decorators/PageDecorator';
|
||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||
import { mockedCompaniesData } from '~/testing/mock-data/companies';
|
||||
import { sleep } from '~/testing/sleep';
|
||||
|
||||
import { Companies } from '../Companies';
|
||||
|
||||
import { Story } from './Companies.stories';
|
||||
|
||||
const meta: Meta<PageDecoratorArgs> = {
|
||||
title: 'Pages/Companies/SortBy',
|
||||
component: Companies,
|
||||
decorators: [PageDecorator],
|
||||
args: { routePath: AppPath.CompaniesPage },
|
||||
parameters: {
|
||||
msw: graphqlMocks,
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
const sortedCompanyNames = [...mockedCompaniesData]
|
||||
.map(({ name }) => name)
|
||||
.sort((a, b) => a.localeCompare(b));
|
||||
|
||||
export const SortByName: Story = {
|
||||
play: async ({ canvasElement, step }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
await step('Wait for rows to appear', async () => {
|
||||
await canvas.findByText(
|
||||
mockedCompaniesData[0].name,
|
||||
{},
|
||||
{ timeout: 3000 },
|
||||
);
|
||||
});
|
||||
|
||||
await step('Click on sort button', async () => {
|
||||
const sortButton = canvas.getByRole('button', { name: 'Sort' });
|
||||
await userEvent.click(sortButton);
|
||||
});
|
||||
|
||||
await step('Select sort by name', async () => {
|
||||
const nameSortButton = canvas.getByTestId('select-sort-0');
|
||||
await userEvent.click(nameSortButton);
|
||||
|
||||
await canvas.findByTestId('remove-icon-name', {}, { timeout: 3000 });
|
||||
});
|
||||
|
||||
await sleep(1000);
|
||||
|
||||
await step('Check rows are sorted by name', async () => {
|
||||
const nameCells = canvas.getAllByText(
|
||||
(_, element) =>
|
||||
sortedCompanyNames.some((name) =>
|
||||
element?.textContent?.includes(name),
|
||||
),
|
||||
{ selector: '[data-testid="editable-cell-display-mode"]' },
|
||||
);
|
||||
|
||||
expect(nameCells).toHaveLength(sortedCompanyNames.length);
|
||||
|
||||
sortedCompanyNames.forEach((name, index) =>
|
||||
expect(nameCells[index]).toHaveTextContent(name),
|
||||
);
|
||||
});
|
||||
},
|
||||
};
|
||||
@ -1,26 +0,0 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import {
|
||||
PageDecorator,
|
||||
PageDecoratorArgs,
|
||||
} from '~/testing/decorators/PageDecorator';
|
||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||
|
||||
import { Companies } from '../Companies';
|
||||
|
||||
const meta: Meta<PageDecoratorArgs> = {
|
||||
title: 'Pages/Companies',
|
||||
component: Companies,
|
||||
decorators: [PageDecorator],
|
||||
args: { routePath: AppPath.CompaniesPage },
|
||||
parameters: {
|
||||
msw: graphqlMocks,
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
export type Story = StoryObj<typeof Companies>;
|
||||
|
||||
export const Default: Story = {};
|
||||
@ -1,171 +0,0 @@
|
||||
import { getOperationName } from '@apollo/client/utilities';
|
||||
import { expect } from '@storybook/jest';
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { fireEvent, within } from '@storybook/testing-library';
|
||||
import { graphql } from 'msw';
|
||||
|
||||
import { UPDATE_ONE_COMPANY } from '@/companies/graphql/mutations/updateOneCompany';
|
||||
import { GET_COMPANY } from '@/companies/graphql/queries/getCompany';
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import { ObjectFilterDropdownScope } from '@/ui/object/object-filter-dropdown/scopes/ObjectFilterDropdownScope';
|
||||
import {
|
||||
PageDecorator,
|
||||
PageDecoratorArgs,
|
||||
} from '~/testing/decorators/PageDecorator';
|
||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||
import { mockedCompaniesData } from '~/testing/mock-data/companies';
|
||||
|
||||
import { CompanyShow } from '../CompanyShow';
|
||||
|
||||
const meta: Meta<PageDecoratorArgs> = {
|
||||
title: 'Pages/Companies/Company',
|
||||
component: CompanyShow,
|
||||
decorators: [PageDecorator],
|
||||
args: {
|
||||
routePath: AppPath.CompanyShowPage,
|
||||
routeParams: { ':companyId': mockedCompaniesData[0].id },
|
||||
},
|
||||
parameters: {
|
||||
msw: [
|
||||
...graphqlMocks,
|
||||
graphql.query(getOperationName(GET_COMPANY) ?? '', (req, res, ctx) => {
|
||||
return res(
|
||||
ctx.data({
|
||||
findUniqueCompany: mockedCompaniesData[0],
|
||||
}),
|
||||
);
|
||||
}),
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
export type Story = StoryObj<typeof CompanyShow>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
export const EditNoteByAddButton: Story = {
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
const firstNoteTitle = await canvas.findByText('My very first note');
|
||||
await firstNoteTitle.click();
|
||||
|
||||
expect(
|
||||
await canvas.findByDisplayValue('My very first note'),
|
||||
).toBeInTheDocument();
|
||||
|
||||
const workspaceName = await canvas.findByText('Twenty');
|
||||
await fireEvent.click(workspaceName);
|
||||
|
||||
expect(await canvas.queryByDisplayValue('My very first note')).toBeNull();
|
||||
|
||||
const addDropdown = await canvas.findByTestId('add-showpage-button');
|
||||
await addDropdown.click();
|
||||
|
||||
const noteButton = await canvas.findByText('Note');
|
||||
await noteButton.click();
|
||||
|
||||
expect(
|
||||
await canvas.findByDisplayValue('My very first note'),
|
||||
).toBeInTheDocument();
|
||||
},
|
||||
parameters: {
|
||||
msw: [
|
||||
...meta.parameters?.msw,
|
||||
graphql.mutation(
|
||||
getOperationName(UPDATE_ONE_COMPANY) ?? '',
|
||||
(req, res, ctx) => {
|
||||
return res(
|
||||
ctx.data({
|
||||
updateOneCompany: [mockedCompaniesData[0]],
|
||||
}),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export const NoteTab: Story = {
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
const noteTab = await canvas.findByTestId('tab-notes');
|
||||
await noteTab.click();
|
||||
|
||||
expect(await canvas.findByText('My very first note')).toBeInTheDocument();
|
||||
|
||||
const workspaceName = await canvas.findByText('Twenty');
|
||||
await fireEvent.click(workspaceName);
|
||||
|
||||
expect(await canvas.queryByDisplayValue('My very first note')).toBeNull();
|
||||
|
||||
const addButton = await canvas.findByText('Add note');
|
||||
await addButton.click();
|
||||
|
||||
const noteButton = await canvas.findByText('Note');
|
||||
await noteButton.click();
|
||||
|
||||
expect(await canvas.findByText('My very first note')).toBeInTheDocument();
|
||||
},
|
||||
parameters: {
|
||||
msw: [
|
||||
...meta.parameters?.msw,
|
||||
graphql.mutation(
|
||||
getOperationName(UPDATE_ONE_COMPANY) ?? '',
|
||||
(req, res, ctx) => {
|
||||
return res(
|
||||
ctx.data({
|
||||
updateOneCompany: [mockedCompaniesData[0]],
|
||||
}),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export const TaskTab: Story = {
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<ObjectFilterDropdownScope filterScopeId="tasks-filter-scope">
|
||||
<Story />
|
||||
</ObjectFilterDropdownScope>
|
||||
),
|
||||
],
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
const taskTab = await canvas.findByTestId('tab-tasks');
|
||||
await taskTab.click();
|
||||
|
||||
expect(await canvas.findByText('My very first task')).toBeInTheDocument();
|
||||
|
||||
const workspaceName = await canvas.findByText('Twenty');
|
||||
await fireEvent.click(workspaceName);
|
||||
|
||||
expect(await canvas.queryByDisplayValue('My very first task')).toBeNull();
|
||||
|
||||
const addButton = await canvas.findByText('Add task');
|
||||
await addButton.click();
|
||||
|
||||
const taskButton = await canvas.findByText('Task');
|
||||
await taskButton.click();
|
||||
|
||||
expect(await canvas.findByText('My very first task')).toBeInTheDocument();
|
||||
},
|
||||
parameters: {
|
||||
msw: [
|
||||
...meta.parameters?.msw,
|
||||
graphql.mutation(
|
||||
getOperationName(UPDATE_ONE_COMPANY) ?? '',
|
||||
(req, res, ctx) => {
|
||||
return res(
|
||||
ctx.data({
|
||||
updateOneCompany: [mockedCompaniesData[0]],
|
||||
}),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
};
|
||||
@ -1,99 +0,0 @@
|
||||
import {
|
||||
IconBrandX,
|
||||
IconCalendar,
|
||||
IconLink,
|
||||
IconMap,
|
||||
IconTarget,
|
||||
IconUserCircle,
|
||||
IconUsers,
|
||||
} from '@/ui/display/icon';
|
||||
import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect';
|
||||
import { FieldDefinition } from '@/ui/object/field/types/FieldDefinition';
|
||||
import {
|
||||
FieldBooleanMetadata,
|
||||
FieldDateMetadata,
|
||||
FieldMetadata,
|
||||
FieldNumberMetadata,
|
||||
FieldRelationMetadata,
|
||||
FieldTextMetadata,
|
||||
FieldURLMetadata,
|
||||
} from '@/ui/object/field/types/FieldMetadata';
|
||||
import { User } from '~/generated/graphql';
|
||||
|
||||
export const companyShowFieldDefinitions: FieldDefinition<FieldMetadata>[] = [
|
||||
{
|
||||
fieldMetadataId: 'domainName',
|
||||
label: 'Domain name',
|
||||
Icon: IconLink,
|
||||
type: 'URL',
|
||||
metadata: {
|
||||
fieldName: 'domainName',
|
||||
placeHolder: 'URL',
|
||||
},
|
||||
} satisfies FieldDefinition<FieldURLMetadata>,
|
||||
{
|
||||
fieldMetadataId: 'accountOwner',
|
||||
label: 'Account owner',
|
||||
Icon: IconUserCircle,
|
||||
type: 'RELATION',
|
||||
metadata: {
|
||||
fieldName: 'accountOwner',
|
||||
relationType: Entity.User,
|
||||
},
|
||||
entityChipDisplayMapper: (dataObject: User) => {
|
||||
return {
|
||||
name: dataObject?.displayName,
|
||||
pictureUrl: dataObject?.avatarUrl ?? undefined,
|
||||
avatarType: 'rounded',
|
||||
};
|
||||
},
|
||||
} satisfies FieldDefinition<FieldRelationMetadata>,
|
||||
{
|
||||
fieldMetadataId: 'employees',
|
||||
label: 'Employees',
|
||||
Icon: IconUsers,
|
||||
type: 'NUMBER',
|
||||
metadata: {
|
||||
fieldName: 'employees',
|
||||
placeHolder: 'Employees',
|
||||
},
|
||||
} satisfies FieldDefinition<FieldNumberMetadata>,
|
||||
{
|
||||
fieldMetadataId: 'address',
|
||||
label: 'Address',
|
||||
Icon: IconMap,
|
||||
type: 'TEXT',
|
||||
metadata: {
|
||||
fieldName: 'address',
|
||||
placeHolder: 'Address',
|
||||
},
|
||||
} satisfies FieldDefinition<FieldTextMetadata>,
|
||||
{
|
||||
fieldMetadataId: 'idealCustomerProfile',
|
||||
label: 'ICP',
|
||||
Icon: IconTarget,
|
||||
type: 'BOOLEAN',
|
||||
metadata: {
|
||||
fieldName: 'idealCustomerProfile',
|
||||
},
|
||||
} satisfies FieldDefinition<FieldBooleanMetadata>,
|
||||
{
|
||||
fieldMetadataId: 'xUrl',
|
||||
label: 'Twitter',
|
||||
Icon: IconBrandX,
|
||||
type: 'URL',
|
||||
metadata: {
|
||||
fieldName: 'xUrl',
|
||||
placeHolder: 'X',
|
||||
},
|
||||
} satisfies FieldDefinition<FieldURLMetadata>,
|
||||
{
|
||||
fieldMetadataId: 'createdAt',
|
||||
label: 'Created at',
|
||||
Icon: IconCalendar,
|
||||
type: 'DATE',
|
||||
metadata: {
|
||||
fieldName: 'createdAt',
|
||||
},
|
||||
} satisfies FieldDefinition<FieldDateMetadata>,
|
||||
];
|
||||
@ -1,52 +0,0 @@
|
||||
import {
|
||||
IconBuildingSkyscraper,
|
||||
IconCalendarEvent,
|
||||
IconLink,
|
||||
IconMap,
|
||||
IconUser,
|
||||
IconUsers,
|
||||
} from '@/ui/display/icon/index';
|
||||
import { FilterDefinitionByEntity } from '@/ui/object/object-filter-dropdown/types/FilterDefinitionByEntity';
|
||||
import { FilterDropdownUserSearchSelect } from '@/users/components/FilterDropdownUserSearchSelect';
|
||||
import { Company } from '~/generated/graphql';
|
||||
|
||||
export const companyTableFilterDefinitions: FilterDefinitionByEntity<Company>[] =
|
||||
[
|
||||
{
|
||||
fieldMetadataId: 'name',
|
||||
label: 'Name',
|
||||
Icon: IconBuildingSkyscraper,
|
||||
type: 'TEXT',
|
||||
},
|
||||
{
|
||||
fieldMetadataId: 'employees',
|
||||
label: 'Employees',
|
||||
Icon: IconUsers,
|
||||
type: 'NUMBER',
|
||||
},
|
||||
{
|
||||
fieldMetadataId: 'domainName',
|
||||
label: 'URL',
|
||||
Icon: IconLink,
|
||||
type: 'TEXT',
|
||||
},
|
||||
{
|
||||
fieldMetadataId: 'address',
|
||||
label: 'Address',
|
||||
Icon: IconMap,
|
||||
type: 'TEXT',
|
||||
},
|
||||
{
|
||||
fieldMetadataId: 'createdAt',
|
||||
label: 'Created at',
|
||||
Icon: IconCalendarEvent,
|
||||
type: 'DATE',
|
||||
},
|
||||
{
|
||||
fieldMetadataId: 'accountOwnerId',
|
||||
label: 'Account owner',
|
||||
Icon: IconUser,
|
||||
type: 'ENTITY',
|
||||
entitySelectComponent: <FilterDropdownUserSearchSelect />,
|
||||
},
|
||||
];
|
||||
@ -1,36 +0,0 @@
|
||||
import {
|
||||
IconBuildingSkyscraper,
|
||||
IconCalendarEvent,
|
||||
IconLink,
|
||||
IconMap,
|
||||
IconUsers,
|
||||
} from '@/ui/display/icon/index';
|
||||
import { SortDefinition } from '@/ui/object/object-sort-dropdown/types/SortDefinition';
|
||||
|
||||
export const companyTableSortDefinitions: SortDefinition[] = [
|
||||
{
|
||||
fieldMetadataId: 'name',
|
||||
label: 'Name',
|
||||
Icon: IconBuildingSkyscraper,
|
||||
},
|
||||
{
|
||||
fieldMetadataId: 'employees',
|
||||
label: 'Employees',
|
||||
Icon: IconUsers,
|
||||
},
|
||||
{
|
||||
fieldMetadataId: 'domainName',
|
||||
label: 'Url',
|
||||
Icon: IconLink,
|
||||
},
|
||||
{
|
||||
fieldMetadataId: 'address',
|
||||
label: 'Address',
|
||||
Icon: IconMap,
|
||||
},
|
||||
{
|
||||
fieldMetadataId: 'createdAt',
|
||||
label: 'Creation',
|
||||
Icon: IconCalendarEvent,
|
||||
},
|
||||
];
|
||||
@ -28,7 +28,7 @@ export const Opportunities = () => {
|
||||
|
||||
const { updateOneObject: updateOnePipelineStep } =
|
||||
useUpdateOneObjectRecord<PipelineStep>({
|
||||
objectNameSingular: 'pipelineStepV2',
|
||||
objectNameSingular: 'pipelineStep',
|
||||
});
|
||||
|
||||
const handleEditColumnTitle = (
|
||||
@ -46,7 +46,7 @@ export const Opportunities = () => {
|
||||
};
|
||||
|
||||
const opportunitiesV2MetadataId = useFindOneObjectMetadataItem({
|
||||
objectNameSingular: 'opportunityV2',
|
||||
objectNameSingular: 'opportunity',
|
||||
}).foundObjectMetadataItem?.id;
|
||||
|
||||
const { setViewObjectMetadataId } = useView({
|
||||
|
||||
@ -1,40 +1,34 @@
|
||||
import { FilterDropdownCompanySearchSelect } from '@/companies/components/FilterDropdownCompanySearchSelect';
|
||||
import {
|
||||
IconBuildingSkyscraper,
|
||||
IconCalendarEvent,
|
||||
IconCurrencyDollar,
|
||||
IconUser,
|
||||
} from '@/ui/display/icon/index';
|
||||
import { Opportunity } from '@/pipeline/types/Opportunity';
|
||||
import { FilterDefinitionByEntity } from '@/ui/object/object-filter-dropdown/types/FilterDefinitionByEntity';
|
||||
import { PipelineProgress } from '~/generated/graphql';
|
||||
|
||||
import { FilterDropdownPeopleSearchSelect } from '../../../modules/people/components/FilterDropdownPeopleSearchSelect';
|
||||
|
||||
export const opportunityBoardFilterDefinitions: FilterDefinitionByEntity<PipelineProgress>[] =
|
||||
export const opportunityBoardFilterDefinitions: FilterDefinitionByEntity<Opportunity>[] =
|
||||
[
|
||||
{
|
||||
fieldMetadataId: 'amount',
|
||||
label: 'Amount',
|
||||
Icon: IconCurrencyDollar,
|
||||
iconName: 'IconCurrencyDollar',
|
||||
type: 'NUMBER',
|
||||
},
|
||||
{
|
||||
fieldMetadataId: 'closeDate',
|
||||
label: 'Close date',
|
||||
Icon: IconCalendarEvent,
|
||||
iconName: 'IconCalendarEvent',
|
||||
type: 'DATE',
|
||||
},
|
||||
{
|
||||
fieldMetadataId: 'companyId',
|
||||
label: 'Company',
|
||||
Icon: IconBuildingSkyscraper,
|
||||
iconName: 'IconBuildingSkyscraper',
|
||||
type: 'ENTITY',
|
||||
entitySelectComponent: <FilterDropdownCompanySearchSelect />,
|
||||
},
|
||||
{
|
||||
fieldMetadataId: 'pointOfContactId',
|
||||
label: 'Point of contact',
|
||||
Icon: IconUser,
|
||||
iconName: 'IconUser',
|
||||
type: 'ENTITY',
|
||||
entitySelectComponent: <FilterDropdownPeopleSearchSelect />,
|
||||
},
|
||||
|
||||
@ -1,71 +0,0 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { useOptimisticEffect } from '@/apollo/optimistic-effect/hooks/useOptimisticEffect';
|
||||
import { PersonTable } from '@/people/table/components/PersonTable';
|
||||
import { SpreadsheetImportProvider } from '@/spreadsheet-import/provider/components/SpreadsheetImportProvider';
|
||||
import { IconUser } from '@/ui/display/icon';
|
||||
import { PageAddButton } from '@/ui/layout/page/PageAddButton';
|
||||
import { PageBody } from '@/ui/layout/page/PageBody';
|
||||
import { PageContainer } from '@/ui/layout/page/PageContainer';
|
||||
import { PageHeader } from '@/ui/layout/page/PageHeader';
|
||||
import { PageHotkeysEffect } from '@/ui/layout/page/PageHotkeysEffect';
|
||||
import { RecordTableActionBar } from '@/ui/object/record-table/action-bar/components/RecordTableActionBar';
|
||||
import { RecordTableContextMenu } from '@/ui/object/record-table/context-menu/components/RecordTableContextMenu';
|
||||
import { useRecordTable } from '@/ui/object/record-table/hooks/useRecordTable';
|
||||
import { useUpsertTableRowId } from '@/ui/object/record-table/hooks/useUpsertTableRowId';
|
||||
import { useInsertOnePersonMutation } from '~/generated/graphql';
|
||||
|
||||
const StyledTableContainer = styled.div`
|
||||
display: flex;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export const People = () => {
|
||||
const [insertOnePerson] = useInsertOnePersonMutation();
|
||||
const { upsertRecordTableItem } = useRecordTable({
|
||||
recordTableScopeId: 'people',
|
||||
});
|
||||
const upsertTableRowIds = useUpsertTableRowId();
|
||||
const { triggerOptimisticEffects } = useOptimisticEffect({
|
||||
objectNameSingular: 'Person',
|
||||
});
|
||||
|
||||
const handleAddButtonClick = async () => {
|
||||
const newPersonId: string = v4();
|
||||
await insertOnePerson({
|
||||
variables: {
|
||||
data: {
|
||||
id: newPersonId,
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
},
|
||||
},
|
||||
update: (_cache, { data }) => {
|
||||
if (data?.createOnePerson) {
|
||||
upsertTableRowIds(data?.createOnePerson.id);
|
||||
upsertRecordTableItem(data?.createOnePerson);
|
||||
triggerOptimisticEffects('Person', [data?.createOnePerson]);
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<SpreadsheetImportProvider>
|
||||
<PageContainer>
|
||||
<PageHeader title="People" Icon={IconUser}>
|
||||
<PageHotkeysEffect onAddButtonClick={handleAddButtonClick} />
|
||||
<PageAddButton onClick={handleAddButtonClick} />
|
||||
</PageHeader>
|
||||
<PageBody>
|
||||
<StyledTableContainer>
|
||||
<PersonTable />
|
||||
</StyledTableContainer>
|
||||
<RecordTableActionBar />
|
||||
<RecordTableContextMenu />
|
||||
</PageBody>
|
||||
</PageContainer>
|
||||
</SpreadsheetImportProvider>
|
||||
);
|
||||
};
|
||||
@ -1,161 +0,0 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { getOperationName } from '@apollo/client/utilities';
|
||||
|
||||
import { useFavorites } from '@/favorites/hooks/useFavorites';
|
||||
import { GET_PERSON } from '@/people/graphql/queries/getPerson';
|
||||
import { usePersonQuery } from '@/people/hooks/usePersonQuery';
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import { IconUser } from '@/ui/display/icon';
|
||||
import { PageBody } from '@/ui/layout/page/PageBody';
|
||||
import { PageContainer } from '@/ui/layout/page/PageContainer';
|
||||
import { PageFavoriteButton } from '@/ui/layout/page/PageFavoriteButton';
|
||||
import { PageHeader } from '@/ui/layout/page/PageHeader';
|
||||
import { ShowPageAddButton } from '@/ui/layout/show-page/components/ShowPageAddButton';
|
||||
import { ShowPageLeftContainer } from '@/ui/layout/show-page/components/ShowPageLeftContainer';
|
||||
import { ShowPageRightContainer } from '@/ui/layout/show-page/components/ShowPageRightContainer';
|
||||
import { ShowPageSummaryCard } from '@/ui/layout/show-page/components/ShowPageSummaryCard';
|
||||
import { ShowPageRecoilScopeContext } from '@/ui/layout/states/ShowPageRecoilScopeContext';
|
||||
import { FieldContext } from '@/ui/object/field/contexts/FieldContext';
|
||||
import { RecordInlineCell } from '@/ui/object/record-inline-cell/components/RecordInlineCell';
|
||||
import { PropertyBox } from '@/ui/object/record-inline-cell/property-box/components/PropertyBox';
|
||||
import { InlineCellHotkeyScope } from '@/ui/object/record-inline-cell/types/InlineCellHotkeyScope';
|
||||
import { PageTitle } from '@/ui/utilities/page-title/PageTitle';
|
||||
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
|
||||
import {
|
||||
useUpdateOnePersonMutation,
|
||||
useUploadPersonPictureMutation,
|
||||
} from '~/generated/graphql';
|
||||
|
||||
import { PeopleFullNameEditableField } from '../../modules/people/editable-field/components/PeopleFullNameEditableField';
|
||||
import { ShowPageContainer } from '../../modules/ui/layout/page/ShowPageContainer';
|
||||
|
||||
import { personShowFieldDefinition } from './constants/personShowFieldDefinitions';
|
||||
|
||||
export const PersonShow = () => {
|
||||
const personId = useParams().personId ?? '';
|
||||
const { createFavorite, deleteFavorite } = useFavorites({
|
||||
objectNamePlural: 'peopleV2',
|
||||
});
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { data, loading } = usePersonQuery(personId);
|
||||
const person = data?.findUniquePerson;
|
||||
|
||||
const [uploadPicture] = useUploadPersonPictureMutation();
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading && !person) {
|
||||
navigate(AppPath.NotFound);
|
||||
}
|
||||
}, [loading, person, navigate]);
|
||||
|
||||
if (!person) return <></>;
|
||||
|
||||
const isFavorite =
|
||||
person.Favorite && person.Favorite?.length > 0 ? true : false;
|
||||
|
||||
const onUploadPicture = async (file: File) => {
|
||||
if (!file || !person?.id) {
|
||||
return;
|
||||
}
|
||||
await uploadPicture({
|
||||
variables: {
|
||||
file,
|
||||
id: person.id,
|
||||
},
|
||||
refetchQueries: [getOperationName(GET_PERSON) ?? ''],
|
||||
});
|
||||
};
|
||||
|
||||
const handleFavoriteButtonClick = async () => {
|
||||
if (isFavorite) deleteFavorite(person.id);
|
||||
else createFavorite('person', person.id);
|
||||
};
|
||||
|
||||
return (
|
||||
<PageContainer>
|
||||
<PageTitle title={person.displayName || 'No Name'} />
|
||||
<PageHeader title={person.firstName ?? ''} Icon={IconUser} hasBackButton>
|
||||
<PageFavoriteButton
|
||||
isFavorite={isFavorite}
|
||||
onClick={handleFavoriteButtonClick}
|
||||
/>
|
||||
<ShowPageAddButton
|
||||
key="add"
|
||||
entity={{
|
||||
id: person.id,
|
||||
type: 'Person',
|
||||
relatedEntities: person.company?.id
|
||||
? [
|
||||
{
|
||||
id: person.company?.id,
|
||||
type: 'Company',
|
||||
},
|
||||
]
|
||||
: undefined,
|
||||
}}
|
||||
/>
|
||||
</PageHeader>
|
||||
<PageBody>
|
||||
<RecoilScope CustomRecoilScopeContext={ShowPageRecoilScopeContext}>
|
||||
<ShowPageContainer>
|
||||
<ShowPageLeftContainer>
|
||||
<ShowPageSummaryCard
|
||||
id={person.id}
|
||||
title={person.displayName ?? 'No name'}
|
||||
logoOrAvatar={person.avatarUrl ?? undefined}
|
||||
date={person.createdAt ?? ''}
|
||||
renderTitleEditComponent={() =>
|
||||
person ? (
|
||||
<PeopleFullNameEditableField people={person} />
|
||||
) : (
|
||||
<></>
|
||||
)
|
||||
}
|
||||
onUploadPicture={onUploadPicture}
|
||||
avatarType="rounded"
|
||||
/>
|
||||
<PropertyBox extraPadding={true}>
|
||||
{personShowFieldDefinition.map((fieldDefinition) => {
|
||||
return (
|
||||
<FieldContext.Provider
|
||||
value={{
|
||||
entityId: person.id,
|
||||
recoilScopeId: person.id + fieldDefinition.label,
|
||||
fieldDefinition,
|
||||
useUpdateEntityMutation: useUpdateOnePersonMutation,
|
||||
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
||||
}}
|
||||
key={person.id + fieldDefinition.label}
|
||||
>
|
||||
<RecordInlineCell />
|
||||
</FieldContext.Provider>
|
||||
);
|
||||
})}
|
||||
</PropertyBox>
|
||||
</ShowPageLeftContainer>
|
||||
<ShowPageRightContainer
|
||||
entity={{
|
||||
id: person.id ?? '',
|
||||
type: 'Person',
|
||||
relatedEntities: person.company?.id
|
||||
? [
|
||||
{
|
||||
id: person.company?.id,
|
||||
type: 'Company',
|
||||
},
|
||||
]
|
||||
: undefined,
|
||||
}}
|
||||
timeline
|
||||
tasks
|
||||
notes
|
||||
emails
|
||||
/>
|
||||
</ShowPageContainer>
|
||||
</RecoilScope>
|
||||
</PageBody>
|
||||
</PageContainer>
|
||||
);
|
||||
};
|
||||
@ -1,62 +0,0 @@
|
||||
import { expect } from '@storybook/jest';
|
||||
import { Meta } from '@storybook/react';
|
||||
import { userEvent, within } from '@storybook/testing-library';
|
||||
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import {
|
||||
PageDecorator,
|
||||
PageDecoratorArgs,
|
||||
} from '~/testing/decorators/PageDecorator';
|
||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||
import { mockedPeopleData } from '~/testing/mock-data/people';
|
||||
import { sleep } from '~/testing/sleep';
|
||||
|
||||
import { People } from '../People';
|
||||
|
||||
import { Story } from './People.stories';
|
||||
|
||||
const meta: Meta<PageDecoratorArgs> = {
|
||||
title: 'Pages/People/Add',
|
||||
component: People,
|
||||
decorators: [PageDecorator],
|
||||
args: { routePath: AppPath.PeoplePage },
|
||||
parameters: {
|
||||
msw: graphqlMocks,
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
export const AddNewPerson: Story = {
|
||||
play: async ({ canvasElement, step }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
await step('Wait for rows to appear', async () => {
|
||||
await canvas.findByText(
|
||||
mockedPeopleData[0].displayName,
|
||||
{},
|
||||
{ timeout: 3000 },
|
||||
);
|
||||
});
|
||||
|
||||
const rowsBeforeAdd = canvas.getAllByRole('row');
|
||||
|
||||
await step('Click on add button', async () => {
|
||||
const addButton = canvas.getByRole('button', { name: 'Add' });
|
||||
|
||||
await userEvent.click(addButton);
|
||||
});
|
||||
|
||||
await sleep(1000);
|
||||
|
||||
await step('Check an empty row has been added', async () => {
|
||||
const rowsAfterAdd = canvas.getAllByRole('row');
|
||||
|
||||
const firstRow = rowsAfterAdd[1];
|
||||
const cells = within(firstRow).getAllByRole('cell');
|
||||
|
||||
expect(cells[1].textContent).toBe('');
|
||||
expect(rowsAfterAdd).toHaveLength(rowsBeforeAdd.length + 1);
|
||||
});
|
||||
},
|
||||
};
|
||||
@ -1,113 +0,0 @@
|
||||
import { expect } from '@storybook/jest';
|
||||
import { Meta } from '@storybook/react';
|
||||
import { userEvent, within } from '@storybook/testing-library';
|
||||
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import {
|
||||
PageDecorator,
|
||||
PageDecoratorArgs,
|
||||
} from '~/testing/decorators/PageDecorator';
|
||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||
import { mockedPeopleData } from '~/testing/mock-data/people';
|
||||
import { sleep } from '~/testing/sleep';
|
||||
|
||||
import { People } from '../People';
|
||||
|
||||
import { Story } from './People.stories';
|
||||
|
||||
const meta: Meta<PageDecoratorArgs> = {
|
||||
title: 'Pages/People/FilterBy',
|
||||
component: People,
|
||||
decorators: [PageDecorator],
|
||||
args: { routePath: AppPath.PeoplePage },
|
||||
parameters: {
|
||||
msw: graphqlMocks,
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
export const Email: Story = {
|
||||
play: async ({ canvasElement, step }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
await step('Wait for rows to appear', async () => {
|
||||
await canvas.findByText(
|
||||
mockedPeopleData[0].displayName,
|
||||
{},
|
||||
{ timeout: 3000 },
|
||||
);
|
||||
});
|
||||
|
||||
await step('Click on filter button', async () => {
|
||||
const filterButton = canvas.getByText('Filter');
|
||||
await userEvent.click(filterButton);
|
||||
});
|
||||
|
||||
await step('Select email filter', async () => {
|
||||
const emailFilterButton = canvas.getByTestId('select-filter-2');
|
||||
await userEvent.click(emailFilterButton);
|
||||
|
||||
const emailInput = canvas.getByPlaceholderText('Email');
|
||||
await userEvent.type(emailInput, 'al', { delay: 200 });
|
||||
|
||||
const emailFilter = canvas.getAllByText(
|
||||
(_, element) => !!element?.textContent?.includes('Email: al'),
|
||||
);
|
||||
expect(emailFilter).not.toHaveLength(0);
|
||||
});
|
||||
|
||||
await sleep(1000);
|
||||
|
||||
await step('Check filtered rows', async () => {
|
||||
expect(canvas.getByText('Alexandre Prot')).toBeVisible();
|
||||
expect(canvas.queryByText('John Doe')).toBeNull();
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export const CompanyName: Story = {
|
||||
play: async ({ canvasElement, step }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
await step('Wait for rows to appear', async () => {
|
||||
await canvas.findByText(
|
||||
mockedPeopleData[0].displayName,
|
||||
{},
|
||||
{ timeout: 3000 },
|
||||
);
|
||||
});
|
||||
|
||||
await step('Click on filter button', async () => {
|
||||
const filterButton = canvas.getByText('Filter');
|
||||
await userEvent.click(filterButton);
|
||||
});
|
||||
|
||||
await step('Select company filter', async () => {
|
||||
const companyFilterButton = canvas.getByTestId('select-filter-3');
|
||||
await userEvent.click(companyFilterButton);
|
||||
|
||||
const companyNameInput = canvas.getByPlaceholderText('Company');
|
||||
await userEvent.type(companyNameInput, 'Qon', { delay: 200 });
|
||||
|
||||
const qontoChip = await canvas.findByRole(
|
||||
'listitem',
|
||||
{ name: (_, element) => !!element?.textContent?.includes('Qonto') },
|
||||
{ timeout: 1000 },
|
||||
);
|
||||
await userEvent.click(qontoChip);
|
||||
|
||||
const companyFilter = canvas.getAllByText(
|
||||
(_, element) => !!element?.textContent?.includes('Company: Qonto'),
|
||||
);
|
||||
expect(companyFilter).not.toHaveLength(0);
|
||||
});
|
||||
|
||||
await sleep(1000);
|
||||
|
||||
await step('Check filtered rows', async () => {
|
||||
expect(canvas.getByText('Alexandre Prot')).toBeVisible();
|
||||
expect(canvas.queryByText('John Doe')).toBeNull();
|
||||
});
|
||||
},
|
||||
};
|
||||
@ -1,199 +0,0 @@
|
||||
import { expect } from '@storybook/jest';
|
||||
import { Meta } from '@storybook/react';
|
||||
import { userEvent, within } from '@storybook/testing-library';
|
||||
import assert from 'assert';
|
||||
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import {
|
||||
PageDecorator,
|
||||
PageDecoratorArgs,
|
||||
} from '~/testing/decorators/PageDecorator';
|
||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||
import { mockedPeopleData } from '~/testing/mock-data/people';
|
||||
import { sleep } from '~/testing/sleep';
|
||||
|
||||
import { People } from '../People';
|
||||
|
||||
import { Story } from './People.stories';
|
||||
|
||||
const meta: Meta<PageDecoratorArgs> = {
|
||||
title: 'Pages/People/Input',
|
||||
component: People,
|
||||
decorators: [PageDecorator],
|
||||
args: { routePath: AppPath.PeoplePage },
|
||||
parameters: {
|
||||
msw: graphqlMocks,
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
export const InteractWithManyRows: Story = {
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
const firstRowEmailCell = await canvas.findByText(
|
||||
mockedPeopleData[0].email,
|
||||
{},
|
||||
{ timeout: 3000 },
|
||||
);
|
||||
assert(firstRowEmailCell.parentElement);
|
||||
|
||||
const secondRowEmailCell = canvas.getByText(mockedPeopleData[1].email);
|
||||
assert(secondRowEmailCell.parentElement);
|
||||
|
||||
expect(
|
||||
canvas.queryByTestId('editable-cell-edit-mode-container'),
|
||||
).toBeNull();
|
||||
|
||||
await userEvent.click(firstRowEmailCell.parentElement);
|
||||
|
||||
expect(
|
||||
canvas.getByTestId('editable-cell-edit-mode-container'),
|
||||
).toBeVisible();
|
||||
|
||||
await userEvent.click(secondRowEmailCell.parentElement);
|
||||
|
||||
await sleep(25);
|
||||
|
||||
expect(
|
||||
canvas.queryByTestId('editable-cell-edit-mode-container'),
|
||||
).toBeNull();
|
||||
|
||||
await userEvent.click(secondRowEmailCell.parentElement);
|
||||
|
||||
expect(
|
||||
canvas.getByTestId('editable-cell-edit-mode-container'),
|
||||
).toBeVisible();
|
||||
},
|
||||
};
|
||||
|
||||
export const CheckCheckboxes: Story = {
|
||||
play: async ({ canvasElement, step }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
await step('Wait for rows to appear', async () => {
|
||||
await canvas.findByText(
|
||||
mockedPeopleData[0].displayName,
|
||||
{},
|
||||
{ timeout: 3000 },
|
||||
);
|
||||
});
|
||||
|
||||
const [, firstRowCheckbox, secondRowCheckbox] =
|
||||
canvas.getAllByRole<HTMLInputElement>('checkbox');
|
||||
|
||||
await step('Select first row', async () => {
|
||||
assert(firstRowCheckbox.parentElement);
|
||||
|
||||
await userEvent.click(firstRowCheckbox.parentElement);
|
||||
await sleep(25);
|
||||
|
||||
expect(firstRowCheckbox).toBeChecked();
|
||||
});
|
||||
|
||||
await step('Select second row', async () => {
|
||||
await userEvent.click(secondRowCheckbox);
|
||||
await sleep(25);
|
||||
|
||||
expect(secondRowCheckbox).toBeChecked();
|
||||
});
|
||||
|
||||
await step('Unselect second row', async () => {
|
||||
assert(secondRowCheckbox.parentElement);
|
||||
|
||||
await userEvent.click(secondRowCheckbox.parentElement);
|
||||
await sleep(25);
|
||||
|
||||
expect(secondRowCheckbox).not.toBeChecked();
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export const EditRelation: Story = {
|
||||
play: async ({ canvasElement, step }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
await step('Click on third row company cell', async () => {
|
||||
const thirdRowCompanyCell = await canvas.findByText(
|
||||
mockedPeopleData[2].company.name,
|
||||
{},
|
||||
{ timeout: 3000 },
|
||||
);
|
||||
|
||||
await userEvent.click(thirdRowCompanyCell);
|
||||
});
|
||||
|
||||
await step('Type "Air" in relation picker', async () => {
|
||||
const relationSearchInput = canvas.getByPlaceholderText('Search');
|
||||
|
||||
await userEvent.type(relationSearchInput, 'Air', { delay: 200 });
|
||||
});
|
||||
|
||||
await step('Select "Airbnb"', async () => {
|
||||
const airbnbChip = await canvas.findByRole('listitem', {
|
||||
name: (_, element) => !!element?.textContent?.includes('Airbnb'),
|
||||
});
|
||||
|
||||
await userEvent.click(airbnbChip);
|
||||
});
|
||||
|
||||
await step('Check if Airbnb is in the table', async () => {
|
||||
expect(
|
||||
await canvas.findByText('Airbnb', {}, { timeout: 3000 }),
|
||||
).toBeVisible();
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export const SelectRelationWithKeys: Story = {
|
||||
play: async ({ canvasElement, step }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
await step('Click on first row company cell', async () => {
|
||||
const firstRowCompanyCell = await canvas.findByText(
|
||||
mockedPeopleData[0].company.name,
|
||||
{},
|
||||
{ timeout: 3000 },
|
||||
);
|
||||
|
||||
await userEvent.click(firstRowCompanyCell);
|
||||
});
|
||||
|
||||
const relationSearchInput = canvas.getByPlaceholderText('Search');
|
||||
|
||||
await step('Type "Air" in relation picker', async () => {
|
||||
await userEvent.type(relationSearchInput, 'Air', { delay: 200 });
|
||||
});
|
||||
|
||||
await step('Select "Aircall"', async () => {
|
||||
await userEvent.keyboard('{arrowdown}');
|
||||
|
||||
await sleep(50);
|
||||
|
||||
await userEvent.keyboard('{arrowup}');
|
||||
|
||||
await sleep(50);
|
||||
|
||||
await userEvent.keyboard('{arrowdown}');
|
||||
|
||||
await sleep(50);
|
||||
|
||||
await userEvent.keyboard('{arrowdown}');
|
||||
|
||||
await sleep(50);
|
||||
|
||||
await userEvent.keyboard('{arrowdown}');
|
||||
|
||||
await sleep(50);
|
||||
|
||||
await userEvent.keyboard('{enter}');
|
||||
});
|
||||
|
||||
await step('Check if Aircall is in the table', async () => {
|
||||
expect(
|
||||
await canvas.findByText('Aircall', {}, { timeout: 3000 }),
|
||||
).toBeVisible();
|
||||
});
|
||||
},
|
||||
};
|
||||
@ -1,124 +0,0 @@
|
||||
import { expect } from '@storybook/jest';
|
||||
import { Meta } from '@storybook/react';
|
||||
import { userEvent, within } from '@storybook/testing-library';
|
||||
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import {
|
||||
PageDecorator,
|
||||
PageDecoratorArgs,
|
||||
} from '~/testing/decorators/PageDecorator';
|
||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||
import { mockedPeopleData } from '~/testing/mock-data/people';
|
||||
import { sleep } from '~/testing/sleep';
|
||||
|
||||
import { People } from '../People';
|
||||
|
||||
import { Story } from './People.stories';
|
||||
|
||||
const meta: Meta<PageDecoratorArgs> = {
|
||||
title: 'Pages/People/SortBy',
|
||||
component: People,
|
||||
decorators: [PageDecorator],
|
||||
args: { routePath: AppPath.PeoplePage },
|
||||
parameters: {
|
||||
msw: graphqlMocks,
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
const peopleEmails = mockedPeopleData.map(({ email }) => email);
|
||||
const sortedPeopleEmails = [...peopleEmails].sort((a, b) => a.localeCompare(b));
|
||||
|
||||
export const Email: Story = {
|
||||
play: async ({ canvasElement, step }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
await step('Wait for rows to appear', async () => {
|
||||
await canvas.findByText(
|
||||
mockedPeopleData[0].displayName,
|
||||
{},
|
||||
{ timeout: 1000 },
|
||||
);
|
||||
});
|
||||
|
||||
await step('Click on sort button', async () => {
|
||||
const sortButton = canvas.getByRole('button', { name: 'Sort' });
|
||||
await userEvent.click(sortButton);
|
||||
});
|
||||
|
||||
await step('Select sort by email', async () => {
|
||||
const emailSortButton = canvas.getByTestId('select-sort-2');
|
||||
await userEvent.click(emailSortButton);
|
||||
|
||||
await canvas.findByTestId('remove-icon-email', {}, { timeout: 3000 });
|
||||
});
|
||||
|
||||
await sleep(1000);
|
||||
|
||||
await step('Check rows are sorted by email', async () => {
|
||||
const emailCells = canvas.getAllByText(
|
||||
(_, element) =>
|
||||
sortedPeopleEmails.some((email) =>
|
||||
element?.textContent?.includes(email),
|
||||
),
|
||||
{ selector: '[data-testid="editable-cell-display-mode"]' },
|
||||
);
|
||||
|
||||
expect(emailCells).toHaveLength(sortedPeopleEmails.length);
|
||||
|
||||
sortedPeopleEmails.forEach((email, index) =>
|
||||
expect(emailCells[index]).toHaveTextContent(email),
|
||||
);
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export const Reset: Story = {
|
||||
play: async ({ canvasElement, step }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
await step('Wait for rows to appear', async () => {
|
||||
await canvas.findByText(
|
||||
mockedPeopleData[0].displayName,
|
||||
{},
|
||||
{ timeout: 3000 },
|
||||
);
|
||||
});
|
||||
|
||||
await step('Click on sort button', async () => {
|
||||
const sortButton = canvas.getByRole('button', { name: 'Sort' });
|
||||
await userEvent.click(sortButton);
|
||||
});
|
||||
|
||||
await step('Select sort by email', async () => {
|
||||
const emailSortButton = canvas.getByTestId('select-sort-2');
|
||||
await userEvent.click(emailSortButton);
|
||||
|
||||
expect(
|
||||
await canvas.findByTestId('remove-icon-email'),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
await step('Click on reset button', async () => {
|
||||
const resetButton = canvas.getByRole('button', { name: 'Reset' });
|
||||
await userEvent.click(resetButton);
|
||||
|
||||
expect(canvas.queryByTestId('remove-icon-email')).toBeNull();
|
||||
});
|
||||
|
||||
await step('Check rows are in initial order', async () => {
|
||||
const emailCells = canvas.getAllByText(
|
||||
(_, element) =>
|
||||
peopleEmails.some((email) => element?.textContent?.includes(email)),
|
||||
{ selector: '[data-testid="editable-cell-display-mode"]' },
|
||||
);
|
||||
|
||||
expect(emailCells).toHaveLength(peopleEmails.length);
|
||||
|
||||
peopleEmails.forEach((email, index) =>
|
||||
expect(emailCells[index]).toHaveTextContent(email),
|
||||
);
|
||||
});
|
||||
},
|
||||
};
|
||||
@ -1,26 +0,0 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import {
|
||||
PageDecorator,
|
||||
PageDecoratorArgs,
|
||||
} from '~/testing/decorators/PageDecorator';
|
||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||
|
||||
import { People } from '../People';
|
||||
|
||||
const meta: Meta<PageDecoratorArgs> = {
|
||||
title: 'Pages/People',
|
||||
component: People,
|
||||
decorators: [PageDecorator],
|
||||
args: { routePath: AppPath.PeoplePage },
|
||||
parameters: {
|
||||
msw: graphqlMocks,
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
export type Story = StoryObj<typeof People>;
|
||||
|
||||
export const Default: Story = {};
|
||||
@ -1,30 +0,0 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import {
|
||||
PageDecorator,
|
||||
PageDecoratorArgs,
|
||||
} from '~/testing/decorators/PageDecorator';
|
||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||
import { mockedPeopleData } from '~/testing/mock-data/people';
|
||||
|
||||
import { PersonShow } from '../PersonShow';
|
||||
|
||||
const meta: Meta<PageDecoratorArgs> = {
|
||||
title: 'Pages/People/Person',
|
||||
component: PersonShow,
|
||||
decorators: [PageDecorator],
|
||||
args: {
|
||||
routePath: AppPath.PersonShowPage,
|
||||
routeParams: { ':personId': mockedPeopleData[0].id },
|
||||
},
|
||||
parameters: {
|
||||
msw: graphqlMocks,
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
export type Story = StoryObj<typeof PersonShow>;
|
||||
|
||||
export const Default: Story = {};
|
||||
@ -1,111 +0,0 @@
|
||||
import {
|
||||
IconBrandLinkedin,
|
||||
IconBrandX,
|
||||
IconBriefcase,
|
||||
IconBuildingSkyscraper,
|
||||
IconCalendar,
|
||||
IconMail,
|
||||
IconMap,
|
||||
IconPhone,
|
||||
} from '@/ui/display/icon';
|
||||
import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect';
|
||||
import { FieldDefinition } from '@/ui/object/field/types/FieldDefinition';
|
||||
import {
|
||||
FieldDateMetadata,
|
||||
FieldMetadata,
|
||||
FieldPhoneMetadata,
|
||||
FieldRelationMetadata,
|
||||
FieldTextMetadata,
|
||||
FieldURLMetadata,
|
||||
} from '@/ui/object/field/types/FieldMetadata';
|
||||
import { Company } from '~/generated/graphql';
|
||||
import { getLogoUrlFromDomainName } from '~/utils';
|
||||
|
||||
export const personShowFieldDefinition: FieldDefinition<FieldMetadata>[] = [
|
||||
{
|
||||
fieldMetadataId: 'email',
|
||||
label: 'Email',
|
||||
Icon: IconMail,
|
||||
type: 'TEXT',
|
||||
metadata: {
|
||||
fieldName: 'email',
|
||||
placeHolder: 'Email',
|
||||
},
|
||||
} satisfies FieldDefinition<FieldTextMetadata>,
|
||||
{
|
||||
fieldMetadataId: 'company',
|
||||
label: 'Company',
|
||||
Icon: IconBuildingSkyscraper,
|
||||
type: 'RELATION',
|
||||
metadata: {
|
||||
fieldName: 'company',
|
||||
relationType: Entity.Company,
|
||||
},
|
||||
entityChipDisplayMapper: (dataObject: Company) => {
|
||||
return {
|
||||
name: dataObject?.name,
|
||||
pictureUrl: getLogoUrlFromDomainName(dataObject?.domainName),
|
||||
avatarType: 'squared',
|
||||
};
|
||||
},
|
||||
} satisfies FieldDefinition<FieldRelationMetadata>,
|
||||
{
|
||||
fieldMetadataId: 'phone',
|
||||
label: 'Phone',
|
||||
Icon: IconPhone,
|
||||
type: 'PHONE',
|
||||
metadata: {
|
||||
fieldName: 'phone',
|
||||
placeHolder: 'Phone',
|
||||
},
|
||||
} satisfies FieldDefinition<FieldPhoneMetadata>,
|
||||
{
|
||||
fieldMetadataId: 'jobTitle',
|
||||
label: 'Job Title',
|
||||
Icon: IconBriefcase,
|
||||
type: 'TEXT',
|
||||
metadata: {
|
||||
fieldName: 'jobTitle',
|
||||
placeHolder: 'Job Title',
|
||||
},
|
||||
} satisfies FieldDefinition<FieldTextMetadata>,
|
||||
{
|
||||
fieldMetadataId: 'city',
|
||||
label: 'City',
|
||||
Icon: IconMap,
|
||||
type: 'TEXT',
|
||||
metadata: {
|
||||
fieldName: 'city',
|
||||
placeHolder: 'City',
|
||||
},
|
||||
} satisfies FieldDefinition<FieldTextMetadata>,
|
||||
{
|
||||
fieldMetadataId: 'linkedinUrl',
|
||||
label: 'Linkedin URL',
|
||||
Icon: IconBrandLinkedin,
|
||||
type: 'URL',
|
||||
metadata: {
|
||||
fieldName: 'linkedinUrl',
|
||||
placeHolder: 'Linkedin URL',
|
||||
},
|
||||
} satisfies FieldDefinition<FieldURLMetadata>,
|
||||
{
|
||||
fieldMetadataId: 'xUrl',
|
||||
label: 'X URL',
|
||||
Icon: IconBrandX,
|
||||
type: 'URL',
|
||||
metadata: {
|
||||
fieldName: 'xUrl',
|
||||
placeHolder: 'X URL',
|
||||
},
|
||||
} satisfies FieldDefinition<FieldURLMetadata>,
|
||||
{
|
||||
fieldMetadataId: 'createdAt',
|
||||
label: 'Created at',
|
||||
Icon: IconCalendar,
|
||||
type: 'DATE',
|
||||
metadata: {
|
||||
fieldName: 'createdAt',
|
||||
},
|
||||
} satisfies FieldDefinition<FieldDateMetadata>,
|
||||
];
|
||||
@ -1,59 +0,0 @@
|
||||
import { FilterDropdownCompanySearchSelect } from '@/companies/components/FilterDropdownCompanySearchSelect';
|
||||
import {
|
||||
IconBuildingSkyscraper,
|
||||
IconCalendarEvent,
|
||||
IconMail,
|
||||
IconMap,
|
||||
IconPhone,
|
||||
IconUser,
|
||||
} from '@/ui/display/icon/index';
|
||||
import { FilterDefinitionByEntity } from '@/ui/object/object-filter-dropdown/types/FilterDefinitionByEntity';
|
||||
import { Person } from '~/generated/graphql';
|
||||
|
||||
export const personTableFilterDefinitions: FilterDefinitionByEntity<Person>[] =
|
||||
[
|
||||
{
|
||||
fieldMetadataId: 'firstName',
|
||||
label: 'First name',
|
||||
Icon: IconUser,
|
||||
type: 'TEXT',
|
||||
},
|
||||
{
|
||||
fieldMetadataId: 'lastName',
|
||||
label: 'Last name',
|
||||
Icon: IconUser,
|
||||
type: 'TEXT',
|
||||
},
|
||||
{
|
||||
fieldMetadataId: 'email',
|
||||
label: 'Email',
|
||||
Icon: IconMail,
|
||||
type: 'TEXT',
|
||||
},
|
||||
{
|
||||
fieldMetadataId: 'companyId',
|
||||
label: 'Company',
|
||||
Icon: IconBuildingSkyscraper,
|
||||
type: 'ENTITY',
|
||||
// TODO: replace this with a component that selects the dropdown to use based on the entity type
|
||||
entitySelectComponent: <FilterDropdownCompanySearchSelect />,
|
||||
},
|
||||
{
|
||||
fieldMetadataId: 'phone',
|
||||
label: 'Phone',
|
||||
Icon: IconPhone,
|
||||
type: 'TEXT',
|
||||
},
|
||||
{
|
||||
fieldMetadataId: 'createdAt',
|
||||
label: 'Created at',
|
||||
Icon: IconCalendarEvent,
|
||||
type: 'DATE',
|
||||
},
|
||||
{
|
||||
fieldMetadataId: 'city',
|
||||
label: 'City',
|
||||
Icon: IconMap,
|
||||
type: 'TEXT',
|
||||
},
|
||||
];
|
||||
@ -1,51 +0,0 @@
|
||||
import {
|
||||
IconBuildingSkyscraper,
|
||||
IconCalendarEvent,
|
||||
IconMail,
|
||||
IconMap,
|
||||
IconPhone,
|
||||
IconUser,
|
||||
} from '@/ui/display/icon/index';
|
||||
import { SortDefinition } from '@/ui/object/object-sort-dropdown/types/SortDefinition';
|
||||
import { SortDirection } from '@/ui/object/object-sort-dropdown/types/SortDirection';
|
||||
|
||||
export const personTableSortDefinitions: SortDefinition[] = [
|
||||
{
|
||||
fieldMetadataId: 'fullname',
|
||||
label: 'People',
|
||||
Icon: IconUser,
|
||||
|
||||
getOrderByTemplate: (direction: SortDirection) => [
|
||||
{ firstName: direction },
|
||||
{ lastName: direction },
|
||||
],
|
||||
},
|
||||
{
|
||||
fieldMetadataId: 'company_name',
|
||||
label: 'Company',
|
||||
Icon: IconBuildingSkyscraper,
|
||||
getOrderByTemplate: (direction: SortDirection) => [
|
||||
{ company: { name: direction } },
|
||||
],
|
||||
},
|
||||
{
|
||||
fieldMetadataId: 'email',
|
||||
label: 'Email',
|
||||
Icon: IconMail,
|
||||
},
|
||||
{
|
||||
fieldMetadataId: 'phone',
|
||||
label: 'Phone',
|
||||
Icon: IconPhone,
|
||||
},
|
||||
{
|
||||
fieldMetadataId: 'createdAt',
|
||||
label: 'Created at',
|
||||
Icon: IconCalendarEvent,
|
||||
},
|
||||
{
|
||||
fieldMetadataId: 'city',
|
||||
label: 'City',
|
||||
Icon: IconMap,
|
||||
},
|
||||
];
|
||||
@ -36,11 +36,11 @@ export const SettingsWorkspaceMembers = () => {
|
||||
|
||||
const { objects: workspaceMembers } =
|
||||
useFindManyObjectRecords<WorkspaceMember>({
|
||||
objectNamePlural: 'workspaceMembersV2',
|
||||
objectNamePlural: 'workspaceMembers',
|
||||
});
|
||||
const { deleteOneObject: deleteOneWorkspaceMember } =
|
||||
useDeleteOneObjectRecord<WorkspaceMember>({
|
||||
objectNameSingular: 'workspaceMemberV2',
|
||||
objectNameSingular: 'workspaceMember',
|
||||
});
|
||||
const currentWorkspace = useRecoilValue(currentWorkspaceState);
|
||||
|
||||
|
||||
@ -19,7 +19,6 @@ import { H2Title } from '@/ui/display/typography/components/H2Title';
|
||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
|
||||
import { Section } from '@/ui/layout/section/components/Section';
|
||||
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
|
||||
import { ViewType } from '@/views/types/ViewType';
|
||||
|
||||
export const SettingsNewObject = () => {
|
||||
const navigate = useNavigate();
|
||||
@ -33,7 +32,7 @@ export const SettingsNewObject = () => {
|
||||
} = useObjectMetadataItemForSettings();
|
||||
|
||||
const { createOneObject: createOneView } = useCreateOneObjectRecord({
|
||||
objectNameSingular: 'viewV2',
|
||||
objectNameSingular: 'view',
|
||||
});
|
||||
|
||||
const [
|
||||
@ -79,12 +78,6 @@ export const SettingsNewObject = () => {
|
||||
icon: customFormValues.icon,
|
||||
});
|
||||
|
||||
await createOneView?.({
|
||||
objectMetadataId: createdObject.data?.createOneObject.id,
|
||||
type: ViewType.Table,
|
||||
name: `All ${customFormValues.labelPlural}`,
|
||||
});
|
||||
|
||||
navigate(
|
||||
createdObject.data?.createOneObject.isActive
|
||||
? `/settings/objects/${getObjectSlug(
|
||||
|
||||
@ -54,10 +54,10 @@ export const SettingsObjectDetail = () => {
|
||||
if (!activeObjectMetadataItem) return null;
|
||||
|
||||
const activeMetadataFields = activeObjectMetadataItem.fields.filter(
|
||||
(metadataField) => metadataField.isActive,
|
||||
(metadataField) => metadataField.isActive && !metadataField.isSystem,
|
||||
);
|
||||
const disabledMetadataFields = activeObjectMetadataItem.fields.filter(
|
||||
(metadataField) => !metadataField.isActive,
|
||||
(metadataField) => !metadataField.isActive && !metadataField.isSystem,
|
||||
);
|
||||
|
||||
const handleDisable = async () => {
|
||||
|
||||
@ -54,7 +54,7 @@ export const SettingsObjectNewFieldStep2 = () => {
|
||||
initForm({
|
||||
relation: {
|
||||
field: { icon: activeObjectMetadataItem.icon },
|
||||
objectMetadataId: findObjectMetadataItemByNamePlural('peopleV2')?.id,
|
||||
objectMetadataId: findObjectMetadataItemByNamePlural('people')?.id,
|
||||
},
|
||||
});
|
||||
}, [
|
||||
@ -69,11 +69,11 @@ export const SettingsObjectNewFieldStep2 = () => {
|
||||
const [relationObjectViews, setRelationObjectViews] = useState<View[]>([]);
|
||||
|
||||
const { createOneObject: createOneViewField } = useCreateOneObjectRecord({
|
||||
objectNameSingular: 'viewFieldV2',
|
||||
objectNameSingular: 'viewField',
|
||||
});
|
||||
|
||||
useFindManyObjectRecords({
|
||||
objectNamePlural: 'viewsV2',
|
||||
objectNamePlural: 'views',
|
||||
filter: {
|
||||
type: { eq: ViewType.Table },
|
||||
objectMetadataId: { eq: activeObjectMetadataItem?.id },
|
||||
@ -88,7 +88,7 @@ export const SettingsObjectNewFieldStep2 = () => {
|
||||
});
|
||||
|
||||
useFindManyObjectRecords({
|
||||
objectNamePlural: 'viewsV2',
|
||||
objectNamePlural: 'views',
|
||||
skip: !formValues.relation?.objectMetadataId,
|
||||
filter: {
|
||||
type: { eq: ViewType.Table },
|
||||
|
||||
@ -13,6 +13,7 @@ import { SettingsPageContainer } from '@/settings/components/SettingsPageContain
|
||||
import { ApiKeyInput } from '@/settings/developers/components/ApiKeyInput';
|
||||
import { useGeneratedApiKeys } from '@/settings/developers/hooks/useGeneratedApiKeys';
|
||||
import { generatedApiKeyFamilyState } from '@/settings/developers/states/generatedApiKeyFamilyState';
|
||||
import { ApiKey } from '@/settings/developers/types/ApiKey';
|
||||
import { computeNewExpirationDate } from '@/settings/developers/utils/compute-new-expiration-date';
|
||||
import { formatExpiration } from '@/settings/developers/utils/format-expiration';
|
||||
import { IconRepeat, IconSettings, IconTrash } from '@/ui/display/icon';
|
||||
@ -22,7 +23,7 @@ import { TextInput } from '@/ui/input/components/TextInput';
|
||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
|
||||
import { Section } from '@/ui/layout/section/components/Section';
|
||||
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
|
||||
import { ApiKey, useGenerateOneApiKeyTokenMutation } from '~/generated/graphql';
|
||||
import { useGenerateApiKeyTokenMutation } from '~/generated/graphql';
|
||||
|
||||
const StyledInfo = styled.span`
|
||||
color: ${({ theme }) => theme.font.color.light};
|
||||
@ -48,18 +49,18 @@ export const SettingsDevelopersApiKeyDetail = () => {
|
||||
);
|
||||
const { performOptimisticEvict } = useOptimisticEvict();
|
||||
|
||||
const [generateOneApiKeyToken] = useGenerateOneApiKeyTokenMutation();
|
||||
const [generateOneApiKeyToken] = useGenerateApiKeyTokenMutation();
|
||||
const { createOneObject: createOneApiKey } = useCreateOneObjectRecord<ApiKey>(
|
||||
{
|
||||
objectNameSingular: 'apiKeyV2',
|
||||
objectNameSingular: 'apiKey',
|
||||
},
|
||||
);
|
||||
const { updateOneObject: updateApiKey } = useUpdateOneObjectRecord<ApiKey>({
|
||||
objectNameSingular: 'apiKeyV2',
|
||||
objectNameSingular: 'apiKey',
|
||||
});
|
||||
|
||||
const { object: apiKeyData } = useFindOneObjectRecord({
|
||||
objectNameSingular: 'apiKeyV2',
|
||||
objectNameSingular: 'apiKey',
|
||||
objectRecordId: apiKeyId,
|
||||
});
|
||||
|
||||
@ -68,7 +69,7 @@ export const SettingsDevelopersApiKeyDetail = () => {
|
||||
idToUpdate: apiKeyId,
|
||||
input: { revokedAt: DateTime.now().toString() },
|
||||
});
|
||||
performOptimisticEvict('ApiKeyV2', 'id', apiKeyId);
|
||||
performOptimisticEvict('ApiKey', 'id', apiKeyId);
|
||||
if (redirect) {
|
||||
navigate('/settings/developers/api-keys');
|
||||
}
|
||||
@ -89,16 +90,13 @@ export const SettingsDevelopersApiKeyDetail = () => {
|
||||
|
||||
const tokenData = await generateOneApiKeyToken({
|
||||
variables: {
|
||||
data: {
|
||||
id: newApiKey.id,
|
||||
expiresAt: newApiKey.expiresAt,
|
||||
name: newApiKey.name, // TODO update typing to remove useless name param here
|
||||
},
|
||||
apiKeyId: newApiKey.id,
|
||||
expiresAt: newApiKey?.expiresAt,
|
||||
},
|
||||
});
|
||||
return {
|
||||
id: newApiKey.id,
|
||||
token: tokenData.data?.generateApiKeyV2Token.token,
|
||||
token: tokenData.data?.generateApiKeyToken.token,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@ -45,14 +45,14 @@ export const SettingsDevelopersApiKeys = () => {
|
||||
|
||||
const [apiKeys, setApiKeys] = useState<Array<ApiFieldItem>>([]);
|
||||
const { registerOptimisticEffect } = useOptimisticEffect({
|
||||
objectNameSingular: 'apiKeyV2',
|
||||
objectNameSingular: 'apiKey',
|
||||
});
|
||||
const { foundObjectMetadataItem } = useFindOneObjectMetadataItem({
|
||||
objectNameSingular: 'apiKeyV2',
|
||||
objectNameSingular: 'apiKey',
|
||||
});
|
||||
const filter = { revokedAt: { eq: null } };
|
||||
useFindManyObjectRecords({
|
||||
objectNamePlural: 'apiKeysV2',
|
||||
objectNamePlural: 'apiKeys',
|
||||
filter,
|
||||
onCompleted: (data) => {
|
||||
setApiKeys(
|
||||
@ -66,7 +66,7 @@ export const SettingsDevelopersApiKeys = () => {
|
||||
);
|
||||
if (foundObjectMetadataItem) {
|
||||
registerOptimisticEffect({
|
||||
variables: { filter },
|
||||
variables: { filter, first: 30, orderBy: {} },
|
||||
definition: getRecordOptimisticEffectDefinition({
|
||||
objectMetadataItem: foundObjectMetadataItem,
|
||||
}),
|
||||
|
||||
@ -8,6 +8,7 @@ import { SettingsHeaderContainer } from '@/settings/components/SettingsHeaderCon
|
||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||
import { ExpirationDates } from '@/settings/developers/constants/expirationDates';
|
||||
import { useGeneratedApiKeys } from '@/settings/developers/hooks/useGeneratedApiKeys';
|
||||
import { ApiKey } from '@/settings/developers/types/ApiKey';
|
||||
import { IconSettings } from '@/ui/display/icon';
|
||||
import { H2Title } from '@/ui/display/typography/components/H2Title';
|
||||
import { Select } from '@/ui/input/components/Select';
|
||||
@ -15,10 +16,10 @@ import { TextInput } from '@/ui/input/components/TextInput';
|
||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
|
||||
import { Section } from '@/ui/layout/section/components/Section';
|
||||
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
|
||||
import { ApiKey, useGenerateOneApiKeyTokenMutation } from '~/generated/graphql';
|
||||
import { useGenerateApiKeyTokenMutation } from '~/generated/graphql';
|
||||
|
||||
export const SettingsDevelopersApiKeysNew = () => {
|
||||
const [generateOneApiKeyToken] = useGenerateOneApiKeyTokenMutation();
|
||||
const [generateOneApiKeyToken] = useGenerateApiKeyTokenMutation();
|
||||
const navigate = useNavigate();
|
||||
const setGeneratedApi = useGeneratedApiKeys();
|
||||
const [formValues, setFormValues] = useState<{
|
||||
@ -30,14 +31,12 @@ export const SettingsDevelopersApiKeysNew = () => {
|
||||
});
|
||||
|
||||
const { createOneObject: createOneApiKey } = useCreateOneObjectRecord<ApiKey>(
|
||||
{
|
||||
objectNameSingular: 'apiKeyV2',
|
||||
},
|
||||
{ objectNameSingular: 'apiKey' },
|
||||
);
|
||||
const onSave = async () => {
|
||||
const expiresAt = formValues.expirationDate
|
||||
? DateTime.now().plus({ days: formValues.expirationDate }).toString()
|
||||
: null;
|
||||
const expiresAt = DateTime.now()
|
||||
.plus({ days: formValues.expirationDate ?? 30 })
|
||||
.toString();
|
||||
const newApiKey = await createOneApiKey?.({
|
||||
name: formValues.name,
|
||||
expiresAt,
|
||||
@ -49,15 +48,12 @@ export const SettingsDevelopersApiKeysNew = () => {
|
||||
|
||||
const tokenData = await generateOneApiKeyToken({
|
||||
variables: {
|
||||
data: {
|
||||
id: newApiKey.id,
|
||||
expiresAt: newApiKey.expiresAt,
|
||||
name: newApiKey.name, // TODO update typing to remove useless name param here
|
||||
},
|
||||
apiKeyId: newApiKey.id,
|
||||
expiresAt: expiresAt,
|
||||
},
|
||||
});
|
||||
if (tokenData.data?.generateApiKeyV2Token) {
|
||||
setGeneratedApi(newApiKey.id, tokenData.data.generateApiKeyV2Token.token);
|
||||
if (tokenData.data?.generateApiKeyToken) {
|
||||
setGeneratedApi(newApiKey.id, tokenData.data.generateApiKeyToken.token);
|
||||
navigate(`/settings/developers/api-keys/${newApiKey.id}`);
|
||||
}
|
||||
};
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
import { IconUser, IconUserCircle } from '@/ui/display/icon';
|
||||
import { Activity } from '@/activities/types/Activity';
|
||||
import { IconUserCircle } from '@/ui/display/icon';
|
||||
import { FilterDefinitionByEntity } from '@/ui/object/object-filter-dropdown/types/FilterDefinitionByEntity';
|
||||
import { FilterDropdownUserSearchSelect } from '@/users/components/FilterDropdownUserSearchSelect';
|
||||
import { Activity } from '~/generated/graphql';
|
||||
|
||||
export const tasksFilterDefinitions: FilterDefinitionByEntity<Activity>[] = [
|
||||
{
|
||||
fieldMetadataId: 'assigneeId',
|
||||
label: 'Assignee',
|
||||
Icon: IconUser,
|
||||
iconName: 'IconUser',
|
||||
type: 'ENTITY',
|
||||
entitySelectComponent: <FilterDropdownUserSearchSelect />,
|
||||
selectAllLabel: 'All assignees',
|
||||
|
||||
Reference in New Issue
Block a user