Feat/error boundaries (#2779)

* - Changed to objectNameSingular always defined
- Added ErrorCatchAll

* - Added mock mode for companies logged out
- Added a proper ErrorBoundary component

* Removed react-error-boundary

* Implemented proper ErrorBoundary

* Fixes

* Change strategy about mocks

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Lucas Bordeau
2023-12-01 22:06:38 +01:00
committed by GitHub
parent a301f451f9
commit 74b077f3ca
75 changed files with 4213 additions and 674 deletions

View File

@ -40,6 +40,7 @@
"react-datepicker": "^4.11.0", "react-datepicker": "^4.11.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-dropzone": "^14.2.3", "react-dropzone": "^14.2.3",
"react-error-boundary": "^4.0.11",
"react-helmet-async": "^1.3.0", "react-helmet-async": "^1.3.0",
"react-hook-form": "^7.45.1", "react-hook-form": "^7.45.1",
"react-hotkeys-hook": "^4.4.0", "react-hotkeys-hook": "^4.4.0",

View File

@ -7,6 +7,8 @@ import { RecoilRoot } from 'recoil';
import { ApolloProvider } from '@/apollo/components/ApolloProvider'; import { ApolloProvider } from '@/apollo/components/ApolloProvider';
import { ClientConfigProvider } from '@/client-config/components/ClientConfigProvider'; import { ClientConfigProvider } from '@/client-config/components/ClientConfigProvider';
import { RecoilDebugObserverEffect } from '@/debug/components/RecoilDebugObserver'; import { RecoilDebugObserverEffect } from '@/debug/components/RecoilDebugObserver';
import { AppErrorBoundary } from '@/error-handler/components/AppErrorBoundary';
import { PromiseRejectionEffect } from '@/error-handler/components/PromiseRejectionEffect';
import { ApolloMetadataClientProvider } from '@/object-metadata/components/ApolloMetadataClientProvider'; import { ApolloMetadataClientProvider } from '@/object-metadata/components/ApolloMetadataClientProvider';
import { ObjectMetadataItemsProvider } from '@/object-metadata/components/ObjectMetadataItemsProvider'; import { ObjectMetadataItemsProvider } from '@/object-metadata/components/ObjectMetadataItemsProvider';
import { DialogManager } from '@/ui/feedback/dialog-manager/components/DialogManager'; import { DialogManager } from '@/ui/feedback/dialog-manager/components/DialogManager';
@ -16,12 +18,11 @@ import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/Sn
import { AppThemeProvider } from '@/ui/theme/components/AppThemeProvider'; import { AppThemeProvider } from '@/ui/theme/components/AppThemeProvider';
import { ThemeType } from '@/ui/theme/constants/theme'; import { ThemeType } from '@/ui/theme/constants/theme';
import { UserProvider } from '@/users/components/UserProvider'; import { UserProvider } from '@/users/components/UserProvider';
import { App } from '~/App';
import { PageChangeEffect } from '~/effect-components/PageChangeEffect';
import '@emotion/react'; import '@emotion/react';
import { PageChangeEffect } from './effect-components/PageChangeEffect';
import { App } from './App';
import './index.css'; import './index.css';
import 'react-loading-skeleton/dist/skeleton.css'; import 'react-loading-skeleton/dist/skeleton.css';
@ -31,35 +32,38 @@ const root = ReactDOM.createRoot(
root.render( root.render(
<RecoilRoot> <RecoilRoot>
<RecoilDebugObserverEffect /> <AppErrorBoundary>
<BrowserRouter> <RecoilDebugObserverEffect />
<ApolloProvider> <BrowserRouter>
<HelmetProvider> <SnackBarProviderScope snackBarManagerScopeId="snack-bar-manager">
<ClientConfigProvider> <ApolloProvider>
<UserProvider> <HelmetProvider>
<ApolloMetadataClientProvider> <ClientConfigProvider>
<SnackBarProviderScope snackBarManagerScopeId="snack-bar-manager"> <UserProvider>
<ObjectMetadataItemsProvider> <ApolloMetadataClientProvider>
<PageChangeEffect /> <ObjectMetadataItemsProvider>
<AppThemeProvider> <AppThemeProvider>
<SnackBarProvider> <SnackBarProvider>
<DialogManagerScope dialogManagerScopeId="dialog-manager"> <DialogManagerScope dialogManagerScopeId="dialog-manager">
<DialogManager> <DialogManager>
<StrictMode> <StrictMode>
<App /> <PromiseRejectionEffect />
</StrictMode> <App />
</DialogManager> </StrictMode>
</DialogManagerScope> </DialogManager>
</SnackBarProvider> </DialogManagerScope>
</AppThemeProvider> </SnackBarProvider>
</ObjectMetadataItemsProvider> </AppThemeProvider>
</SnackBarProviderScope> <PageChangeEffect />
</ApolloMetadataClientProvider> </ObjectMetadataItemsProvider>
</UserProvider> </ApolloMetadataClientProvider>
</ClientConfigProvider> </UserProvider>
</HelmetProvider> </ClientConfigProvider>
</ApolloProvider> </HelmetProvider>
</BrowserRouter> </ApolloProvider>
</SnackBarProviderScope>
</BrowserRouter>
</AppErrorBoundary>
</RecoilRoot>, </RecoilRoot>,
); );

View File

@ -67,7 +67,7 @@ export const ActivityComments = ({
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
const { records: comments } = useFindManyRecords({ const { records: comments } = useFindManyRecords({
objectNamePlural: 'comments', objectNameSingular: 'comment',
filter: { filter: {
activityId: { activityId: {
eq: activity?.id ?? '', eq: activity?.id ?? '',

View File

@ -5,7 +5,7 @@ import { ActivityTargetableEntity } from '../../types/ActivityTargetableEntity';
export const useAttachments = (entity: ActivityTargetableEntity) => { export const useAttachments = (entity: ActivityTargetableEntity) => {
const { records: attachments } = useFindManyRecords({ const { records: attachments } = useFindManyRecords({
objectNamePlural: 'attachments', objectNameSingular: 'attachment',
filter: { filter: {
[entity.type === 'Company' ? 'companyId' : 'personId']: { eq: entity.id }, [entity.type === 'Company' ? 'companyId' : 'personId']: { eq: entity.id },
}, },

View File

@ -87,7 +87,7 @@ export const ActivityTargetInlineCellEditMode = ({
orderByField: 'createdAt', orderByField: 'createdAt',
mappingFunction: (record: any) => identifiersMapper?.(record, 'person'), mappingFunction: (record: any) => identifiersMapper?.(record, 'person'),
selectedIds: initialPeopleIds, selectedIds: initialPeopleIds,
objectNamePlural: 'people', objectNameSingular: 'person',
limit: 3, limit: 3,
}); });
@ -102,7 +102,7 @@ export const ActivityTargetInlineCellEditMode = ({
orderByField: 'createdAt', orderByField: 'createdAt',
mappingFunction: (record: any) => identifiersMapper?.(record, 'company'), mappingFunction: (record: any) => identifiersMapper?.(record, 'company'),
selectedIds: initialCompanyIds, selectedIds: initialCompanyIds,
objectNamePlural: 'companies', objectNameSingular: 'company',
limit: 3, limit: 3,
}); });

View File

@ -28,7 +28,7 @@ export const ActivityTargetsInlineCell = ({
) ?? []; ) ?? [];
const { records: activityTargets } = useFindManyRecords<ActivityTarget>({ const { records: activityTargets } = useFindManyRecords<ActivityTarget>({
objectNamePlural: 'activityTargets', objectNameSingular: 'activityTarget',
filter: { id: { in: activityTargetIds } }, filter: { id: { in: activityTargetIds } },
}); });

View File

@ -5,7 +5,7 @@ import { ActivityTargetableEntity } from '../../types/ActivityTargetableEntity';
export const useNotes = (entity: ActivityTargetableEntity) => { export const useNotes = (entity: ActivityTargetableEntity) => {
const { records: activityTargets } = useFindManyRecords({ const { records: activityTargets } = useFindManyRecords({
objectNamePlural: 'activityTargets', objectNameSingular: 'activityTarget',
filter: { filter: {
[entity.type === 'Company' ? 'companyId' : 'personId']: { eq: entity.id }, [entity.type === 'Company' ? 'companyId' : 'personId']: { eq: entity.id },
}, },
@ -23,7 +23,7 @@ export const useNotes = (entity: ActivityTargetableEntity) => {
const { records: notes } = useFindManyRecords({ const { records: notes } = useFindManyRecords({
skip: !activityTargets?.length, skip: !activityTargets?.length,
objectNamePlural: 'activities', objectNameSingular: 'activity',
filter, filter,
orderBy, orderBy,
}); });

View File

@ -82,7 +82,7 @@ export const TaskRow = ({
) ?? []; ) ?? [];
const { records: activityTargets } = useFindManyRecords<ActivityTarget>({ const { records: activityTargets } = useFindManyRecords<ActivityTarget>({
objectNamePlural: 'activityTargets', objectNameSingular: 'activityTarget',
filter: { id: { in: activityTargetIds } }, filter: { id: { in: activityTargetIds } },
}); });

View File

@ -9,7 +9,7 @@ export const useCurrentUserTaskCount = () => {
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
const { records: tasks } = useFindManyRecords({ const { records: tasks } = useFindManyRecords({
objectNamePlural: 'activities', objectNameSingular: 'activity',
filter: { filter: {
type: { eq: 'Task' }, type: { eq: 'Task' },
completedAt: { is: 'NULL' }, completedAt: { is: 'NULL' },

View File

@ -22,7 +22,7 @@ export const useTasks = (props?: UseTasksProps) => {
}); });
const { records: activityTargets } = useFindManyRecords({ const { records: activityTargets } = useFindManyRecords({
objectNamePlural: 'activityTargets', objectNameSingular: 'activityTarget',
filter: isDefined(entity) filter: isDefined(entity)
? { ? {
[entity?.type === 'Company' ? 'companyId' : 'personId']: { [entity?.type === 'Company' ? 'companyId' : 'personId']: {
@ -33,7 +33,7 @@ export const useTasks = (props?: UseTasksProps) => {
}); });
const { records: completeTasksData } = useFindManyRecords({ const { records: completeTasksData } = useFindManyRecords({
objectNamePlural: 'activities', objectNameSingular: 'activity',
skip: !entity && !selectedFilter, skip: !entity && !selectedFilter,
filter: { filter: {
completedAt: { is: 'NOT_NULL' }, completedAt: { is: 'NOT_NULL' },
@ -57,7 +57,7 @@ export const useTasks = (props?: UseTasksProps) => {
}); });
const { records: incompleteTaskData } = useFindManyRecords({ const { records: incompleteTaskData } = useFindManyRecords({
objectNamePlural: 'activities', objectNameSingular: 'activity',
skip: !entity && !selectedFilter, skip: !entity && !selectedFilter,
filter: { filter: {
completedAt: { is: 'NULL' }, completedAt: { is: 'NULL' },

View File

@ -49,7 +49,7 @@ const StyledEmptyTimelineSubTitle = styled.div`
export const Timeline = ({ entity }: { entity: ActivityTargetableEntity }) => { export const Timeline = ({ entity }: { entity: ActivityTargetableEntity }) => {
const { records: activityTargets, loading } = useFindManyRecords({ const { records: activityTargets, loading } = useFindManyRecords({
objectNamePlural: 'activityTargets', objectNameSingular: 'activityTarget',
filter: { filter: {
[entity.type === 'Company' ? 'companyId' : 'personId']: { eq: entity.id }, [entity.type === 'Company' ? 'companyId' : 'personId']: { eq: entity.id },
}, },
@ -57,7 +57,7 @@ export const Timeline = ({ entity }: { entity: ActivityTargetableEntity }) => {
const { records: activities } = useFindManyRecords({ const { records: activities } = useFindManyRecords({
skip: !activityTargets?.length, skip: !activityTargets?.length,
objectNamePlural: 'activities', objectNameSingular: 'activity',
filter: { filter: {
id: { id: {
in: activityTargets?.map((activityTarget) => activityTarget.activityId), in: activityTargets?.map((activityTarget) => activityTarget.activityId),

View File

@ -12,6 +12,7 @@ import {
useObjectMetadataItem, useObjectMetadataItem,
} from '@/object-metadata/hooks/useObjectMetadataItem'; } from '@/object-metadata/hooks/useObjectMetadataItem';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier';
import { optimisticEffectState } from '../states/optimisticEffectState'; import { optimisticEffectState } from '../states/optimisticEffectState';
import { OptimisticEffect } from '../types/internal/OptimisticEffect'; import { OptimisticEffect } from '../types/internal/OptimisticEffect';
@ -19,10 +20,9 @@ import { OptimisticEffectDefinition } from '../types/OptimisticEffectDefinition'
export const useOptimisticEffect = ({ export const useOptimisticEffect = ({
objectNameSingular, objectNameSingular,
}: { }: ObjectMetadataItemIdentifier) => {
objectNameSingular: string | undefined;
}) => {
const apolloClient = useApolloClient(); const apolloClient = useApolloClient();
const { findManyRecordsQuery } = useObjectMetadataItem({ const { findManyRecordsQuery } = useObjectMetadataItem({
objectNameSingular, objectNameSingular,
}); });

View File

@ -28,6 +28,7 @@ export const useAuth = () => {
const setCurrentWorkspaceMember = useSetRecoilState( const setCurrentWorkspaceMember = useSetRecoilState(
currentWorkspaceMemberState, currentWorkspaceMemberState,
); );
const setCurrentWorkspace = useSetRecoilState(currentWorkspaceState); const setCurrentWorkspace = useSetRecoilState(currentWorkspaceState);
const setIsVerifyPendingState = useSetRecoilState(isVerifyPendingState); const setIsVerifyPendingState = useSetRecoilState(isVerifyPendingState);

View File

@ -116,7 +116,7 @@ export const CommandMenu = () => {
const { records: people } = useFindManyRecords<Person>({ const { records: people } = useFindManyRecords<Person>({
skip: !isCommandMenuOpened, skip: !isCommandMenuOpened,
objectNamePlural: 'people', objectNameSingular: 'person',
filter: { filter: {
or: [ or: [
{ name: { firstName: { ilike: `%${search}%` } } }, { name: { firstName: { ilike: `%${search}%` } } },
@ -128,7 +128,7 @@ export const CommandMenu = () => {
const { records: companies } = useFindManyRecords<Person>({ const { records: companies } = useFindManyRecords<Person>({
skip: !isCommandMenuOpened, skip: !isCommandMenuOpened,
objectNamePlural: 'companies', objectNameSingular: 'company',
filter: { filter: {
name: { ilike: `%${search}%` }, name: { ilike: `%${search}%` },
}, },
@ -137,7 +137,7 @@ export const CommandMenu = () => {
const { records: activities } = useFindManyRecords<Person>({ const { records: activities } = useFindManyRecords<Person>({
skip: !isCommandMenuOpened, skip: !isCommandMenuOpened,
objectNamePlural: 'activities', objectNameSingular: 'activity',
filter: { filter: {
or: [ or: [
{ title: { like: `%${search}%` } }, { title: { like: `%${search}%` } },

View File

@ -61,7 +61,7 @@ export const HooksCompanyBoardEffect = ({
const currentViewSorts = useRecoilValue(currentViewSortsState); const currentViewSorts = useRecoilValue(currentViewSortsState);
const { objectMetadataItem } = useObjectMetadataItem({ const { objectMetadataItem } = useObjectMetadataItem({
objectNamePlural: 'opportunities', objectNameSingular: 'opportunity',
}); });
const { columnDefinitions, filterDefinitions, sortDefinitions } = const { columnDefinitions, filterDefinitions, sortDefinitions } =
@ -85,7 +85,7 @@ export const HooksCompanyBoardEffect = ({
); );
useFindManyRecords({ useFindManyRecords({
objectNamePlural: 'pipelineSteps', objectNameSingular: 'pipelineStep',
filter: {}, filter: {},
onCompleted: useCallback( onCompleted: useCallback(
(data: PaginatedRecordTypeResults<PipelineStep>) => { (data: PaginatedRecordTypeResults<PipelineStep>) => {
@ -107,7 +107,7 @@ export const HooksCompanyBoardEffect = ({
const { fetchMoreRecords: fetchMoreOpportunities } = useFindManyRecords({ const { fetchMoreRecords: fetchMoreOpportunities } = useFindManyRecords({
skip: !pipelineSteps.length, skip: !pipelineSteps.length,
objectNamePlural: 'opportunities', objectNameSingular: 'opportunity',
filter: filter, filter: filter,
orderBy: orderBy, orderBy: orderBy,
onCompleted: useCallback( onCompleted: useCallback(
@ -132,7 +132,7 @@ export const HooksCompanyBoardEffect = ({
const { fetchMoreRecords: fetchMoreCompanies } = useFindManyRecords({ const { fetchMoreRecords: fetchMoreCompanies } = useFindManyRecords({
skip: !opportunities.length, skip: !opportunities.length,
objectNamePlural: 'companies', objectNameSingular: 'company',
filter: { filter: {
id: { id: {
in: opportunities.map((opportunity) => opportunity.companyId || ''), in: opportunities.map((opportunity) => opportunity.companyId || ''),

View File

@ -78,7 +78,7 @@ export const NewOpportunityButton = () => {
orderByField: 'createdAt', orderByField: 'createdAt',
selectedIds: [], selectedIds: [],
mappingFunction: (record: any) => identifiersMapper?.(record, 'company'), mappingFunction: (record: any) => identifiersMapper?.(record, 'company'),
objectNamePlural: 'companies', objectNameSingular: 'company',
}); });
return ( return (

View File

@ -54,7 +54,7 @@ export const OpportunityPicker = ({
orderByField: 'createdAt', orderByField: 'createdAt',
selectedIds: [], selectedIds: [],
mappingFunction: (record: any) => identifiersMapper?.(record, 'company'), mappingFunction: (record: any) => identifiersMapper?.(record, 'company'),
objectNamePlural: 'companies', objectNameSingular: 'company',
}); });
const [isProgressSelectionUnfolded, setIsProgressSelectionUnfolded] = const [isProgressSelectionUnfolded, setIsProgressSelectionUnfolded] =

View File

@ -0,0 +1,19 @@
import { ErrorInfo, ReactNode } from 'react';
import { ErrorBoundary } from 'react-error-boundary';
import { GenericErrorFallback } from '@/error-handler/components/GenericErrorFallback';
export const AppErrorBoundary = ({ children }: { children: ReactNode }) => {
const handleError = (_error: Error, _info: ErrorInfo) => {
// TODO: log error to Sentry
};
return (
<ErrorBoundary
FallbackComponent={GenericErrorFallback}
onError={handleError}
>
{children}
</ErrorBoundary>
);
};

View File

@ -0,0 +1,26 @@
import { FallbackProps } from 'react-error-boundary';
type GenericErrorFallbackProps = FallbackProps;
export const GenericErrorFallback = ({
error,
resetErrorBoundary,
}: GenericErrorFallbackProps) => {
return (
<div
style={{
color: 'red',
display: 'flex',
flexDirection: 'column',
gap: '20px',
alignItems: 'center',
justifyContent: 'center',
width: '100%',
height: '100%',
}}
>
<div>{error.message}</div>
<button onClick={() => resetErrorBoundary()}>Retry</button>
</div>
);
};

View File

@ -0,0 +1,39 @@
import React, { useCallback, useEffect } from 'react';
import { ObjectMetadataItemNotFoundError } from '@/object-metadata/errors/ObjectMetadataNotFoundError';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
export const PromiseRejectionEffect = () => {
const { enqueueSnackBar } = useSnackBar();
const handlePromiseRejection = useCallback(
(event: PromiseRejectionEvent) => {
const error = event.reason;
// TODO: connect Sentry here
if (error instanceof ObjectMetadataItemNotFoundError) {
enqueueSnackBar(
`Error with custom object that cannot be found : ${event.reason}`,
{
variant: 'error',
},
);
} else {
enqueueSnackBar(`Error: ${event.reason}`, {
variant: 'error',
});
}
},
[enqueueSnackBar],
);
useEffect(() => {
window.addEventListener('unhandledrejection', handlePromiseRejection);
return () => {
window.removeEventListener('unhandledrejection', handlePromiseRejection);
};
}, [handlePromiseRejection]);
return <></>;
};

View File

@ -8,6 +8,7 @@ import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMembe
import { Favorite } from '@/favorites/types/Favorite'; import { Favorite } from '@/favorites/types/Favorite';
import { mapFavorites } from '@/favorites/utils/mapFavorites'; import { mapFavorites } from '@/favorites/utils/mapFavorites';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
import { getRecordOptimisticEffectDefinition } from '@/object-record/graphql/optimistic-effect-definition/getRecordOptimisticEffectDefinition'; import { getRecordOptimisticEffectDefinition } from '@/object-record/graphql/optimistic-effect-definition/getRecordOptimisticEffectDefinition';
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
import { PaginatedRecordTypeResults } from '@/object-record/types/PaginatedRecordTypeResults'; import { PaginatedRecordTypeResults } from '@/object-record/types/PaginatedRecordTypeResults';
@ -18,7 +19,7 @@ import { favoritesState } from '../states/favoritesState';
export const useFavorites = ({ export const useFavorites = ({
objectNamePlural, objectNamePlural,
}: { }: {
objectNamePlural: string | undefined; objectNamePlural: string;
}) => { }) => {
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
@ -30,7 +31,7 @@ export const useFavorites = ({
deleteOneRecordMutation: deleteOneFavoriteMutation, deleteOneRecordMutation: deleteOneFavoriteMutation,
objectMetadataItem: favoriteObjectMetadataItem, objectMetadataItem: favoriteObjectMetadataItem,
} = useObjectMetadataItem({ } = useObjectMetadataItem({
objectNamePlural: 'favorites', objectNameSingular: 'favorite',
}); });
const { registerOptimisticEffect, triggerOptimisticEffects } = const { registerOptimisticEffect, triggerOptimisticEffects } =
@ -39,15 +40,19 @@ export const useFavorites = ({
}); });
const { performOptimisticEvict } = useOptimisticEvict(); const { performOptimisticEvict } = useOptimisticEvict();
const { objectNameSingular } = useObjectNameSingularFromPlural({
objectNamePlural,
});
const { objectMetadataItem: favoriteTargetObjectMetadataItem } = const { objectMetadataItem: favoriteTargetObjectMetadataItem } =
useObjectMetadataItem({ useObjectMetadataItem({
objectNamePlural, objectNameSingular,
}); });
const apolloClient = useApolloClient(); const apolloClient = useApolloClient();
useFindManyRecords({ useFindManyRecords({
objectNamePlural: 'favorites', objectNameSingular: 'favorite',
onCompleted: useRecoilCallback( onCompleted: useRecoilCallback(
({ snapshot, set }) => ({ snapshot, set }) =>
async (data: PaginatedRecordTypeResults<Required<Favorite>>) => { async (data: PaginatedRecordTypeResults<Required<Favorite>>) => {

View File

@ -14,7 +14,6 @@ export const ApolloMetadataClientProvider = ({
children: React.ReactNode; children: React.ReactNode;
}) => { }) => {
const [tokenPair] = useRecoilState(tokenPairState); const [tokenPair] = useRecoilState(tokenPairState);
const apolloMetadataClient = useMemo(() => { const apolloMetadataClient = useMemo(() => {
if (tokenPair?.accessToken.token) { if (tokenPair?.accessToken.token) {
return new ApolloClient({ return new ApolloClient({

View File

@ -0,0 +1,7 @@
import { useFindManyObjectMetadataItems } from '@/object-metadata/hooks/useFindManyObjectMetadataItems';
export const ObjectMetadataItemsLoadEffect = () => {
useFindManyObjectMetadataItems();
return <></>;
};

View File

@ -1,14 +1,21 @@
import { useRecoilValue } from 'recoil';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { ObjectMetadataItemsLoadEffect } from '@/object-metadata/components/ObjectMetadataItemsLoadEffect';
import { ObjectMetadataItemsRelationPickerEffect } from '@/object-metadata/components/ObjectMetadataItemsRelationPickerEffect'; import { ObjectMetadataItemsRelationPickerEffect } from '@/object-metadata/components/ObjectMetadataItemsRelationPickerEffect';
import { useFindManyObjectMetadataItems } from '@/object-metadata/hooks/useFindManyObjectMetadataItems'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { RelationPickerScope } from '@/ui/input/components/internal/relation-picker/scopes/RelationPickerScope'; import { RelationPickerScope } from '@/ui/input/components/internal/relation-picker/scopes/RelationPickerScope';
export const ObjectMetadataItemsProvider = ({ export const ObjectMetadataItemsProvider = ({
children, children,
}: React.PropsWithChildren) => { }: React.PropsWithChildren) => {
const { loading } = useFindManyObjectMetadataItems(); const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
const currentWorkspace = useRecoilValue(currentWorkspaceState);
return loading ? ( return objectMetadataItems.length < 1 && currentWorkspace ? (
<></> <>
<ObjectMetadataItemsLoadEffect />
</>
) : ( ) : (
<> <>
<ObjectMetadataItemsRelationPickerEffect /> <ObjectMetadataItemsRelationPickerEffect />

View File

@ -0,0 +1,13 @@
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
export class ObjectMetadataItemNotFoundError extends Error {
constructor(objectName: string, objectMetadataItems: ObjectMetadataItem[]) {
const message = `Object metadata item "${objectName}" cannot be found in an array of ${
objectMetadataItems?.length ?? 0
} elements`;
super(message);
Object.setPrototypeOf(this, ObjectMetadataItemNotFoundError.prototype);
}
}

View File

@ -1,7 +1,11 @@
import { gql } from '@apollo/client'; import { gql } from '@apollo/client';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { ObjectMetadataItemNotFoundError } from '@/object-metadata/errors/ObjectMetadataNotFoundError';
import { objectMetadataItemFamilySelector } from '@/object-metadata/states/objectMetadataItemFamilySelector'; import { objectMetadataItemFamilySelector } from '@/object-metadata/states/objectMetadataItemFamilySelector';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock';
import { useGenerateCreateOneRecordMutation } from '@/object-record/hooks/useGenerateCreateOneRecordMutation'; import { useGenerateCreateOneRecordMutation } from '@/object-record/hooks/useGenerateCreateOneRecordMutation';
import { useGenerateFindManyRecordsQuery } from '@/object-record/hooks/useGenerateFindManyRecordsQuery'; import { useGenerateFindManyRecordsQuery } from '@/object-record/hooks/useGenerateFindManyRecordsQuery';
import { useGenerateFindOneRecordQuery } from '@/object-record/hooks/useGenerateFindOneRecordQuery'; import { useGenerateFindOneRecordQuery } from '@/object-record/hooks/useGenerateFindOneRecordQuery';
@ -26,17 +30,36 @@ export const EMPTY_MUTATION = gql`
`; `;
export const useObjectMetadataItem = ( export const useObjectMetadataItem = (
{ objectNamePlural, objectNameSingular }: ObjectMetadataItemIdentifier, { objectNameSingular }: ObjectMetadataItemIdentifier,
depth?: number, depth?: number,
) => { ) => {
const objectMetadataItem = useRecoilValue( const currentWorkspace = useRecoilValue(currentWorkspaceState);
const mockObjectMetadataItems = getObjectMetadataItemsMock();
let objectMetadataItem = useRecoilValue(
objectMetadataItemFamilySelector({ objectMetadataItemFamilySelector({
objectNamePlural, objectName: objectNameSingular,
objectNameSingular, objectNameType: 'singular',
}), }),
); );
const objectMetadataItemNotFound = !isDefined(objectMetadataItem); let objectMetadataItems = useRecoilValue(objectMetadataItemsState);
if (!currentWorkspace) {
objectMetadataItem =
mockObjectMetadataItems.find(
(objectMetadataItem) =>
objectMetadataItem.nameSingular === objectNameSingular,
) ?? null;
objectMetadataItems = mockObjectMetadataItems;
}
if (!isDefined(objectMetadataItem)) {
throw new ObjectMetadataItemNotFoundError(
objectNameSingular,
objectMetadataItems,
);
}
const getRecordFromCache = useGetRecordFromCache({ const getRecordFromCache = useGetRecordFromCache({
objectMetadataItem, objectMetadataItem,
@ -68,17 +91,16 @@ export const useObjectMetadataItem = (
objectMetadataItem, objectMetadataItem,
}); });
const labelIdentifierFieldMetadataId = objectMetadataItem?.fields.find( const labelIdentifierFieldMetadataId = objectMetadataItem.fields.find(
({ name }) => name === 'name', ({ name }) => name === 'name',
)?.id; )?.id;
const basePathToShowPage = `/object/${objectMetadataItem?.nameSingular}/`; const basePathToShowPage = `/object/${objectMetadataItem.nameSingular}/`;
return { return {
labelIdentifierFieldMetadataId, labelIdentifierFieldMetadataId,
basePathToShowPage, basePathToShowPage,
objectMetadataItem, objectMetadataItem,
objectMetadataItemNotFound,
getRecordFromCache, getRecordFromCache,
modifyRecordFromCache, modifyRecordFromCache,
findManyRecordsQuery, findManyRecordsQuery,

View File

@ -0,0 +1,38 @@
import { useRecoilValue } from 'recoil';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { objectMetadataItemFamilySelector } from '@/object-metadata/states/objectMetadataItemFamilySelector';
import { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock';
import { isDefined } from '~/utils/isDefined';
export const useObjectNamePluralFromSingular = ({
objectNameSingular,
}: {
objectNameSingular: string;
}) => {
const currentWorkspace = useRecoilValue(currentWorkspaceState);
const mockObjectMetadataItems = getObjectMetadataItemsMock();
let objectMetadataItem = useRecoilValue(
objectMetadataItemFamilySelector({
objectName: objectNameSingular,
objectNameType: 'singular',
}),
);
if (!currentWorkspace) {
objectMetadataItem =
mockObjectMetadataItems.find(
(objectMetadataItem) =>
objectMetadataItem.nameSingular === objectNameSingular,
) ?? null;
}
if (!isDefined(objectMetadataItem)) {
throw new Error(
`Object metadata item not found for ${objectNameSingular} object`,
);
}
return { objectNamePlural: objectMetadataItem.namePlural };
};

View File

@ -0,0 +1,38 @@
import { useRecoilValue } from 'recoil';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { objectMetadataItemFamilySelector } from '@/object-metadata/states/objectMetadataItemFamilySelector';
import { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock';
import { isDefined } from '~/utils/isDefined';
export const useObjectNameSingularFromPlural = ({
objectNamePlural,
}: {
objectNamePlural: string;
}) => {
const currentWorkspace = useRecoilValue(currentWorkspaceState);
const mockObjectMetadataItems = getObjectMetadataItemsMock();
let objectMetadataItem = useRecoilValue(
objectMetadataItemFamilySelector({
objectName: objectNamePlural,
objectNameType: 'plural',
}),
);
if (!currentWorkspace) {
objectMetadataItem =
mockObjectMetadataItems.find(
(objectMetadataItem) =>
objectMetadataItem.namePlural === objectNamePlural,
) ?? null;
}
if (!isDefined(objectMetadataItem)) {
throw new Error(
`Object metadata item not found for ${objectNamePlural} object`,
);
}
return { objectNameSingular: objectMetadataItem.nameSingular };
};

View File

@ -3,27 +3,37 @@ import { selectorFamily } from 'recoil';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
type ObjectMetadataItemSelector = {
objectName: string;
objectNameType: 'singular' | 'plural';
};
export const objectMetadataItemFamilySelector = selectorFamily< export const objectMetadataItemFamilySelector = selectorFamily<
ObjectMetadataItem | null, ObjectMetadataItem | null,
{ objectNameSingular?: string; objectNamePlural?: string } ObjectMetadataItemSelector
>({ >({
key: 'objectMetadataItemFamilySelector', key: 'objectMetadataItemFamilySelector',
get: get:
({ ({ objectNameType, objectName }: ObjectMetadataItemSelector) =>
objectNameSingular,
objectNamePlural,
}: {
objectNameSingular?: string;
objectNamePlural?: string;
}) =>
({ get }) => { ({ get }) => {
const objectMetadataItems = get(objectMetadataItemsState); const objectMetadataItems = get(objectMetadataItemsState);
return (
objectMetadataItems.find( if (objectNameType === 'singular') {
(objectMetadataItem) => return (
objectMetadataItem.nameSingular === objectNameSingular || objectMetadataItems.find(
objectMetadataItem.namePlural === objectNamePlural, (objectMetadataItem) =>
) ?? null objectMetadataItem.nameSingular === objectName,
); ) ?? null
);
} else if (objectNameType === 'plural') {
return (
objectMetadataItems.find(
(objectMetadataItem) =>
objectMetadataItem.namePlural === objectName,
) ?? null
);
}
return null;
}, },
}); });

View File

@ -1,4 +1,3 @@
export type ObjectMetadataItemIdentifier = { export type ObjectMetadataItemIdentifier = {
objectNamePlural?: string; objectNameSingular: string;
objectNameSingular?: string;
}; };

File diff suppressed because it is too large Load Diff

View File

@ -37,6 +37,10 @@ export const RecordShowPage = () => {
objectRecordId: string; objectRecordId: string;
}>(); }>();
if (!objectNameSingular) {
throw new Error(`Object name is not defined`);
}
const { objectMetadataItem } = useObjectMetadataItem({ const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular, objectNameSingular,
}); });
@ -44,7 +48,7 @@ export const RecordShowPage = () => {
const { identifiersMapper } = useRelationPicker(); const { identifiersMapper } = useRelationPicker();
const { favorites, createFavorite, deleteFavorite } = useFavorites({ const { favorites, createFavorite, deleteFavorite } = useFavorites({
objectNamePlural: objectMetadataItem?.namePlural, objectNamePlural: objectMetadataItem.namePlural,
}); });
const [, setEntityFields] = useRecoilState( const [, setEntityFields] = useRecoilState(

View File

@ -2,6 +2,7 @@ import styled from '@emotion/styled';
import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata'; import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
import { RecordTable } from '@/ui/object/record-table/components/RecordTable'; import { RecordTable } from '@/ui/object/record-table/components/RecordTable';
import { TableOptionsDropdownId } from '@/ui/object/record-table/constants/TableOptionsDropdownId'; import { TableOptionsDropdownId } from '@/ui/object/record-table/constants/TableOptionsDropdownId';
import { useRecordTable } from '@/ui/object/record-table/hooks/useRecordTable'; import { useRecordTable } from '@/ui/object/record-table/hooks/useRecordTable';
@ -29,17 +30,19 @@ export const RecordTableContainer = ({
objectNamePlural: string; objectNamePlural: string;
createRecord: () => void; createRecord: () => void;
}) => { }) => {
const { objectMetadataItem: foundObjectMetadataItem } = useObjectMetadataItem( const { objectNameSingular } = useObjectNameSingularFromPlural({
{ objectNamePlural,
objectNamePlural, });
},
); const { objectMetadataItem } = useObjectMetadataItem({
const { columnDefinitions } = useColumnDefinitionsFromFieldMetadata( objectNameSingular,
foundObjectMetadataItem, });
);
const { columnDefinitions } =
useColumnDefinitionsFromFieldMetadata(objectMetadataItem);
const { updateOneRecord } = useUpdateOneRecord({ const { updateOneRecord } = useUpdateOneRecord({
objectNameSingular: foundObjectMetadataItem?.nameSingular, objectNameSingular,
}); });
const recordTableId = objectNamePlural ?? ''; const recordTableId = objectNamePlural ?? '';

View File

@ -2,6 +2,7 @@ import { useEffect } from 'react';
import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata'; import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
import { useRecordTableContextMenuEntries } from '@/object-record/hooks/useRecordTableContextMenuEntries'; import { useRecordTableContextMenuEntries } from '@/object-record/hooks/useRecordTableContextMenuEntries';
import { filterAvailableTableColumns } from '@/object-record/utils/filterAvailableTableColumns'; import { filterAvailableTableColumns } from '@/object-record/utils/filterAvailableTableColumns';
import { useRecordTable } from '@/ui/object/record-table/hooks/useRecordTable'; import { useRecordTable } from '@/ui/object/record-table/hooks/useRecordTable';
@ -16,18 +17,23 @@ export const RecordTableEffect = ({
viewBarId: string; viewBarId: string;
}) => { }) => {
const { const {
// Todo: do not infer objectNamePlural from recordTableId
scopeId: objectNamePlural, scopeId: objectNamePlural,
setAvailableTableColumns, setAvailableTableColumns,
setOnEntityCountChange, setOnEntityCountChange,
setObjectMetadataConfig, setObjectMetadataConfig,
} = useRecordTable({ recordTableScopeId: recordTableId }); } = useRecordTable({ recordTableScopeId: recordTableId });
const { objectNameSingular } = useObjectNameSingularFromPlural({
objectNamePlural,
});
const { const {
objectMetadataItem, objectMetadataItem,
basePathToShowPage, basePathToShowPage,
labelIdentifierFieldMetadataId, labelIdentifierFieldMetadataId,
} = useObjectMetadataItem({ } = useObjectMetadataItem({
objectNamePlural, objectNameSingular,
}); });
const { columnDefinitions, filterDefinitions, sortDefinitions } = const { columnDefinitions, filterDefinitions, sortDefinitions } =

View File

@ -1,11 +1,11 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useNavigate, useParams } from 'react-router-dom'; import { useNavigate, useParams } from 'react-router-dom';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { isNonEmptyString } from '@sniptt/guards';
import { useOnboardingStatus } from '@/auth/hooks/useOnboardingStatus'; import { useOnboardingStatus } from '@/auth/hooks/useOnboardingStatus';
import { OnboardingStatus } from '@/auth/utils/getOnboardingStatus'; import { OnboardingStatus } from '@/auth/utils/getOnboardingStatus';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
import { ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier';
import { IconBuildingSkyscraper } from '@/ui/display/icon'; import { IconBuildingSkyscraper } from '@/ui/display/icon';
import { PageAddButton } from '@/ui/layout/page/PageAddButton'; import { PageAddButton } from '@/ui/layout/page/PageAddButton';
import { PageBody } from '@/ui/layout/page/PageBody'; import { PageBody } from '@/ui/layout/page/PageBody';
@ -25,18 +25,12 @@ const StyledTableContainer = styled.div`
width: 100%; width: 100%;
`; `;
export type RecordTablePageProps = Pick<
ObjectMetadataItemIdentifier,
'objectNamePlural'
>;
export const RecordTablePage = () => { export const RecordTablePage = () => {
const objectNamePlural = useParams().objectNamePlural ?? ''; const objectNamePlural = useParams().objectNamePlural ?? '';
const { objectMetadataItemNotFound, objectMetadataItem } = const { objectNameSingular } = useObjectNameSingularFromPlural({
useObjectMetadataItem({ objectNamePlural,
objectNamePlural, });
});
const onboardingStatus = useOnboardingStatus(); const onboardingStatus = useOnboardingStatus();
@ -44,15 +38,15 @@ export const RecordTablePage = () => {
useEffect(() => { useEffect(() => {
if ( if (
objectMetadataItemNotFound && !isNonEmptyString(objectNamePlural) &&
onboardingStatus === OnboardingStatus.Completed onboardingStatus === OnboardingStatus.Completed
) { ) {
navigate('/'); navigate('/');
} }
}, [objectMetadataItemNotFound, navigate, onboardingStatus]); }, [objectNamePlural, navigate, onboardingStatus]);
const { createOneRecord: createOneObject } = useCreateOneRecord({ const { createOneRecord: createOneObject } = useCreateOneRecord({
objectNameSingular: objectMetadataItem?.nameSingular, objectNameSingular,
}); });
const handleAddButtonClick = async () => { const handleAddButtonClick = async () => {

View File

@ -8,42 +8,39 @@ import { capitalize } from '~/utils/string/capitalize';
export const useCreateOneRecord = <T>({ export const useCreateOneRecord = <T>({
objectNameSingular, objectNameSingular,
}: Pick<ObjectMetadataItemIdentifier, 'objectNameSingular'>) => { }: ObjectMetadataItemIdentifier) => {
const { triggerOptimisticEffects } = useOptimisticEffect({ const { triggerOptimisticEffects } = useOptimisticEffect({
objectNameSingular, objectNameSingular,
}); });
const { const { objectMetadataItem, createOneRecordMutation } = useObjectMetadataItem(
objectMetadataItem, {
objectMetadataItemNotFound, objectNameSingular,
createOneRecordMutation, },
} = useObjectMetadataItem({ );
objectNameSingular,
});
// TODO: type this with a minimal type at least with Record<string, any> // TODO: type this with a minimal type at least with Record<string, any>
const [mutate] = useMutation(createOneRecordMutation); const [mutate] = useMutation(createOneRecordMutation);
const createOneRecord = async (input: Record<string, any>) => { const createOneRecord = async (input: Record<string, any>) => {
if (!objectMetadataItem || !objectNameSingular) { const createdObject = await mutate({
return null;
}
const createdRecord = await mutate({
variables: { variables: {
input: { ...input, id: v4() }, input: { ...input, id: v4() },
}, },
}); });
triggerOptimisticEffects( triggerOptimisticEffects(
`${capitalize(objectNameSingular)}Edge`, `${capitalize(objectMetadataItem.nameSingular)}Edge`,
createdRecord.data[`create${capitalize(objectNameSingular)}`], createdObject.data[
`create${capitalize(objectMetadataItem.nameSingular)}`
],
); );
return createdRecord.data[`create${capitalize(objectNameSingular)}`] as T; return createdObject.data[
`create${capitalize(objectMetadataItem.nameSingular)}`
] as T;
}; };
return { return {
createOneRecord, createOneRecord,
objectMetadataItemNotFound,
}; };
}; };

View File

@ -8,41 +8,40 @@ import { capitalize } from '~/utils/string/capitalize';
export const useDeleteOneRecord = <T>({ export const useDeleteOneRecord = <T>({
objectNameSingular, objectNameSingular,
}: Pick<ObjectMetadataItemIdentifier, 'objectNameSingular'>) => { }: ObjectMetadataItemIdentifier) => {
const { performOptimisticEvict } = useOptimisticEvict(); const { performOptimisticEvict } = useOptimisticEvict();
const { const { objectMetadataItem, deleteOneRecordMutation } = useObjectMetadataItem(
objectMetadataItem, {
objectMetadataItemNotFound, objectNameSingular,
deleteOneRecordMutation, },
} = useObjectMetadataItem({ );
objectNameSingular,
});
// TODO: type this with a minimal type at least with Record<string, any> // TODO: type this with a minimal type at least with Record<string, any>
const [mutate] = useMutation(deleteOneRecordMutation); const [mutate] = useMutation(deleteOneRecordMutation);
const deleteOneRecord = useCallback( const deleteOneRecord = useCallback(
async (idToDelete: string) => { async (idToDelete: string) => {
if (!objectMetadataItem || !objectNameSingular) {
return null;
}
const deletedRecord = await mutate({ const deletedRecord = await mutate({
variables: { variables: {
idToDelete, idToDelete,
}, },
}); });
performOptimisticEvict(capitalize(objectNameSingular), 'id', idToDelete); performOptimisticEvict(
capitalize(objectMetadataItem.nameSingular),
'id',
idToDelete,
);
return deletedRecord.data[`create${capitalize(objectNameSingular)}`] as T; return deletedRecord.data[
`create${capitalize(objectMetadataItem.nameSingular)}`
] as T;
}, },
[performOptimisticEvict, objectMetadataItem, mutate, objectNameSingular], [performOptimisticEvict, objectMetadataItem, mutate],
); );
return { return {
deleteOneRecord, deleteOneRecord,
objectMetadataItemNotFound,
}; };
}; };

View File

@ -2,9 +2,10 @@ import { useCallback, useMemo } from 'react';
import { useQuery } from '@apollo/client'; import { useQuery } from '@apollo/client';
import { isNonEmptyArray } from '@apollo/client/utilities'; import { isNonEmptyArray } from '@apollo/client/utilities';
import { isNonEmptyString } from '@sniptt/guards'; import { isNonEmptyString } from '@sniptt/guards';
import { useRecoilState } from 'recoil'; import { useRecoilState, useRecoilValue } from 'recoil';
import { useOptimisticEffect } from '@/apollo/optimistic-effect/hooks/useOptimisticEffect'; import { useOptimisticEffect } from '@/apollo/optimistic-effect/hooks/useOptimisticEffect';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier'; import { ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier';
import { getRecordOptimisticEffectDefinition } from '@/object-record/graphql/optimistic-effect-definition/getRecordOptimisticEffectDefinition'; import { getRecordOptimisticEffectDefinition } from '@/object-record/graphql/optimistic-effect-definition/getRecordOptimisticEffectDefinition';
@ -27,13 +28,13 @@ import { mapPaginatedRecordsToRecords } from '../utils/mapPaginatedRecordsToReco
export const useFindManyRecords = < export const useFindManyRecords = <
RecordType extends { id: string } & Record<string, any>, RecordType extends { id: string } & Record<string, any>,
>({ >({
objectNamePlural, objectNameSingular,
filter, filter,
orderBy, orderBy,
limit = DEFAULT_SEARCH_REQUEST_LIMIT, limit = DEFAULT_SEARCH_REQUEST_LIMIT,
onCompleted, onCompleted,
skip, skip,
}: Pick<ObjectMetadataItemIdentifier, 'objectNamePlural'> & { }: ObjectMetadataItemIdentifier & {
filter?: any; filter?: any;
orderBy?: any; orderBy?: any;
limit?: number; limit?: number;
@ -41,7 +42,10 @@ export const useFindManyRecords = <
skip?: boolean; skip?: boolean;
}) => { }) => {
const findManyQueryStateIdentifier = const findManyQueryStateIdentifier =
objectNamePlural + JSON.stringify(filter) + JSON.stringify(orderBy) + limit; objectNameSingular +
JSON.stringify(filter) +
JSON.stringify(orderBy) +
limit;
const [lastCursor, setLastCursor] = useRecoilState( const [lastCursor, setLastCursor] = useRecoilState(
cursorFamilyState(findManyQueryStateIdentifier), cursorFamilyState(findManyQueryStateIdentifier),
@ -55,24 +59,21 @@ export const useFindManyRecords = <
isFetchingMoreRecordsFamilyState(findManyQueryStateIdentifier), isFetchingMoreRecordsFamilyState(findManyQueryStateIdentifier),
); );
const { const { objectMetadataItem, findManyRecordsQuery } = useObjectMetadataItem({
objectMetadataItem, objectNameSingular,
objectMetadataItemNotFound,
findManyRecordsQuery,
} = useObjectMetadataItem({
objectNamePlural,
}); });
const { registerOptimisticEffect } = useOptimisticEffect({ const { registerOptimisticEffect } = useOptimisticEffect({
objectNameSingular: objectMetadataItem?.nameSingular, objectNameSingular,
}); });
const { enqueueSnackBar } = useSnackBar(); const { enqueueSnackBar } = useSnackBar();
const currentWorkspace = useRecoilValue(currentWorkspaceState);
const { data, loading, error, fetchMore } = useQuery< const { data, loading, error, fetchMore } = useQuery<
PaginatedRecordType<RecordType> PaginatedRecordType<RecordType>
>(findManyRecordsQuery, { >(findManyRecordsQuery, {
skip: skip || !objectMetadataItem || !objectNamePlural, skip: skip || !objectMetadataItem || !currentWorkspace,
variables: { variables: {
filter: filter ?? {}, filter: filter ?? {},
limit: limit, limit: limit,
@ -92,21 +93,24 @@ export const useFindManyRecords = <
}); });
} }
if (objectNamePlural) { onCompleted?.(data[objectMetadataItem.namePlural]);
onCompleted?.(data[objectNamePlural]);
if (objectNamePlural && data?.[objectNamePlural]) { if (data?.[objectMetadataItem.namePlural]) {
setLastCursor(data?.[objectNamePlural]?.pageInfo.endCursor); setLastCursor(
setHasNextPage(data?.[objectNamePlural]?.pageInfo.hasNextPage); data?.[objectMetadataItem.namePlural]?.pageInfo.endCursor,
} );
setHasNextPage(
data?.[objectMetadataItem.namePlural]?.pageInfo.hasNextPage,
);
} }
}, },
onError: (error) => { onError: (error) => {
logError( logError(
`useFindManyObjectRecords for "${objectNamePlural}" error : ` + error, `useFindManyRecords for "${objectMetadataItem.namePlural}" error : ` +
error,
); );
enqueueSnackBar( enqueueSnackBar(
`Error during useFindManyObjectRecords for "${objectNamePlural}", ${error.message}`, `Error during useFindManyRecords for "${objectMetadataItem.namePlural}", ${error.message}`,
{ {
variant: 'error', variant: 'error',
}, },
@ -115,7 +119,7 @@ export const useFindManyRecords = <
}); });
const fetchMoreRecords = useCallback(async () => { const fetchMoreRecords = useCallback(async () => {
if (objectNamePlural && hasNextPage) { if (hasNextPage) {
setIsFetchingMoreObjects(true); setIsFetchingMoreObjects(true);
try { try {
@ -126,33 +130,38 @@ export const useFindManyRecords = <
lastCursor: isNonEmptyString(lastCursor) ? lastCursor : undefined, lastCursor: isNonEmptyString(lastCursor) ? lastCursor : undefined,
}, },
updateQuery: (prev, { fetchMoreResult }) => { updateQuery: (prev, { fetchMoreResult }) => {
const previousEdges = prev?.[objectNamePlural]?.edges; const previousEdges = prev?.[objectMetadataItem.namePlural]?.edges;
const nextEdges = fetchMoreResult?.[objectNamePlural]?.edges; const nextEdges =
fetchMoreResult?.[objectMetadataItem.namePlural]?.edges;
let newEdges: PaginatedRecordTypeEdge<RecordType>[] = []; let newEdges: PaginatedRecordTypeEdge<RecordType>[] = [];
if (isNonEmptyArray(previousEdges) && isNonEmptyArray(nextEdges)) { if (isNonEmptyArray(previousEdges) && isNonEmptyArray(nextEdges)) {
newEdges = filterUniqueRecordEdgesByCursor([ newEdges = filterUniqueRecordEdgesByCursor([
...prev?.[objectNamePlural]?.edges, ...prev?.[objectMetadataItem.namePlural]?.edges,
...fetchMoreResult?.[objectNamePlural]?.edges, ...fetchMoreResult?.[objectMetadataItem.namePlural]?.edges,
]); ]);
} }
return Object.assign({}, prev, { return Object.assign({}, prev, {
[objectNamePlural]: { [objectMetadataItem.namePlural]: {
__typename: `${capitalize( __typename: `${capitalize(
objectMetadataItem?.nameSingular ?? '', objectMetadataItem.nameSingular,
)}Connection`, )}Connection`,
edges: newEdges, edges: newEdges,
pageInfo: fetchMoreResult?.[objectNamePlural].pageInfo, pageInfo:
fetchMoreResult?.[objectMetadataItem.namePlural].pageInfo,
}, },
} as PaginatedRecordType<RecordType>); } as PaginatedRecordType<RecordType>);
}, },
}); });
} catch (error) { } catch (error) {
logError(`fetchMoreObjects for "${objectNamePlural}" error : ` + error); logError(
`fetchMoreObjects for "${objectMetadataItem.namePlural}" error : ` +
error,
);
enqueueSnackBar( enqueueSnackBar(
`Error during fetchMoreObjects for "${objectNamePlural}", ${error}`, `Error during fetchMoreObjects for "${objectMetadataItem.namePlural}", ${error}`,
{ {
variant: 'error', variant: 'error',
}, },
@ -162,7 +171,6 @@ export const useFindManyRecords = <
} }
} }
}, [ }, [
objectNamePlural,
lastCursor, lastCursor,
fetchMore, fetchMore,
filter, filter,
@ -175,13 +183,11 @@ export const useFindManyRecords = <
const records = useMemo( const records = useMemo(
() => () =>
objectNamePlural mapPaginatedRecordsToRecords({
? mapPaginatedRecordsToRecords({ pagedRecords: data,
pagedRecords: data, objectNamePlural: objectMetadataItem.namePlural,
objectNamePlural, }),
}) [data, objectMetadataItem],
: [],
[data, objectNamePlural],
); );
return { return {
@ -189,7 +195,6 @@ export const useFindManyRecords = <
records, records,
loading, loading,
error, error,
objectMetadataItemNotFound,
fetchMoreRecords, fetchMoreRecords,
}; };
}; };

View File

@ -11,19 +11,18 @@ export const useFindOneRecord = <
onCompleted, onCompleted,
depth, depth,
skip, skip,
}: Pick<ObjectMetadataItemIdentifier, 'objectNameSingular'> & { }: ObjectMetadataItemIdentifier & {
objectRecordId: string | undefined; objectRecordId: string | undefined;
onCompleted?: (data: ObjectType) => void; onCompleted?: (data: ObjectType) => void;
skip?: boolean; skip?: boolean;
depth?: number; depth?: number;
}) => { }) => {
const { objectMetadataItem, objectMetadataItemNotFound, findOneRecordQuery } = const { objectMetadataItem, findOneRecordQuery } = useObjectMetadataItem(
useObjectMetadataItem( {
{ objectNameSingular,
objectNameSingular, },
}, depth,
depth, );
);
const { data, loading, error } = useQuery< const { data, loading, error } = useQuery<
{ [nameSingular: string]: ObjectType }, { [nameSingular: string]: ObjectType },
@ -34,19 +33,17 @@ export const useFindOneRecord = <
objectRecordId: objectRecordId ?? '', objectRecordId: objectRecordId ?? '',
}, },
onCompleted: (data) => { onCompleted: (data) => {
if (onCompleted && objectNameSingular) { if (onCompleted) {
onCompleted(data[objectNameSingular]); onCompleted(data[objectNameSingular]);
} }
}, },
}); });
const record = const record = data ? data[objectNameSingular] : undefined;
objectNameSingular && data ? data[objectNameSingular] : undefined;
return { return {
record, record,
loading, loading,
error, error,
objectMetadataItemNotFound,
}; };
}; };

View File

@ -8,7 +8,7 @@ import { capitalize } from '~/utils/string/capitalize';
export const useGenerateCreateOneRecordMutation = ({ export const useGenerateCreateOneRecordMutation = ({
objectMetadataItem, objectMetadataItem,
}: { }: {
objectMetadataItem: ObjectMetadataItem | undefined | null; objectMetadataItem: ObjectMetadataItem;
}) => { }) => {
const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery(); const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery();

View File

@ -9,7 +9,7 @@ export const useGenerateFindManyRecordsQuery = ({
objectMetadataItem, objectMetadataItem,
depth, depth,
}: { }: {
objectMetadataItem: ObjectMetadataItem | undefined | null; objectMetadataItem: ObjectMetadataItem;
depth?: number; depth?: number;
}) => { }) => {
const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery(); const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery();

View File

@ -8,7 +8,7 @@ export const useGenerateFindOneRecordQuery = ({
objectMetadataItem, objectMetadataItem,
depth, depth,
}: { }: {
objectMetadataItem: ObjectMetadataItem | null | undefined; objectMetadataItem: ObjectMetadataItem;
depth?: number; depth?: number;
}) => { }) => {
const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery(); const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery();

View File

@ -16,7 +16,7 @@ export const getUpdateOneRecordMutationGraphQLField = ({
export const useGenerateUpdateOneRecordMutation = ({ export const useGenerateUpdateOneRecordMutation = ({
objectMetadataItem, objectMetadataItem,
}: { }: {
objectMetadataItem: ObjectMetadataItem | undefined | null; objectMetadataItem: ObjectMetadataItem;
}) => { }) => {
const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery(); const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery();

View File

@ -8,7 +8,7 @@ import { capitalize } from '~/utils/string/capitalize';
export const useGetRecordFromCache = ({ export const useGetRecordFromCache = ({
objectMetadataItem, objectMetadataItem,
}: { }: {
objectMetadataItem: ObjectMetadataItem | undefined | null; objectMetadataItem: ObjectMetadataItem;
}) => { }) => {
const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery(); const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery();
const apolloClient = useApolloClient(); const apolloClient = useApolloClient();
@ -31,7 +31,7 @@ export const useGetRecordFromCache = ({
const cache = apolloClient.cache; const cache = apolloClient.cache;
const cachedRecordId = cache.identify({ const cachedRecordId = cache.identify({
__typename: capitalize(objectMetadataItem?.nameSingular ?? ''), __typename: capitalize(objectMetadataItem.nameSingular),
id: recordId, id: recordId,
}); });

View File

@ -8,7 +8,7 @@ import { capitalize } from '~/utils/string/capitalize';
export const useModifyRecordFromCache = ({ export const useModifyRecordFromCache = ({
objectMetadataItem, objectMetadataItem,
}: { }: {
objectMetadataItem: ObjectMetadataItem | undefined | null; objectMetadataItem: ObjectMetadataItem;
}) => { }) => {
const apolloClient = useApolloClient(); const apolloClient = useApolloClient();
@ -19,7 +19,7 @@ export const useModifyRecordFromCache = ({
const cache = apolloClient.cache; const cache = apolloClient.cache;
const cachedRecordId = cache.identify({ const cachedRecordId = cache.identify({
__typename: capitalize(objectMetadataItem?.nameSingular ?? ''), __typename: capitalize(objectMetadataItem.nameSingular),
id: recordId, id: recordId,
}); });

View File

@ -2,6 +2,7 @@ import { useRecoilValue } from 'recoil';
import { useOptimisticEffect } from '@/apollo/optimistic-effect/hooks/useOptimisticEffect'; import { useOptimisticEffect } from '@/apollo/optimistic-effect/hooks/useOptimisticEffect';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
import { turnFiltersIntoWhereClause } from '@/ui/object/object-filter-dropdown/utils/turnFiltersIntoWhereClause'; import { turnFiltersIntoWhereClause } from '@/ui/object/object-filter-dropdown/utils/turnFiltersIntoWhereClause';
import { turnSortsIntoOrderBy } from '@/ui/object/object-sort-dropdown/utils/turnSortsIntoOrderBy'; import { turnSortsIntoOrderBy } from '@/ui/object/object-sort-dropdown/utils/turnSortsIntoOrderBy';
import { useRecordTableScopedStates } from '@/ui/object/record-table/hooks/internal/useRecordTableScopedStates'; import { useRecordTableScopedStates } from '@/ui/object/record-table/hooks/internal/useRecordTableScopedStates';
@ -14,14 +15,18 @@ import { useFindManyRecords } from './useFindManyRecords';
export const useObjectRecordTable = () => { export const useObjectRecordTable = () => {
const { scopeId: objectNamePlural, setRecordTableData } = useRecordTable(); const { scopeId: objectNamePlural, setRecordTableData } = useRecordTable();
const { objectNameSingular } = useObjectNameSingularFromPlural({
objectNamePlural,
});
const { objectMetadataItem: foundObjectMetadataItem } = useObjectMetadataItem( const { objectMetadataItem: foundObjectMetadataItem } = useObjectMetadataItem(
{ {
objectNamePlural, objectNameSingular,
}, },
); );
const { registerOptimisticEffect } = useOptimisticEffect({ const { registerOptimisticEffect } = useOptimisticEffect({
objectNameSingular: foundObjectMetadataItem?.nameSingular, objectNameSingular,
}); });
const { tableFiltersState, tableSortsState } = useRecordTableScopedStates(); const { tableFiltersState, tableSortsState } = useRecordTableScopedStates();
@ -39,7 +44,7 @@ export const useObjectRecordTable = () => {
); );
const { records, loading, fetchMoreRecords } = useFindManyRecords({ const { records, loading, fetchMoreRecords } = useFindManyRecords({
objectNamePlural, objectNameSingular,
filter, filter,
orderBy, orderBy,
onCompleted: (data) => { onCompleted: (data) => {

View File

@ -3,7 +3,7 @@ import { isNonEmptyString } from '@sniptt/guards';
import { useRecoilCallback, useRecoilValue, useSetRecoilState } from 'recoil'; import { useRecoilCallback, useRecoilValue, useSetRecoilState } from 'recoil';
import { useFavorites } from '@/favorites/hooks/useFavorites'; import { useFavorites } from '@/favorites/hooks/useFavorites';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord'; import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
import { IconHeart, IconHeartOff, IconTrash } from '@/ui/display/icon'; import { IconHeart, IconHeartOff, IconTrash } from '@/ui/display/icon';
import { actionBarEntriesState } from '@/ui/navigation/action-bar/states/actionBarEntriesState'; import { actionBarEntriesState } from '@/ui/navigation/action-bar/states/actionBarEntriesState';
@ -38,7 +38,7 @@ export const useRecordTableContextMenuEntries = (
recordTableScopeId: scopeId, recordTableScopeId: scopeId,
}); });
const { objectMetadataItem } = useObjectMetadataItem({ const { objectNameSingular } = useObjectNameSingularFromPlural({
objectNamePlural, objectNamePlural,
}); });
@ -67,7 +67,7 @@ export const useRecordTableContextMenuEntries = (
}); });
const { deleteOneRecord } = useDeleteOneRecord({ const { deleteOneRecord } = useDeleteOneRecord({
objectNameSingular: objectMetadataItem?.nameSingular, objectNameSingular,
}); });
const handleDeleteClick = useRecoilCallback(({ snapshot }) => async () => { const handleDeleteClick = useRecoilCallback(({ snapshot }) => async () => {

View File

@ -7,10 +7,9 @@ import { capitalize } from '~/utils/string/capitalize';
export const useUpdateOneRecord = <T>({ export const useUpdateOneRecord = <T>({
objectNameSingular, objectNameSingular,
}: Pick<ObjectMetadataItemIdentifier, 'objectNameSingular'>) => { }: ObjectMetadataItemIdentifier) => {
const { const {
objectMetadataItem, objectMetadataItem,
objectMetadataItemNotFound,
updateOneRecordMutation, updateOneRecordMutation,
getRecordFromCache, getRecordFromCache,
findManyRecordsQuery, findManyRecordsQuery,
@ -29,10 +28,6 @@ export const useUpdateOneRecord = <T>({
input: Record<string, any>; input: Record<string, any>;
forceRefetch?: boolean; forceRefetch?: boolean;
}) => { }) => {
if (!objectMetadataItem || !objectNameSingular) {
return null;
}
const cachedRecord = getRecordFromCache(idToUpdate); const cachedRecord = getRecordFromCache(idToUpdate);
const updatedRecord = await mutateUpdateOneRecord({ const updatedRecord = await mutateUpdateOneRecord({
@ -43,7 +38,7 @@ export const useUpdateOneRecord = <T>({
}, },
}, },
optimisticResponse: { optimisticResponse: {
[`update${capitalize(objectNameSingular)}`]: { [`update${capitalize(objectMetadataItem.nameSingular)}`]: {
...(cachedRecord ?? {}), ...(cachedRecord ?? {}),
...input, ...input,
}, },
@ -54,11 +49,12 @@ export const useUpdateOneRecord = <T>({
awaitRefetchQueries: forceRefetch, awaitRefetchQueries: forceRefetch,
}); });
return updatedRecord.data[`update${capitalize(objectNameSingular)}`] as T; return updatedRecord.data[
`update${capitalize(objectMetadataItem.nameSingular)}`
] as T;
}; };
return { return {
updateOneRecord, updateOneRecord,
objectMetadataItemNotFound,
}; };
}; };

View File

@ -1,9 +1,13 @@
export type PaginatedRecordTypeEdge<RecordType extends { id: string }> = { export type PaginatedRecordTypeEdge<
RecordType extends { id: string } & Record<string, any>,
> = {
node: RecordType; node: RecordType;
cursor: string; cursor: string;
}; };
export type PaginatedRecordTypeResults<RecordType extends { id: string }> = { export type PaginatedRecordTypeResults<
RecordType extends { id: string } & Record<string, any>,
> = {
__typename?: string; __typename?: string;
edges: PaginatedRecordTypeEdge<RecordType>[]; edges: PaginatedRecordTypeEdge<RecordType>[];
pageInfo: { pageInfo: {

View File

@ -7,13 +7,13 @@ import { capitalize } from '~/utils/string/capitalize';
export const generateDeleteOneRecordMutation = ({ export const generateDeleteOneRecordMutation = ({
objectMetadataItem, objectMetadataItem,
}: { }: {
objectMetadataItem: ObjectMetadataItem | undefined | null; objectMetadataItem: ObjectMetadataItem;
}) => { }) => {
if (!objectMetadataItem) { if (!objectMetadataItem) {
return EMPTY_MUTATION; return EMPTY_MUTATION;
} }
const capitalizedObjectName = capitalize(objectMetadataItem?.nameSingular); const capitalizedObjectName = capitalize(objectMetadataItem.nameSingular);
return gql` return gql`
mutation DeleteOne${capitalizedObjectName}($idToDelete: ID!) { mutation DeleteOne${capitalizedObjectName}($idToDelete: ID!) {

View File

@ -107,7 +107,6 @@ export const PeopleCard = ({
setIsOptionsOpen(!isOptionsOpen); setIsOptionsOpen(!isOptionsOpen);
}; };
// TODO: refactor with useObjectMetadataItem V2 with typed hooks
const { const {
findManyRecordsQuery, findManyRecordsQuery,
updateOneRecordMutation, updateOneRecordMutation,

View File

@ -1,6 +1,7 @@
import { QueryHookOptions, QueryResult } from '@apollo/client'; import { QueryHookOptions, QueryResult } from '@apollo/client';
import { isNonEmptyString } from '@sniptt/guards'; import { isNonEmptyString } from '@sniptt/guards';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { mapPaginatedRecordsToRecords } from '@/object-record/utils/mapPaginatedRecordsToRecords'; import { mapPaginatedRecordsToRecords } from '@/object-record/utils/mapPaginatedRecordsToRecords';
import { EntitiesForMultipleEntitySelect } from '@/ui/input/relation-picker/components/MultipleEntitySelect'; import { EntitiesForMultipleEntitySelect } from '@/ui/input/relation-picker/components/MultipleEntitySelect';
import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect'; import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect';
@ -30,7 +31,7 @@ export const useFilteredSearchEntityQuery = ({
mappingFunction, mappingFunction,
limit, limit,
excludeEntityIds = [], excludeEntityIds = [],
objectNamePlural, objectNameSingular,
}: { }: {
queryHook: ( queryHook: (
queryOptions?: QueryHookOptions<any, any>, queryOptions?: QueryHookOptions<any, any>,
@ -42,8 +43,12 @@ export const useFilteredSearchEntityQuery = ({
mappingFunction: (entity: any) => EntityForSelect | undefined; mappingFunction: (entity: any) => EntityForSelect | undefined;
limit?: number; limit?: number;
excludeEntityIds?: string[]; excludeEntityIds?: string[];
objectNamePlural: string; objectNameSingular: string;
}): EntitiesForMultipleEntitySelect<EntityForSelect> => { }): EntitiesForMultipleEntitySelect<EntityForSelect> => {
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular,
});
const { loading: selectedEntitiesLoading, data: selectedEntitiesData } = const { loading: selectedEntitiesLoading, data: selectedEntitiesData } =
queryHook({ queryHook({
variables: { variables: {
@ -138,19 +143,19 @@ export const useFilteredSearchEntityQuery = ({
return { return {
selectedEntities: mapPaginatedRecordsToRecords({ selectedEntities: mapPaginatedRecordsToRecords({
objectNamePlural: objectNamePlural, objectNamePlural: objectMetadataItem.namePlural,
pagedRecords: selectedEntitiesData, pagedRecords: selectedEntitiesData,
}) })
.map(mappingFunction) .map(mappingFunction)
.filter(assertNotNull), .filter(assertNotNull),
filteredSelectedEntities: mapPaginatedRecordsToRecords({ filteredSelectedEntities: mapPaginatedRecordsToRecords({
objectNamePlural: objectNamePlural, objectNamePlural: objectMetadataItem.namePlural,
pagedRecords: filteredSelectedEntitiesData, pagedRecords: filteredSelectedEntitiesData,
}) })
.map(mappingFunction) .map(mappingFunction)
.filter(assertNotNull), .filter(assertNotNull),
entitiesToSelect: mapPaginatedRecordsToRecords({ entitiesToSelect: mapPaginatedRecordsToRecords({
objectNamePlural: objectNamePlural, objectNamePlural: objectMetadataItem.namePlural,
pagedRecords: entitiesToSelectData, pagedRecords: entitiesToSelectData,
}) })
.map(mappingFunction) .map(mappingFunction)

View File

@ -31,7 +31,7 @@ export const useFieldPreview = ({
const { value: firstRecordFieldValue } = useFieldPreviewValue({ const { value: firstRecordFieldValue } = useFieldPreviewValue({
fieldName: fieldName || '', fieldName: fieldName || '',
objectNamePlural: objectMetadataItem?.namePlural || '', objectNamePlural: objectMetadataItem?.namePlural ?? '',
skip: skip:
!fieldName || !fieldName ||
!objectMetadataItem || !objectMetadataItem ||

View File

@ -1,3 +1,4 @@
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
import { assertNotNull } from '~/utils/assert'; import { assertNotNull } from '~/utils/assert';
@ -10,8 +11,12 @@ export const useFieldPreviewValue = ({
objectNamePlural: string; objectNamePlural: string;
skip?: boolean; skip?: boolean;
}) => { }) => {
const { records } = useFindManyRecords({ const { objectNameSingular } = useObjectNameSingularFromPlural({
objectNamePlural, objectNamePlural,
});
const { records } = useFindManyRecords({
objectNameSingular,
skip, skip,
}); });

View File

@ -10,12 +10,13 @@ export const useRelationFieldPreviewValue = ({
}) => { }) => {
const { findObjectMetadataItemById } = useObjectMetadataItemForSettings(); const { findObjectMetadataItemById } = useObjectMetadataItemForSettings();
// TODO: make this impossible to be undefined
const relationObjectMetadataItem = relationObjectMetadataId const relationObjectMetadataItem = relationObjectMetadataId
? findObjectMetadataItemById(relationObjectMetadataId) ? findObjectMetadataItemById(relationObjectMetadataId)
: undefined; : undefined;
const { records: relationObjects } = useFindManyRecords({ const { records: relationObjects } = useFindManyRecords({
objectNamePlural: relationObjectMetadataItem?.namePlural, objectNameSingular: relationObjectMetadataItem?.nameSingular ?? '',
skip: skip || !relationObjectMetadataItem, skip: skip || !relationObjectMetadataItem,
}); });

View File

@ -37,7 +37,7 @@ export const SettingsObjectItemTableRow = ({
const theme = useTheme(); const theme = useTheme();
const { records } = useFindManyRecords({ const { records } = useFindManyRecords({
objectNamePlural: objectItem.namePlural, objectNameSingular: objectItem.nameSingular,
}); });
const { Icon } = useLazyLoadIcon(objectItem.icon ?? ''); const { Icon } = useLazyLoadIcon(objectItem.icon ?? '');

View File

@ -40,7 +40,7 @@ export const NameFields = ({
currentWorkspaceMember?.name?.lastName ?? '', currentWorkspaceMember?.name?.lastName ?? '',
); );
const { updateOneRecord, objectMetadataItemNotFound } = useUpdateOneRecord({ const { updateOneRecord } = useUpdateOneRecord({
objectNameSingular: 'workspaceMember', objectNameSingular: 'workspaceMember',
}); });
@ -58,9 +58,6 @@ export const NameFields = ({
} }
if (autoSave) { if (autoSave) {
if (!updateOneRecord || objectMetadataItemNotFound) {
throw new Error('Object not found in metadata');
}
await updateOneRecord({ await updateOneRecord({
idToUpdate: currentWorkspaceMember?.id, idToUpdate: currentWorkspaceMember?.id,
input: { input: {

View File

@ -19,7 +19,7 @@ export const ProfilePictureUploader = () => {
useState<AbortController | null>(null); useState<AbortController | null>(null);
const [errorMessage, setErrorMessage] = useState<string | null>(null); const [errorMessage, setErrorMessage] = useState<string | null>(null);
const { updateOneRecord, objectMetadataItemNotFound } = useUpdateOneRecord({ const { updateOneRecord } = useUpdateOneRecord({
objectNameSingular: 'workspaceMember', objectNameSingular: 'workspaceMember',
}); });
@ -54,9 +54,7 @@ export const ProfilePictureUploader = () => {
if (!avatarUrl) { if (!avatarUrl) {
throw new Error('Avatar URL not found'); throw new Error('Avatar URL not found');
} }
if (!updateOneRecord || objectMetadataItemNotFound) {
throw new Error('Object not found in metadata');
}
await updateOneRecord({ await updateOneRecord({
idToUpdate: currentWorkspaceMember?.id, idToUpdate: currentWorkspaceMember?.id,
input: { input: {
@ -80,12 +78,10 @@ export const ProfilePictureUploader = () => {
}; };
const handleRemove = async () => { const handleRemove = async () => {
if (!updateOneRecord || objectMetadataItemNotFound) {
throw new Error('Object not found in metadata');
}
if (!currentWorkspaceMember?.id) { if (!currentWorkspaceMember?.id) {
throw new Error('User is not logged in'); throw new Error('User is not logged in');
} }
await updateOneRecord({ await updateOneRecord({
idToUpdate: currentWorkspaceMember?.id, idToUpdate: currentWorkspaceMember?.id,
input: { input: {

View File

@ -14,8 +14,8 @@ const StyledContainer = styled.div`
`; `;
export const SignInBackgroundMockContainer = () => { export const SignInBackgroundMockContainer = () => {
const recordTableId = 'sign-in-background-mock-table'; const recordTableId = 'companies';
const viewBarId = 'sign-in-background-mock-view'; const viewBarId = 'companies-mock';
return ( return (
<StyledContainer> <StyledContainer>

View File

@ -1,6 +1,7 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
import { useRecordTableContextMenuEntries } from '@/object-record/hooks/useRecordTableContextMenuEntries'; import { useRecordTableContextMenuEntries } from '@/object-record/hooks/useRecordTableContextMenuEntries';
import { filterAvailableTableColumns } from '@/object-record/utils/filterAvailableTableColumns'; import { filterAvailableTableColumns } from '@/object-record/utils/filterAvailableTableColumns';
import { signInBackgroundMockCompanies } from '@/sign-in-background-mock/constants/signInBackgroundMockCompanies'; import { signInBackgroundMockCompanies } from '@/sign-in-background-mock/constants/signInBackgroundMockCompanies';
@ -35,10 +36,14 @@ export const SignInBackgroundMockContainerEffect = ({
recordTableScopeId: recordTableId, recordTableScopeId: recordTableId,
}); });
const { objectMetadataItem } = useObjectMetadataItem({ const { objectNameSingular } = useObjectNameSingularFromPlural({
objectNamePlural, objectNamePlural,
}); });
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular,
});
const { const {
setAvailableSortDefinitions, setAvailableSortDefinitions,
setAvailableFilterDefinitions, setAvailableFilterDefinitions,
@ -49,7 +54,7 @@ export const SignInBackgroundMockContainerEffect = ({
} = useViewBar({ viewBarId: viewId }); } = useViewBar({ viewBarId: viewId });
useEffect(() => { useEffect(() => {
setViewObjectMetadataId?.('company-mock-object-metadata-id'); setViewObjectMetadataId?.(objectMetadataItem.id);
setViewType?.(ViewType.Table); setViewType?.(ViewType.Table);
setAvailableSortDefinitions?.(signInBackgroundMockSortDefinitions); setAvailableSortDefinitions?.(signInBackgroundMockSortDefinitions);

View File

@ -1,400 +0,0 @@
export const metadataItem = {
__typename: 'object',
id: '20202020-480c-434e-b4c7-e22408b97047',
dataSourceId: '20202020-7f63-47a9-b1b3-6c7290ca9fb1',
nameSingular: 'company',
namePlural: 'companies',
labelSingular: 'Company',
labelPlural: 'Companies',
description: 'A company',
icon: 'IconBuildingSkyscraper',
isCustom: false,
isActive: true,
isSystem: false,
createdAt: '2023-11-23T15:38:02.187Z',
updatedAt: '2023-11-23T15:38:02.187Z',
fields: [
{
__typename: 'field',
id: '20202020-5e4e-4007-a630-8a2617914889',
type: 'TEXT',
name: 'domainName',
label: 'Domain Name',
description:
'The company website URL. We use this url to fetch the company icon',
icon: 'IconLink',
isCustom: false,
isActive: true,
isSystem: false,
isNullable: true,
createdAt: '2023-11-23T15:38:02.292Z',
updatedAt: '2023-11-23T15:38:02.292Z',
fromRelationMetadata: null,
toRelationMetadata: null,
},
{
__typename: 'field',
id: '20202020-64b8-41bf-a01c-be6a806e8b70',
type: 'DATE_TIME',
name: 'updatedAt',
label: 'Update date',
description: null,
icon: 'IconCalendar',
isCustom: false,
isActive: true,
isSystem: true,
isNullable: false,
createdAt: '2023-11-23T15:38:02.292Z',
updatedAt: '2023-11-23T15:38:02.292Z',
fromRelationMetadata: null,
toRelationMetadata: null,
},
{
__typename: 'field',
id: '20202020-7fbd-41ad-b64d-25a15ff62f04',
type: 'NUMBER',
name: 'employees',
label: 'Employees',
description: 'Number of employees in the company',
icon: 'IconUsers',
isCustom: false,
isActive: true,
isSystem: false,
isNullable: true,
createdAt: '2023-11-23T15:38:02.292Z',
updatedAt: '2023-11-23T15:38:02.292Z',
fromRelationMetadata: null,
toRelationMetadata: null,
},
{
__typename: 'field',
id: '20202020-6d30-4111-9f40-b4301906fd3c',
type: 'TEXT',
name: 'name',
label: 'Name',
description: 'The company name',
icon: 'IconBuildingSkyscraper',
isCustom: false,
isActive: true,
isSystem: false,
isNullable: true,
createdAt: '2023-11-23T15:38:02.292Z',
updatedAt: '2023-11-23T15:38:02.292Z',
fromRelationMetadata: null,
toRelationMetadata: null,
},
{
__typename: 'field',
id: '20202020-e7c8-4771-8cc4-ce0e8c36a3c0',
type: 'RELATION',
name: 'favorites',
label: 'Favorites',
description: 'Favorites linked to the company',
icon: 'IconHeart',
isCustom: false,
isActive: true,
isSystem: false,
isNullable: true,
createdAt: '2023-11-23T15:38:02.292Z',
updatedAt: '2023-11-23T15:38:02.292Z',
fromRelationMetadata: {
__typename: 'relation',
id: '73d9610a-a196-4186-b5b2-9a1da2d3bede',
relationType: 'ONE_TO_MANY',
toObjectMetadata: {
__typename: 'object',
id: '20202020-90e4-4701-a350-8ab75e23e3b8',
dataSourceId: '20202020-7f63-47a9-b1b3-6c7290ca9fb1',
nameSingular: 'favorite',
namePlural: 'favorites',
},
toFieldMetadataId: '20202020-09e1-4384-ae3e-39e7956396ff',
},
toRelationMetadata: null,
},
{
__typename: 'field',
id: '20202020-ad10-4117-a039-3f04b7a5f939',
type: 'TEXT',
name: 'address',
label: 'Address',
description: 'The company address',
icon: 'IconMap',
isCustom: false,
isActive: true,
isSystem: false,
isNullable: true,
createdAt: '2023-11-23T15:38:02.292Z',
updatedAt: '2023-11-23T15:38:02.292Z',
fromRelationMetadata: null,
toRelationMetadata: null,
},
{
__typename: 'field',
id: '20202020-0739-495d-8e70-c0807f6b2268',
type: 'RELATION',
name: 'accountOwner',
label: 'Account Owner',
description:
'Your team member responsible for managing the company account',
icon: 'IconUserCircle',
isCustom: false,
isActive: true,
isSystem: false,
isNullable: true,
createdAt: '2023-11-23T15:38:02.292Z',
updatedAt: '2023-11-23T15:38:02.292Z',
fromRelationMetadata: null,
toRelationMetadata: {
__typename: 'relation',
id: '729283e1-8134-494d-9df5-24a44e92c38b',
relationType: 'ONE_TO_MANY',
fromObjectMetadata: {
__typename: 'object',
id: '20202020-b550-40bb-a96b-9ab54b664753',
dataSourceId: '20202020-7f63-47a9-b1b3-6c7290ca9fb1',
nameSingular: 'workspaceMember',
namePlural: 'workspaceMembers',
},
fromFieldMetadataId: '20202020-41bb-4c17-8979-40fa915df9e1',
},
},
{
__typename: 'field',
id: '20202020-68b4-4c8e-af19-738eba2a42a5',
type: 'RELATION',
name: 'people',
label: 'People',
description: 'People linked to the company.',
icon: 'IconUsers',
isCustom: false,
isActive: true,
isSystem: false,
isNullable: true,
createdAt: '2023-11-23T15:38:02.292Z',
updatedAt: '2023-11-23T15:38:02.292Z',
fromRelationMetadata: {
__typename: 'relation',
id: '8256c11a-710d-48b5-bc86-f607895abec4',
relationType: 'ONE_TO_MANY',
toObjectMetadata: {
__typename: 'object',
id: '20202020-c64b-44bc-bd2c-502c99f49dca',
dataSourceId: '20202020-7f63-47a9-b1b3-6c7290ca9fb1',
nameSingular: 'person',
namePlural: 'people',
},
toFieldMetadataId: '20202020-64e1-4080-b6ad-db03c3809885',
},
toRelationMetadata: null,
},
{
__typename: 'field',
id: '20202020-61af-4ffd-b79b-baed6db8ad11',
type: 'RELATION',
name: 'attachments',
label: 'Attachments',
description: 'Attachments linked to the company.',
icon: 'IconFileImport',
isCustom: false,
isActive: true,
isSystem: false,
isNullable: true,
createdAt: '2023-11-23T15:38:02.292Z',
updatedAt: '2023-11-23T15:38:02.292Z',
fromRelationMetadata: {
__typename: 'relation',
id: '54c7c707-09f8-4ce0-b2f6-0161443fffa8',
relationType: 'ONE_TO_MANY',
toObjectMetadata: {
__typename: 'object',
id: '20202020-5f98-4317-915d-3779bb821be2',
dataSourceId: '20202020-7f63-47a9-b1b3-6c7290ca9fb1',
nameSingular: 'attachment',
namePlural: 'attachments',
},
toFieldMetadataId: '20202020-5463-4d03-9124-1775b9b7f955',
},
toRelationMetadata: null,
},
{
__typename: 'field',
id: '20202020-4dc2-47c9-bb15-6e6f19ba9e46',
type: 'DATE_TIME',
name: 'createdAt',
label: 'Creation date',
description: null,
icon: 'IconCalendar',
isCustom: false,
isActive: true,
isSystem: false,
isNullable: false,
createdAt: '2023-11-23T15:38:02.292Z',
updatedAt: '2023-11-23T15:38:02.292Z',
fromRelationMetadata: null,
toRelationMetadata: null,
},
{
__typename: 'field',
id: '20202020-9e9f-4235-98b2-c76f3e2d281e',
type: 'BOOLEAN',
name: 'idealCustomerProfile',
label: 'ICP',
description:
'Ideal Customer Profile: Indicates whether the company is the most suitable and valuable customer for you',
icon: 'IconTarget',
isCustom: false,
isActive: true,
isSystem: false,
isNullable: true,
createdAt: '2023-11-23T15:38:02.292Z',
updatedAt: '2023-11-23T15:38:02.292Z',
fromRelationMetadata: null,
toRelationMetadata: null,
},
{
__typename: 'field',
id: '20202020-8169-44a3-9e0b-6bad1ac50f87',
type: 'UUID',
name: 'id',
label: 'Id',
description: null,
icon: null,
isCustom: false,
isActive: true,
isSystem: true,
isNullable: false,
createdAt: '2023-11-23T15:38:02.292Z',
updatedAt: '2023-11-23T15:38:02.292Z',
fromRelationMetadata: null,
toRelationMetadata: null,
},
{
__typename: 'field',
id: '20202020-a61d-4b78-b998-3fd88b4f73a1',
type: 'LINK',
name: 'linkedinLink',
label: 'Linkedin',
description: 'The company Linkedin account',
icon: 'IconBrandLinkedin',
isCustom: false,
isActive: true,
isSystem: false,
isNullable: true,
createdAt: '2023-11-23T15:38:02.292Z',
updatedAt: '2023-11-23T15:38:02.292Z',
fromRelationMetadata: null,
toRelationMetadata: null,
},
{
__typename: 'field',
id: '20202020-e3fc-46ff-b552-3e757843f06e',
type: 'RELATION',
name: 'opportunities',
label: 'Opportunities',
description: 'Opportunities linked to the company.',
icon: 'IconTargetArrow',
isCustom: false,
isActive: true,
isSystem: false,
isNullable: true,
createdAt: '2023-11-23T15:38:02.292Z',
updatedAt: '2023-11-23T15:38:02.292Z',
fromRelationMetadata: {
__typename: 'relation',
id: '831b6b14-125c-4563-83a3-99d5e07c0a85',
relationType: 'ONE_TO_MANY',
toObjectMetadata: {
__typename: 'object',
id: '20202020-cae9-4ff4-9579-f7d9fe44c937',
dataSourceId: '20202020-7f63-47a9-b1b3-6c7290ca9fb1',
nameSingular: 'opportunity',
namePlural: 'opportunities',
},
toFieldMetadataId: '20202020-31d5-4af5-b016-c61c1c265706',
},
toRelationMetadata: null,
},
{
__typename: 'field',
id: '20202020-0b9e-4b9e-8b0a-5b0b5b0b5b0b',
type: 'UUID',
name: 'accountOwnerId',
label: 'Account Owner ID (foreign key)',
description: 'Foreign key for account owner',
icon: null,
isCustom: false,
isActive: true,
isSystem: true,
isNullable: true,
createdAt: '2023-11-23T15:38:02.292Z',
updatedAt: '2023-11-23T15:38:02.292Z',
fromRelationMetadata: null,
toRelationMetadata: null,
},
{
__typename: 'field',
id: '20202020-46e3-479a-b8f4-77137c74daa6',
type: 'LINK',
name: 'xLink',
label: 'X',
description: 'The company Twitter/X account',
icon: 'IconBrandX',
isCustom: false,
isActive: true,
isSystem: false,
isNullable: true,
createdAt: '2023-11-23T15:38:02.292Z',
updatedAt: '2023-11-23T15:38:02.292Z',
fromRelationMetadata: null,
toRelationMetadata: null,
},
{
__typename: 'field',
id: '20202020-4a2e-4b41-8562-279963e8947e',
type: 'RELATION',
name: 'activityTargets',
label: 'Activities',
description: 'Activities tied to the company',
icon: 'IconCheckbox',
isCustom: false,
isActive: true,
isSystem: false,
isNullable: true,
createdAt: '2023-11-23T15:38:02.292Z',
updatedAt: '2023-11-23T15:38:02.292Z',
fromRelationMetadata: {
__typename: 'relation',
id: '56e2ab44-4718-4c05-b61c-a7f978d7bdd1',
relationType: 'ONE_TO_MANY',
toObjectMetadata: {
__typename: 'object',
id: '20202020-439a-4a41-83a3-3cda03d01d38',
dataSourceId: '20202020-7f63-47a9-b1b3-6c7290ca9fb1',
nameSingular: 'activityTarget',
namePlural: 'activityTargets',
},
toFieldMetadataId: '20202020-9408-4cc0-9fe1-51467edb530b',
},
toRelationMetadata: null,
},
{
__typename: 'field',
id: '20202020-4a5a-466f-92d9-c3870d9502a9',
type: 'CURRENCY',
name: 'annualRecurringRevenue',
label: 'ARR',
description:
'Annual Recurring Revenue: The actual or estimated annual revenue of the company',
icon: 'IconMoneybag',
isCustom: false,
isActive: true,
isSystem: false,
isNullable: true,
createdAt: '2023-11-23T15:38:02.292Z',
updatedAt: '2023-11-23T15:38:02.292Z',
fromRelationMetadata: null,
toRelationMetadata: null,
},
],
};

View File

@ -2,6 +2,7 @@ import { useEffect } from 'react';
import { useQuery } from '@apollo/client'; import { useQuery } from '@apollo/client';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
import { useFilteredSearchEntityQuery } from '@/search/hooks/useFilteredSearchEntityQuery'; import { useFilteredSearchEntityQuery } from '@/search/hooks/useFilteredSearchEntityQuery';
import { IconForbid } from '@/ui/display/icon'; import { IconForbid } from '@/ui/display/icon';
import { useRelationPicker } from '@/ui/input/components/internal/relation-picker/hooks/useRelationPicker'; import { useRelationPicker } from '@/ui/input/components/internal/relation-picker/hooks/useRelationPicker';
@ -49,6 +50,12 @@ export const RelationPicker = ({
const { identifiersMapper, searchQuery } = useRelationPicker(); const { identifiersMapper, searchQuery } = useRelationPicker();
const { objectNameSingular: relationObjectNameSingular } =
useObjectNameSingularFromPlural({
objectNamePlural:
fieldDefinition.metadata.relationObjectMetadataNamePlural,
});
const records = useFilteredSearchEntityQuery({ const records = useFilteredSearchEntityQuery({
queryHook: useFindManyQuery, queryHook: useFindManyQuery,
filters: [ filters: [
@ -68,7 +75,7 @@ export const RelationPicker = ({
), ),
selectedIds: recordId ? [recordId] : [], selectedIds: recordId ? [recordId] : [],
excludeEntityIds: excludeRecordIds, excludeEntityIds: excludeRecordIds,
objectNamePlural: fieldDefinition.metadata.relationObjectMetadataNamePlural, objectNameSingular: relationObjectNameSingular,
}); });
const handleEntitySelected = async (selectedUser: any | null | undefined) => { const handleEntitySelected = async (selectedUser: any | null | undefined) => {

View File

@ -6,6 +6,7 @@ import { AuthModal } from '@/auth/components/Modal';
import { useOnboardingStatus } from '@/auth/hooks/useOnboardingStatus'; import { useOnboardingStatus } from '@/auth/hooks/useOnboardingStatus';
import { OnboardingStatus } from '@/auth/utils/getOnboardingStatus'; import { OnboardingStatus } from '@/auth/utils/getOnboardingStatus';
import { CommandMenu } from '@/command-menu/components/CommandMenu'; import { CommandMenu } from '@/command-menu/components/CommandMenu';
import { AppErrorBoundary } from '@/error-handler/components/AppErrorBoundary';
import { KeyboardShortcutMenu } from '@/keyboard-shortcut-menu/components/KeyboardShortcutMenu'; import { KeyboardShortcutMenu } from '@/keyboard-shortcut-menu/components/KeyboardShortcutMenu';
import { SignInBackgroundMockPage } from '@/sign-in-background-mock/components/SignInBackgroundMockPage'; import { SignInBackgroundMockPage } from '@/sign-in-background-mock/components/SignInBackgroundMockPage';
import { NavbarAnimatedContainer } from '@/ui/navigation/navbar/components/NavbarAnimatedContainer'; import { NavbarAnimatedContainer } from '@/ui/navigation/navbar/components/NavbarAnimatedContainer';
@ -74,7 +75,7 @@ export const DefaultLayout = ({ children }: DefaultLayoutProps) => {
</AnimatePresence> </AnimatePresence>
</> </>
) : ( ) : (
<>{children}</> <AppErrorBoundary>{children}</AppErrorBoundary>
)} )}
</StyledMainContainer> </StyledMainContainer>
</StyledLayout> </StyledLayout>

View File

@ -16,9 +16,6 @@ export const ObjectFilterDropdownEntitySelect = () => {
const objectMetadataNameSingular = const objectMetadataNameSingular =
filterDefinitionUsedInDropdown?.relationObjectMetadataNameSingular ?? ''; filterDefinitionUsedInDropdown?.relationObjectMetadataNameSingular ?? '';
const objectMetadataNamePlural =
filterDefinitionUsedInDropdown?.relationObjectMetadataNamePlural ?? '';
// TODO: refactor useFilteredSearchEntityQuery // TODO: refactor useFilteredSearchEntityQuery
const { findManyRecordsQuery } = useObjectMetadataItem({ const { findManyRecordsQuery } = useObjectMetadataItem({
objectNameSingular: objectMetadataNameSingular, objectNameSingular: objectMetadataNameSingular,
@ -44,7 +41,7 @@ export const ObjectFilterDropdownEntitySelect = () => {
: [], : [],
mappingFunction: (record: any) => mappingFunction: (record: any) =>
identifiersMapper?.(record, objectMetadataNameSingular), identifiersMapper?.(record, objectMetadataNameSingular),
objectNamePlural: objectMetadataNamePlural, objectNameSingular: objectMetadataNameSingular,
}); });
if (filterDefinitionUsedInDropdown?.type !== 'RELATION') { if (filterDefinitionUsedInDropdown?.type !== 'RELATION') {

View File

@ -3,6 +3,7 @@ import { useInView } from 'react-intersection-observer';
import { useRecoilState, useRecoilValue } from 'recoil'; import { useRecoilState, useRecoilValue } from 'recoil';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
import { useObjectRecordTable } from '@/object-record/hooks/useObjectRecordTable'; import { useObjectRecordTable } from '@/object-record/hooks/useObjectRecordTable';
import { isFetchingMoreRecordsFamilyState } from '@/object-record/states/isFetchingMoreRecordsFamilyState'; import { isFetchingMoreRecordsFamilyState } from '@/object-record/states/isFetchingMoreRecordsFamilyState';
import { isDefined } from '~/utils/isDefined'; import { isDefined } from '~/utils/isDefined';
@ -22,9 +23,13 @@ export const RecordTableBody = () => {
const { scopeId: objectNamePlural } = useRecordTable(); const { scopeId: objectNamePlural } = useRecordTable();
const { objectNameSingular } = useObjectNameSingularFromPlural({
objectNamePlural,
});
const { objectMetadataItem: foundObjectMetadataItem } = useObjectMetadataItem( const { objectMetadataItem: foundObjectMetadataItem } = useObjectMetadataItem(
{ {
objectNamePlural, objectNameSingular,
}, },
); );

View File

@ -16,6 +16,7 @@ export const AppThemeProvider = ({ children }: AppThemeProviderProps) => {
const computedColorScheme = const computedColorScheme =
colorScheme === 'System' ? systemColorScheme : colorScheme; colorScheme === 'System' ? systemColorScheme : colorScheme;
const theme = computedColorScheme === 'Dark' ? darkTheme : lightTheme; const theme = computedColorScheme === 'Dark' ? darkTheme : lightTheme;
return <ThemeProvider theme={theme}>{children}</ThemeProvider>; return <ThemeProvider theme={theme}>{children}</ThemeProvider>;

View File

@ -32,7 +32,7 @@ export const ViewBarEffect = () => {
useFindManyRecords({ useFindManyRecords({
skip: !viewObjectMetadataId, skip: !viewObjectMetadataId,
objectNamePlural: 'views', objectNameSingular: 'view',
filter: { filter: {
type: { eq: viewType }, type: { eq: viewType },
objectMetadataId: { eq: viewObjectMetadataId }, objectMetadataId: { eq: viewObjectMetadataId },

View File

@ -14,6 +14,7 @@ export const useViews = (scopeId: string) => {
} = useObjectMetadataItem({ } = useObjectMetadataItem({
objectNameSingular: 'view', objectNameSingular: 'view',
}); });
const apolloClient = useApolloClient(); const apolloClient = useApolloClient();
const createView = useRecoilCallback( const createView = useRecoilCallback(

View File

@ -62,10 +62,9 @@ export const CreateProfile = () => {
currentWorkspaceMemberState, currentWorkspaceMemberState,
); );
const { updateOneRecord, objectMetadataItemNotFound } = const { updateOneRecord } = useUpdateOneRecord<WorkspaceMember>({
useUpdateOneRecord<WorkspaceMember>({ objectNameSingular: 'workspaceMember',
objectNameSingular: 'workspaceMember', });
});
// Form // Form
const { const {
@ -91,9 +90,6 @@ export const CreateProfile = () => {
if (!data.firstName || !data.lastName) { if (!data.firstName || !data.lastName) {
throw new Error('First name or last name is missing'); throw new Error('First name or last name is missing');
} }
if (!updateOneRecord || objectMetadataItemNotFound) {
throw new Error('Object not found in metadata');
}
await updateOneRecord({ await updateOneRecord({
idToUpdate: currentWorkspaceMember?.id, idToUpdate: currentWorkspaceMember?.id,
@ -127,7 +123,6 @@ export const CreateProfile = () => {
currentWorkspaceMember?.id, currentWorkspaceMember?.id,
enqueueSnackBar, enqueueSnackBar,
navigate, navigate,
objectMetadataItemNotFound,
setCurrentWorkspaceMember, setCurrentWorkspaceMember,
updateOneRecord, updateOneRecord,
], ],

View File

@ -35,7 +35,7 @@ export const SettingsWorkspaceMembers = () => {
>(); >();
const { records: workspaceMembers } = useFindManyRecords<WorkspaceMember>({ const { records: workspaceMembers } = useFindManyRecords<WorkspaceMember>({
objectNamePlural: 'workspaceMembers', objectNameSingular: 'workspaceMember',
}); });
const { deleteOneRecord: deleteOneWorkspaceMember } = const { deleteOneRecord: deleteOneWorkspaceMember } =
useDeleteOneRecord<WorkspaceMember>({ useDeleteOneRecord<WorkspaceMember>({

View File

@ -79,7 +79,7 @@ export const SettingsObjectNewFieldStep2 = () => {
}); });
useFindManyRecords({ useFindManyRecords({
objectNamePlural: 'views', objectNameSingular: 'view',
filter: { filter: {
type: { eq: ViewType.Table }, type: { eq: ViewType.Table },
objectMetadataId: { eq: activeObjectMetadataItem?.id }, objectMetadataId: { eq: activeObjectMetadataItem?.id },
@ -94,7 +94,7 @@ export const SettingsObjectNewFieldStep2 = () => {
}); });
useFindManyRecords({ useFindManyRecords({
objectNamePlural: 'views', objectNameSingular: 'view',
skip: !formValues.relation?.objectMetadataId, skip: !formValues.relation?.objectMetadataId,
filter: { filter: {
type: { eq: ViewType.Table }, type: { eq: ViewType.Table },

View File

@ -43,8 +43,9 @@ export const SettingsDevelopersApiKeys = () => {
const [apiKeys, setApiKeys] = useState<Array<ApiFieldItem>>([]); const [apiKeys, setApiKeys] = useState<Array<ApiFieldItem>>([]);
const filter = { revokedAt: { is: 'NULL' } }; const filter = { revokedAt: { is: 'NULL' } };
useFindManyRecords({ useFindManyRecords({
objectNamePlural: 'apiKeys', objectNameSingular: 'apiKey',
filter, filter,
orderBy: {}, orderBy: {},
onCompleted: (data) => { onCompleted: (data) => {

View File

@ -33,6 +33,7 @@ export const SettingsDevelopersApiKeysNew = () => {
const { createOneRecord: createOneApiKey } = useCreateOneRecord<ApiKey>({ const { createOneRecord: createOneApiKey } = useCreateOneRecord<ApiKey>({
objectNameSingular: 'apiKey', objectNameSingular: 'apiKey',
}); });
const onSave = async () => { const onSave = async () => {
const expiresAt = DateTime.now() const expiresAt = DateTime.now()
.plus({ days: formValues.expirationDate ?? 30 }) .plus({ days: formValues.expirationDate ?? 30 })

View File

@ -2,5 +2,6 @@ import { isNonEmptyString } from '@sniptt/guards';
export const capitalize = (stringToCapitalize: string) => { export const capitalize = (stringToCapitalize: string) => {
if (!isNonEmptyString(stringToCapitalize)) return ''; if (!isNonEmptyString(stringToCapitalize)) return '';
return stringToCapitalize[0].toUpperCase() + stringToCapitalize.slice(1); return stringToCapitalize[0].toUpperCase() + stringToCapitalize.slice(1);
}; };

View File

@ -16476,6 +16476,13 @@ react-element-to-jsx-string@^15.0.0:
is-plain-object "5.0.0" is-plain-object "5.0.0"
react-is "18.1.0" react-is "18.1.0"
react-error-boundary@^4.0.11:
version "4.0.11"
resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-4.0.11.tgz#36bf44de7746714725a814630282fee83a7c9a1c"
integrity sha512-U13ul67aP5DOSPNSCWQ/eO0AQEYzEFkVljULQIjMV0KlffTAhxuDoBKdO0pb/JZ8mDhMKFZ9NZi0BmLGUiNphw==
dependencies:
"@babel/runtime" "^7.12.5"
react-error-overlay@^6.0.11: react-error-overlay@^6.0.11:
version "6.0.11" version "6.0.11"
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.11.tgz#92835de5841c5cf08ba00ddd2d677b6d17ff9adb" resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.11.tgz#92835de5841c5cf08ba00ddd2d677b6d17ff9adb"