diff --git a/packages/twenty-front/codegen.cjs b/packages/twenty-front/codegen.cjs
index f318d5de0..eeb1da9af 100644
--- a/packages/twenty-front/codegen.cjs
+++ b/packages/twenty-front/codegen.cjs
@@ -6,6 +6,8 @@ module.exports = {
'./src/modules/**/*.tsx',
'./src/modules/**/*.ts',
'!./src/**/*.test.tsx',
+ '!./src/**/__mocks__/*.ts',
+ '!./src/modules/users/graphql/queries/getCurrentUserAndViews.ts'
],
overwrite: true,
generates: {
diff --git a/packages/twenty-front/src/App.tsx b/packages/twenty-front/src/App.tsx
index adcdc606c..827e1751b 100644
--- a/packages/twenty-front/src/App.tsx
+++ b/packages/twenty-front/src/App.tsx
@@ -8,6 +8,7 @@ import { DefaultLayout } from '@/ui/layout/page/DefaultLayout';
import { PageTitle } from '@/ui/utilities/page-title/PageTitle';
import { CommandMenuEffect } from '~/effect-components/CommandMenuEffect';
import { GotoHotkeysEffect } from '~/effect-components/GotoHotkeysEffect';
+import { useDefaultHomePagePath } from '~/hooks/useDefaultHomePagePath';
import { CreateProfile } from '~/pages/auth/CreateProfile';
import { CreateWorkspace } from '~/pages/auth/CreateWorkspace';
import { PlanRequired } from '~/pages/auth/PlanRequired';
@@ -39,7 +40,10 @@ import { getPageTitleFromPath } from '~/utils/title-utils';
export const App = () => {
const { pathname } = useLocation();
+ const { defaultHomePagePath } = useDefaultHomePagePath();
+
const pageTitle = getPageTitleFromPath(pathname);
+
return (
<>
@@ -54,7 +58,7 @@ export const App = () => {
} />
} />
} />
- } />
+ } />
} />
} />
diff --git a/packages/twenty-front/src/generated/graphql.tsx b/packages/twenty-front/src/generated/graphql.tsx
index ed10d96ec..9d21cdb89 100644
--- a/packages/twenty-front/src/generated/graphql.tsx
+++ b/packages/twenty-front/src/generated/graphql.tsx
@@ -436,7 +436,7 @@ export enum RelationMetadataType {
export type Sentry = {
__typename?: 'Sentry';
- dsn: Scalars['String'];
+ dsn?: Maybe;
};
/** Sort Directions */
@@ -747,7 +747,7 @@ export type CheckUserExistsQuery = { __typename?: 'Query', checkUserExists: { __
export type GetClientConfigQueryVariables = Exact<{ [key: string]: never; }>;
-export type GetClientConfigQuery = { __typename?: 'Query', clientConfig: { __typename?: 'ClientConfig', signInPrefilled: boolean, signUpDisabled: boolean, debugMode: boolean, authProviders: { __typename?: 'AuthProviders', google: boolean, password: boolean }, billing: { __typename?: 'Billing', isBillingEnabled: boolean, billingUrl: string }, telemetry: { __typename?: 'Telemetry', enabled: boolean, anonymizationEnabled: boolean }, support: { __typename?: 'Support', supportDriver: string, supportFrontChatId?: string | null }, sentry: { __typename?: 'Sentry', dsn: string } } };
+export type GetClientConfigQuery = { __typename?: 'Query', clientConfig: { __typename?: 'ClientConfig', signInPrefilled: boolean, signUpDisabled: boolean, debugMode: boolean, authProviders: { __typename?: 'AuthProviders', google: boolean, password: boolean }, billing: { __typename?: 'Billing', isBillingEnabled: boolean, billingUrl: string }, telemetry: { __typename?: 'Telemetry', enabled: boolean, anonymizationEnabled: boolean }, support: { __typename?: 'Support', supportDriver: string, supportFrontChatId?: string | null }, sentry: { __typename?: 'Sentry', dsn?: string | null } } };
export type UploadFileMutationVariables = Exact<{
file: Scalars['Upload'];
@@ -779,11 +779,6 @@ export type UploadProfilePictureMutationVariables = Exact<{
export type UploadProfilePictureMutation = { __typename?: 'Mutation', uploadProfilePicture: string };
-export type GetCurrentUserQueryVariables = Exact<{ [key: string]: never; }>;
-
-
-export type GetCurrentUserQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', id: string, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, workspaceMember: { __typename?: 'WorkspaceMember', id: string, colorScheme: string, avatarUrl?: string | null, locale: string, name: { __typename?: 'FullName', firstName: string, lastName: string } }, defaultWorkspace: { __typename?: 'Workspace', id: string, displayName?: string | null, logo?: string | null, domainName?: string | null, inviteHash?: string | null, allowImpersonation: boolean, subscriptionStatus: string, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: string, key: string, value: boolean, workspaceId: string }> | null } } };
-
export type DeleteCurrentWorkspaceMutationVariables = Exact<{ [key: string]: never; }>;
@@ -1452,40 +1447,6 @@ export function useUploadProfilePictureMutation(baseOptions?: Apollo.MutationHoo
export type UploadProfilePictureMutationHookResult = ReturnType;
export type UploadProfilePictureMutationResult = Apollo.MutationResult;
export type UploadProfilePictureMutationOptions = Apollo.BaseMutationOptions;
-export const GetCurrentUserDocument = gql`
- query GetCurrentUser {
- currentUser {
- ...UserQueryFragment
- }
-}
- ${UserQueryFragmentFragmentDoc}`;
-
-/**
- * __useGetCurrentUserQuery__
- *
- * To run a query within a React component, call `useGetCurrentUserQuery` and pass it any options that fit your needs.
- * When your component renders, `useGetCurrentUserQuery` returns an object from Apollo Client that contains loading, error, and data properties
- * you can use to render your UI.
- *
- * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
- *
- * @example
- * const { data, loading, error } = useGetCurrentUserQuery({
- * variables: {
- * },
- * });
- */
-export function useGetCurrentUserQuery(baseOptions?: Apollo.QueryHookOptions) {
- const options = {...defaultOptions, ...baseOptions}
- return Apollo.useQuery(GetCurrentUserDocument, options);
- }
-export function useGetCurrentUserLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) {
- const options = {...defaultOptions, ...baseOptions}
- return Apollo.useLazyQuery(GetCurrentUserDocument, options);
- }
-export type GetCurrentUserQueryHookResult = ReturnType;
-export type GetCurrentUserLazyQueryHookResult = ReturnType;
-export type GetCurrentUserQueryResult = Apollo.QueryResult;
export const DeleteCurrentWorkspaceDocument = gql`
mutation DeleteCurrentWorkspace {
deleteCurrentWorkspace {
diff --git a/packages/twenty-front/src/hooks/useDefaultHomePagePath.tsx b/packages/twenty-front/src/hooks/useDefaultHomePagePath.tsx
new file mode 100644
index 000000000..6f0c25d4a
--- /dev/null
+++ b/packages/twenty-front/src/hooks/useDefaultHomePagePath.tsx
@@ -0,0 +1,27 @@
+import { useCachedRootQuery } from '@/apollo/hooks/useCachedRootQuery';
+import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
+import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
+import { QueryMethodName } from '@/object-metadata/types/QueryMethodName';
+
+export const useDefaultHomePagePath = () => {
+ const { objectMetadataItem: companyObjectMetadataItem } =
+ useObjectMetadataItem({
+ objectNameSingular: CoreObjectNameSingular.Company,
+ });
+ const { objectMetadataItem: viewObjectMetadataItem } = useObjectMetadataItem({
+ objectNameSingular: CoreObjectNameSingular.View,
+ });
+ const { cachedRootQuery } = useCachedRootQuery({
+ objectMetadataItem: viewObjectMetadataItem,
+ queryMethodName: QueryMethodName.FindMany,
+ });
+
+ const companyViewId = cachedRootQuery?.views?.edges?.find(
+ (view: any) =>
+ view?.node?.objectMetadataId === companyObjectMetadataItem.id,
+ )?.node.id;
+ const defaultHomePagePath =
+ '/objects/companies' + (companyViewId ? `?view=${companyViewId}` : '');
+
+ return { defaultHomePagePath };
+};
diff --git a/packages/twenty-front/src/index.tsx b/packages/twenty-front/src/index.tsx
index 9dfb43090..3fe6f23ad 100644
--- a/packages/twenty-front/src/index.tsx
+++ b/packages/twenty-front/src/index.tsx
@@ -20,11 +20,12 @@ import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/Sn
import { AppThemeProvider } from '@/ui/theme/components/AppThemeProvider';
import { ThemeType } from '@/ui/theme/constants/theme';
import { UserProvider } from '@/users/components/UserProvider';
-import { App } from '~/App';
import { PageChangeEffect } from '~/effect-components/PageChangeEffect';
import '@emotion/react';
+import { App } from './App';
+
import './index.css';
import 'react-loading-skeleton/dist/skeleton.css';
diff --git a/packages/twenty-front/src/modules/apollo/hooks/useCachedRootQuery.ts b/packages/twenty-front/src/modules/apollo/hooks/useCachedRootQuery.ts
new file mode 100644
index 000000000..f3d7b981a
--- /dev/null
+++ b/packages/twenty-front/src/modules/apollo/hooks/useCachedRootQuery.ts
@@ -0,0 +1,50 @@
+import { useApolloClient } from '@apollo/client/react/hooks/useApolloClient';
+import gql from 'graphql-tag';
+
+import { useMapFieldMetadataToGraphQLQuery } from '@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery';
+import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
+import { QueryMethodName } from '@/object-metadata/types/QueryMethodName';
+
+export const useCachedRootQuery = ({
+ objectMetadataItem,
+ queryMethodName,
+}: {
+ objectMetadataItem: ObjectMetadataItem | undefined;
+ queryMethodName: QueryMethodName;
+}) => {
+ const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery();
+ const apolloClient = useApolloClient();
+
+ if (!objectMetadataItem) {
+ return { cachedRootQuery: null };
+ }
+
+ const buildRecordFieldsFragment = () => {
+ return objectMetadataItem.fields
+ .filter((field) => field.type !== 'RELATION')
+ .map((field) => mapFieldMetadataToGraphQLQuery(field))
+ .join(' \n');
+ };
+
+ const cacheReadFragment = gql`
+ fragment RootQuery on Query {
+ ${
+ QueryMethodName.FindMany === queryMethodName
+ ? objectMetadataItem.namePlural
+ : objectMetadataItem.nameSingular
+ } {
+ ${QueryMethodName.FindMany === queryMethodName ? 'edges { node { ' : ''}
+ ${buildRecordFieldsFragment()}
+ ${QueryMethodName.FindMany === queryMethodName ? '}}' : ''}
+
+ }
+ }
+ `;
+
+ const cachedRootQuery = apolloClient.readFragment({
+ id: 'ROOT_QUERY',
+ fragment: cacheReadFragment,
+ });
+
+ return { cachedRootQuery };
+};
diff --git a/packages/twenty-front/src/modules/object-metadata/components/ObjectMetadataNavItems.tsx b/packages/twenty-front/src/modules/object-metadata/components/ObjectMetadataNavItems.tsx
index 4f5939c44..83194ee56 100644
--- a/packages/twenty-front/src/modules/object-metadata/components/ObjectMetadataNavItems.tsx
+++ b/packages/twenty-front/src/modules/object-metadata/components/ObjectMetadataNavItems.tsx
@@ -1,15 +1,38 @@
import { useLocation, useNavigate } from 'react-router-dom';
+import { useCachedRootQuery } from '@/apollo/hooks/useCachedRootQuery';
import { useObjectMetadataItemForSettings } from '@/object-metadata/hooks/useObjectMetadataItemForSettings';
+import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
+import { QueryMethodName } from '@/object-metadata/types/QueryMethodName';
+import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
import { useIcons } from '@/ui/display/icon/hooks/useIcons';
import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem';
export const ObjectMetadataNavItems = () => {
- const { activeObjectMetadataItems } = useObjectMetadataItemForSettings();
+ const { activeObjectMetadataItems, findObjectMetadataItemByNamePlural } =
+ useObjectMetadataItemForSettings();
const navigate = useNavigate();
const { getIcon } = useIcons();
const currentPath = useLocation().pathname;
+ const viewObjectMetadataItem = findObjectMetadataItemByNamePlural('views');
+
+ const { cachedRootQuery } = useCachedRootQuery({
+ objectMetadataItem: viewObjectMetadataItem,
+ queryMethodName: QueryMethodName.FindMany,
+ });
+
+ const { records } = useFindManyRecords({
+ skip: cachedRootQuery?.views,
+ objectNameSingular: CoreObjectNameSingular.View,
+ useRecordsWithoutConnection: true,
+ });
+
+ const views =
+ records.length > 0
+ ? records
+ : cachedRootQuery?.views?.edges?.map((edge: any) => edge?.node);
+
return (
<>
{[
@@ -39,18 +62,28 @@ export const ObjectMetadataNavItems = () => {
? 1
: -1;
}),
- ].map((objectMetadataItem) => (
- {
- navigate(`/objects/${objectMetadataItem.namePlural}`);
- }}
- />
- ))}
+ ].map((objectMetadataItem) => {
+ const viewId = views?.find(
+ (view: any) => view?.objectMetadataId === objectMetadataItem.id,
+ )?.id;
+
+ const navigationPath = `/objects/${objectMetadataItem.namePlural}${
+ viewId ? `?view=${viewId}` : ''
+ }`;
+
+ return (
+ {
+ navigate(navigationPath);
+ }}
+ />
+ );
+ })}
>
);
};
diff --git a/packages/twenty-front/src/modules/object-metadata/types/QueryMethodName.ts b/packages/twenty-front/src/modules/object-metadata/types/QueryMethodName.ts
new file mode 100644
index 000000000..851dcffc0
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-metadata/types/QueryMethodName.ts
@@ -0,0 +1,4 @@
+export enum QueryMethodName {
+ FindOne = 'findOne',
+ FindMany = 'findMany',
+}
diff --git a/packages/twenty-front/src/modules/object-record/hooks/useGenerateFindManyRecordsQuery.ts b/packages/twenty-front/src/modules/object-record/hooks/useGenerateFindManyRecordsQuery.ts
index bf9043023..10f0aa06c 100644
--- a/packages/twenty-front/src/modules/object-record/hooks/useGenerateFindManyRecordsQuery.ts
+++ b/packages/twenty-front/src/modules/object-record/hooks/useGenerateFindManyRecordsQuery.ts
@@ -20,7 +20,7 @@ export const useGenerateFindManyRecordsQuery = () => {
objectMetadataItem.nameSingular,
)}FilterInput, $orderBy: ${capitalize(
objectMetadataItem.nameSingular,
- )}OrderByInput, $lastCursor: String, $limit: Float = 30) {
+ )}OrderByInput, $lastCursor: String, $limit: Float = 60) {
${
objectMetadataItem.namePlural
}(filter: $filter, orderBy: $orderBy, first: $limit, after: $lastCursor){
diff --git a/packages/twenty-front/src/modules/users/components/UserProvider.tsx b/packages/twenty-front/src/modules/users/components/UserProvider.tsx
index cb31acb04..fc39fe6e1 100644
--- a/packages/twenty-front/src/modules/users/components/UserProvider.tsx
+++ b/packages/twenty-front/src/modules/users/components/UserProvider.tsx
@@ -1,11 +1,12 @@
import { useEffect, useState } from 'react';
+import { useQuery } from '@apollo/client';
import { useSetRecoilState } from 'recoil';
import { currentUserState } from '@/auth/states/currentUserState';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
+import { GET_CURRENT_USER_AND_VIEWS } from '@/users/graphql/queries/getCurrentUserAndViews';
import { ColorScheme } from '@/workspace-member/types/WorkspaceMember';
-import { useGetCurrentUserQuery } from '~/generated/graphql';
export const UserProvider = ({ children }: React.PropsWithChildren) => {
const [isLoading, setIsLoading] = useState(true);
@@ -16,16 +17,18 @@ export const UserProvider = ({ children }: React.PropsWithChildren) => {
currentWorkspaceMemberState,
);
- const { data: userData, loading: userLoading } = useGetCurrentUserQuery({});
+ const { loading: queryLoading, data: queryData } = useQuery(
+ GET_CURRENT_USER_AND_VIEWS,
+ );
useEffect(() => {
- if (!userLoading) {
+ if (!queryLoading) {
setIsLoading(false);
}
- if (userData?.currentUser?.workspaceMember) {
- setCurrentUser(userData.currentUser);
- setCurrentWorkspace(userData.currentUser.defaultWorkspace);
- const workspaceMember = userData.currentUser.workspaceMember;
+ if (queryData?.currentUser?.workspaceMember) {
+ setCurrentUser(queryData.currentUser);
+ setCurrentWorkspace(queryData.currentUser.defaultWorkspace);
+ const workspaceMember = queryData.currentUser.workspaceMember;
setCurrentWorkspaceMember({
...workspaceMember,
colorScheme: (workspaceMember.colorScheme as ColorScheme) ?? 'Light',
@@ -34,10 +37,10 @@ export const UserProvider = ({ children }: React.PropsWithChildren) => {
}, [
setCurrentUser,
isLoading,
- userLoading,
+ queryLoading,
setCurrentWorkspace,
setCurrentWorkspaceMember,
- userData?.currentUser,
+ queryData?.currentUser,
]);
return isLoading ? <>> : <>{children}>;
diff --git a/packages/twenty-front/src/modules/users/graphql/queries/getCurrentUser.ts b/packages/twenty-front/src/modules/users/graphql/queries/getCurrentUser.ts
deleted file mode 100644
index 8b1a6eac5..000000000
--- a/packages/twenty-front/src/modules/users/graphql/queries/getCurrentUser.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import { gql } from '@apollo/client';
-
-export const GET_CURRENT_USER = gql`
- query GetCurrentUser {
- currentUser {
- ...UserQueryFragment
- }
- }
-`;
diff --git a/packages/twenty-front/src/modules/users/graphql/queries/getCurrentUserAndViews.ts b/packages/twenty-front/src/modules/users/graphql/queries/getCurrentUserAndViews.ts
new file mode 100644
index 000000000..9b0da4252
--- /dev/null
+++ b/packages/twenty-front/src/modules/users/graphql/queries/getCurrentUserAndViews.ts
@@ -0,0 +1,103 @@
+// This query cannot be put in the graphQL folder because it cannot be generated by the graphQL codegen.
+import { gql } from '@apollo/client';
+
+export const GET_CURRENT_USER_AND_VIEWS = gql`
+ query GetCurrentUserAndViews {
+ currentUser {
+ id
+ firstName
+ lastName
+ email
+ canImpersonate
+ supportUserHash
+ workspaceMember {
+ id
+ name {
+ firstName
+ lastName
+ }
+ colorScheme
+ avatarUrl
+ locale
+ }
+ defaultWorkspace {
+ id
+ displayName
+ logo
+ domainName
+ inviteHash
+ allowImpersonation
+ subscriptionStatus
+ featureFlags {
+ id
+ key
+ value
+ workspaceId
+ }
+ }
+ }
+ views {
+ pageInfo {
+ hasNextPage
+ hasPreviousPage
+ startCursor
+ endCursor
+ }
+ edges {
+ cursor
+ node {
+ id
+ createdAt
+ updatedAt
+ name
+ objectMetadataId
+ type
+ deletedAt
+ viewFilters {
+ edges {
+ cursor
+ node {
+ id
+ createdAt
+ updatedAt
+ fieldMetadataId
+ operand
+ value
+ displayValue
+ deletedAt
+ }
+ }
+ }
+ viewSorts {
+ edges {
+ cursor
+ node {
+ id
+ createdAt
+ updatedAt
+ fieldMetadataId
+ direction
+ deletedAt
+ }
+ }
+ }
+ viewFields {
+ edges {
+ cursor
+ node {
+ id
+ createdAt
+ updatedAt
+ fieldMetadataId
+ isVisible
+ size
+ position
+ deletedAt
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+`;
diff --git a/packages/twenty-front/src/modules/views/components/ViewBar.tsx b/packages/twenty-front/src/modules/views/components/ViewBar.tsx
index f1797d0d6..220c1af64 100644
--- a/packages/twenty-front/src/modules/views/components/ViewBar.tsx
+++ b/packages/twenty-front/src/modules/views/components/ViewBar.tsx
@@ -7,6 +7,7 @@ import { ObjectSortDropdownButton } from '@/object-record/object-sort-dropdown/c
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { TopBar } from '@/ui/layout/top-bar/TopBar';
import { FilterQueryParamsEffect } from '@/views/components/FilterQueryParamsEffect';
+import { ViewBarEffect } from '@/views/components/ViewBarEffect';
import { ViewBarFilterEffect } from '@/views/components/ViewBarFilterEffect';
import { ViewBarSortEffect } from '@/views/components/ViewBarSortEffect';
import { useViewBar } from '@/views/hooks/useViewBar';
@@ -19,7 +20,6 @@ import { ViewsHotkeyScope } from '../types/ViewsHotkeyScope';
import { UpdateViewButtonGroup } from './UpdateViewButtonGroup';
import { ViewBarDetails } from './ViewBarDetails';
-import { ViewBarEffect } from './ViewBarEffect';
import { ViewsDropdownButton } from './ViewsDropdownButton';
export type ViewBarProps = {
diff --git a/packages/twenty-front/src/pages/auth/__stories__/CreateProfile.stories.tsx b/packages/twenty-front/src/pages/auth/__stories__/CreateProfile.stories.tsx
index 302883712..953be52cd 100644
--- a/packages/twenty-front/src/pages/auth/__stories__/CreateProfile.stories.tsx
+++ b/packages/twenty-front/src/pages/auth/__stories__/CreateProfile.stories.tsx
@@ -4,7 +4,7 @@ import { within } from '@storybook/test';
import { graphql, HttpResponse } from 'msw';
import { AppPath } from '@/types/AppPath';
-import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser';
+import { GET_CURRENT_USER_AND_VIEWS } from '@/users/graphql/queries/getCurrentUserAndViews';
import {
PageDecorator,
PageDecoratorArgs,
@@ -22,13 +22,16 @@ const meta: Meta = {
parameters: {
msw: {
handlers: [
- graphql.query(getOperationName(GET_CURRENT_USER) ?? '', () => {
- return HttpResponse.json({
- data: {
- currentUser: mockedOnboardingUsersData[0],
- },
- });
- }),
+ graphql.query(
+ getOperationName(GET_CURRENT_USER_AND_VIEWS) ?? '',
+ () => {
+ return HttpResponse.json({
+ data: {
+ currentUser: mockedOnboardingUsersData[0],
+ },
+ });
+ },
+ ),
graphqlMocks.handlers,
],
},
diff --git a/packages/twenty-front/src/pages/auth/__stories__/PlanRequired.stories.tsx b/packages/twenty-front/src/pages/auth/__stories__/PlanRequired.stories.tsx
index f325f824c..7cfea3cae 100644
--- a/packages/twenty-front/src/pages/auth/__stories__/PlanRequired.stories.tsx
+++ b/packages/twenty-front/src/pages/auth/__stories__/PlanRequired.stories.tsx
@@ -4,7 +4,7 @@ import { within } from '@storybook/test';
import { graphql, HttpResponse } from 'msw';
import { AppPath } from '@/types/AppPath';
-import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser';
+import { GET_CURRENT_USER_AND_VIEWS } from '@/users/graphql/queries/getCurrentUserAndViews';
import {
PageDecorator,
PageDecoratorArgs,
@@ -22,19 +22,22 @@ const meta: Meta = {
parameters: {
msw: {
handlers: [
- graphql.query(getOperationName(GET_CURRENT_USER) ?? '', () => {
- return HttpResponse.json({
- data: {
- currentUser: {
- ...mockedOnboardingUsersData[0],
- defaultWorkspace: {
- ...mockedOnboardingUsersData[0].defaultWorkspace,
- subscriptionStatus: 'incomplete',
+ graphql.query(
+ getOperationName(GET_CURRENT_USER_AND_VIEWS) ?? '',
+ () => {
+ return HttpResponse.json({
+ data: {
+ currentUser: {
+ ...mockedOnboardingUsersData[0],
+ defaultWorkspace: {
+ ...mockedOnboardingUsersData[0].defaultWorkspace,
+ subscriptionStatus: 'incomplete',
+ },
},
},
- },
- });
- }),
+ });
+ },
+ ),
graphqlMocks.handlers,
],
},
diff --git a/packages/twenty-front/src/pages/auth/__stories__/SignInUp.stories.tsx b/packages/twenty-front/src/pages/auth/__stories__/SignInUp.stories.tsx
index 2c40e0359..f9ce7546b 100644
--- a/packages/twenty-front/src/pages/auth/__stories__/SignInUp.stories.tsx
+++ b/packages/twenty-front/src/pages/auth/__stories__/SignInUp.stories.tsx
@@ -4,7 +4,7 @@ import { fireEvent, within } from '@storybook/test';
import { graphql, HttpResponse } from 'msw';
import { AppPath } from '@/types/AppPath';
-import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser';
+import { GET_CURRENT_USER_AND_VIEWS } from '@/users/graphql/queries/getCurrentUserAndViews';
import {
PageDecorator,
PageDecoratorArgs,
@@ -22,13 +22,16 @@ const meta: Meta = {
parameters: {
msw: {
handlers: [
- graphql.query(getOperationName(GET_CURRENT_USER) ?? '', () => {
- return HttpResponse.json({
- data: {
- currentUser: mockedOnboardingUsersData[0],
- },
- });
- }),
+ graphql.query(
+ getOperationName(GET_CURRENT_USER_AND_VIEWS) ?? '',
+ () => {
+ return HttpResponse.json({
+ data: {
+ currentUser: mockedOnboardingUsersData[0],
+ },
+ });
+ },
+ ),
graphqlMocks.handlers,
],
},
diff --git a/packages/twenty-front/src/testing/graphqlMocks.ts b/packages/twenty-front/src/testing/graphqlMocks.ts
index 9520c35ca..0a6e92dfa 100644
--- a/packages/twenty-front/src/testing/graphqlMocks.ts
+++ b/packages/twenty-front/src/testing/graphqlMocks.ts
@@ -4,7 +4,7 @@ import { graphql, HttpResponse } from 'msw';
import { CREATE_EVENT } from '@/analytics/graphql/queries/createEvent';
import { GET_CLIENT_CONFIG } from '@/client-config/graphql/queries/getClientConfig';
import { FIND_MANY_OBJECT_METADATA_ITEMS } from '@/object-metadata/graphql/queries';
-import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser';
+import { GET_CURRENT_USER_AND_VIEWS } from '@/users/graphql/queries/getCurrentUserAndViews';
import { REACT_APP_SERVER_BASE_URL } from '~/config';
import { mockedActivities } from '~/testing/mock-data/activities';
import { mockedCompaniesData } from '~/testing/mock-data/companies';
@@ -22,10 +22,22 @@ const metadataGraphql = graphql.link(`${REACT_APP_SERVER_BASE_URL}/metadata`);
export const graphqlMocks = {
handlers: [
- graphql.query(getOperationName(GET_CURRENT_USER) ?? '', () => {
+ graphql.query(getOperationName(GET_CURRENT_USER_AND_VIEWS) ?? '', () => {
return HttpResponse.json({
data: {
currentUser: mockedUsersData[0],
+ views: {
+ edges: mockedViewsData.map((view) => ({
+ node: view,
+ cursor: null,
+ })),
+ pageInfo: {
+ hasNextPage: false,
+ hasPreviousPage: false,
+ startCursor: null,
+ endCursor: null,
+ },
+ },
},
});
}),
diff --git a/packages/twenty-server/src/core/auth/services/token.service.ts b/packages/twenty-server/src/core/auth/services/token.service.ts
index 438b5fd37..a95c8f9ef 100644
--- a/packages/twenty-server/src/core/auth/services/token.service.ts
+++ b/packages/twenty-server/src/core/auth/services/token.service.ts
@@ -172,6 +172,12 @@ export class TokenService {
return { token };
}
+ isTokenPresent(request: Request): boolean {
+ const token = ExtractJwt.fromAuthHeaderAsBearerToken()(request);
+
+ return !!token;
+ }
+
async validateToken(request: Request): Promise {
const token = ExtractJwt.fromAuthHeaderAsBearerToken()(request);
diff --git a/packages/twenty-server/src/graphql-config.service.ts b/packages/twenty-server/src/graphql-config.service.ts
index fa18d6532..aa543c8e4 100644
--- a/packages/twenty-server/src/graphql-config.service.ts
+++ b/packages/twenty-server/src/graphql-config.service.ts
@@ -1,4 +1,4 @@
-import { Injectable } from '@nestjs/common';
+import { Injectable, UnauthorizedException } from '@nestjs/common';
import { ContextIdFactory, ModuleRef } from '@nestjs/core';
import { GqlOptionsFactory } from '@nestjs/graphql';
@@ -56,17 +56,22 @@ export class GraphQLConfigService
},
conditionalSchema: async (context) => {
try {
- let workspace: Workspace;
-
- // If token is not valid, it will return an empty schema
- try {
- workspace = await this.tokenService.validateToken(context.req);
- } catch (err) {
+ if (!this.tokenService.isTokenPresent(context.req)) {
return new GraphQLSchema({});
}
+ const workspace = await this.tokenService.validateToken(context.req);
+
return await this.createSchema(context, workspace);
} catch (error) {
+ if (error instanceof UnauthorizedException) {
+ throw new GraphQLError('Unauthenticated', {
+ extensions: {
+ code: 'UNAUTHENTICATED',
+ },
+ });
+ }
+
if (error instanceof JsonWebTokenError) {
//mockedUserJWT
throw new GraphQLError('Unauthenticated', {