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:
@ -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",
|
||||||
@ -176,4 +177,4 @@
|
|||||||
"msw": {
|
"msw": {
|
||||||
"workerDirectory": "public"
|
"workerDirectory": "public"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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>,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -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 ?? '',
|
||||||
|
|||||||
@ -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 },
|
||||||
},
|
},
|
||||||
|
|||||||
@ -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,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -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 } },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -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,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -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 } },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -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' },
|
||||||
|
|||||||
@ -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' },
|
||||||
|
|||||||
@ -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),
|
||||||
|
|||||||
@ -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,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -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);
|
||||||
|
|
||||||
|
|||||||
@ -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}%` } },
|
||||||
|
|||||||
@ -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 || ''),
|
||||||
|
|||||||
@ -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 (
|
||||||
|
|||||||
@ -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] =
|
||||||
|
|||||||
@ -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>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -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>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -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 <></>;
|
||||||
|
};
|
||||||
@ -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>>) => {
|
||||||
|
|||||||
@ -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({
|
||||||
|
|||||||
@ -0,0 +1,7 @@
|
|||||||
|
import { useFindManyObjectMetadataItems } from '@/object-metadata/hooks/useFindManyObjectMetadataItems';
|
||||||
|
|
||||||
|
export const ObjectMetadataItemsLoadEffect = () => {
|
||||||
|
useFindManyObjectMetadataItems();
|
||||||
|
|
||||||
|
return <></>;
|
||||||
|
};
|
||||||
@ -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 />
|
||||||
|
|||||||
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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,
|
||||||
|
|||||||
@ -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 };
|
||||||
|
};
|
||||||
@ -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 };
|
||||||
|
};
|
||||||
@ -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;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -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
@ -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(
|
||||||
|
|||||||
@ -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 ?? '';
|
||||||
|
|||||||
@ -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 } =
|
||||||
|
|||||||
@ -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 () => {
|
||||||
|
|||||||
@ -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,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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();
|
||||||
|
|
||||||
|
|||||||
@ -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();
|
||||||
|
|||||||
@ -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();
|
||||||
|
|||||||
@ -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();
|
||||||
|
|
||||||
|
|||||||
@ -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,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -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,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -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) => {
|
||||||
|
|||||||
@ -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 () => {
|
||||||
|
|||||||
@ -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,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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: {
|
||||||
|
|||||||
@ -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!) {
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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 ||
|
||||||
|
|||||||
@ -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,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -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,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -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 ?? '');
|
||||||
|
|||||||
@ -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: {
|
||||||
|
|||||||
@ -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: {
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
@ -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) => {
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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') {
|
||||||
|
|||||||
@ -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,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -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>;
|
||||||
|
|||||||
@ -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 },
|
||||||
|
|||||||
@ -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(
|
||||||
|
|||||||
@ -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,
|
||||||
],
|
],
|
||||||
|
|||||||
@ -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>({
|
||||||
|
|||||||
@ -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 },
|
||||||
|
|||||||
@ -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) => {
|
||||||
|
|||||||
@ -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 })
|
||||||
|
|||||||
@ -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);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user