5622 add a syncemail onboarding step (#5689)

- add sync email onboarding step
- refactor calendar and email visibility enums
- add a new table `keyValuePair` in `core` schema
- add a new resolved boolean field `skipSyncEmail` in current user




https://github.com/twentyhq/twenty/assets/29927851/de791475-5bfe-47f9-8e90-76c349fba56f
This commit is contained in:
martmull
2024-06-05 18:16:53 +02:00
committed by GitHub
parent fda0d2a170
commit 9f6a6c3282
92 changed files with 2707 additions and 1246 deletions

View File

@@ -1,3 +1,6 @@
VITE_SERVER_BASE_URL=https://api.twenty.com VITE_SERVER_BASE_URL=https://api.twenty.com
VITE_FRONT_BASE_URL=https://app.twenty.com VITE_FRONT_BASE_URL=https://app.twenty.com
VITE_MODE=production VITE_MODE=production
# Used to generate packages/twenty-chrome-extension/src/generated/graphql.tsx
AUTH_TOKEN=<YOUR-TOKEN-HERE>

View File

@@ -1,17 +1,19 @@
import { CodegenConfig } from '@graphql-codegen/cli'; import { CodegenConfig } from '@graphql-codegen/cli';
const config: CodegenConfig = { const config: CodegenConfig = {
schema: [{ schema: [
[`${import.meta.env.VITE_SERVER_BASE_URL}/graphql`]: { {
// some of the mutations and queries require authorization (people or companies) [`${import.meta.env.VITE_SERVER_BASE_URL}/graphql`]: {
// so to regenrate the schema with types we need to pass a auth token // some of the mutations and queries require authorization (people or companies)
headers: { // so to regenerate the schema with types we need to pass an auth token
Authorization: 'YOUR-TOKEN-HERE', headers: {
Authorization: `Bearer ${import.meta.env.AUTH_TOKEN}`,
},
}, },
} },
}], ],
overwrite: true, overwrite: true,
documents: ['./src/**/*.ts', '!src/generated/**/*.*' ], documents: ['./src/**/*.ts', '!src/generated/**/*.*'],
generates: { generates: {
'./src/generated/graphql.tsx': { './src/generated/graphql.tsx': {
plugins: [ plugins: [

File diff suppressed because it is too large Load Diff

View File

@@ -4,6 +4,7 @@ interface ImportMetaEnv {
readonly VITE_SERVER_BASE_URL: string; readonly VITE_SERVER_BASE_URL: string;
readonly VITE_FRONT_BASE_URL: string; readonly VITE_FRONT_BASE_URL: string;
readonly VITE_MODE: string; readonly VITE_MODE: string;
readonly AUTH_TOKEN: string | undefined;
} }
interface ImportMeta { interface ImportMeta {

View File

@@ -38,17 +38,18 @@ import { CommandMenuEffect } from '~/effect-components/CommandMenuEffect';
import { GotoHotkeysEffect } from '~/effect-components/GotoHotkeysEffect'; import { GotoHotkeysEffect } from '~/effect-components/GotoHotkeysEffect';
import { PageChangeEffect } from '~/effect-components/PageChangeEffect'; import { PageChangeEffect } from '~/effect-components/PageChangeEffect';
import { Authorize } from '~/pages/auth/Authorize'; import { Authorize } from '~/pages/auth/Authorize';
import { ChooseYourPlan } from '~/pages/auth/ChooseYourPlan';
import { CreateProfile } from '~/pages/auth/CreateProfile';
import { CreateWorkspace } from '~/pages/auth/CreateWorkspace';
import { Invite } from '~/pages/auth/Invite'; import { Invite } from '~/pages/auth/Invite';
import { PasswordReset } from '~/pages/auth/PasswordReset'; import { PasswordReset } from '~/pages/auth/PasswordReset';
import { PaymentSuccess } from '~/pages/auth/PaymentSuccess';
import { SignInUp } from '~/pages/auth/SignInUp'; import { SignInUp } from '~/pages/auth/SignInUp';
import { ImpersonateEffect } from '~/pages/impersonate/ImpersonateEffect'; import { ImpersonateEffect } from '~/pages/impersonate/ImpersonateEffect';
import { NotFound } from '~/pages/not-found/NotFound'; import { NotFound } from '~/pages/not-found/NotFound';
import { RecordIndexPage } from '~/pages/object-record/RecordIndexPage'; import { RecordIndexPage } from '~/pages/object-record/RecordIndexPage';
import { RecordShowPage } from '~/pages/object-record/RecordShowPage'; import { RecordShowPage } from '~/pages/object-record/RecordShowPage';
import { ChooseYourPlan } from '~/pages/onboarding/ChooseYourPlan';
import { CreateProfile } from '~/pages/onboarding/CreateProfile';
import { CreateWorkspace } from '~/pages/onboarding/CreateWorkspace';
import { PaymentSuccess } from '~/pages/onboarding/PaymentSuccess';
import { SyncEmails } from '~/pages/onboarding/SyncEmails';
import { SettingsAccounts } from '~/pages/settings/accounts/SettingsAccounts'; import { SettingsAccounts } from '~/pages/settings/accounts/SettingsAccounts';
import { SettingsAccountsCalendars } from '~/pages/settings/accounts/SettingsAccountsCalendars'; import { SettingsAccountsCalendars } from '~/pages/settings/accounts/SettingsAccountsCalendars';
import { SettingsAccountsCalendarsSettings } from '~/pages/settings/accounts/SettingsAccountsCalendarsSettings'; import { SettingsAccountsCalendarsSettings } from '~/pages/settings/accounts/SettingsAccountsCalendarsSettings';
@@ -141,6 +142,7 @@ const createRouter = (isBillingEnabled?: boolean) =>
<Route path={AppPath.ResetPassword} element={<PasswordReset />} /> <Route path={AppPath.ResetPassword} element={<PasswordReset />} />
<Route path={AppPath.CreateWorkspace} element={<CreateWorkspace />} /> <Route path={AppPath.CreateWorkspace} element={<CreateWorkspace />} />
<Route path={AppPath.CreateProfile} element={<CreateProfile />} /> <Route path={AppPath.CreateProfile} element={<CreateProfile />} />
<Route path={AppPath.SyncEmails} element={<SyncEmails />} />
<Route path={AppPath.PlanRequired} element={<ChooseYourPlan />} /> <Route path={AppPath.PlanRequired} element={<ChooseYourPlan />} />
<Route <Route
path={AppPath.PlanRequiredSuccess} path={AppPath.PlanRequiredSuccess}

View File

@@ -123,6 +123,12 @@ export type BooleanFieldComparison = {
isNot?: InputMaybe<Scalars['Boolean']['input']>; isNot?: InputMaybe<Scalars['Boolean']['input']>;
}; };
/** Visibility of the calendar channel */
export enum CalendarChannelVisibility {
Metadata = 'METADATA',
ShareEverything = 'SHARE_EVERYTHING'
}
export type Captcha = { export type Captcha = {
__typename?: 'Captcha'; __typename?: 'Captcha';
provider?: Maybe<CaptchaDriverType>; provider?: Maybe<CaptchaDriverType>;
@@ -367,6 +373,13 @@ export type LoginToken = {
loginToken: AuthToken; loginToken: AuthToken;
}; };
/** Visibility of the message channel */
export enum MessageChannelVisibility {
Metadata = 'METADATA',
ShareEverything = 'SHARE_EVERYTHING',
Subject = 'SUBJECT'
}
export type Mutation = { export type Mutation = {
__typename?: 'Mutation'; __typename?: 'Mutation';
activateWorkspace: Workspace; activateWorkspace: Workspace;
@@ -394,6 +407,7 @@ export type Mutation = {
renewToken: AuthTokens; renewToken: AuthTokens;
sendInviteLink: SendInviteLink; sendInviteLink: SendInviteLink;
signUp: LoginToken; signUp: LoginToken;
skipSyncEmailOnboardingStep: SkipSyncEmailOnboardingStep;
syncRemoteTable: RemoteTable; syncRemoteTable: RemoteTable;
syncRemoteTableSchemaChanges: RemoteTable; syncRemoteTableSchemaChanges: RemoteTable;
track: Analytics; track: Analytics;
@@ -874,6 +888,12 @@ export type SessionEntity = {
url?: Maybe<Scalars['String']['output']>; url?: Maybe<Scalars['String']['output']>;
}; };
export type SkipSyncEmailOnboardingStep = {
__typename?: 'SkipSyncEmailOnboardingStep';
/** Boolean that confirms query was dispatched */
success: Scalars['Boolean']['output'];
};
/** Sort Directions */ /** Sort Directions */
export enum SortDirection { export enum SortDirection {
Asc = 'ASC', Asc = 'ASC',
@@ -911,7 +931,7 @@ export type TimelineCalendarEvent = {
participants: Array<TimelineCalendarEventParticipant>; participants: Array<TimelineCalendarEventParticipant>;
startsAt: Scalars['DateTime']['output']; startsAt: Scalars['DateTime']['output'];
title: Scalars['String']['output']; title: Scalars['String']['output'];
visibility: TimelineCalendarEventVisibility; visibility: CalendarChannelVisibility;
}; };
export type TimelineCalendarEventParticipant = { export type TimelineCalendarEventParticipant = {
@@ -925,12 +945,6 @@ export type TimelineCalendarEventParticipant = {
workspaceMemberId?: Maybe<Scalars['UUID']['output']>; workspaceMemberId?: Maybe<Scalars['UUID']['output']>;
}; };
/** Visibility of the calendar event */
export enum TimelineCalendarEventVisibility {
Metadata = 'METADATA',
ShareEverything = 'SHARE_EVERYTHING'
}
export type TimelineCalendarEventsWithTotal = { export type TimelineCalendarEventsWithTotal = {
__typename?: 'TimelineCalendarEventsWithTotal'; __typename?: 'TimelineCalendarEventsWithTotal';
timelineCalendarEvents: Array<TimelineCalendarEvent>; timelineCalendarEvents: Array<TimelineCalendarEvent>;
@@ -948,7 +962,7 @@ export type TimelineThread = {
participantCount: Scalars['Float']['output']; participantCount: Scalars['Float']['output'];
read: Scalars['Boolean']['output']; read: Scalars['Boolean']['output'];
subject: Scalars['String']['output']; subject: Scalars['String']['output'];
visibility: Scalars['String']['output']; visibility: MessageChannelVisibility;
}; };
export type TimelineThreadParticipant = { export type TimelineThreadParticipant = {
@@ -1070,6 +1084,7 @@ export type User = {
passwordResetToken?: Maybe<Scalars['String']['output']>; passwordResetToken?: Maybe<Scalars['String']['output']>;
/** @deprecated field migrated into the AppTokens Table ref: https://github.com/twentyhq/twenty/issues/5021 */ /** @deprecated field migrated into the AppTokens Table ref: https://github.com/twentyhq/twenty/issues/5021 */
passwordResetTokenExpiresAt?: Maybe<Scalars['DateTime']['output']>; passwordResetTokenExpiresAt?: Maybe<Scalars['DateTime']['output']>;
state: UserState;
supportUserHash?: Maybe<Scalars['String']['output']>; supportUserHash?: Maybe<Scalars['String']['output']>;
updatedAt: Scalars['DateTime']['output']; updatedAt: Scalars['DateTime']['output'];
workspaceMember?: Maybe<WorkspaceMember>; workspaceMember?: Maybe<WorkspaceMember>;
@@ -1104,6 +1119,11 @@ export type UserMappingOptionsUser = {
user?: Maybe<Scalars['String']['output']>; user?: Maybe<Scalars['String']['output']>;
}; };
export type UserState = {
__typename?: 'UserState';
skipSyncEmailOnboardingStep?: Maybe<Scalars['Boolean']['output']>;
};
export type UserWorkspace = { export type UserWorkspace = {
__typename?: 'UserWorkspace'; __typename?: 'UserWorkspace';
createdAt: Scalars['DateTime']['output']; createdAt: Scalars['DateTime']['output'];

View File

@@ -117,6 +117,12 @@ export type BooleanFieldComparison = {
isNot?: InputMaybe<Scalars['Boolean']>; isNot?: InputMaybe<Scalars['Boolean']>;
}; };
/** Visibility of the calendar channel */
export enum CalendarChannelVisibility {
Metadata = 'METADATA',
ShareEverything = 'SHARE_EVERYTHING'
}
export type Captcha = { export type Captcha = {
__typename?: 'Captcha'; __typename?: 'Captcha';
provider?: Maybe<CaptchaDriverType>; provider?: Maybe<CaptchaDriverType>;
@@ -266,6 +272,13 @@ export type LoginToken = {
loginToken: AuthToken; loginToken: AuthToken;
}; };
/** Visibility of the message channel */
export enum MessageChannelVisibility {
Metadata = 'METADATA',
ShareEverything = 'SHARE_EVERYTHING',
Subject = 'SUBJECT'
}
export type Mutation = { export type Mutation = {
__typename?: 'Mutation'; __typename?: 'Mutation';
activateWorkspace: Workspace; activateWorkspace: Workspace;
@@ -287,6 +300,7 @@ export type Mutation = {
renewToken: AuthTokens; renewToken: AuthTokens;
sendInviteLink: SendInviteLink; sendInviteLink: SendInviteLink;
signUp: LoginToken; signUp: LoginToken;
skipSyncEmailOnboardingStep: SkipSyncEmailOnboardingStep;
track: Analytics; track: Analytics;
updateBillingSubscription: UpdateBillingEntity; updateBillingSubscription: UpdateBillingEntity;
updateOneObject: Object; updateOneObject: Object;
@@ -629,6 +643,12 @@ export type SessionEntity = {
url?: Maybe<Scalars['String']>; url?: Maybe<Scalars['String']>;
}; };
export type SkipSyncEmailOnboardingStep = {
__typename?: 'SkipSyncEmailOnboardingStep';
/** Boolean that confirms query was dispatched */
success: Scalars['Boolean'];
};
/** Sort Directions */ /** Sort Directions */
export enum SortDirection { export enum SortDirection {
Asc = 'ASC', Asc = 'ASC',
@@ -666,7 +686,7 @@ export type TimelineCalendarEvent = {
participants: Array<TimelineCalendarEventParticipant>; participants: Array<TimelineCalendarEventParticipant>;
startsAt: Scalars['DateTime']; startsAt: Scalars['DateTime'];
title: Scalars['String']; title: Scalars['String'];
visibility: TimelineCalendarEventVisibility; visibility: CalendarChannelVisibility;
}; };
export type TimelineCalendarEventParticipant = { export type TimelineCalendarEventParticipant = {
@@ -680,12 +700,6 @@ export type TimelineCalendarEventParticipant = {
workspaceMemberId?: Maybe<Scalars['UUID']>; workspaceMemberId?: Maybe<Scalars['UUID']>;
}; };
/** Visibility of the calendar event */
export enum TimelineCalendarEventVisibility {
Metadata = 'METADATA',
ShareEverything = 'SHARE_EVERYTHING'
}
export type TimelineCalendarEventsWithTotal = { export type TimelineCalendarEventsWithTotal = {
__typename?: 'TimelineCalendarEventsWithTotal'; __typename?: 'TimelineCalendarEventsWithTotal';
timelineCalendarEvents: Array<TimelineCalendarEvent>; timelineCalendarEvents: Array<TimelineCalendarEvent>;
@@ -703,7 +717,7 @@ export type TimelineThread = {
participantCount: Scalars['Float']; participantCount: Scalars['Float'];
read: Scalars['Boolean']; read: Scalars['Boolean'];
subject: Scalars['String']; subject: Scalars['String'];
visibility: Scalars['String']; visibility: MessageChannelVisibility;
}; };
export type TimelineThreadParticipant = { export type TimelineThreadParticipant = {
@@ -796,6 +810,7 @@ export type User = {
passwordResetToken?: Maybe<Scalars['String']>; passwordResetToken?: Maybe<Scalars['String']>;
/** @deprecated field migrated into the AppTokens Table ref: https://github.com/twentyhq/twenty/issues/5021 */ /** @deprecated field migrated into the AppTokens Table ref: https://github.com/twentyhq/twenty/issues/5021 */
passwordResetTokenExpiresAt?: Maybe<Scalars['DateTime']>; passwordResetTokenExpiresAt?: Maybe<Scalars['DateTime']>;
state: UserState;
supportUserHash?: Maybe<Scalars['String']>; supportUserHash?: Maybe<Scalars['String']>;
updatedAt: Scalars['DateTime']; updatedAt: Scalars['DateTime'];
workspaceMember?: Maybe<WorkspaceMember>; workspaceMember?: Maybe<WorkspaceMember>;
@@ -820,6 +835,11 @@ export type UserMappingOptionsUser = {
user?: Maybe<Scalars['String']>; user?: Maybe<Scalars['String']>;
}; };
export type UserState = {
__typename?: 'UserState';
skipSyncEmailOnboardingStep?: Maybe<Scalars['Boolean']>;
};
export type UserWorkspace = { export type UserWorkspace = {
__typename?: 'UserWorkspace'; __typename?: 'UserWorkspace';
createdAt: Scalars['DateTime']; createdAt: Scalars['DateTime'];
@@ -993,11 +1013,11 @@ export type RelationEdge = {
node: Relation; node: Relation;
}; };
export type TimelineCalendarEventFragmentFragment = { __typename?: 'TimelineCalendarEvent', id: any, title: string, description: string, location: string, startsAt: string, endsAt: string, isFullDay: boolean, visibility: TimelineCalendarEventVisibility, participants: Array<{ __typename?: 'TimelineCalendarEventParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }> }; export type TimelineCalendarEventFragmentFragment = { __typename?: 'TimelineCalendarEvent', id: any, title: string, description: string, location: string, startsAt: string, endsAt: string, isFullDay: boolean, visibility: CalendarChannelVisibility, participants: Array<{ __typename?: 'TimelineCalendarEventParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }> };
export type TimelineCalendarEventParticipantFragmentFragment = { __typename?: 'TimelineCalendarEventParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }; export type TimelineCalendarEventParticipantFragmentFragment = { __typename?: 'TimelineCalendarEventParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string };
export type TimelineCalendarEventsWithTotalFragmentFragment = { __typename?: 'TimelineCalendarEventsWithTotal', totalNumberOfCalendarEvents: number, timelineCalendarEvents: Array<{ __typename?: 'TimelineCalendarEvent', id: any, title: string, description: string, location: string, startsAt: string, endsAt: string, isFullDay: boolean, visibility: TimelineCalendarEventVisibility, participants: Array<{ __typename?: 'TimelineCalendarEventParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }> }> }; export type TimelineCalendarEventsWithTotalFragmentFragment = { __typename?: 'TimelineCalendarEventsWithTotal', totalNumberOfCalendarEvents: number, timelineCalendarEvents: Array<{ __typename?: 'TimelineCalendarEvent', id: any, title: string, description: string, location: string, startsAt: string, endsAt: string, isFullDay: boolean, visibility: CalendarChannelVisibility, participants: Array<{ __typename?: 'TimelineCalendarEventParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }> }> };
export type GetTimelineCalendarEventsFromCompanyIdQueryVariables = Exact<{ export type GetTimelineCalendarEventsFromCompanyIdQueryVariables = Exact<{
companyId: Scalars['UUID']; companyId: Scalars['UUID'];
@@ -1006,7 +1026,7 @@ export type GetTimelineCalendarEventsFromCompanyIdQueryVariables = Exact<{
}>; }>;
export type GetTimelineCalendarEventsFromCompanyIdQuery = { __typename?: 'Query', getTimelineCalendarEventsFromCompanyId: { __typename?: 'TimelineCalendarEventsWithTotal', totalNumberOfCalendarEvents: number, timelineCalendarEvents: Array<{ __typename?: 'TimelineCalendarEvent', id: any, title: string, description: string, location: string, startsAt: string, endsAt: string, isFullDay: boolean, visibility: TimelineCalendarEventVisibility, participants: Array<{ __typename?: 'TimelineCalendarEventParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }> }> } }; export type GetTimelineCalendarEventsFromCompanyIdQuery = { __typename?: 'Query', getTimelineCalendarEventsFromCompanyId: { __typename?: 'TimelineCalendarEventsWithTotal', totalNumberOfCalendarEvents: number, timelineCalendarEvents: Array<{ __typename?: 'TimelineCalendarEvent', id: any, title: string, description: string, location: string, startsAt: string, endsAt: string, isFullDay: boolean, visibility: CalendarChannelVisibility, participants: Array<{ __typename?: 'TimelineCalendarEventParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }> }> } };
export type GetTimelineCalendarEventsFromPersonIdQueryVariables = Exact<{ export type GetTimelineCalendarEventsFromPersonIdQueryVariables = Exact<{
personId: Scalars['UUID']; personId: Scalars['UUID'];
@@ -1015,13 +1035,13 @@ export type GetTimelineCalendarEventsFromPersonIdQueryVariables = Exact<{
}>; }>;
export type GetTimelineCalendarEventsFromPersonIdQuery = { __typename?: 'Query', getTimelineCalendarEventsFromPersonId: { __typename?: 'TimelineCalendarEventsWithTotal', totalNumberOfCalendarEvents: number, timelineCalendarEvents: Array<{ __typename?: 'TimelineCalendarEvent', id: any, title: string, description: string, location: string, startsAt: string, endsAt: string, isFullDay: boolean, visibility: TimelineCalendarEventVisibility, participants: Array<{ __typename?: 'TimelineCalendarEventParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }> }> } }; export type GetTimelineCalendarEventsFromPersonIdQuery = { __typename?: 'Query', getTimelineCalendarEventsFromPersonId: { __typename?: 'TimelineCalendarEventsWithTotal', totalNumberOfCalendarEvents: number, timelineCalendarEvents: Array<{ __typename?: 'TimelineCalendarEvent', id: any, title: string, description: string, location: string, startsAt: string, endsAt: string, isFullDay: boolean, visibility: CalendarChannelVisibility, participants: Array<{ __typename?: 'TimelineCalendarEventParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }> }> } };
export type ParticipantFragmentFragment = { __typename?: 'TimelineThreadParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }; export type ParticipantFragmentFragment = { __typename?: 'TimelineThreadParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string };
export type TimelineThreadFragmentFragment = { __typename?: 'TimelineThread', id: any, read: boolean, visibility: string, lastMessageReceivedAt: string, lastMessageBody: string, subject: string, numberOfMessagesInThread: number, participantCount: number, firstParticipant: { __typename?: 'TimelineThreadParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }, lastTwoParticipants: Array<{ __typename?: 'TimelineThreadParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }> }; export type TimelineThreadFragmentFragment = { __typename?: 'TimelineThread', id: any, read: boolean, visibility: MessageChannelVisibility, lastMessageReceivedAt: string, lastMessageBody: string, subject: string, numberOfMessagesInThread: number, participantCount: number, firstParticipant: { __typename?: 'TimelineThreadParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }, lastTwoParticipants: Array<{ __typename?: 'TimelineThreadParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }> };
export type TimelineThreadsWithTotalFragmentFragment = { __typename?: 'TimelineThreadsWithTotal', totalNumberOfThreads: number, timelineThreads: Array<{ __typename?: 'TimelineThread', id: any, read: boolean, visibility: string, lastMessageReceivedAt: string, lastMessageBody: string, subject: string, numberOfMessagesInThread: number, participantCount: number, firstParticipant: { __typename?: 'TimelineThreadParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }, lastTwoParticipants: Array<{ __typename?: 'TimelineThreadParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }> }> }; export type TimelineThreadsWithTotalFragmentFragment = { __typename?: 'TimelineThreadsWithTotal', totalNumberOfThreads: number, timelineThreads: Array<{ __typename?: 'TimelineThread', id: any, read: boolean, visibility: MessageChannelVisibility, lastMessageReceivedAt: string, lastMessageBody: string, subject: string, numberOfMessagesInThread: number, participantCount: number, firstParticipant: { __typename?: 'TimelineThreadParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }, lastTwoParticipants: Array<{ __typename?: 'TimelineThreadParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }> }> };
export type GetTimelineThreadsFromCompanyIdQueryVariables = Exact<{ export type GetTimelineThreadsFromCompanyIdQueryVariables = Exact<{
companyId: Scalars['UUID']; companyId: Scalars['UUID'];
@@ -1030,7 +1050,7 @@ export type GetTimelineThreadsFromCompanyIdQueryVariables = Exact<{
}>; }>;
export type GetTimelineThreadsFromCompanyIdQuery = { __typename?: 'Query', getTimelineThreadsFromCompanyId: { __typename?: 'TimelineThreadsWithTotal', totalNumberOfThreads: number, timelineThreads: Array<{ __typename?: 'TimelineThread', id: any, read: boolean, visibility: string, lastMessageReceivedAt: string, lastMessageBody: string, subject: string, numberOfMessagesInThread: number, participantCount: number, firstParticipant: { __typename?: 'TimelineThreadParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }, lastTwoParticipants: Array<{ __typename?: 'TimelineThreadParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }> }> } }; export type GetTimelineThreadsFromCompanyIdQuery = { __typename?: 'Query', getTimelineThreadsFromCompanyId: { __typename?: 'TimelineThreadsWithTotal', totalNumberOfThreads: number, timelineThreads: Array<{ __typename?: 'TimelineThread', id: any, read: boolean, visibility: MessageChannelVisibility, lastMessageReceivedAt: string, lastMessageBody: string, subject: string, numberOfMessagesInThread: number, participantCount: number, firstParticipant: { __typename?: 'TimelineThreadParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }, lastTwoParticipants: Array<{ __typename?: 'TimelineThreadParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }> }> } };
export type GetTimelineThreadsFromPersonIdQueryVariables = Exact<{ export type GetTimelineThreadsFromPersonIdQueryVariables = Exact<{
personId: Scalars['UUID']; personId: Scalars['UUID'];
@@ -1039,7 +1059,7 @@ export type GetTimelineThreadsFromPersonIdQueryVariables = Exact<{
}>; }>;
export type GetTimelineThreadsFromPersonIdQuery = { __typename?: 'Query', getTimelineThreadsFromPersonId: { __typename?: 'TimelineThreadsWithTotal', totalNumberOfThreads: number, timelineThreads: Array<{ __typename?: 'TimelineThread', id: any, read: boolean, visibility: string, lastMessageReceivedAt: string, lastMessageBody: string, subject: string, numberOfMessagesInThread: number, participantCount: number, firstParticipant: { __typename?: 'TimelineThreadParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }, lastTwoParticipants: Array<{ __typename?: 'TimelineThreadParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }> }> } }; export type GetTimelineThreadsFromPersonIdQuery = { __typename?: 'Query', getTimelineThreadsFromPersonId: { __typename?: 'TimelineThreadsWithTotal', totalNumberOfThreads: number, timelineThreads: Array<{ __typename?: 'TimelineThread', id: any, read: boolean, visibility: MessageChannelVisibility, lastMessageReceivedAt: string, lastMessageBody: string, subject: string, numberOfMessagesInThread: number, participantCount: number, firstParticipant: { __typename?: 'TimelineThreadParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }, lastTwoParticipants: Array<{ __typename?: 'TimelineThreadParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }> }> } };
export type TimelineThreadFragment = { __typename?: 'TimelineThread', id: any, subject: string, lastMessageReceivedAt: string }; export type TimelineThreadFragment = { __typename?: 'TimelineThread', id: any, subject: string, lastMessageReceivedAt: string };
@@ -1121,7 +1141,7 @@ export type ImpersonateMutationVariables = Exact<{
}>; }>;
export type ImpersonateMutation = { __typename?: 'Mutation', impersonate: { __typename?: 'Verify', user: { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale: string, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, defaultWorkspace: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, domainName?: string | null, inviteHash?: string | null, allowImpersonation: boolean, subscriptionStatus: string, activationStatus: string, currentCacheVersion?: string | null, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: string, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: string, interval?: string | null } | null }, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, domainName?: string | null } | null }> }, tokens: { __typename?: 'AuthTokenPair', accessToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } } }; export type ImpersonateMutation = { __typename?: 'Mutation', impersonate: { __typename?: 'Verify', user: { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, state: { __typename?: 'UserState', skipSyncEmailOnboardingStep?: boolean | null }, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale: string, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, defaultWorkspace: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, domainName?: string | null, inviteHash?: string | null, allowImpersonation: boolean, subscriptionStatus: string, activationStatus: string, currentCacheVersion?: string | null, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: string, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: string, interval?: string | null } | null }, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, domainName?: string | null } | null }> }, tokens: { __typename?: 'AuthTokenPair', accessToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } } };
export type RenewTokenMutationVariables = Exact<{ export type RenewTokenMutationVariables = Exact<{
appToken: Scalars['String']; appToken: Scalars['String'];
@@ -1153,7 +1173,7 @@ export type VerifyMutationVariables = Exact<{
}>; }>;
export type VerifyMutation = { __typename?: 'Mutation', verify: { __typename?: 'Verify', user: { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale: string, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, defaultWorkspace: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, domainName?: string | null, inviteHash?: string | null, allowImpersonation: boolean, subscriptionStatus: string, activationStatus: string, currentCacheVersion?: string | null, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: string, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: string, interval?: string | null } | null }, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, domainName?: string | null } | null }> }, tokens: { __typename?: 'AuthTokenPair', accessToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } } }; export type VerifyMutation = { __typename?: 'Mutation', verify: { __typename?: 'Verify', user: { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, state: { __typename?: 'UserState', skipSyncEmailOnboardingStep?: boolean | null }, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale: string, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, defaultWorkspace: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, domainName?: string | null, inviteHash?: string | null, allowImpersonation: boolean, subscriptionStatus: string, activationStatus: string, currentCacheVersion?: string | null, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: string, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: string, interval?: string | null } | null }, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, domainName?: string | null } | null }> }, tokens: { __typename?: 'AuthTokenPair', accessToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } } };
export type CheckUserExistsQueryVariables = Exact<{ export type CheckUserExistsQueryVariables = Exact<{
email: Scalars['String']; email: Scalars['String'];
@@ -1202,7 +1222,12 @@ export type GetClientConfigQueryVariables = Exact<{ [key: string]: never; }>;
export type GetClientConfigQuery = { __typename?: 'Query', clientConfig: { __typename?: 'ClientConfig', signInPrefilled: boolean, signUpDisabled: boolean, debugMode: boolean, chromeExtensionId?: string | null, authProviders: { __typename?: 'AuthProviders', google: boolean, password: boolean, microsoft: boolean }, billing: { __typename?: 'Billing', isBillingEnabled: boolean, billingUrl?: string | null, billingFreeTrialDurationInDays?: number | null }, telemetry: { __typename?: 'Telemetry', enabled: boolean, anonymizationEnabled: boolean }, support: { __typename?: 'Support', supportDriver: string, supportFrontChatId?: string | null }, sentry: { __typename?: 'Sentry', dsn?: string | null, environment?: string | null, release?: string | null }, captcha: { __typename?: 'Captcha', provider?: CaptchaDriverType | null, siteKey?: string | null } } }; export type GetClientConfigQuery = { __typename?: 'Query', clientConfig: { __typename?: 'ClientConfig', signInPrefilled: boolean, signUpDisabled: boolean, debugMode: boolean, chromeExtensionId?: string | null, authProviders: { __typename?: 'AuthProviders', google: boolean, password: boolean, microsoft: boolean }, billing: { __typename?: 'Billing', isBillingEnabled: boolean, billingUrl?: string | null, billingFreeTrialDurationInDays?: number | null }, telemetry: { __typename?: 'Telemetry', enabled: boolean, anonymizationEnabled: boolean }, support: { __typename?: 'Support', supportDriver: string, supportFrontChatId?: string | null }, sentry: { __typename?: 'Sentry', dsn?: string | null, environment?: string | null, release?: string | null }, captcha: { __typename?: 'Captcha', provider?: CaptchaDriverType | null, siteKey?: string | null } } };
export type UserQueryFragmentFragment = { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale: string, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, defaultWorkspace: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, domainName?: string | null, inviteHash?: string | null, allowImpersonation: boolean, subscriptionStatus: string, activationStatus: string, currentCacheVersion?: string | null, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: string, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: string, interval?: string | null } | null }, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, domainName?: string | null } | null }> }; export type SkipSyncEmailOnboardingStepMutationVariables = Exact<{ [key: string]: never; }>;
export type SkipSyncEmailOnboardingStepMutation = { __typename?: 'Mutation', skipSyncEmailOnboardingStep: { __typename?: 'SkipSyncEmailOnboardingStep', success: boolean } };
export type UserQueryFragmentFragment = { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, state: { __typename?: 'UserState', skipSyncEmailOnboardingStep?: boolean | null }, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale: string, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, defaultWorkspace: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, domainName?: string | null, inviteHash?: string | null, allowImpersonation: boolean, subscriptionStatus: string, activationStatus: string, currentCacheVersion?: string | null, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: string, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: string, interval?: string | null } | null }, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, domainName?: string | null } | null }> };
export type DeleteUserAccountMutationVariables = Exact<{ [key: string]: never; }>; export type DeleteUserAccountMutationVariables = Exact<{ [key: string]: never; }>;
@@ -1219,7 +1244,7 @@ export type UploadProfilePictureMutation = { __typename?: 'Mutation', uploadProf
export type GetCurrentUserQueryVariables = Exact<{ [key: string]: never; }>; export type GetCurrentUserQueryVariables = Exact<{ [key: string]: never; }>;
export type GetCurrentUserQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale: string, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, defaultWorkspace: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, domainName?: string | null, inviteHash?: string | null, allowImpersonation: boolean, subscriptionStatus: string, activationStatus: string, currentCacheVersion?: string | null, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: string, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: string, interval?: string | null } | null }, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, domainName?: string | null } | null }> } }; export type GetCurrentUserQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, state: { __typename?: 'UserState', skipSyncEmailOnboardingStep?: boolean | null }, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale: string, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, defaultWorkspace: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, domainName?: string | null, inviteHash?: string | null, allowImpersonation: boolean, subscriptionStatus: string, activationStatus: string, currentCacheVersion?: string | null, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: string, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: string, interval?: string | null } | null }, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, domainName?: string | null } | null }> } };
export type AddUserToWorkspaceMutationVariables = Exact<{ export type AddUserToWorkspaceMutationVariables = Exact<{
inviteHash: Scalars['String']; inviteHash: Scalars['String'];
@@ -1370,6 +1395,9 @@ export const UserQueryFragmentFragmentDoc = gql`
email email
canImpersonate canImpersonate
supportUserHash supportUserHash
state {
skipSyncEmailOnboardingStep
}
workspaceMember { workspaceMember {
id id
name { name {
@@ -2359,6 +2387,38 @@ export function useGetClientConfigLazyQuery(baseOptions?: Apollo.LazyQueryHookOp
export type GetClientConfigQueryHookResult = ReturnType<typeof useGetClientConfigQuery>; export type GetClientConfigQueryHookResult = ReturnType<typeof useGetClientConfigQuery>;
export type GetClientConfigLazyQueryHookResult = ReturnType<typeof useGetClientConfigLazyQuery>; export type GetClientConfigLazyQueryHookResult = ReturnType<typeof useGetClientConfigLazyQuery>;
export type GetClientConfigQueryResult = Apollo.QueryResult<GetClientConfigQuery, GetClientConfigQueryVariables>; export type GetClientConfigQueryResult = Apollo.QueryResult<GetClientConfigQuery, GetClientConfigQueryVariables>;
export const SkipSyncEmailOnboardingStepDocument = gql`
mutation SkipSyncEmailOnboardingStep {
skipSyncEmailOnboardingStep {
success
}
}
`;
export type SkipSyncEmailOnboardingStepMutationFn = Apollo.MutationFunction<SkipSyncEmailOnboardingStepMutation, SkipSyncEmailOnboardingStepMutationVariables>;
/**
* __useSkipSyncEmailOnboardingStepMutation__
*
* To run a mutation, you first call `useSkipSyncEmailOnboardingStepMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useSkipSyncEmailOnboardingStepMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [skipSyncEmailOnboardingStepMutation, { data, loading, error }] = useSkipSyncEmailOnboardingStepMutation({
* variables: {
* },
* });
*/
export function useSkipSyncEmailOnboardingStepMutation(baseOptions?: Apollo.MutationHookOptions<SkipSyncEmailOnboardingStepMutation, SkipSyncEmailOnboardingStepMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<SkipSyncEmailOnboardingStepMutation, SkipSyncEmailOnboardingStepMutationVariables>(SkipSyncEmailOnboardingStepDocument, options);
}
export type SkipSyncEmailOnboardingStepMutationHookResult = ReturnType<typeof useSkipSyncEmailOnboardingStepMutation>;
export type SkipSyncEmailOnboardingStepMutationResult = Apollo.MutationResult<SkipSyncEmailOnboardingStepMutation>;
export type SkipSyncEmailOnboardingStepMutationOptions = Apollo.BaseMutationOptions<SkipSyncEmailOnboardingStepMutation, SkipSyncEmailOnboardingStepMutationVariables>;
export const DeleteUserAccountDocument = gql` export const DeleteUserAccountDocument = gql`
mutation DeleteUserAccount { mutation DeleteUserAccount {
deleteUser { deleteUser {
@@ -2425,55 +2485,10 @@ export type UploadProfilePictureMutationOptions = Apollo.BaseMutationOptions<Upl
export const GetCurrentUserDocument = gql` export const GetCurrentUserDocument = gql`
query GetCurrentUser { query GetCurrentUser {
currentUser { currentUser {
id ...UserQueryFragment
firstName
lastName
email
canImpersonate
supportUserHash
workspaceMember {
id
name {
firstName
lastName
}
colorScheme
avatarUrl
locale
}
defaultWorkspace {
id
displayName
logo
domainName
inviteHash
allowImpersonation
subscriptionStatus
activationStatus
featureFlags {
id
key
value
workspaceId
}
currentCacheVersion
currentBillingSubscription {
id
status
interval
}
}
workspaces {
workspace {
id
displayName
logo
domainName
}
}
} }
} }
`; ${UserQueryFragmentFragmentDoc}`;
/** /**
* __useGetCurrentUserQuery__ * __useGetCurrentUserQuery__

View File

@@ -35,6 +35,7 @@ const testCases = [
{ loc: AppPath.Verify, status: OnboardingStatus.OngoingUserCreation, res: undefined }, { loc: AppPath.Verify, status: OnboardingStatus.OngoingUserCreation, res: undefined },
{ loc: AppPath.Verify, status: OnboardingStatus.OngoingWorkspaceActivation, res: AppPath.CreateWorkspace }, { loc: AppPath.Verify, status: OnboardingStatus.OngoingWorkspaceActivation, res: AppPath.CreateWorkspace },
{ loc: AppPath.Verify, status: OnboardingStatus.OngoingProfileCreation, res: AppPath.CreateProfile }, { loc: AppPath.Verify, status: OnboardingStatus.OngoingProfileCreation, res: AppPath.CreateProfile },
{ loc: AppPath.Verify, status: OnboardingStatus.OngoingSyncEmail, res: AppPath.SyncEmails },
{ loc: AppPath.Verify, status: OnboardingStatus.Completed, res: defaultHomePagePath }, { loc: AppPath.Verify, status: OnboardingStatus.Completed, res: defaultHomePagePath },
{ loc: AppPath.Verify, status: OnboardingStatus.CompletedWithoutSubscription, res: defaultHomePagePath }, { loc: AppPath.Verify, status: OnboardingStatus.CompletedWithoutSubscription, res: defaultHomePagePath },
@@ -45,6 +46,7 @@ const testCases = [
{ loc: AppPath.SignInUp, status: OnboardingStatus.OngoingUserCreation, res: undefined }, { loc: AppPath.SignInUp, status: OnboardingStatus.OngoingUserCreation, res: undefined },
{ loc: AppPath.SignInUp, status: OnboardingStatus.OngoingWorkspaceActivation, res: AppPath.CreateWorkspace }, { loc: AppPath.SignInUp, status: OnboardingStatus.OngoingWorkspaceActivation, res: AppPath.CreateWorkspace },
{ loc: AppPath.SignInUp, status: OnboardingStatus.OngoingProfileCreation, res: AppPath.CreateProfile }, { loc: AppPath.SignInUp, status: OnboardingStatus.OngoingProfileCreation, res: AppPath.CreateProfile },
{ loc: AppPath.SignInUp, status: OnboardingStatus.OngoingSyncEmail, res: AppPath.SyncEmails },
{ loc: AppPath.SignInUp, status: OnboardingStatus.Completed, res: defaultHomePagePath }, { loc: AppPath.SignInUp, status: OnboardingStatus.Completed, res: defaultHomePagePath },
{ loc: AppPath.SignInUp, status: OnboardingStatus.CompletedWithoutSubscription, res: defaultHomePagePath }, { loc: AppPath.SignInUp, status: OnboardingStatus.CompletedWithoutSubscription, res: defaultHomePagePath },
@@ -55,6 +57,7 @@ const testCases = [
{ loc: AppPath.Invite, status: OnboardingStatus.OngoingUserCreation, res: undefined }, { loc: AppPath.Invite, status: OnboardingStatus.OngoingUserCreation, res: undefined },
{ loc: AppPath.Invite, status: OnboardingStatus.OngoingWorkspaceActivation, res: AppPath.CreateWorkspace }, { loc: AppPath.Invite, status: OnboardingStatus.OngoingWorkspaceActivation, res: AppPath.CreateWorkspace },
{ loc: AppPath.Invite, status: OnboardingStatus.OngoingProfileCreation, res: AppPath.CreateProfile }, { loc: AppPath.Invite, status: OnboardingStatus.OngoingProfileCreation, res: AppPath.CreateProfile },
{ loc: AppPath.Invite, status: OnboardingStatus.OngoingSyncEmail, res: AppPath.SyncEmails },
{ loc: AppPath.Invite, status: OnboardingStatus.Completed, res: undefined }, { loc: AppPath.Invite, status: OnboardingStatus.Completed, res: undefined },
{ loc: AppPath.Invite, status: OnboardingStatus.CompletedWithoutSubscription, res: undefined }, { loc: AppPath.Invite, status: OnboardingStatus.CompletedWithoutSubscription, res: undefined },
@@ -65,6 +68,7 @@ const testCases = [
{ loc: AppPath.ResetPassword, status: OnboardingStatus.OngoingUserCreation, res: undefined }, { loc: AppPath.ResetPassword, status: OnboardingStatus.OngoingUserCreation, res: undefined },
{ loc: AppPath.ResetPassword, status: OnboardingStatus.OngoingWorkspaceActivation, res: AppPath.CreateWorkspace }, { loc: AppPath.ResetPassword, status: OnboardingStatus.OngoingWorkspaceActivation, res: AppPath.CreateWorkspace },
{ loc: AppPath.ResetPassword, status: OnboardingStatus.OngoingProfileCreation, res: AppPath.CreateProfile }, { loc: AppPath.ResetPassword, status: OnboardingStatus.OngoingProfileCreation, res: AppPath.CreateProfile },
{ loc: AppPath.ResetPassword, status: OnboardingStatus.OngoingSyncEmail, res: AppPath.SyncEmails },
{ loc: AppPath.ResetPassword, status: OnboardingStatus.Completed, res: undefined }, { loc: AppPath.ResetPassword, status: OnboardingStatus.Completed, res: undefined },
{ loc: AppPath.ResetPassword, status: OnboardingStatus.CompletedWithoutSubscription, res: undefined }, { loc: AppPath.ResetPassword, status: OnboardingStatus.CompletedWithoutSubscription, res: undefined },
@@ -75,6 +79,7 @@ const testCases = [
{ loc: AppPath.CreateWorkspace, status: OnboardingStatus.OngoingUserCreation, res: AppPath.SignInUp }, { loc: AppPath.CreateWorkspace, status: OnboardingStatus.OngoingUserCreation, res: AppPath.SignInUp },
{ loc: AppPath.CreateWorkspace, status: OnboardingStatus.OngoingWorkspaceActivation, res: undefined }, { loc: AppPath.CreateWorkspace, status: OnboardingStatus.OngoingWorkspaceActivation, res: undefined },
{ loc: AppPath.CreateWorkspace, status: OnboardingStatus.OngoingProfileCreation, res: AppPath.CreateProfile }, { loc: AppPath.CreateWorkspace, status: OnboardingStatus.OngoingProfileCreation, res: AppPath.CreateProfile },
{ loc: AppPath.CreateWorkspace, status: OnboardingStatus.OngoingSyncEmail, res: AppPath.SyncEmails },
{ loc: AppPath.CreateWorkspace, status: OnboardingStatus.Completed, res: defaultHomePagePath }, { loc: AppPath.CreateWorkspace, status: OnboardingStatus.Completed, res: defaultHomePagePath },
{ loc: AppPath.CreateWorkspace, status: OnboardingStatus.CompletedWithoutSubscription, res: defaultHomePagePath }, { loc: AppPath.CreateWorkspace, status: OnboardingStatus.CompletedWithoutSubscription, res: defaultHomePagePath },
@@ -85,9 +90,21 @@ const testCases = [
{ loc: AppPath.CreateProfile, status: OnboardingStatus.OngoingUserCreation, res: AppPath.SignInUp }, { loc: AppPath.CreateProfile, status: OnboardingStatus.OngoingUserCreation, res: AppPath.SignInUp },
{ loc: AppPath.CreateProfile, status: OnboardingStatus.OngoingWorkspaceActivation, res: AppPath.CreateWorkspace }, { loc: AppPath.CreateProfile, status: OnboardingStatus.OngoingWorkspaceActivation, res: AppPath.CreateWorkspace },
{ loc: AppPath.CreateProfile, status: OnboardingStatus.OngoingProfileCreation, res: undefined }, { loc: AppPath.CreateProfile, status: OnboardingStatus.OngoingProfileCreation, res: undefined },
{ loc: AppPath.CreateProfile, status: OnboardingStatus.OngoingSyncEmail, res: AppPath.SyncEmails },
{ loc: AppPath.CreateProfile, status: OnboardingStatus.Completed, res: defaultHomePagePath }, { loc: AppPath.CreateProfile, status: OnboardingStatus.Completed, res: defaultHomePagePath },
{ loc: AppPath.CreateProfile, status: OnboardingStatus.CompletedWithoutSubscription, res: defaultHomePagePath }, { loc: AppPath.CreateProfile, status: OnboardingStatus.CompletedWithoutSubscription, res: defaultHomePagePath },
{ loc: AppPath.SyncEmails, status: OnboardingStatus.Incomplete, res: AppPath.PlanRequired },
{ loc: AppPath.SyncEmails, status: OnboardingStatus.Canceled, res: '/settings/billing' },
{ loc: AppPath.SyncEmails, status: OnboardingStatus.Unpaid, res: '/settings/billing' },
{ loc: AppPath.SyncEmails, status: OnboardingStatus.PastDue, res: undefined },
{ loc: AppPath.SyncEmails, status: OnboardingStatus.OngoingUserCreation, res: AppPath.SignInUp },
{ loc: AppPath.SyncEmails, status: OnboardingStatus.OngoingWorkspaceActivation, res: AppPath.CreateWorkspace },
{ loc: AppPath.SyncEmails, status: OnboardingStatus.OngoingProfileCreation, res: AppPath.CreateProfile },
{ loc: AppPath.SyncEmails, status: OnboardingStatus.OngoingSyncEmail, res: undefined },
{ loc: AppPath.SyncEmails, status: OnboardingStatus.Completed, res: defaultHomePagePath },
{ loc: AppPath.SyncEmails, status: OnboardingStatus.CompletedWithoutSubscription, res: defaultHomePagePath },
{ loc: AppPath.PlanRequired, status: OnboardingStatus.Incomplete, res: undefined }, { loc: AppPath.PlanRequired, status: OnboardingStatus.Incomplete, res: undefined },
{ loc: AppPath.PlanRequired, status: OnboardingStatus.Canceled, res: undefined }, { loc: AppPath.PlanRequired, status: OnboardingStatus.Canceled, res: undefined },
{ loc: AppPath.PlanRequired, status: OnboardingStatus.Unpaid, res: undefined }, { loc: AppPath.PlanRequired, status: OnboardingStatus.Unpaid, res: undefined },
@@ -95,6 +112,7 @@ const testCases = [
{ loc: AppPath.PlanRequired, status: OnboardingStatus.OngoingUserCreation, res: AppPath.SignInUp }, { loc: AppPath.PlanRequired, status: OnboardingStatus.OngoingUserCreation, res: AppPath.SignInUp },
{ loc: AppPath.PlanRequired, status: OnboardingStatus.OngoingWorkspaceActivation, res: AppPath.CreateWorkspace }, { loc: AppPath.PlanRequired, status: OnboardingStatus.OngoingWorkspaceActivation, res: AppPath.CreateWorkspace },
{ loc: AppPath.PlanRequired, status: OnboardingStatus.OngoingProfileCreation, res: AppPath.CreateProfile }, { loc: AppPath.PlanRequired, status: OnboardingStatus.OngoingProfileCreation, res: AppPath.CreateProfile },
{ loc: AppPath.PlanRequired, status: OnboardingStatus.OngoingSyncEmail, res: AppPath.SyncEmails },
{ loc: AppPath.PlanRequired, status: OnboardingStatus.Completed, res: defaultHomePagePath }, { loc: AppPath.PlanRequired, status: OnboardingStatus.Completed, res: defaultHomePagePath },
{ loc: AppPath.PlanRequired, status: OnboardingStatus.CompletedWithoutSubscription, res: undefined }, { loc: AppPath.PlanRequired, status: OnboardingStatus.CompletedWithoutSubscription, res: undefined },
@@ -105,6 +123,7 @@ const testCases = [
{ loc: AppPath.PlanRequiredSuccess, status: OnboardingStatus.OngoingUserCreation, res: AppPath.SignInUp }, { loc: AppPath.PlanRequiredSuccess, status: OnboardingStatus.OngoingUserCreation, res: AppPath.SignInUp },
{ loc: AppPath.PlanRequiredSuccess, status: OnboardingStatus.OngoingWorkspaceActivation, res: undefined }, { loc: AppPath.PlanRequiredSuccess, status: OnboardingStatus.OngoingWorkspaceActivation, res: undefined },
{ loc: AppPath.PlanRequiredSuccess, status: OnboardingStatus.OngoingProfileCreation, res: AppPath.CreateProfile }, { loc: AppPath.PlanRequiredSuccess, status: OnboardingStatus.OngoingProfileCreation, res: AppPath.CreateProfile },
{ loc: AppPath.PlanRequiredSuccess, status: OnboardingStatus.OngoingSyncEmail, res: AppPath.SyncEmails },
{ loc: AppPath.PlanRequiredSuccess, status: OnboardingStatus.Completed, res: defaultHomePagePath }, { loc: AppPath.PlanRequiredSuccess, status: OnboardingStatus.Completed, res: defaultHomePagePath },
{ loc: AppPath.PlanRequiredSuccess, status: OnboardingStatus.CompletedWithoutSubscription, res: defaultHomePagePath }, { loc: AppPath.PlanRequiredSuccess, status: OnboardingStatus.CompletedWithoutSubscription, res: defaultHomePagePath },
@@ -115,6 +134,7 @@ const testCases = [
{ loc: AppPath.Index, status: OnboardingStatus.OngoingUserCreation, res: AppPath.SignInUp }, { loc: AppPath.Index, status: OnboardingStatus.OngoingUserCreation, res: AppPath.SignInUp },
{ loc: AppPath.Index, status: OnboardingStatus.OngoingWorkspaceActivation, res: AppPath.CreateWorkspace }, { loc: AppPath.Index, status: OnboardingStatus.OngoingWorkspaceActivation, res: AppPath.CreateWorkspace },
{ loc: AppPath.Index, status: OnboardingStatus.OngoingProfileCreation, res: AppPath.CreateProfile }, { loc: AppPath.Index, status: OnboardingStatus.OngoingProfileCreation, res: AppPath.CreateProfile },
{ loc: AppPath.Index, status: OnboardingStatus.OngoingSyncEmail, res: AppPath.SyncEmails },
{ loc: AppPath.Index, status: OnboardingStatus.Completed, res: defaultHomePagePath }, { loc: AppPath.Index, status: OnboardingStatus.Completed, res: defaultHomePagePath },
{ loc: AppPath.Index, status: OnboardingStatus.CompletedWithoutSubscription, res: defaultHomePagePath }, { loc: AppPath.Index, status: OnboardingStatus.CompletedWithoutSubscription, res: defaultHomePagePath },
@@ -125,6 +145,7 @@ const testCases = [
{ loc: AppPath.TasksPage, status: OnboardingStatus.OngoingUserCreation, res: AppPath.SignInUp }, { loc: AppPath.TasksPage, status: OnboardingStatus.OngoingUserCreation, res: AppPath.SignInUp },
{ loc: AppPath.TasksPage, status: OnboardingStatus.OngoingWorkspaceActivation, res: AppPath.CreateWorkspace }, { loc: AppPath.TasksPage, status: OnboardingStatus.OngoingWorkspaceActivation, res: AppPath.CreateWorkspace },
{ loc: AppPath.TasksPage, status: OnboardingStatus.OngoingProfileCreation, res: AppPath.CreateProfile }, { loc: AppPath.TasksPage, status: OnboardingStatus.OngoingProfileCreation, res: AppPath.CreateProfile },
{ loc: AppPath.TasksPage, status: OnboardingStatus.OngoingSyncEmail, res: AppPath.SyncEmails },
{ loc: AppPath.TasksPage, status: OnboardingStatus.Completed, res: undefined }, { loc: AppPath.TasksPage, status: OnboardingStatus.Completed, res: undefined },
{ loc: AppPath.TasksPage, status: OnboardingStatus.CompletedWithoutSubscription, res: undefined }, { loc: AppPath.TasksPage, status: OnboardingStatus.CompletedWithoutSubscription, res: undefined },
@@ -135,6 +156,7 @@ const testCases = [
{ loc: AppPath.OpportunitiesPage, status: OnboardingStatus.OngoingUserCreation, res: AppPath.SignInUp }, { loc: AppPath.OpportunitiesPage, status: OnboardingStatus.OngoingUserCreation, res: AppPath.SignInUp },
{ loc: AppPath.OpportunitiesPage, status: OnboardingStatus.OngoingWorkspaceActivation, res: AppPath.CreateWorkspace }, { loc: AppPath.OpportunitiesPage, status: OnboardingStatus.OngoingWorkspaceActivation, res: AppPath.CreateWorkspace },
{ loc: AppPath.OpportunitiesPage, status: OnboardingStatus.OngoingProfileCreation, res: AppPath.CreateProfile }, { loc: AppPath.OpportunitiesPage, status: OnboardingStatus.OngoingProfileCreation, res: AppPath.CreateProfile },
{ loc: AppPath.OpportunitiesPage, status: OnboardingStatus.OngoingSyncEmail, res: AppPath.SyncEmails },
{ loc: AppPath.OpportunitiesPage, status: OnboardingStatus.Completed, res: undefined }, { loc: AppPath.OpportunitiesPage, status: OnboardingStatus.Completed, res: undefined },
{ loc: AppPath.OpportunitiesPage, status: OnboardingStatus.CompletedWithoutSubscription, res: undefined }, { loc: AppPath.OpportunitiesPage, status: OnboardingStatus.CompletedWithoutSubscription, res: undefined },
@@ -145,6 +167,7 @@ const testCases = [
{ loc: AppPath.RecordIndexPage, status: OnboardingStatus.OngoingUserCreation, res: AppPath.SignInUp }, { loc: AppPath.RecordIndexPage, status: OnboardingStatus.OngoingUserCreation, res: AppPath.SignInUp },
{ loc: AppPath.RecordIndexPage, status: OnboardingStatus.OngoingWorkspaceActivation, res: AppPath.CreateWorkspace }, { loc: AppPath.RecordIndexPage, status: OnboardingStatus.OngoingWorkspaceActivation, res: AppPath.CreateWorkspace },
{ loc: AppPath.RecordIndexPage, status: OnboardingStatus.OngoingProfileCreation, res: AppPath.CreateProfile }, { loc: AppPath.RecordIndexPage, status: OnboardingStatus.OngoingProfileCreation, res: AppPath.CreateProfile },
{ loc: AppPath.RecordIndexPage, status: OnboardingStatus.OngoingSyncEmail, res: AppPath.SyncEmails },
{ loc: AppPath.RecordIndexPage, status: OnboardingStatus.Completed, res: undefined }, { loc: AppPath.RecordIndexPage, status: OnboardingStatus.Completed, res: undefined },
{ loc: AppPath.RecordIndexPage, status: OnboardingStatus.CompletedWithoutSubscription, res: undefined }, { loc: AppPath.RecordIndexPage, status: OnboardingStatus.CompletedWithoutSubscription, res: undefined },
@@ -155,6 +178,7 @@ const testCases = [
{ loc: AppPath.RecordShowPage, status: OnboardingStatus.OngoingUserCreation, res: AppPath.SignInUp }, { loc: AppPath.RecordShowPage, status: OnboardingStatus.OngoingUserCreation, res: AppPath.SignInUp },
{ loc: AppPath.RecordShowPage, status: OnboardingStatus.OngoingWorkspaceActivation, res: AppPath.CreateWorkspace }, { loc: AppPath.RecordShowPage, status: OnboardingStatus.OngoingWorkspaceActivation, res: AppPath.CreateWorkspace },
{ loc: AppPath.RecordShowPage, status: OnboardingStatus.OngoingProfileCreation, res: AppPath.CreateProfile }, { loc: AppPath.RecordShowPage, status: OnboardingStatus.OngoingProfileCreation, res: AppPath.CreateProfile },
{ loc: AppPath.RecordShowPage, status: OnboardingStatus.OngoingSyncEmail, res: AppPath.SyncEmails },
{ loc: AppPath.RecordShowPage, status: OnboardingStatus.Completed, res: undefined }, { loc: AppPath.RecordShowPage, status: OnboardingStatus.Completed, res: undefined },
{ loc: AppPath.RecordShowPage, status: OnboardingStatus.CompletedWithoutSubscription, res: undefined }, { loc: AppPath.RecordShowPage, status: OnboardingStatus.CompletedWithoutSubscription, res: undefined },
@@ -165,6 +189,7 @@ const testCases = [
{ loc: AppPath.SettingsCatchAll, status: OnboardingStatus.OngoingUserCreation, res: AppPath.SignInUp }, { loc: AppPath.SettingsCatchAll, status: OnboardingStatus.OngoingUserCreation, res: AppPath.SignInUp },
{ loc: AppPath.SettingsCatchAll, status: OnboardingStatus.OngoingWorkspaceActivation, res: AppPath.CreateWorkspace }, { loc: AppPath.SettingsCatchAll, status: OnboardingStatus.OngoingWorkspaceActivation, res: AppPath.CreateWorkspace },
{ loc: AppPath.SettingsCatchAll, status: OnboardingStatus.OngoingProfileCreation, res: AppPath.CreateProfile }, { loc: AppPath.SettingsCatchAll, status: OnboardingStatus.OngoingProfileCreation, res: AppPath.CreateProfile },
{ loc: AppPath.SettingsCatchAll, status: OnboardingStatus.OngoingSyncEmail, res: AppPath.SyncEmails },
{ loc: AppPath.SettingsCatchAll, status: OnboardingStatus.Completed, res: undefined }, { loc: AppPath.SettingsCatchAll, status: OnboardingStatus.Completed, res: undefined },
{ loc: AppPath.SettingsCatchAll, status: OnboardingStatus.CompletedWithoutSubscription, res: undefined }, { loc: AppPath.SettingsCatchAll, status: OnboardingStatus.CompletedWithoutSubscription, res: undefined },
@@ -175,6 +200,7 @@ const testCases = [
{ loc: AppPath.DevelopersCatchAll, status: OnboardingStatus.OngoingUserCreation, res: AppPath.SignInUp }, { loc: AppPath.DevelopersCatchAll, status: OnboardingStatus.OngoingUserCreation, res: AppPath.SignInUp },
{ loc: AppPath.DevelopersCatchAll, status: OnboardingStatus.OngoingWorkspaceActivation, res: AppPath.CreateWorkspace }, { loc: AppPath.DevelopersCatchAll, status: OnboardingStatus.OngoingWorkspaceActivation, res: AppPath.CreateWorkspace },
{ loc: AppPath.DevelopersCatchAll, status: OnboardingStatus.OngoingProfileCreation, res: AppPath.CreateProfile }, { loc: AppPath.DevelopersCatchAll, status: OnboardingStatus.OngoingProfileCreation, res: AppPath.CreateProfile },
{ loc: AppPath.DevelopersCatchAll, status: OnboardingStatus.OngoingSyncEmail, res: AppPath.SyncEmails },
{ loc: AppPath.DevelopersCatchAll, status: OnboardingStatus.Completed, res: undefined }, { loc: AppPath.DevelopersCatchAll, status: OnboardingStatus.Completed, res: undefined },
{ loc: AppPath.DevelopersCatchAll, status: OnboardingStatus.CompletedWithoutSubscription, res: undefined }, { loc: AppPath.DevelopersCatchAll, status: OnboardingStatus.CompletedWithoutSubscription, res: undefined },
@@ -185,6 +211,7 @@ const testCases = [
{ loc: AppPath.Impersonate, status: OnboardingStatus.OngoingUserCreation, res: AppPath.SignInUp }, { loc: AppPath.Impersonate, status: OnboardingStatus.OngoingUserCreation, res: AppPath.SignInUp },
{ loc: AppPath.Impersonate, status: OnboardingStatus.OngoingWorkspaceActivation, res: AppPath.CreateWorkspace }, { loc: AppPath.Impersonate, status: OnboardingStatus.OngoingWorkspaceActivation, res: AppPath.CreateWorkspace },
{ loc: AppPath.Impersonate, status: OnboardingStatus.OngoingProfileCreation, res: AppPath.CreateProfile }, { loc: AppPath.Impersonate, status: OnboardingStatus.OngoingProfileCreation, res: AppPath.CreateProfile },
{ loc: AppPath.Impersonate, status: OnboardingStatus.OngoingSyncEmail, res: AppPath.SyncEmails },
{ loc: AppPath.Impersonate, status: OnboardingStatus.Completed, res: undefined }, { loc: AppPath.Impersonate, status: OnboardingStatus.Completed, res: undefined },
{ loc: AppPath.Impersonate, status: OnboardingStatus.CompletedWithoutSubscription, res: undefined }, { loc: AppPath.Impersonate, status: OnboardingStatus.CompletedWithoutSubscription, res: undefined },
@@ -195,6 +222,7 @@ const testCases = [
{ loc: AppPath.Authorize, status: OnboardingStatus.OngoingUserCreation, res: AppPath.SignInUp }, { loc: AppPath.Authorize, status: OnboardingStatus.OngoingUserCreation, res: AppPath.SignInUp },
{ loc: AppPath.Authorize, status: OnboardingStatus.OngoingWorkspaceActivation, res: AppPath.CreateWorkspace }, { loc: AppPath.Authorize, status: OnboardingStatus.OngoingWorkspaceActivation, res: AppPath.CreateWorkspace },
{ loc: AppPath.Authorize, status: OnboardingStatus.OngoingProfileCreation, res: AppPath.CreateProfile }, { loc: AppPath.Authorize, status: OnboardingStatus.OngoingProfileCreation, res: AppPath.CreateProfile },
{ loc: AppPath.Authorize, status: OnboardingStatus.OngoingSyncEmail, res: AppPath.SyncEmails },
{ loc: AppPath.Authorize, status: OnboardingStatus.Completed, res: undefined }, { loc: AppPath.Authorize, status: OnboardingStatus.Completed, res: undefined },
{ loc: AppPath.Authorize, status: OnboardingStatus.CompletedWithoutSubscription, res: undefined }, { loc: AppPath.Authorize, status: OnboardingStatus.CompletedWithoutSubscription, res: undefined },
@@ -205,6 +233,7 @@ const testCases = [
{ loc: AppPath.NotFoundWildcard, status: OnboardingStatus.OngoingUserCreation, res: AppPath.SignInUp }, { loc: AppPath.NotFoundWildcard, status: OnboardingStatus.OngoingUserCreation, res: AppPath.SignInUp },
{ loc: AppPath.NotFoundWildcard, status: OnboardingStatus.OngoingWorkspaceActivation, res: AppPath.CreateWorkspace }, { loc: AppPath.NotFoundWildcard, status: OnboardingStatus.OngoingWorkspaceActivation, res: AppPath.CreateWorkspace },
{ loc: AppPath.NotFoundWildcard, status: OnboardingStatus.OngoingProfileCreation, res: AppPath.CreateProfile }, { loc: AppPath.NotFoundWildcard, status: OnboardingStatus.OngoingProfileCreation, res: AppPath.CreateProfile },
{ loc: AppPath.NotFoundWildcard, status: OnboardingStatus.OngoingSyncEmail, res: AppPath.SyncEmails },
{ loc: AppPath.NotFoundWildcard, status: OnboardingStatus.Completed, res: undefined }, { loc: AppPath.NotFoundWildcard, status: OnboardingStatus.Completed, res: undefined },
{ loc: AppPath.NotFoundWildcard, status: OnboardingStatus.CompletedWithoutSubscription, res: undefined }, { loc: AppPath.NotFoundWildcard, status: OnboardingStatus.CompletedWithoutSubscription, res: undefined },
@@ -215,6 +244,7 @@ const testCases = [
{ loc: AppPath.NotFound, status: OnboardingStatus.OngoingUserCreation, res: AppPath.SignInUp }, { loc: AppPath.NotFound, status: OnboardingStatus.OngoingUserCreation, res: AppPath.SignInUp },
{ loc: AppPath.NotFound, status: OnboardingStatus.OngoingWorkspaceActivation, res: AppPath.CreateWorkspace }, { loc: AppPath.NotFound, status: OnboardingStatus.OngoingWorkspaceActivation, res: AppPath.CreateWorkspace },
{ loc: AppPath.NotFound, status: OnboardingStatus.OngoingProfileCreation, res: AppPath.CreateProfile }, { loc: AppPath.NotFound, status: OnboardingStatus.OngoingProfileCreation, res: AppPath.CreateProfile },
{ loc: AppPath.NotFound, status: OnboardingStatus.OngoingSyncEmail, res: AppPath.SyncEmails },
{ loc: AppPath.NotFound, status: OnboardingStatus.Completed, res: undefined }, { loc: AppPath.NotFound, status: OnboardingStatus.Completed, res: undefined },
{ loc: AppPath.NotFound, status: OnboardingStatus.CompletedWithoutSubscription, res: undefined }, { loc: AppPath.NotFound, status: OnboardingStatus.CompletedWithoutSubscription, res: undefined },
]; ];

View File

@@ -24,6 +24,7 @@ export const usePageChangeEffectNavigateLocation = () => {
isMatchingOngoingUserCreationRoute || isMatchingOngoingUserCreationRoute ||
isMatchingLocation(AppPath.CreateWorkspace) || isMatchingLocation(AppPath.CreateWorkspace) ||
isMatchingLocation(AppPath.CreateProfile) || isMatchingLocation(AppPath.CreateProfile) ||
isMatchingLocation(AppPath.SyncEmails) ||
isMatchingLocation(AppPath.PlanRequired) || isMatchingLocation(AppPath.PlanRequired) ||
isMatchingLocation(AppPath.PlanRequiredSuccess); isMatchingLocation(AppPath.PlanRequiredSuccess);
@@ -71,6 +72,13 @@ export const usePageChangeEffectNavigateLocation = () => {
return AppPath.CreateProfile; return AppPath.CreateProfile;
} }
if (
onboardingStatus === OnboardingStatus.OngoingSyncEmail &&
!isMatchingLocation(AppPath.SyncEmails)
) {
return AppPath.SyncEmails;
}
if ( if (
onboardingStatus === OnboardingStatus.Completed && onboardingStatus === OnboardingStatus.Completed &&
isMatchingOnboardingRoute && isMatchingOnboardingRoute &&

View File

@@ -1,7 +1,4 @@
export enum CalendarChannelVisibility { import { CalendarChannelVisibility } from '~/generated/graphql';
Everything = 'SHARE_EVERYTHING',
Metadata = 'METADATA',
}
export type CalendarChannel = { export type CalendarChannel = {
id: string; id: string;

View File

@@ -1,11 +1,11 @@
import { InboxSettingsVisibilityValue } from '@/settings/accounts/components/SettingsAccountsInboxVisibilitySettingsCard'; import { MessageChannelVisibility } from '~/generated/graphql';
export type MessageChannel = { export type MessageChannel = {
id: string; id: string;
handle: string; handle: string;
isContactAutoCreationEnabled?: boolean; isContactAutoCreationEnabled?: boolean;
isSyncEnabled: boolean; isSyncEnabled: boolean;
visibility: InboxSettingsVisibilityValue; visibility: MessageChannelVisibility;
syncStatus: string; syncStatus: string;
__typename: 'MessageChannel'; __typename: 'MessageChannel';
}; };

View File

@@ -14,6 +14,7 @@ import { hasCalendarEventEnded } from '@/activities/calendar/utils/hasCalendarEv
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { Card } from '@/ui/layout/card/components/Card'; import { Card } from '@/ui/layout/card/components/Card';
import { CardContent } from '@/ui/layout/card/components/CardContent'; import { CardContent } from '@/ui/layout/card/components/CardContent';
import { CalendarChannelVisibility } from '~/generated/graphql';
import { TimelineCalendarEvent } from '~/generated-metadata/graphql'; import { TimelineCalendarEvent } from '~/generated-metadata/graphql';
import { getImageAbsoluteURIOrBase64 } from '~/utils/image/getImageAbsoluteURIOrBase64'; import { getImageAbsoluteURIOrBase64 } from '~/utils/image/getImageAbsoluteURIOrBase64';
import { isDefined } from '~/utils/isDefined'; import { isDefined } from '~/utils/isDefined';
@@ -118,7 +119,8 @@ export const CalendarEventRow = ({
const isCurrentWorkspaceMemberAttending = calendarEvent.participants?.some( const isCurrentWorkspaceMemberAttending = calendarEvent.participants?.some(
({ workspaceMemberId }) => workspaceMemberId === currentWorkspaceMember?.id, ({ workspaceMemberId }) => workspaceMemberId === currentWorkspaceMember?.id,
); );
const showTitle = calendarEvent.visibility === 'SHARE_EVERYTHING'; const showTitle =
calendarEvent.visibility === CalendarChannelVisibility.ShareEverything;
return ( return (
<StyledContainer <StyledContainer

View File

@@ -2,6 +2,7 @@ import { act, renderHook } from '@testing-library/react';
import { useCalendarEvents } from '@/activities/calendar/hooks/useCalendarEvents'; import { useCalendarEvents } from '@/activities/calendar/hooks/useCalendarEvents';
import { CalendarEvent } from '@/activities/calendar/types/CalendarEvent'; import { CalendarEvent } from '@/activities/calendar/types/CalendarEvent';
import { CalendarChannelVisibility } from '~/generated/graphql';
const calendarEvents: CalendarEvent[] = [ const calendarEvents: CalendarEvent[] = [
{ {
@@ -9,7 +10,7 @@ const calendarEvents: CalendarEvent[] = [
externalCreatedAt: '2024-02-17T20:45:43.854Z', externalCreatedAt: '2024-02-17T20:45:43.854Z',
isFullDay: false, isFullDay: false,
startsAt: '2024-02-17T21:45:27.822Z', startsAt: '2024-02-17T21:45:27.822Z',
visibility: 'METADATA', visibility: CalendarChannelVisibility.Metadata,
__typename: 'CalendarEvent', __typename: 'CalendarEvent',
}, },
{ {
@@ -17,7 +18,7 @@ const calendarEvents: CalendarEvent[] = [
externalCreatedAt: '2024-02-18T19:43:37.854Z', externalCreatedAt: '2024-02-18T19:43:37.854Z',
isFullDay: false, isFullDay: false,
startsAt: '2024-02-18T21:43:27.754Z', startsAt: '2024-02-18T21:43:27.754Z',
visibility: 'SHARE_EVERYTHING', visibility: CalendarChannelVisibility.ShareEverything,
__typename: 'CalendarEvent', __typename: 'CalendarEvent',
}, },
{ {
@@ -25,7 +26,7 @@ const calendarEvents: CalendarEvent[] = [
externalCreatedAt: '2024-02-19T20:45:20.854Z', externalCreatedAt: '2024-02-19T20:45:20.854Z',
isFullDay: true, isFullDay: true,
startsAt: '2024-02-19T22:05:27.653Z', startsAt: '2024-02-19T22:05:27.653Z',
visibility: 'METADATA', visibility: CalendarChannelVisibility.Metadata,
__typename: 'CalendarEvent', __typename: 'CalendarEvent',
}, },
{ {
@@ -33,7 +34,7 @@ const calendarEvents: CalendarEvent[] = [
externalCreatedAt: '2024-02-20T20:45:12.854Z', externalCreatedAt: '2024-02-20T20:45:12.854Z',
isFullDay: true, isFullDay: true,
startsAt: '2024-02-20T23:15:23.150Z', startsAt: '2024-02-20T23:15:23.150Z',
visibility: 'SHARE_EVERYTHING', visibility: CalendarChannelVisibility.ShareEverything,
__typename: 'CalendarEvent', __typename: 'CalendarEvent',
}, },
]; ];

View File

@@ -1,4 +1,5 @@
import { CalendarEventParticipant } from '@/activities/calendar/types/CalendarEventParticipant'; import { CalendarEventParticipant } from '@/activities/calendar/types/CalendarEventParticipant';
import { CalendarChannelVisibility } from '~/generated/graphql';
// TODO: use backend CalendarEvent type when ready // TODO: use backend CalendarEvent type when ready
export type CalendarEvent = { export type CalendarEvent = {
@@ -15,7 +16,7 @@ export type CalendarEvent = {
location?: string; location?: string;
startsAt: string; startsAt: string;
title?: string; title?: string;
visibility: 'METADATA' | 'SHARE_EVERYTHING'; visibility: CalendarChannelVisibility;
calendarEventParticipants?: CalendarEventParticipant[]; calendarEventParticipants?: CalendarEventParticipant[];
__typename: 'CalendarEvent'; __typename: 'CalendarEvent';
}; };

View File

@@ -8,18 +8,22 @@ import { useEmailThread } from '@/activities/emails/hooks/useEmailThread';
import { emailThreadIdWhenEmailThreadWasClosedState } from '@/activities/emails/states/lastViewableEmailThreadIdState'; import { emailThreadIdWhenEmailThreadWasClosedState } from '@/activities/emails/states/lastViewableEmailThreadIdState';
import { CardContent } from '@/ui/layout/card/components/CardContent'; import { CardContent } from '@/ui/layout/card/components/CardContent';
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer'; import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
import { TimelineThread } from '~/generated/graphql'; import { MessageChannelVisibility, TimelineThread } from '~/generated/graphql';
import { formatToHumanReadableDate } from '~/utils'; import { formatToHumanReadableDate } from '~/utils';
import { getImageAbsoluteURIOrBase64 } from '~/utils/image/getImageAbsoluteURIOrBase64'; import { getImageAbsoluteURIOrBase64 } from '~/utils/image/getImageAbsoluteURIOrBase64';
const StyledCardContent = styled(CardContent)<{ visibility: string }>` const StyledCardContent = styled(CardContent)<{
visibility: MessageChannelVisibility;
}>`
align-items: center; align-items: center;
display: flex; display: flex;
gap: ${({ theme }) => theme.spacing(2)}; gap: ${({ theme }) => theme.spacing(2)};
height: ${({ theme }) => theme.spacing(12)}; height: ${({ theme }) => theme.spacing(12)};
padding: ${({ theme }) => theme.spacing(0, 4)}; padding: ${({ theme }) => theme.spacing(0, 4)};
cursor: ${({ visibility }) => cursor: ${({ visibility }) =>
visibility === 'share_everything' ? 'pointer' : 'default'}; visibility === MessageChannelVisibility.ShareEverything
? 'pointer'
: 'default'};
`; `;
const StyledHeading = styled.div<{ unread: boolean }>` const StyledHeading = styled.div<{ unread: boolean }>`
@@ -78,8 +82,6 @@ const StyledReceivedAt = styled.div`
padding: ${({ theme }) => theme.spacing(0, 1)}; padding: ${({ theme }) => theme.spacing(0, 1)};
`; `;
export type EmailThreadVisibility = 'metadata' | 'subject' | 'share_everything';
type EmailThreadPreviewProps = { type EmailThreadPreviewProps = {
divider?: boolean; divider?: boolean;
thread: TimelineThread; thread: TimelineThread;
@@ -93,7 +95,7 @@ export const EmailThreadPreview = ({
const { openEmailThread } = useEmailThread(); const { openEmailThread } = useEmailThread();
const visibility = thread.visibility as EmailThreadVisibility; const visibility = thread.visibility;
const senderNames = const senderNames =
thread.firstParticipant.displayName + thread.firstParticipant.displayName +
@@ -126,7 +128,7 @@ export const EmailThreadPreview = ({
.getValue(); .getValue();
const canOpen = const canOpen =
thread.visibility === 'share_everything' && thread.visibility === MessageChannelVisibility.ShareEverything &&
(!clickJustTriggeredEmailDrawerClose || (!clickJustTriggeredEmailDrawerClose ||
emailThreadIdWhenEmailThreadWasClosed !== thread.id); emailThreadIdWhenEmailThreadWasClosed !== thread.id);
@@ -183,13 +185,15 @@ export const EmailThreadPreview = ({
</StyledHeading> </StyledHeading>
<StyledSubjectAndBody> <StyledSubjectAndBody>
{visibility !== 'metadata' && ( {visibility !== MessageChannelVisibility.Metadata && (
<StyledSubject>{thread.subject}</StyledSubject> <StyledSubject>{thread.subject}</StyledSubject>
)} )}
{visibility === 'share_everything' && ( {visibility === MessageChannelVisibility.ShareEverything && (
<StyledBody>{thread.lastMessageBody}</StyledBody> <StyledBody>{thread.lastMessageBody}</StyledBody>
)} )}
{visibility !== 'share_everything' && <EmailThreadNotShared />} {visibility !== MessageChannelVisibility.ShareEverything && (
<EmailThreadNotShared />
)}
</StyledSubjectAndBody> </StyledSubjectAndBody>
<StyledReceivedAt> <StyledReceivedAt>
{formatToHumanReadableDate(thread.lastMessageReceivedAt)} {formatToHumanReadableDate(thread.lastMessageReceivedAt)}

View File

@@ -3,6 +3,7 @@ import { renderHook } from '@testing-library/react';
import { RecoilRoot, useSetRecoilState } from 'recoil'; import { RecoilRoot, useSetRecoilState } from 'recoil';
import { useOnboardingStatus } from '@/auth/hooks/useOnboardingStatus'; import { useOnboardingStatus } from '@/auth/hooks/useOnboardingStatus';
import { CurrentUser, currentUserState } from '@/auth/states/currentUserState';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { import {
CurrentWorkspace, CurrentWorkspace,
@@ -20,6 +21,13 @@ const billing = {
billingUrl: 'testing.com', billingUrl: 'testing.com',
isBillingEnabled: true, isBillingEnabled: true,
}; };
const currentUser = {
id: '1',
email: 'test@test',
supportUserHash: '1',
canImpersonate: false,
state: { skipSyncEmailOnboardingStep: true },
} as CurrentUser;
const currentWorkspace = { const currentWorkspace = {
activationStatus: 'active', activationStatus: 'active',
id: '1', id: '1',
@@ -27,7 +35,7 @@ const currentWorkspace = {
currentBillingSubscription: { currentBillingSubscription: {
status: 'trialing', status: 'trialing',
}, },
}; } as CurrentWorkspace;
const currentWorkspaceMember = { const currentWorkspaceMember = {
id: '1', id: '1',
locale: '', locale: '',
@@ -46,12 +54,14 @@ const renderHooks = () => {
const setCurrentWorkspaceMember = useSetRecoilState( const setCurrentWorkspaceMember = useSetRecoilState(
currentWorkspaceMemberState, currentWorkspaceMemberState,
); );
const setCurrentUser = useSetRecoilState(currentUserState);
const setTokenPair = useSetRecoilState(tokenPairState); const setTokenPair = useSetRecoilState(tokenPairState);
const setVerifyPending = useSetRecoilState(isVerifyPendingState); const setVerifyPending = useSetRecoilState(isVerifyPendingState);
return { return {
onboardingStatus, onboardingStatus,
setBilling, setBilling,
setCurrentUser,
setCurrentWorkspace, setCurrentWorkspace,
setCurrentWorkspaceMember, setCurrentWorkspaceMember,
setTokenPair, setTokenPair,
@@ -77,6 +87,7 @@ describe('useOnboardingStatus', () => {
const { const {
setTokenPair, setTokenPair,
setBilling, setBilling,
setCurrentUser,
setCurrentWorkspace, setCurrentWorkspace,
setCurrentWorkspaceMember, setCurrentWorkspaceMember,
} = result.current; } = result.current;
@@ -84,10 +95,11 @@ describe('useOnboardingStatus', () => {
act(() => { act(() => {
setTokenPair(tokenPair); setTokenPair(tokenPair);
setBilling(billing); setBilling(billing);
setCurrentUser(currentUser);
setCurrentWorkspace({ setCurrentWorkspace({
...currentWorkspace, ...currentWorkspace,
subscriptionStatus: 'incomplete', subscriptionStatus: 'incomplete',
} as CurrentWorkspace); });
setCurrentWorkspaceMember(currentWorkspaceMember); setCurrentWorkspaceMember(currentWorkspaceMember);
}); });
@@ -99,6 +111,7 @@ describe('useOnboardingStatus', () => {
const { const {
setTokenPair, setTokenPair,
setBilling, setBilling,
setCurrentUser,
setCurrentWorkspace, setCurrentWorkspace,
setCurrentWorkspaceMember, setCurrentWorkspaceMember,
} = result.current; } = result.current;
@@ -106,10 +119,11 @@ describe('useOnboardingStatus', () => {
act(() => { act(() => {
setTokenPair(tokenPair); setTokenPair(tokenPair);
setBilling(billing); setBilling(billing);
setCurrentUser(currentUser);
setCurrentWorkspace({ setCurrentWorkspace({
...currentWorkspace, ...currentWorkspace,
subscriptionStatus: 'canceled', subscriptionStatus: 'canceled',
} as CurrentWorkspace); });
setCurrentWorkspaceMember({ setCurrentWorkspaceMember({
...currentWorkspaceMember, ...currentWorkspaceMember,
name: { name: {
@@ -124,16 +138,18 @@ describe('useOnboardingStatus', () => {
it('should return "ongoing_workspace_activation"', async () => { it('should return "ongoing_workspace_activation"', async () => {
const { result } = renderHooks(); const { result } = renderHooks();
const { setTokenPair, setBilling, setCurrentWorkspace } = result.current; const { setTokenPair, setBilling, setCurrentUser, setCurrentWorkspace } =
result.current;
act(() => { act(() => {
setTokenPair(tokenPair); setTokenPair(tokenPair);
setBilling(billing); setBilling(billing);
setCurrentUser(currentUser);
setCurrentWorkspace({ setCurrentWorkspace({
...currentWorkspace, ...currentWorkspace,
activationStatus: 'inactive', activationStatus: 'inactive',
subscriptionStatus: 'active', subscriptionStatus: 'active',
} as CurrentWorkspace); });
}); });
expect(result.current.onboardingStatus).toBe( expect(result.current.onboardingStatus).toBe(
@@ -146,6 +162,7 @@ describe('useOnboardingStatus', () => {
const { const {
setTokenPair, setTokenPair,
setBilling, setBilling,
setCurrentUser,
setCurrentWorkspace, setCurrentWorkspace,
setCurrentWorkspaceMember, setCurrentWorkspaceMember,
} = result.current; } = result.current;
@@ -153,21 +170,56 @@ describe('useOnboardingStatus', () => {
act(() => { act(() => {
setTokenPair(tokenPair); setTokenPair(tokenPair);
setBilling(billing); setBilling(billing);
setCurrentUser(currentUser);
setCurrentWorkspace({ setCurrentWorkspace({
...currentWorkspace, ...currentWorkspace,
subscriptionStatus: 'active', subscriptionStatus: 'active',
} as CurrentWorkspace); });
setCurrentWorkspaceMember(currentWorkspaceMember); setCurrentWorkspaceMember(currentWorkspaceMember);
}); });
expect(result.current.onboardingStatus).toBe('ongoing_profile_creation'); expect(result.current.onboardingStatus).toBe('ongoing_profile_creation');
}); });
it('should return "ongoing_sync_email"', async () => {
const { result } = renderHooks();
const {
setTokenPair,
setBilling,
setCurrentUser,
setCurrentWorkspace,
setCurrentWorkspaceMember,
} = result.current;
act(() => {
setTokenPair(tokenPair);
setBilling(billing);
setCurrentUser({
...currentUser,
state: { skipSyncEmailOnboardingStep: false },
});
setCurrentWorkspace({
...currentWorkspace,
subscriptionStatus: 'active',
});
setCurrentWorkspaceMember({
...currentWorkspaceMember,
name: {
firstName: 'John',
lastName: 'Doe',
},
});
});
expect(result.current.onboardingStatus).toBe('ongoing_sync_email');
});
it('should return "completed"', async () => { it('should return "completed"', async () => {
const { result } = renderHooks(); const { result } = renderHooks();
const { const {
setTokenPair, setTokenPair,
setBilling, setBilling,
setCurrentUser,
setCurrentWorkspace, setCurrentWorkspace,
setCurrentWorkspaceMember, setCurrentWorkspaceMember,
} = result.current; } = result.current;
@@ -175,10 +227,11 @@ describe('useOnboardingStatus', () => {
act(() => { act(() => {
setTokenPair(tokenPair); setTokenPair(tokenPair);
setBilling(billing); setBilling(billing);
setCurrentUser(currentUser);
setCurrentWorkspace({ setCurrentWorkspace({
...currentWorkspace, ...currentWorkspace,
subscriptionStatus: 'active', subscriptionStatus: 'active',
} as CurrentWorkspace); });
setCurrentWorkspaceMember({ setCurrentWorkspaceMember({
...currentWorkspaceMember, ...currentWorkspaceMember,
name: { name: {
@@ -196,6 +249,7 @@ describe('useOnboardingStatus', () => {
const { const {
setTokenPair, setTokenPair,
setBilling, setBilling,
setCurrentUser,
setCurrentWorkspace, setCurrentWorkspace,
setCurrentWorkspaceMember, setCurrentWorkspaceMember,
} = result.current; } = result.current;
@@ -203,10 +257,11 @@ describe('useOnboardingStatus', () => {
act(() => { act(() => {
setTokenPair(tokenPair); setTokenPair(tokenPair);
setBilling(billing); setBilling(billing);
setCurrentUser(currentUser);
setCurrentWorkspace({ setCurrentWorkspace({
...currentWorkspace, ...currentWorkspace,
subscriptionStatus: 'past_due', subscriptionStatus: 'past_due',
} as CurrentWorkspace); });
setCurrentWorkspaceMember({ setCurrentWorkspaceMember({
...currentWorkspaceMember, ...currentWorkspaceMember,
name: { name: {
@@ -224,6 +279,7 @@ describe('useOnboardingStatus', () => {
const { const {
setTokenPair, setTokenPair,
setBilling, setBilling,
setCurrentUser,
setCurrentWorkspace, setCurrentWorkspace,
setCurrentWorkspaceMember, setCurrentWorkspaceMember,
} = result.current; } = result.current;
@@ -231,10 +287,11 @@ describe('useOnboardingStatus', () => {
act(() => { act(() => {
setTokenPair(tokenPair); setTokenPair(tokenPair);
setBilling(billing); setBilling(billing);
setCurrentUser(currentUser);
setCurrentWorkspace({ setCurrentWorkspace({
...currentWorkspace, ...currentWorkspace,
subscriptionStatus: 'unpaid', subscriptionStatus: 'unpaid',
} as CurrentWorkspace); });
setCurrentWorkspaceMember({ setCurrentWorkspaceMember({
...currentWorkspaceMember, ...currentWorkspaceMember,
name: { name: {
@@ -252,6 +309,7 @@ describe('useOnboardingStatus', () => {
const { const {
setTokenPair, setTokenPair,
setBilling, setBilling,
setCurrentUser,
setCurrentWorkspace, setCurrentWorkspace,
setCurrentWorkspaceMember, setCurrentWorkspaceMember,
} = result.current; } = result.current;
@@ -259,6 +317,7 @@ describe('useOnboardingStatus', () => {
act(() => { act(() => {
setTokenPair(tokenPair); setTokenPair(tokenPair);
setBilling(billing); setBilling(billing);
setCurrentUser(currentUser);
setCurrentWorkspace({ setCurrentWorkspace({
...currentWorkspace, ...currentWorkspace,
subscriptionStatus: 'trialing', subscriptionStatus: 'trialing',

View File

@@ -1,5 +1,6 @@
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { currentUserState } from '@/auth/states/currentUserState';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { billingState } from '@/client-config/states/billingState'; import { billingState } from '@/client-config/states/billingState';
@@ -14,12 +15,14 @@ export const useOnboardingStatus = (): OnboardingStatus | undefined => {
const billing = useRecoilValue(billingState); const billing = useRecoilValue(billingState);
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
const currentWorkspace = useRecoilValue(currentWorkspaceState); const currentWorkspace = useRecoilValue(currentWorkspaceState);
const currentUser = useRecoilValue(currentUserState);
const isLoggedIn = useIsLogged(); const isLoggedIn = useIsLogged();
return getOnboardingStatus({ return getOnboardingStatus({
isLoggedIn, isLoggedIn,
currentWorkspaceMember, currentWorkspaceMember,
currentWorkspace, currentWorkspace,
currentUser,
isBillingEnabled: billing?.isBillingEnabled || false, isBillingEnabled: billing?.isBillingEnabled || false,
}); });
}; };

View File

@@ -4,7 +4,7 @@ import { User } from '~/generated/graphql';
export type CurrentUser = Pick< export type CurrentUser = Pick<
User, User,
'id' | 'email' | 'supportUserHash' | 'canImpersonate' 'id' | 'email' | 'supportUserHash' | 'canImpersonate' | 'state'
>; >;
export const currentUserState = createState<CurrentUser | null>({ export const currentUserState = createState<CurrentUser | null>({

View File

@@ -1,3 +1,4 @@
import { CurrentUser } from '@/auth/states/currentUserState';
import { CurrentWorkspace } from '@/auth/states/currentWorkspaceState'; import { CurrentWorkspace } from '@/auth/states/currentWorkspaceState';
import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember'; import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember';
@@ -9,6 +10,7 @@ describe('getOnboardingStatus', () => {
isLoggedIn: false, isLoggedIn: false,
currentWorkspaceMember: null, currentWorkspaceMember: null,
currentWorkspace: null, currentWorkspace: null,
currentUser: null,
isBillingEnabled: false, isBillingEnabled: false,
}); });
@@ -19,6 +21,9 @@ describe('getOnboardingStatus', () => {
id: '1', id: '1',
activationStatus: 'inactive', activationStatus: 'inactive',
} as CurrentWorkspace, } as CurrentWorkspace,
currentUser: {
state: { skipSyncEmailOnboardingStep: true },
} as CurrentUser,
isBillingEnabled: false, isBillingEnabled: false,
}); });
@@ -32,6 +37,28 @@ describe('getOnboardingStatus', () => {
id: '1', id: '1',
activationStatus: 'active', activationStatus: 'active',
} as CurrentWorkspace, } as CurrentWorkspace,
currentUser: {
state: { skipSyncEmailOnboardingStep: true },
} as CurrentUser,
isBillingEnabled: false,
});
const ongoingSyncEmail = getOnboardingStatus({
isLoggedIn: true,
currentWorkspaceMember: {
id: '1',
name: {
firstName: 'John',
lastName: 'Doe',
},
} as WorkspaceMember,
currentWorkspace: {
id: '1',
activationStatus: 'active',
} as CurrentWorkspace,
currentUser: {
state: { skipSyncEmailOnboardingStep: false },
} as CurrentUser,
isBillingEnabled: false, isBillingEnabled: false,
}); });
@@ -48,6 +75,9 @@ describe('getOnboardingStatus', () => {
id: '1', id: '1',
activationStatus: 'active', activationStatus: 'active',
} as CurrentWorkspace, } as CurrentWorkspace,
currentUser: {
state: { skipSyncEmailOnboardingStep: true },
} as CurrentUser,
isBillingEnabled: false, isBillingEnabled: false,
}); });
@@ -65,6 +95,9 @@ describe('getOnboardingStatus', () => {
activationStatus: 'active', activationStatus: 'active',
subscriptionStatus: 'incomplete', subscriptionStatus: 'incomplete',
} as CurrentWorkspace, } as CurrentWorkspace,
currentUser: {
state: { skipSyncEmailOnboardingStep: true },
} as CurrentUser,
isBillingEnabled: true, isBillingEnabled: true,
}); });
@@ -82,6 +115,9 @@ describe('getOnboardingStatus', () => {
activationStatus: 'active', activationStatus: 'active',
subscriptionStatus: 'incomplete', subscriptionStatus: 'incomplete',
} as CurrentWorkspace, } as CurrentWorkspace,
currentUser: {
state: { skipSyncEmailOnboardingStep: true },
} as CurrentUser,
isBillingEnabled: false, isBillingEnabled: false,
}); });
@@ -99,12 +135,16 @@ describe('getOnboardingStatus', () => {
activationStatus: 'active', activationStatus: 'active',
subscriptionStatus: 'canceled', subscriptionStatus: 'canceled',
} as CurrentWorkspace, } as CurrentWorkspace,
currentUser: {
state: { skipSyncEmailOnboardingStep: true },
} as CurrentUser,
isBillingEnabled: true, isBillingEnabled: true,
}); });
expect(ongoingUserCreation).toBe('ongoing_user_creation'); expect(ongoingUserCreation).toBe('ongoing_user_creation');
expect(ongoingWorkspaceActivation).toBe('ongoing_workspace_activation'); expect(ongoingWorkspaceActivation).toBe('ongoing_workspace_activation');
expect(ongoingProfileCreation).toBe('ongoing_profile_creation'); expect(ongoingProfileCreation).toBe('ongoing_profile_creation');
expect(ongoingSyncEmail).toBe('ongoing_sync_email');
expect(completed).toBe('completed'); expect(completed).toBe('completed');
expect(incomplete).toBe('incomplete'); expect(incomplete).toBe('incomplete');
expect(canceled).toBe('canceled'); expect(canceled).toBe('canceled');

View File

@@ -1,3 +1,4 @@
import { CurrentUser } from '@/auth/states/currentUserState';
import { CurrentWorkspace } from '@/auth/states/currentWorkspaceState'; import { CurrentWorkspace } from '@/auth/states/currentWorkspaceState';
import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember'; import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember';
@@ -9,6 +10,7 @@ export enum OnboardingStatus {
OngoingUserCreation = 'ongoing_user_creation', OngoingUserCreation = 'ongoing_user_creation',
OngoingWorkspaceActivation = 'ongoing_workspace_activation', OngoingWorkspaceActivation = 'ongoing_workspace_activation',
OngoingProfileCreation = 'ongoing_profile_creation', OngoingProfileCreation = 'ongoing_profile_creation',
OngoingSyncEmail = 'ongoing_sync_email',
Completed = 'completed', Completed = 'completed',
CompletedWithoutSubscription = 'completed_without_subscription', CompletedWithoutSubscription = 'completed_without_subscription',
} }
@@ -17,6 +19,7 @@ export const getOnboardingStatus = ({
isLoggedIn, isLoggedIn,
currentWorkspaceMember, currentWorkspaceMember,
currentWorkspace, currentWorkspace,
currentUser,
isBillingEnabled, isBillingEnabled,
}: { }: {
isLoggedIn: boolean; isLoggedIn: boolean;
@@ -25,6 +28,7 @@ export const getOnboardingStatus = ({
'createdAt' | 'updatedAt' | 'userId' | 'userEmail' | '__typename' 'createdAt' | 'updatedAt' | 'userId' | 'userEmail' | '__typename'
> | null; > | null;
currentWorkspace: CurrentWorkspace | null; currentWorkspace: CurrentWorkspace | null;
currentUser: CurrentUser | null;
isBillingEnabled: boolean; isBillingEnabled: boolean;
}) => { }) => {
if (!isLoggedIn) { if (!isLoggedIn) {
@@ -33,7 +37,7 @@ export const getOnboardingStatus = ({
// After SignInUp, the user should have a current workspace assigned. // After SignInUp, the user should have a current workspace assigned.
// If not, it indicates that the data is still being requested. // If not, it indicates that the data is still being requested.
if (!currentWorkspace) { if (!currentWorkspace || !currentUser) {
return undefined; return undefined;
} }
@@ -55,6 +59,10 @@ export const getOnboardingStatus = ({
return OnboardingStatus.OngoingProfileCreation; return OnboardingStatus.OngoingProfileCreation;
} }
if (!currentUser.state.skipSyncEmailOnboardingStep) {
return OnboardingStatus.OngoingSyncEmail;
}
if (isBillingEnabled && currentWorkspace.subscriptionStatus === 'canceled') { if (isBillingEnabled && currentWorkspace.subscriptionStatus === 'canceled') {
return OnboardingStatus.Canceled; return OnboardingStatus.Canceled;
} }

View File

@@ -292,6 +292,66 @@ export const getObjectMetadataItemsMock = () => {
updatedAt: '2023-11-30T11:13:15.206Z', updatedAt: '2023-11-30T11:13:15.206Z',
fields: [], fields: [],
}, },
{
__typename: 'object',
id: '3aac4582-f677-4d7d-acd5-3e33a039acdd',
dataSourceId: '20202020-7f63-47a9-b1b3-6c7290ca9fb1',
nameSingular: 'connectedAccount',
namePlural: 'connectedAccounts',
labelSingular: 'Connected Account',
labelPlural: 'Connected Accounts',
description: 'A connected account',
icon: 'IconAt',
isCustom: false,
isRemote: false,
isActive: true,
isSystem: true,
createdAt: '2024-06-01T14:55:04.039Z',
updatedAt: '2024-06-01T14:55:04.039Z',
labelIdentifierFieldMetadataId: null,
imageIdentifierFieldMetadataId: null,
fields: [],
},
{
__typename: 'object',
id: '3aac4582-f677-4d7d-acd5-3e33a039acde',
dataSourceId: '20202020-7f63-47a9-b1b3-6c7290ca9fb1',
nameSingular: 'messageChannel',
namePlural: 'messageChannels',
labelSingular: 'Message Channel',
labelPlural: 'Message Channels',
description: 'A message channel',
icon: 'IconAt',
isCustom: false,
isRemote: false,
isActive: true,
isSystem: true,
createdAt: '2024-06-01T14:55:04.039Z',
updatedAt: '2024-06-01T14:55:04.039Z',
labelIdentifierFieldMetadataId: null,
imageIdentifierFieldMetadataId: null,
fields: [],
},
{
__typename: 'object',
id: '3aac4582-f677-4d7d-acd5-3e33a039acdf',
dataSourceId: '20202020-7f63-47a9-b1b3-6c7290ca9fb1',
nameSingular: 'calendarChannel',
namePlural: 'calendarChannels',
labelSingular: 'Calendar Channel',
labelPlural: 'Calendar Channels',
description: 'A calendar channel',
icon: 'IconAt',
isCustom: false,
isRemote: false,
isActive: true,
isSystem: true,
createdAt: '2024-06-01T14:55:04.039Z',
updatedAt: '2024-06-01T14:55:04.039Z',
labelIdentifierFieldMetadataId: null,
imageIdentifierFieldMetadataId: null,
fields: [],
},
{ {
__typename: 'object', __typename: 'object',
id: '20202020-cae9-4ff4-9579-f7d9fe44c937', id: '20202020-cae9-4ff4-9579-f7d9fe44c937',

View File

@@ -0,0 +1,19 @@
import { onboardingSyncEmailsOptions } from '@/onboarding/components/onboardingSyncEmailsOptions';
import { SettingsAccountsRadioSettingsCard } from '@/settings/accounts/components/SettingsAccountsRadioSettingsCard';
import { MessageChannelVisibility } from '~/generated/graphql';
type OnboardingSyncEmailsSettingsCardProps = {
onChange: (nextValue: MessageChannelVisibility) => void;
value?: MessageChannelVisibility;
};
export const OnboardingSyncEmailsSettingsCard = ({
onChange,
value = MessageChannelVisibility.ShareEverything,
}: OnboardingSyncEmailsSettingsCardProps) => (
<SettingsAccountsRadioSettingsCard
options={onboardingSyncEmailsOptions}
value={value}
onChange={onChange}
/>
);

View File

@@ -0,0 +1,38 @@
import styled from '@emotion/styled';
import { SettingsAccountsVisibilitySettingCardMedia } from '@/settings/accounts/components/SettingsAccountsVisibilitySettingCardMedia';
import { MessageChannelVisibility } from '~/generated/graphql';
const StyledCardMedia = styled(SettingsAccountsVisibilitySettingCardMedia)`
width: ${({ theme }) => theme.spacing(10)};
`;
export const onboardingSyncEmailsOptions = [
{
title: 'Everything',
description:
'Your emails and events content will be shared with your team.',
value: MessageChannelVisibility.ShareEverything,
cardMedia: (
<StyledCardMedia metadata="active" subject="active" body="active" />
),
},
{
title: 'Subject and metadata',
description:
'Your email subjects and meeting titles will be shared with your team.',
value: MessageChannelVisibility.Subject,
cardMedia: (
<StyledCardMedia metadata="active" subject="active" body="inactive" />
),
},
{
title: 'Metadata',
description:
'Only the timestamp & participants will be shared with your team.',
value: MessageChannelVisibility.Metadata,
cardMedia: (
<StyledCardMedia metadata="active" subject="inactive" body="inactive" />
),
},
];

View File

@@ -0,0 +1,9 @@
import { gql } from '@apollo/client';
export const SKIP_SYNC_EMAIL_ONBOARDING_STEP = gql`
mutation SkipSyncEmailOnboardingStep {
skipSyncEmailOnboardingStep {
success
}
}
`;

View File

@@ -1,8 +1,8 @@
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { CalendarChannelVisibility } from '@/accounts/types/CalendarChannel';
import { SettingsAccountsRadioSettingsCard } from '@/settings/accounts/components/SettingsAccountsRadioSettingsCard'; import { SettingsAccountsRadioSettingsCard } from '@/settings/accounts/components/SettingsAccountsRadioSettingsCard';
import { SettingsAccountsVisibilitySettingCardMedia } from '@/settings/accounts/components/SettingsAccountsVisibilitySettingCardMedia'; import { SettingsAccountsVisibilitySettingCardMedia } from '@/settings/accounts/components/SettingsAccountsVisibilitySettingCardMedia';
import { CalendarChannelVisibility } from '~/generated/graphql';
type SettingsAccountsEventVisibilitySettingsCardProps = { type SettingsAccountsEventVisibilitySettingsCardProps = {
onChange: (nextValue: CalendarChannelVisibility) => void; onChange: (nextValue: CalendarChannelVisibility) => void;
@@ -17,7 +17,7 @@ const eventSettingsVisibilityOptions = [
{ {
title: 'Everything', title: 'Everything',
description: 'The whole event details will be shared with your team.', description: 'The whole event details will be shared with your team.',
value: CalendarChannelVisibility.Everything, value: CalendarChannelVisibility.ShareEverything,
cardMedia: <StyledCardMedia subject="active" body="active" />, cardMedia: <StyledCardMedia subject="active" body="active" />,
}, },
{ {
@@ -30,7 +30,7 @@ const eventSettingsVisibilityOptions = [
export const SettingsAccountsEventVisibilitySettingsCard = ({ export const SettingsAccountsEventVisibilitySettingsCard = ({
onChange, onChange,
value = CalendarChannelVisibility.Everything, value = CalendarChannelVisibility.ShareEverything,
}: SettingsAccountsEventVisibilitySettingsCardProps) => ( }: SettingsAccountsEventVisibilitySettingsCardProps) => (
<SettingsAccountsRadioSettingsCard <SettingsAccountsRadioSettingsCard
options={eventSettingsVisibilityOptions} options={eventSettingsVisibilityOptions}

View File

@@ -1,22 +1,17 @@
import { SettingsAccountsRadioSettingsCard } from '@/settings/accounts/components/SettingsAccountsRadioSettingsCard'; import { SettingsAccountsRadioSettingsCard } from '@/settings/accounts/components/SettingsAccountsRadioSettingsCard';
import { SettingsAccountsVisibilitySettingCardMedia } from '@/settings/accounts/components/SettingsAccountsVisibilitySettingCardMedia'; import { SettingsAccountsVisibilitySettingCardMedia } from '@/settings/accounts/components/SettingsAccountsVisibilitySettingCardMedia';
import { MessageChannelVisibility } from '~/generated/graphql';
export enum InboxSettingsVisibilityValue {
Everything = 'share_everything',
SubjectMetadata = 'subject',
Metadata = 'metadata',
}
type SettingsAccountsInboxVisibilitySettingsCardProps = { type SettingsAccountsInboxVisibilitySettingsCardProps = {
onChange: (nextValue: InboxSettingsVisibilityValue) => void; onChange: (nextValue: MessageChannelVisibility) => void;
value?: InboxSettingsVisibilityValue; value?: MessageChannelVisibility;
}; };
const inboxSettingsVisibilityOptions = [ const inboxSettingsVisibilityOptions = [
{ {
title: 'Everything', title: 'Everything',
description: 'Subject, body and attachments will be shared with your team.', description: 'Subject, body and attachments will be shared with your team.',
value: InboxSettingsVisibilityValue.Everything, value: MessageChannelVisibility.ShareEverything,
cardMedia: ( cardMedia: (
<SettingsAccountsVisibilitySettingCardMedia <SettingsAccountsVisibilitySettingCardMedia
metadata="active" metadata="active"
@@ -28,7 +23,7 @@ const inboxSettingsVisibilityOptions = [
{ {
title: 'Subject and metadata', title: 'Subject and metadata',
description: 'Subject and metadata will be shared with your team.', description: 'Subject and metadata will be shared with your team.',
value: InboxSettingsVisibilityValue.SubjectMetadata, value: MessageChannelVisibility.Subject,
cardMedia: ( cardMedia: (
<SettingsAccountsVisibilitySettingCardMedia <SettingsAccountsVisibilitySettingCardMedia
metadata="active" metadata="active"
@@ -40,7 +35,7 @@ const inboxSettingsVisibilityOptions = [
{ {
title: 'Metadata', title: 'Metadata',
description: 'Timestamp and participants will be shared with your team.', description: 'Timestamp and participants will be shared with your team.',
value: InboxSettingsVisibilityValue.Metadata, value: MessageChannelVisibility.Metadata,
cardMedia: ( cardMedia: (
<SettingsAccountsVisibilitySettingCardMedia <SettingsAccountsVisibilitySettingCardMedia
metadata="active" metadata="active"
@@ -53,7 +48,7 @@ const inboxSettingsVisibilityOptions = [
export const SettingsAccountsInboxVisibilitySettingsCard = ({ export const SettingsAccountsInboxVisibilitySettingsCard = ({
onChange, onChange,
value = InboxSettingsVisibilityValue.Everything, value = MessageChannelVisibility.ShareEverything,
}: SettingsAccountsInboxVisibilitySettingsCardProps) => ( }: SettingsAccountsInboxVisibilitySettingsCardProps) => (
<SettingsAccountsRadioSettingsCard <SettingsAccountsRadioSettingsCard
options={inboxSettingsVisibilityOptions} options={inboxSettingsVisibilityOptions}

View File

@@ -27,6 +27,10 @@ export const SettingsAccountsListEmptyStateCard = ({
}: SettingsAccountsListEmptyStateCardProps) => { }: SettingsAccountsListEmptyStateCardProps) => {
const { triggerGoogleApisOAuth } = useTriggerGoogleApisOAuth(); const { triggerGoogleApisOAuth } = useTriggerGoogleApisOAuth();
const handleOnClick = async () => {
await triggerGoogleApisOAuth();
};
return ( return (
<Card> <Card>
<StyledHeader>{label || 'No connected account'}</StyledHeader> <StyledHeader>{label || 'No connected account'}</StyledHeader>
@@ -35,7 +39,7 @@ export const SettingsAccountsListEmptyStateCard = ({
Icon={IconGoogle} Icon={IconGoogle}
title="Connect with Google" title="Connect with Google"
variant="secondary" variant="secondary"
onClick={triggerGoogleApisOAuth} onClick={handleOnClick}
/> />
</StyledBody> </StyledBody>
</Card> </Card>

View File

@@ -50,7 +50,7 @@ export const SettingsAccountsRadioSettingsCard = <
options, options,
value, value,
}: SettingsAccountsRadioSettingsCardProps<Option>) => ( }: SettingsAccountsRadioSettingsCardProps<Option>) => (
<Card> <Card rounded>
{options.map((option, index) => ( {options.map((option, index) => (
<StyledCardContent <StyledCardContent
key={option.value} key={option.value}

View File

@@ -36,7 +36,7 @@ export const SettingsAccountsToggleSettingCard = ({
onToggle, onToggle,
title, title,
}: SettingsAccountsToggleSettingCardProps) => ( }: SettingsAccountsToggleSettingCardProps) => (
<Card> <Card rounded>
<StyledCardContent onClick={() => onToggle(!value)}> <StyledCardContent onClick={() => onToggle(!value)}>
{cardMedia} {cardMedia}
<StyledTitle>{title}</StyledTitle> <StyledTitle>{title}</StyledTitle>

View File

@@ -1,21 +1,47 @@
import { useCallback } from 'react'; import { useCallback } from 'react';
import { AppPath } from '@/types/AppPath';
import { REACT_APP_SERVER_BASE_URL } from '~/config'; import { REACT_APP_SERVER_BASE_URL } from '~/config';
import { useGenerateTransientTokenMutation } from '~/generated/graphql'; import {
CalendarChannelVisibility,
MessageChannelVisibility,
useGenerateTransientTokenMutation,
} from '~/generated/graphql';
export const useTriggerGoogleApisOAuth = () => { export const useTriggerGoogleApisOAuth = () => {
const [generateTransientToken] = useGenerateTransientTokenMutation(); const [generateTransientToken] = useGenerateTransientTokenMutation();
const triggerGoogleApisOAuth = useCallback(async () => { const triggerGoogleApisOAuth = useCallback(
const authServerUrl = REACT_APP_SERVER_BASE_URL; async (
redirectLocation?: AppPath,
messageVisibility?: MessageChannelVisibility,
calendarVisibility?: CalendarChannelVisibility,
) => {
const authServerUrl = REACT_APP_SERVER_BASE_URL;
const transientToken = await generateTransientToken(); const transientToken = await generateTransientToken();
const token = const token =
transientToken.data?.generateTransientToken.transientToken.token; transientToken.data?.generateTransientToken.transientToken.token;
window.location.href = `${authServerUrl}/auth/google-apis?transientToken=${token}`; let params = `transientToken=${token}`;
}, [generateTransientToken]);
params += redirectLocation
? `&redirectLocation=${encodeURIComponent(redirectLocation)}`
: '';
params += calendarVisibility
? `&calendarVisibility=${calendarVisibility}`
: '';
params += messageVisibility
? `&messageVisibility=${messageVisibility}`
: '';
window.location.href = `${authServerUrl}/auth/google-apis?${params}`;
},
[generateTransientToken],
);
return { triggerGoogleApisOAuth }; return { triggerGoogleApisOAuth };
}; };

View File

@@ -8,6 +8,7 @@ export enum AppPath {
// Onboarding // Onboarding
CreateWorkspace = '/create/workspace', CreateWorkspace = '/create/workspace',
CreateProfile = '/create/profile', CreateProfile = '/create/profile',
SyncEmails = '/sync/emails',
PlanRequired = '/plan-required', PlanRequired = '/plan-required',
PlanRequiredSuccess = '/plan-required/payment-success', PlanRequiredSuccess = '/plan-required/payment-success',

View File

@@ -1,8 +1,9 @@
import styled from '@emotion/styled'; import styled from '@emotion/styled';
const StyledCard = styled.div<{ fullWidth?: boolean }>` const StyledCard = styled.div<{ fullWidth?: boolean; rounded?: boolean }>`
border: 1px solid ${({ theme }) => theme.border.color.medium}; border: 1px solid ${({ theme }) => theme.border.color.medium};
border-radius: ${({ theme }) => theme.border.radius.sm}; border-radius: ${({ theme, rounded }) =>
rounded ? theme.border.radius.md : theme.border.radius.sm};
color: ${({ theme }) => theme.font.color.secondary}; color: ${({ theme }) => theme.font.color.secondary};
overflow: hidden; overflow: hidden;
width: ${({ fullWidth }) => (fullWidth ? '100%' : 'auto')}; width: ${({ fullWidth }) => (fullWidth ? '100%' : 'auto')};

View File

@@ -46,6 +46,7 @@ const testCases = [
{ loc: AppPath.Verify, status: OnboardingStatus.OngoingUserCreation, res: false }, { loc: AppPath.Verify, status: OnboardingStatus.OngoingUserCreation, res: false },
{ loc: AppPath.Verify, status: OnboardingStatus.OngoingWorkspaceActivation, res: false }, { loc: AppPath.Verify, status: OnboardingStatus.OngoingWorkspaceActivation, res: false },
{ loc: AppPath.Verify, status: OnboardingStatus.OngoingProfileCreation, res: false }, { loc: AppPath.Verify, status: OnboardingStatus.OngoingProfileCreation, res: false },
{ loc: AppPath.Verify, status: OnboardingStatus.OngoingSyncEmail, res: false },
{ loc: AppPath.Verify, status: OnboardingStatus.Completed, res: false }, { loc: AppPath.Verify, status: OnboardingStatus.Completed, res: false },
{ loc: AppPath.Verify, status: OnboardingStatus.CompletedWithoutSubscription, res: false }, { loc: AppPath.Verify, status: OnboardingStatus.CompletedWithoutSubscription, res: false },
@@ -56,6 +57,7 @@ const testCases = [
{ loc: AppPath.SignInUp, status: OnboardingStatus.OngoingUserCreation, res: true }, { loc: AppPath.SignInUp, status: OnboardingStatus.OngoingUserCreation, res: true },
{ loc: AppPath.SignInUp, status: OnboardingStatus.OngoingWorkspaceActivation, res: true }, { loc: AppPath.SignInUp, status: OnboardingStatus.OngoingWorkspaceActivation, res: true },
{ loc: AppPath.SignInUp, status: OnboardingStatus.OngoingProfileCreation, res: true }, { loc: AppPath.SignInUp, status: OnboardingStatus.OngoingProfileCreation, res: true },
{ loc: AppPath.SignInUp, status: OnboardingStatus.OngoingSyncEmail, res: true },
{ loc: AppPath.SignInUp, status: OnboardingStatus.Completed, res: false }, { loc: AppPath.SignInUp, status: OnboardingStatus.Completed, res: false },
{ loc: AppPath.SignInUp, status: OnboardingStatus.CompletedWithoutSubscription, res: false }, { loc: AppPath.SignInUp, status: OnboardingStatus.CompletedWithoutSubscription, res: false },
@@ -66,6 +68,7 @@ const testCases = [
{ loc: AppPath.Invite, status: OnboardingStatus.OngoingUserCreation, res: true }, { loc: AppPath.Invite, status: OnboardingStatus.OngoingUserCreation, res: true },
{ loc: AppPath.Invite, status: OnboardingStatus.OngoingWorkspaceActivation, res: true }, { loc: AppPath.Invite, status: OnboardingStatus.OngoingWorkspaceActivation, res: true },
{ loc: AppPath.Invite, status: OnboardingStatus.OngoingProfileCreation, res: true }, { loc: AppPath.Invite, status: OnboardingStatus.OngoingProfileCreation, res: true },
{ loc: AppPath.Invite, status: OnboardingStatus.OngoingSyncEmail, res: true },
{ loc: AppPath.Invite, status: OnboardingStatus.Completed, res: true }, { loc: AppPath.Invite, status: OnboardingStatus.Completed, res: true },
{ loc: AppPath.Invite, status: OnboardingStatus.CompletedWithoutSubscription, res: true }, { loc: AppPath.Invite, status: OnboardingStatus.CompletedWithoutSubscription, res: true },
@@ -76,6 +79,7 @@ const testCases = [
{ loc: AppPath.ResetPassword, status: OnboardingStatus.OngoingUserCreation, res: true }, { loc: AppPath.ResetPassword, status: OnboardingStatus.OngoingUserCreation, res: true },
{ loc: AppPath.ResetPassword, status: OnboardingStatus.OngoingWorkspaceActivation, res: true }, { loc: AppPath.ResetPassword, status: OnboardingStatus.OngoingWorkspaceActivation, res: true },
{ loc: AppPath.ResetPassword, status: OnboardingStatus.OngoingProfileCreation, res: true }, { loc: AppPath.ResetPassword, status: OnboardingStatus.OngoingProfileCreation, res: true },
{ loc: AppPath.ResetPassword, status: OnboardingStatus.OngoingSyncEmail, res: true },
{ loc: AppPath.ResetPassword, status: OnboardingStatus.Completed, res: true }, { loc: AppPath.ResetPassword, status: OnboardingStatus.Completed, res: true },
{ loc: AppPath.ResetPassword, status: OnboardingStatus.CompletedWithoutSubscription, res: true }, { loc: AppPath.ResetPassword, status: OnboardingStatus.CompletedWithoutSubscription, res: true },
@@ -86,6 +90,7 @@ const testCases = [
{ loc: AppPath.CreateWorkspace, status: OnboardingStatus.OngoingUserCreation, res: true }, { loc: AppPath.CreateWorkspace, status: OnboardingStatus.OngoingUserCreation, res: true },
{ loc: AppPath.CreateWorkspace, status: OnboardingStatus.OngoingWorkspaceActivation, res: true }, { loc: AppPath.CreateWorkspace, status: OnboardingStatus.OngoingWorkspaceActivation, res: true },
{ loc: AppPath.CreateWorkspace, status: OnboardingStatus.OngoingProfileCreation, res: true }, { loc: AppPath.CreateWorkspace, status: OnboardingStatus.OngoingProfileCreation, res: true },
{ loc: AppPath.CreateWorkspace, status: OnboardingStatus.OngoingSyncEmail, res: true },
{ loc: AppPath.CreateWorkspace, status: OnboardingStatus.Completed, res: false }, { loc: AppPath.CreateWorkspace, status: OnboardingStatus.Completed, res: false },
{ loc: AppPath.CreateWorkspace, status: OnboardingStatus.CompletedWithoutSubscription, res: false }, { loc: AppPath.CreateWorkspace, status: OnboardingStatus.CompletedWithoutSubscription, res: false },
@@ -96,9 +101,21 @@ const testCases = [
{ loc: AppPath.CreateProfile, status: OnboardingStatus.OngoingUserCreation, res: true }, { loc: AppPath.CreateProfile, status: OnboardingStatus.OngoingUserCreation, res: true },
{ loc: AppPath.CreateProfile, status: OnboardingStatus.OngoingWorkspaceActivation, res: true }, { loc: AppPath.CreateProfile, status: OnboardingStatus.OngoingWorkspaceActivation, res: true },
{ loc: AppPath.CreateProfile, status: OnboardingStatus.OngoingProfileCreation, res: true }, { loc: AppPath.CreateProfile, status: OnboardingStatus.OngoingProfileCreation, res: true },
{ loc: AppPath.CreateProfile, status: OnboardingStatus.OngoingSyncEmail, res: true },
{ loc: AppPath.CreateProfile, status: OnboardingStatus.Completed, res: false }, { loc: AppPath.CreateProfile, status: OnboardingStatus.Completed, res: false },
{ loc: AppPath.CreateProfile, status: OnboardingStatus.CompletedWithoutSubscription, res: false }, { loc: AppPath.CreateProfile, status: OnboardingStatus.CompletedWithoutSubscription, res: false },
{ loc: AppPath.SyncEmails, status: OnboardingStatus.Incomplete, res: true },
{ loc: AppPath.SyncEmails, status: OnboardingStatus.Canceled, res: false },
{ loc: AppPath.SyncEmails, status: OnboardingStatus.Unpaid, res: false },
{ loc: AppPath.SyncEmails, status: OnboardingStatus.PastDue, res: false },
{ loc: AppPath.SyncEmails, status: OnboardingStatus.OngoingUserCreation, res: true },
{ loc: AppPath.SyncEmails, status: OnboardingStatus.OngoingWorkspaceActivation, res: true },
{ loc: AppPath.SyncEmails, status: OnboardingStatus.OngoingProfileCreation, res: true },
{ loc: AppPath.SyncEmails, status: OnboardingStatus.OngoingSyncEmail, res: true },
{ loc: AppPath.SyncEmails, status: OnboardingStatus.Completed, res: false },
{ loc: AppPath.SyncEmails, status: OnboardingStatus.CompletedWithoutSubscription, res: false },
{ loc: AppPath.PlanRequired, status: OnboardingStatus.Incomplete, res: true }, { loc: AppPath.PlanRequired, status: OnboardingStatus.Incomplete, res: true },
{ loc: AppPath.PlanRequired, status: OnboardingStatus.Canceled, res: true }, { loc: AppPath.PlanRequired, status: OnboardingStatus.Canceled, res: true },
{ loc: AppPath.PlanRequired, status: OnboardingStatus.Unpaid, res: false }, { loc: AppPath.PlanRequired, status: OnboardingStatus.Unpaid, res: false },
@@ -106,6 +123,7 @@ const testCases = [
{ loc: AppPath.PlanRequired, status: OnboardingStatus.OngoingUserCreation, res: true }, { loc: AppPath.PlanRequired, status: OnboardingStatus.OngoingUserCreation, res: true },
{ loc: AppPath.PlanRequired, status: OnboardingStatus.OngoingWorkspaceActivation, res: true }, { loc: AppPath.PlanRequired, status: OnboardingStatus.OngoingWorkspaceActivation, res: true },
{ loc: AppPath.PlanRequired, status: OnboardingStatus.OngoingProfileCreation, res: true }, { loc: AppPath.PlanRequired, status: OnboardingStatus.OngoingProfileCreation, res: true },
{ loc: AppPath.PlanRequired, status: OnboardingStatus.OngoingSyncEmail, res: true },
{ loc: AppPath.PlanRequired, status: OnboardingStatus.Completed, res: false }, { loc: AppPath.PlanRequired, status: OnboardingStatus.Completed, res: false },
{ loc: AppPath.PlanRequired, status: OnboardingStatus.CompletedWithoutSubscription, res: true }, { loc: AppPath.PlanRequired, status: OnboardingStatus.CompletedWithoutSubscription, res: true },
@@ -116,6 +134,7 @@ const testCases = [
{ loc: AppPath.PlanRequiredSuccess, status: OnboardingStatus.OngoingUserCreation, res: true }, { loc: AppPath.PlanRequiredSuccess, status: OnboardingStatus.OngoingUserCreation, res: true },
{ loc: AppPath.PlanRequiredSuccess, status: OnboardingStatus.OngoingWorkspaceActivation, res: true }, { loc: AppPath.PlanRequiredSuccess, status: OnboardingStatus.OngoingWorkspaceActivation, res: true },
{ loc: AppPath.PlanRequiredSuccess, status: OnboardingStatus.OngoingProfileCreation, res: true }, { loc: AppPath.PlanRequiredSuccess, status: OnboardingStatus.OngoingProfileCreation, res: true },
{ loc: AppPath.PlanRequiredSuccess, status: OnboardingStatus.OngoingSyncEmail, res: true },
{ loc: AppPath.PlanRequiredSuccess, status: OnboardingStatus.Completed, res: false }, { loc: AppPath.PlanRequiredSuccess, status: OnboardingStatus.Completed, res: false },
{ loc: AppPath.PlanRequiredSuccess, status: OnboardingStatus.CompletedWithoutSubscription, res: false }, { loc: AppPath.PlanRequiredSuccess, status: OnboardingStatus.CompletedWithoutSubscription, res: false },
@@ -126,6 +145,7 @@ const testCases = [
{ loc: AppPath.Index, status: OnboardingStatus.OngoingUserCreation, res: true }, { loc: AppPath.Index, status: OnboardingStatus.OngoingUserCreation, res: true },
{ loc: AppPath.Index, status: OnboardingStatus.OngoingWorkspaceActivation, res: true }, { loc: AppPath.Index, status: OnboardingStatus.OngoingWorkspaceActivation, res: true },
{ loc: AppPath.Index, status: OnboardingStatus.OngoingProfileCreation, res: true }, { loc: AppPath.Index, status: OnboardingStatus.OngoingProfileCreation, res: true },
{ loc: AppPath.Index, status: OnboardingStatus.OngoingSyncEmail, res: true },
{ loc: AppPath.Index, status: OnboardingStatus.Completed, res: false }, { loc: AppPath.Index, status: OnboardingStatus.Completed, res: false },
{ loc: AppPath.Index, status: OnboardingStatus.CompletedWithoutSubscription, res: false }, { loc: AppPath.Index, status: OnboardingStatus.CompletedWithoutSubscription, res: false },
@@ -136,6 +156,7 @@ const testCases = [
{ loc: AppPath.TasksPage, status: OnboardingStatus.OngoingUserCreation, res: true }, { loc: AppPath.TasksPage, status: OnboardingStatus.OngoingUserCreation, res: true },
{ loc: AppPath.TasksPage, status: OnboardingStatus.OngoingWorkspaceActivation, res: true }, { loc: AppPath.TasksPage, status: OnboardingStatus.OngoingWorkspaceActivation, res: true },
{ loc: AppPath.TasksPage, status: OnboardingStatus.OngoingProfileCreation, res: true }, { loc: AppPath.TasksPage, status: OnboardingStatus.OngoingProfileCreation, res: true },
{ loc: AppPath.TasksPage, status: OnboardingStatus.OngoingSyncEmail, res: true },
{ loc: AppPath.TasksPage, status: OnboardingStatus.Completed, res: false }, { loc: AppPath.TasksPage, status: OnboardingStatus.Completed, res: false },
{ loc: AppPath.TasksPage, status: OnboardingStatus.CompletedWithoutSubscription, res: false }, { loc: AppPath.TasksPage, status: OnboardingStatus.CompletedWithoutSubscription, res: false },
@@ -146,6 +167,7 @@ const testCases = [
{ loc: AppPath.OpportunitiesPage, status: OnboardingStatus.OngoingUserCreation, res: true }, { loc: AppPath.OpportunitiesPage, status: OnboardingStatus.OngoingUserCreation, res: true },
{ loc: AppPath.OpportunitiesPage, status: OnboardingStatus.OngoingWorkspaceActivation, res: true }, { loc: AppPath.OpportunitiesPage, status: OnboardingStatus.OngoingWorkspaceActivation, res: true },
{ loc: AppPath.OpportunitiesPage, status: OnboardingStatus.OngoingProfileCreation, res: true }, { loc: AppPath.OpportunitiesPage, status: OnboardingStatus.OngoingProfileCreation, res: true },
{ loc: AppPath.OpportunitiesPage, status: OnboardingStatus.OngoingSyncEmail, res: true },
{ loc: AppPath.OpportunitiesPage, status: OnboardingStatus.Completed, res: false }, { loc: AppPath.OpportunitiesPage, status: OnboardingStatus.Completed, res: false },
{ loc: AppPath.OpportunitiesPage, status: OnboardingStatus.CompletedWithoutSubscription, res: false }, { loc: AppPath.OpportunitiesPage, status: OnboardingStatus.CompletedWithoutSubscription, res: false },
@@ -156,6 +178,7 @@ const testCases = [
{ loc: AppPath.RecordIndexPage, status: OnboardingStatus.OngoingUserCreation, res: true }, { loc: AppPath.RecordIndexPage, status: OnboardingStatus.OngoingUserCreation, res: true },
{ loc: AppPath.RecordIndexPage, status: OnboardingStatus.OngoingWorkspaceActivation, res: true }, { loc: AppPath.RecordIndexPage, status: OnboardingStatus.OngoingWorkspaceActivation, res: true },
{ loc: AppPath.RecordIndexPage, status: OnboardingStatus.OngoingProfileCreation, res: true }, { loc: AppPath.RecordIndexPage, status: OnboardingStatus.OngoingProfileCreation, res: true },
{ loc: AppPath.RecordIndexPage, status: OnboardingStatus.OngoingSyncEmail, res: true },
{ loc: AppPath.RecordIndexPage, status: OnboardingStatus.Completed, res: false }, { loc: AppPath.RecordIndexPage, status: OnboardingStatus.Completed, res: false },
{ loc: AppPath.RecordIndexPage, status: OnboardingStatus.CompletedWithoutSubscription, res: false }, { loc: AppPath.RecordIndexPage, status: OnboardingStatus.CompletedWithoutSubscription, res: false },
@@ -166,6 +189,7 @@ const testCases = [
{ loc: AppPath.RecordShowPage, status: OnboardingStatus.OngoingUserCreation, res: true }, { loc: AppPath.RecordShowPage, status: OnboardingStatus.OngoingUserCreation, res: true },
{ loc: AppPath.RecordShowPage, status: OnboardingStatus.OngoingWorkspaceActivation, res: true }, { loc: AppPath.RecordShowPage, status: OnboardingStatus.OngoingWorkspaceActivation, res: true },
{ loc: AppPath.RecordShowPage, status: OnboardingStatus.OngoingProfileCreation, res: true }, { loc: AppPath.RecordShowPage, status: OnboardingStatus.OngoingProfileCreation, res: true },
{ loc: AppPath.RecordShowPage, status: OnboardingStatus.OngoingSyncEmail, res: true },
{ loc: AppPath.RecordShowPage, status: OnboardingStatus.Completed, res: false }, { loc: AppPath.RecordShowPage, status: OnboardingStatus.Completed, res: false },
{ loc: AppPath.RecordShowPage, status: OnboardingStatus.CompletedWithoutSubscription, res: false }, { loc: AppPath.RecordShowPage, status: OnboardingStatus.CompletedWithoutSubscription, res: false },
@@ -176,6 +200,7 @@ const testCases = [
{ loc: AppPath.SettingsCatchAll, status: OnboardingStatus.OngoingUserCreation, res: true }, { loc: AppPath.SettingsCatchAll, status: OnboardingStatus.OngoingUserCreation, res: true },
{ loc: AppPath.SettingsCatchAll, status: OnboardingStatus.OngoingWorkspaceActivation, res: true }, { loc: AppPath.SettingsCatchAll, status: OnboardingStatus.OngoingWorkspaceActivation, res: true },
{ loc: AppPath.SettingsCatchAll, status: OnboardingStatus.OngoingProfileCreation, res: true }, { loc: AppPath.SettingsCatchAll, status: OnboardingStatus.OngoingProfileCreation, res: true },
{ loc: AppPath.SettingsCatchAll, status: OnboardingStatus.OngoingSyncEmail, res: true },
{ loc: AppPath.SettingsCatchAll, status: OnboardingStatus.Completed, res: false }, { loc: AppPath.SettingsCatchAll, status: OnboardingStatus.Completed, res: false },
{ loc: AppPath.SettingsCatchAll, status: OnboardingStatus.CompletedWithoutSubscription, res: false }, { loc: AppPath.SettingsCatchAll, status: OnboardingStatus.CompletedWithoutSubscription, res: false },
@@ -186,6 +211,7 @@ const testCases = [
{ loc: AppPath.DevelopersCatchAll, status: OnboardingStatus.OngoingUserCreation, res: true }, { loc: AppPath.DevelopersCatchAll, status: OnboardingStatus.OngoingUserCreation, res: true },
{ loc: AppPath.DevelopersCatchAll, status: OnboardingStatus.OngoingWorkspaceActivation, res: true }, { loc: AppPath.DevelopersCatchAll, status: OnboardingStatus.OngoingWorkspaceActivation, res: true },
{ loc: AppPath.DevelopersCatchAll, status: OnboardingStatus.OngoingProfileCreation, res: true }, { loc: AppPath.DevelopersCatchAll, status: OnboardingStatus.OngoingProfileCreation, res: true },
{ loc: AppPath.DevelopersCatchAll, status: OnboardingStatus.OngoingSyncEmail, res: true },
{ loc: AppPath.DevelopersCatchAll, status: OnboardingStatus.Completed, res: false }, { loc: AppPath.DevelopersCatchAll, status: OnboardingStatus.Completed, res: false },
{ loc: AppPath.DevelopersCatchAll, status: OnboardingStatus.CompletedWithoutSubscription, res: false }, { loc: AppPath.DevelopersCatchAll, status: OnboardingStatus.CompletedWithoutSubscription, res: false },
@@ -196,6 +222,7 @@ const testCases = [
{ loc: AppPath.Impersonate, status: OnboardingStatus.OngoingUserCreation, res: true }, { loc: AppPath.Impersonate, status: OnboardingStatus.OngoingUserCreation, res: true },
{ loc: AppPath.Impersonate, status: OnboardingStatus.OngoingWorkspaceActivation, res: true }, { loc: AppPath.Impersonate, status: OnboardingStatus.OngoingWorkspaceActivation, res: true },
{ loc: AppPath.Impersonate, status: OnboardingStatus.OngoingProfileCreation, res: true }, { loc: AppPath.Impersonate, status: OnboardingStatus.OngoingProfileCreation, res: true },
{ loc: AppPath.Impersonate, status: OnboardingStatus.OngoingSyncEmail, res: true },
{ loc: AppPath.Impersonate, status: OnboardingStatus.Completed, res: false }, { loc: AppPath.Impersonate, status: OnboardingStatus.Completed, res: false },
{ loc: AppPath.Impersonate, status: OnboardingStatus.CompletedWithoutSubscription, res: false }, { loc: AppPath.Impersonate, status: OnboardingStatus.CompletedWithoutSubscription, res: false },
@@ -206,6 +233,7 @@ const testCases = [
{ loc: AppPath.Authorize, status: OnboardingStatus.OngoingUserCreation, res: true }, { loc: AppPath.Authorize, status: OnboardingStatus.OngoingUserCreation, res: true },
{ loc: AppPath.Authorize, status: OnboardingStatus.OngoingWorkspaceActivation, res: true }, { loc: AppPath.Authorize, status: OnboardingStatus.OngoingWorkspaceActivation, res: true },
{ loc: AppPath.Authorize, status: OnboardingStatus.OngoingProfileCreation, res: true }, { loc: AppPath.Authorize, status: OnboardingStatus.OngoingProfileCreation, res: true },
{ loc: AppPath.Authorize, status: OnboardingStatus.OngoingSyncEmail, res: true },
{ loc: AppPath.Authorize, status: OnboardingStatus.Completed, res: false }, { loc: AppPath.Authorize, status: OnboardingStatus.Completed, res: false },
{ loc: AppPath.Authorize, status: OnboardingStatus.CompletedWithoutSubscription, res: false }, { loc: AppPath.Authorize, status: OnboardingStatus.CompletedWithoutSubscription, res: false },
@@ -216,6 +244,7 @@ const testCases = [
{ loc: AppPath.NotFoundWildcard, status: OnboardingStatus.OngoingUserCreation, res: true }, { loc: AppPath.NotFoundWildcard, status: OnboardingStatus.OngoingUserCreation, res: true },
{ loc: AppPath.NotFoundWildcard, status: OnboardingStatus.OngoingWorkspaceActivation, res: true }, { loc: AppPath.NotFoundWildcard, status: OnboardingStatus.OngoingWorkspaceActivation, res: true },
{ loc: AppPath.NotFoundWildcard, status: OnboardingStatus.OngoingProfileCreation, res: true }, { loc: AppPath.NotFoundWildcard, status: OnboardingStatus.OngoingProfileCreation, res: true },
{ loc: AppPath.NotFoundWildcard, status: OnboardingStatus.OngoingSyncEmail, res: true },
{ loc: AppPath.NotFoundWildcard, status: OnboardingStatus.Completed, res: false }, { loc: AppPath.NotFoundWildcard, status: OnboardingStatus.Completed, res: false },
{ loc: AppPath.NotFoundWildcard, status: OnboardingStatus.CompletedWithoutSubscription, res: false }, { loc: AppPath.NotFoundWildcard, status: OnboardingStatus.CompletedWithoutSubscription, res: false },
@@ -226,6 +255,7 @@ const testCases = [
{ loc: AppPath.NotFound, status: OnboardingStatus.OngoingUserCreation, res: true }, { loc: AppPath.NotFound, status: OnboardingStatus.OngoingUserCreation, res: true },
{ loc: AppPath.NotFound, status: OnboardingStatus.OngoingWorkspaceActivation, res: true }, { loc: AppPath.NotFound, status: OnboardingStatus.OngoingWorkspaceActivation, res: true },
{ loc: AppPath.NotFound, status: OnboardingStatus.OngoingProfileCreation, res: true }, { loc: AppPath.NotFound, status: OnboardingStatus.OngoingProfileCreation, res: true },
{ loc: AppPath.NotFound, status: OnboardingStatus.OngoingSyncEmail, res: true },
{ loc: AppPath.NotFound, status: OnboardingStatus.Completed, res: false }, { loc: AppPath.NotFound, status: OnboardingStatus.Completed, res: false },
{ loc: AppPath.NotFound, status: OnboardingStatus.CompletedWithoutSubscription, res: false }, { loc: AppPath.NotFound, status: OnboardingStatus.CompletedWithoutSubscription, res: false },
]; ];

View File

@@ -27,7 +27,8 @@ export const useShowAuthModal = () => {
OnboardingStatus.Incomplete === onboardingStatus || OnboardingStatus.Incomplete === onboardingStatus ||
OnboardingStatus.OngoingUserCreation === onboardingStatus || OnboardingStatus.OngoingUserCreation === onboardingStatus ||
OnboardingStatus.OngoingProfileCreation === onboardingStatus || OnboardingStatus.OngoingProfileCreation === onboardingStatus ||
OnboardingStatus.OngoingWorkspaceActivation === onboardingStatus OnboardingStatus.OngoingWorkspaceActivation === onboardingStatus ||
OnboardingStatus.OngoingSyncEmail === onboardingStatus
) { ) {
return true; return true;
} }

View File

@@ -1,5 +1,4 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { useQuery } from '@apollo/client';
import { useRecoilState, useSetRecoilState } from 'recoil'; import { useRecoilState, useSetRecoilState } from 'recoil';
import { currentUserState } from '@/auth/states/currentUserState'; import { currentUserState } from '@/auth/states/currentUserState';
@@ -7,9 +6,8 @@ import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMembe
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { isCurrentUserLoadedState } from '@/auth/states/isCurrentUserLoadingState'; import { isCurrentUserLoadedState } from '@/auth/states/isCurrentUserLoadingState';
import { workspacesState } from '@/auth/states/workspaces'; import { workspacesState } from '@/auth/states/workspaces';
import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser';
import { ColorScheme } from '@/workspace-member/types/WorkspaceMember'; import { ColorScheme } from '@/workspace-member/types/WorkspaceMember';
import { User } from '~/generated/graphql'; import { useGetCurrentUserQuery } from '~/generated/graphql';
import { isDefined } from '~/utils/isDefined'; import { isDefined } from '~/utils/isDefined';
export const UserProviderEffect = () => { export const UserProviderEffect = () => {
@@ -26,9 +24,7 @@ export const UserProviderEffect = () => {
currentWorkspaceMemberState, currentWorkspaceMemberState,
); );
const { loading: queryLoading, data: queryData } = useQuery<{ const { loading: queryLoading, data: queryData } = useGetCurrentUserQuery({
currentUser: User;
}>(GET_CURRENT_USER, {
skip: isCurrentUserLoaded, skip: isCurrentUserLoaded,
}); });

View File

@@ -8,6 +8,9 @@ export const USER_QUERY_FRAGMENT = gql`
email email
canImpersonate canImpersonate
supportUserHash supportUserHash
state {
skipSyncEmailOnboardingStep
}
workspaceMember { workspaceMember {
id id
name { name {

View File

@@ -4,52 +4,7 @@ import { gql } from '@apollo/client';
export const GET_CURRENT_USER = gql` export const GET_CURRENT_USER = gql`
query GetCurrentUser { query GetCurrentUser {
currentUser { currentUser {
id ...UserQueryFragment
firstName
lastName
email
canImpersonate
supportUserHash
workspaceMember {
id
name {
firstName
lastName
}
colorScheme
avatarUrl
locale
}
defaultWorkspace {
id
displayName
logo
domainName
inviteHash
allowImpersonation
subscriptionStatus
activationStatus
featureFlags {
id
key
value
workspaceId
}
currentCacheVersion
currentBillingSubscription {
id
status
interval
}
}
workspaces {
workspace {
id
displayName
logo
domainName
}
}
} }
} }
`; `;

View File

@@ -1,6 +1,5 @@
import { useCallback } from 'react'; import { useCallback } from 'react';
import { Controller, SubmitHandler, useForm } from 'react-hook-form'; import { Controller, SubmitHandler, useForm } from 'react-hook-form';
import { useNavigate } from 'react-router-dom';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { useSetRecoilState } from 'recoil'; import { useSetRecoilState } from 'recoil';
@@ -16,7 +15,6 @@ import { OnboardingStatus } from '@/auth/utils/getOnboardingStatus';
import { FIND_MANY_OBJECT_METADATA_ITEMS } from '@/object-metadata/graphql/queries'; import { FIND_MANY_OBJECT_METADATA_ITEMS } from '@/object-metadata/graphql/queries';
import { useApolloMetadataClient } from '@/object-metadata/hooks/useApolloMetadataClient'; import { useApolloMetadataClient } from '@/object-metadata/hooks/useApolloMetadataClient';
import { WorkspaceLogoUploader } from '@/settings/workspace/components/WorkspaceLogoUploader'; import { WorkspaceLogoUploader } from '@/settings/workspace/components/WorkspaceLogoUploader';
import { AppPath } from '@/types/AppPath';
import { Loader } from '@/ui/feedback/loader/components/Loader'; import { Loader } from '@/ui/feedback/loader/components/Loader';
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar'; import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
@@ -47,8 +45,6 @@ const validationSchema = z
type Form = z.infer<typeof validationSchema>; type Form = z.infer<typeof validationSchema>;
export const CreateWorkspace = () => { export const CreateWorkspace = () => {
const navigate = useNavigate();
const { enqueueSnackBar } = useSnackBar(); const { enqueueSnackBar } = useSnackBar();
const onboardingStatus = useOnboardingStatus(); const onboardingStatus = useOnboardingStatus();
@@ -88,10 +84,6 @@ export const CreateWorkspace = () => {
if (isDefined(result.errors)) { if (isDefined(result.errors)) {
throw result.errors ?? new Error('Unknown error'); throw result.errors ?? new Error('Unknown error');
} }
setTimeout(() => {
navigate(AppPath.CreateProfile);
}, 20);
} catch (error: any) { } catch (error: any) {
enqueueSnackBar(error?.message, { enqueueSnackBar(error?.message, {
variant: SnackBarVariant.Error, variant: SnackBarVariant.Error,
@@ -102,7 +94,6 @@ export const CreateWorkspace = () => {
activateWorkspace, activateWorkspace,
setIsCurrentUserLoaded, setIsCurrentUserLoaded,
apolloMetadataClient, apolloMetadataClient,
navigate,
enqueueSnackBar, enqueueSnackBar,
], ],
); );

View File

@@ -0,0 +1,94 @@
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { useSetRecoilState } from 'recoil';
import { IconGoogle } from 'twenty-ui';
import { SubTitle } from '@/auth/components/SubTitle';
import { Title } from '@/auth/components/Title';
import { isCurrentUserLoadedState } from '@/auth/states/isCurrentUserLoadingState';
import { OnboardingSyncEmailsSettingsCard } from '@/onboarding/components/OnboardingSyncEmailsSettingsCard';
import { useTriggerGoogleApisOAuth } from '@/settings/accounts/hooks/useTriggerGoogleApisOAuth';
import { AppPath } from '@/types/AppPath';
import { MainButton } from '@/ui/input/button/components/MainButton';
import { ActionLink } from '@/ui/navigation/link/components/ActionLink';
import {
CalendarChannelVisibility,
MessageChannelVisibility,
useSkipSyncEmailOnboardingStepMutation,
} from '~/generated/graphql';
const StyledSyncEmailsContainer = styled.div`
display: flex;
flex-direction: row;
width: 100%;
margin: ${({ theme }) => theme.spacing(8)} 0;
gap: ${({ theme }) => theme.spacing(2)};
`;
const StyledActionLinkContainer = styled.div`
display: flex;
flex-direction: row;
margin: ${({ theme }) => theme.spacing(3)} 0 0;
`;
export const SyncEmails = () => {
const theme = useTheme();
const navigate = useNavigate();
const { triggerGoogleApisOAuth } = useTriggerGoogleApisOAuth();
const setIsCurrentUserLoaded = useSetRecoilState(isCurrentUserLoadedState);
const [visibility, setVisibility] = useState<MessageChannelVisibility>(
MessageChannelVisibility.ShareEverything,
);
const [skipSyncEmailOnboardingStepMutation] =
useSkipSyncEmailOnboardingStepMutation();
const handleButtonClick = async () => {
const calendarChannelVisibility =
visibility === MessageChannelVisibility.ShareEverything
? CalendarChannelVisibility.ShareEverything
: CalendarChannelVisibility.Metadata;
await triggerGoogleApisOAuth(
AppPath.Index,
visibility,
calendarChannelVisibility,
);
};
const continueWithoutSync = async () => {
await skipSyncEmailOnboardingStepMutation();
setIsCurrentUserLoaded(false);
navigate(AppPath.Index);
};
const isSubmitting = false;
return (
<>
<Title withMarginTop={false}>Emails and Calendar</Title>
<SubTitle>
Sync your Emails and Calendar with Twenty. Choose your privacy settings.
</SubTitle>
<StyledSyncEmailsContainer>
<OnboardingSyncEmailsSettingsCard
value={visibility}
onChange={setVisibility}
/>
</StyledSyncEmailsContainer>
<MainButton
title="Sync with Google"
onClick={handleButtonClick}
width={200}
Icon={() => <IconGoogle size={theme.icon.size.sm} />}
disabled={isSubmitting}
/>
<StyledActionLinkContainer>
<ActionLink onClick={continueWithoutSync}>
Continue without sync
</ActionLink>
</StyledActionLinkContainer>
</>
);
};

View File

@@ -5,6 +5,7 @@ import { graphql, HttpResponse } from 'msw';
import { AppPath } from '@/types/AppPath'; import { AppPath } from '@/types/AppPath';
import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser'; import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser';
import { ChooseYourPlan } from '~/pages/onboarding/ChooseYourPlan';
import { import {
PageDecorator, PageDecorator,
PageDecoratorArgs, PageDecoratorArgs,
@@ -13,10 +14,8 @@ import { graphqlMocks } from '~/testing/graphqlMocks';
import { mockedOnboardingUsersData } from '~/testing/mock-data/users'; import { mockedOnboardingUsersData } from '~/testing/mock-data/users';
import { sleep } from '~/testing/sleep'; import { sleep } from '~/testing/sleep';
import { ChooseYourPlan } from '../ChooseYourPlan';
const meta: Meta<PageDecoratorArgs> = { const meta: Meta<PageDecoratorArgs> = {
title: 'Pages/Auth/ChooseYourPlan', title: 'Pages/Onboarding/ChooseYourPlan',
component: ChooseYourPlan, component: ChooseYourPlan,
decorators: [PageDecorator], decorators: [PageDecorator],
args: { routePath: AppPath.PlanRequired }, args: { routePath: AppPath.PlanRequired },

View File

@@ -5,6 +5,7 @@ import { graphql, HttpResponse } from 'msw';
import { AppPath } from '@/types/AppPath'; import { AppPath } from '@/types/AppPath';
import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser'; import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser';
import { CreateProfile } from '~/pages/onboarding/CreateProfile';
import { import {
PageDecorator, PageDecorator,
PageDecoratorArgs, PageDecoratorArgs,
@@ -12,10 +13,8 @@ import {
import { graphqlMocks } from '~/testing/graphqlMocks'; import { graphqlMocks } from '~/testing/graphqlMocks';
import { mockedOnboardingUsersData } from '~/testing/mock-data/users'; import { mockedOnboardingUsersData } from '~/testing/mock-data/users';
import { CreateProfile } from '../CreateProfile';
const meta: Meta<PageDecoratorArgs> = { const meta: Meta<PageDecoratorArgs> = {
title: 'Pages/Auth/CreateProfile', title: 'Pages/Onboarding/CreateProfile',
component: CreateProfile, component: CreateProfile,
decorators: [PageDecorator], decorators: [PageDecorator],
args: { routePath: AppPath.CreateProfile }, args: { routePath: AppPath.CreateProfile },

View File

@@ -7,6 +7,7 @@ import { useSetRecoilState } from 'recoil';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { AppPath } from '@/types/AppPath'; import { AppPath } from '@/types/AppPath';
import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser'; import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser';
import { CreateWorkspace } from '~/pages/onboarding/CreateWorkspace';
import { import {
PageDecorator, PageDecorator,
PageDecoratorArgs, PageDecoratorArgs,
@@ -14,10 +15,8 @@ import {
import { graphqlMocks } from '~/testing/graphqlMocks'; import { graphqlMocks } from '~/testing/graphqlMocks';
import { mockedOnboardingUsersData } from '~/testing/mock-data/users'; import { mockedOnboardingUsersData } from '~/testing/mock-data/users';
import { CreateWorkspace } from '../CreateWorkspace';
const meta: Meta<PageDecoratorArgs> = { const meta: Meta<PageDecoratorArgs> = {
title: 'Pages/Auth/CreateWorkspace', title: 'Pages/Onboarding/CreateWorkspace',
component: CreateWorkspace, component: CreateWorkspace,
decorators: [ decorators: [
(Story) => { (Story) => {

View File

@@ -5,6 +5,7 @@ import { graphql, HttpResponse } from 'msw';
import { AppPath } from '@/types/AppPath'; import { AppPath } from '@/types/AppPath';
import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser'; import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser';
import { PaymentSuccess } from '~/pages/onboarding/PaymentSuccess';
import { import {
PageDecorator, PageDecorator,
PageDecoratorArgs, PageDecoratorArgs,
@@ -12,10 +13,8 @@ import {
import { graphqlMocks } from '~/testing/graphqlMocks'; import { graphqlMocks } from '~/testing/graphqlMocks';
import { mockedOnboardingUsersData } from '~/testing/mock-data/users'; import { mockedOnboardingUsersData } from '~/testing/mock-data/users';
import { PaymentSuccess } from '../PaymentSuccess';
const meta: Meta<PageDecoratorArgs> = { const meta: Meta<PageDecoratorArgs> = {
title: 'Pages/Auth/PaymentSuccess', title: 'Pages/Onboarding/PaymentSuccess',
component: PaymentSuccess, component: PaymentSuccess,
decorators: [PageDecorator], decorators: [PageDecorator],
args: { routePath: AppPath.PlanRequiredSuccess }, args: { routePath: AppPath.PlanRequiredSuccess },

View File

@@ -0,0 +1,46 @@
import { getOperationName } from '@apollo/client/utilities';
import { Meta, StoryObj } from '@storybook/react';
import { within } from '@storybook/test';
import { graphql, HttpResponse } from 'msw';
import { AppPath } from '~/modules/types/AppPath';
import { GET_CURRENT_USER } from '~/modules/users/graphql/queries/getCurrentUser';
import { SyncEmails } from '~/pages/onboarding/SyncEmails';
import {
PageDecorator,
PageDecoratorArgs,
} from '~/testing/decorators/PageDecorator';
import { graphqlMocks } from '~/testing/graphqlMocks';
import { mockedOnboardingUsersData } from '~/testing/mock-data/users';
const meta: Meta<PageDecoratorArgs> = {
title: 'Pages/Onboarding/SyncEmails',
component: SyncEmails,
decorators: [PageDecorator],
args: { routePath: AppPath.SyncEmails },
parameters: {
msw: {
handlers: [
graphql.query(getOperationName(GET_CURRENT_USER) ?? '', () => {
return HttpResponse.json({
data: {
currentUser: mockedOnboardingUsersData[0],
},
});
}),
graphqlMocks.handlers,
],
},
},
};
export default meta;
export type Story = StoryObj<typeof SyncEmails>;
export const Default: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await canvas.findByText('Emails and Calendar');
},
};

View File

@@ -17,10 +17,8 @@ import { SettingsPath } from '@/types/SettingsPath';
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'; import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
import { Section } from '@/ui/layout/section/components/Section'; import { Section } from '@/ui/layout/section/components/Section';
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb'; import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
import { import { CalendarChannelVisibility } from '~/generated/graphql';
TimelineCalendarEvent, import { TimelineCalendarEvent } from '~/generated-metadata/graphql';
TimelineCalendarEventVisibility,
} from '~/generated-metadata/graphql';
export const SettingsAccountsCalendars = () => { export const SettingsAccountsCalendars = () => {
const calendarSettingsEnabled = false; const calendarSettingsEnabled = false;
@@ -79,7 +77,7 @@ export const SettingsAccountsCalendars = () => {
isCanceled: false, isCanceled: false,
location: '', location: '',
title: 'Onboarding call', title: 'Onboarding call',
visibility: TimelineCalendarEventVisibility.ShareEverything, visibility: CalendarChannelVisibility.ShareEverything,
}; };
return ( return (

View File

@@ -4,10 +4,7 @@ import { useTheme } from '@emotion/react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { H2Title, IconRefresh, IconSettings, IconUser } from 'twenty-ui'; import { H2Title, IconRefresh, IconSettings, IconUser } from 'twenty-ui';
import { import { CalendarChannel } from '@/accounts/types/CalendarChannel';
CalendarChannel,
CalendarChannelVisibility,
} from '@/accounts/types/CalendarChannel';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord'; import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord'; import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
@@ -21,6 +18,7 @@ import { SettingsPath } from '@/types/SettingsPath';
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'; import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
import { Section } from '@/ui/layout/section/components/Section'; import { Section } from '@/ui/layout/section/components/Section';
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb'; import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
import { CalendarChannelVisibility } from '~/generated/graphql';
const StyledCardMedia = styled(SettingsAccountsCardMedia)` const StyledCardMedia = styled(SettingsAccountsCardMedia)`
height: ${({ theme }) => theme.spacing(6)}; height: ${({ theme }) => theme.spacing(6)};

View File

@@ -8,16 +8,14 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSi
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord'; import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord'; import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
import { SettingsAccountsCardMedia } from '@/settings/accounts/components/SettingsAccountsCardMedia'; import { SettingsAccountsCardMedia } from '@/settings/accounts/components/SettingsAccountsCardMedia';
import { import { SettingsAccountsInboxVisibilitySettingsCard } from '@/settings/accounts/components/SettingsAccountsInboxVisibilitySettingsCard';
InboxSettingsVisibilityValue,
SettingsAccountsInboxVisibilitySettingsCard,
} from '@/settings/accounts/components/SettingsAccountsInboxVisibilitySettingsCard';
import { SettingsAccountsToggleSettingCard } from '@/settings/accounts/components/SettingsAccountsToggleSettingCard'; import { SettingsAccountsToggleSettingCard } from '@/settings/accounts/components/SettingsAccountsToggleSettingCard';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { AppPath } from '@/types/AppPath'; import { AppPath } from '@/types/AppPath';
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'; import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
import { Section } from '@/ui/layout/section/components/Section'; import { Section } from '@/ui/layout/section/components/Section';
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb'; import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
import { MessageChannelVisibility } from '~/generated/graphql';
export const SettingsAccountsEmailsInboxSettings = () => { export const SettingsAccountsEmailsInboxSettings = () => {
const theme = useTheme(); const theme = useTheme();
@@ -33,7 +31,7 @@ export const SettingsAccountsEmailsInboxSettings = () => {
objectNameSingular: CoreObjectNameSingular.MessageChannel, objectNameSingular: CoreObjectNameSingular.MessageChannel,
}); });
const handleVisibilityChange = (value: InboxSettingsVisibilityValue) => { const handleVisibilityChange = (value: MessageChannelVisibility) => {
updateOneRecord({ updateOneRecord({
idToUpdate: messageChannelId, idToUpdate: messageChannelId,
updateOneRecordInput: { updateOneRecordInput: {

View File

@@ -2,6 +2,7 @@ import { Meta, StoryObj } from '@storybook/react';
import { within } from '@storybook/test'; import { within } from '@storybook/test';
import { graphql, HttpResponse } from 'msw'; import { graphql, HttpResponse } from 'msw';
import { MessageChannelVisibility } from '~/generated/graphql';
import { SettingsAccountsEmailsInboxSettings } from '~/pages/settings/accounts/SettingsAccountsEmailsInboxSettings'; import { SettingsAccountsEmailsInboxSettings } from '~/pages/settings/accounts/SettingsAccountsEmailsInboxSettings';
import { import {
PageDecorator, PageDecorator,
@@ -26,7 +27,7 @@ const meta: Meta<PageDecoratorArgs> = {
data: { data: {
messageChannel: { messageChannel: {
id: '1', id: '1',
visibility: 'share_everything', visibility: MessageChannelVisibility.ShareEverything,
messageThreads: { edges: [] }, messageThreads: { edges: [] },
createdAt: '2021-08-27T12:00:00Z', createdAt: '2021-08-27T12:00:00Z',
type: 'email', type: 'email',

View File

@@ -1,6 +1,7 @@
import { addDays, subHours, subMonths } from 'date-fns'; import { addDays, subHours, subMonths } from 'date-fns';
import { CalendarEvent } from '@/activities/calendar/types/CalendarEvent'; import { CalendarEvent } from '@/activities/calendar/types/CalendarEvent';
import { CalendarChannelVisibility } from '~/generated/graphql';
export const mockedCalendarEvents: CalendarEvent[] = [ export const mockedCalendarEvents: CalendarEvent[] = [
{ {
@@ -9,7 +10,7 @@ export const mockedCalendarEvents: CalendarEvent[] = [
id: '9a6b35f1-6078-415b-9540-f62671bb81d0', id: '9a6b35f1-6078-415b-9540-f62671bb81d0',
isFullDay: false, isFullDay: false,
startsAt: addDays(new Date().setHours(10, 0), 1).toISOString(), startsAt: addDays(new Date().setHours(10, 0), 1).toISOString(),
visibility: 'METADATA', visibility: CalendarChannelVisibility.Metadata,
calendarEventParticipants: [ calendarEventParticipants: [
{ {
id: '1', id: '1',
@@ -43,7 +44,7 @@ export const mockedCalendarEvents: CalendarEvent[] = [
isFullDay: false, isFullDay: false,
startsAt: new Date(new Date().setHours(18, 0)).toISOString(), startsAt: new Date(new Date().setHours(18, 0)).toISOString(),
title: 'Bug solving', title: 'Bug solving',
visibility: 'SHARE_EVERYTHING', visibility: CalendarChannelVisibility.ShareEverything,
__typename: 'CalendarEvent', __typename: 'CalendarEvent',
}, },
{ {
@@ -53,7 +54,7 @@ export const mockedCalendarEvents: CalendarEvent[] = [
isFullDay: false, isFullDay: false,
startsAt: new Date(new Date().setHours(15, 15)).toISOString(), startsAt: new Date(new Date().setHours(15, 15)).toISOString(),
title: 'Onboarding Follow-Up Call', title: 'Onboarding Follow-Up Call',
visibility: 'SHARE_EVERYTHING', visibility: CalendarChannelVisibility.ShareEverything,
__typename: 'CalendarEvent', __typename: 'CalendarEvent',
}, },
{ {
@@ -63,7 +64,7 @@ export const mockedCalendarEvents: CalendarEvent[] = [
isFullDay: false, isFullDay: false,
startsAt: new Date(new Date().setHours(10, 0)).toISOString(), startsAt: new Date(new Date().setHours(10, 0)).toISOString(),
title: 'Onboarding Call', title: 'Onboarding Call',
visibility: 'SHARE_EVERYTHING', visibility: CalendarChannelVisibility.ShareEverything,
__typename: 'CalendarEvent', __typename: 'CalendarEvent',
}, },
{ {
@@ -71,7 +72,7 @@ export const mockedCalendarEvents: CalendarEvent[] = [
id: '5a792d11-259a-4099-af51-59eb85e15d83', id: '5a792d11-259a-4099-af51-59eb85e15d83',
isFullDay: true, isFullDay: true,
startsAt: subMonths(new Date().setHours(8, 0), 1).toISOString(), startsAt: subMonths(new Date().setHours(8, 0), 1).toISOString(),
visibility: 'METADATA', visibility: CalendarChannelVisibility.Metadata,
__typename: 'CalendarEvent', __typename: 'CalendarEvent',
}, },
{ {
@@ -81,7 +82,7 @@ export const mockedCalendarEvents: CalendarEvent[] = [
isFullDay: false, isFullDay: false,
startsAt: subMonths(new Date().setHours(14, 0), 3).toISOString(), startsAt: subMonths(new Date().setHours(14, 0), 3).toISOString(),
title: 'Alan x Garry', title: 'Alan x Garry',
visibility: 'SHARE_EVERYTHING', visibility: CalendarChannelVisibility.ShareEverything,
__typename: 'CalendarEvent', __typename: 'CalendarEvent',
}, },
]; ];

View File

@@ -4,6 +4,7 @@ import {
ObjectEdge, ObjectEdge,
ObjectMetadataItemsQuery, ObjectMetadataItemsQuery,
} from '~/generated-metadata/graphql'; } from '~/generated-metadata/graphql';
import { CalendarChannelVisibility, MessageChannelVisibility } from "~/generated/graphql";
// This file is not designed to be manually edited. // This file is not designed to be manually edited.
// It's an extract from the dev seeded environment metadata call // It's an extract from the dev seeded environment metadata call
@@ -2924,20 +2925,20 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery =
isNullable: false, isNullable: false,
createdAt: '2024-04-08T12:48:49.538Z', createdAt: '2024-04-08T12:48:49.538Z',
updatedAt: '2024-04-08T12:48:49.538Z', updatedAt: '2024-04-08T12:48:49.538Z',
defaultValue: "'SHARE_EVERYTHING'", defaultValue: `'${CalendarChannelVisibility.ShareEverything}'`,
options: [ options: [
{ {
id: 'b60eeb97-c67b-4d01-b647-1143d58ed49f', id: 'b60eeb97-c67b-4d01-b647-1143d58ed49f',
color: 'green', color: 'green',
label: 'Metadata', label: 'Metadata',
value: 'METADATA', value: CalendarChannelVisibility.Metadata,
position: 0, position: 0,
}, },
{ {
id: '7064c804-deb0-4f65-b7d6-3fe4df9a474c', id: '7064c804-deb0-4f65-b7d6-3fe4df9a474c',
color: 'orange', color: 'orange',
label: 'Share Everything', label: 'Share Everything',
value: 'SHARE_EVERYTHING', value: CalendarChannelVisibility.ShareEverything,
position: 1, position: 1,
}, },
], ],
@@ -7044,27 +7045,27 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery =
isNullable: false, isNullable: false,
createdAt: '2024-04-08T12:48:49.538Z', createdAt: '2024-04-08T12:48:49.538Z',
updatedAt: '2024-04-08T12:48:49.538Z', updatedAt: '2024-04-08T12:48:49.538Z',
defaultValue: "'share_everything'", defaultValue: `'${MessageChannelVisibility.ShareEverything}'`,
options: [ options: [
{ {
id: '112e7633-0451-4f7e-bc79-f988b78fabb8', id: '112e7633-0451-4f7e-bc79-f988b78fabb8',
color: 'green', color: 'green',
label: 'Metadata', label: 'Metadata',
value: 'metadata', value: MessageChannelVisibility.Metadata,
position: 0, position: 0,
}, },
{ {
id: '148143c4-882d-4c94-b8db-ead6f4581ab1', id: '148143c4-882d-4c94-b8db-ead6f4581ab1',
color: 'blue', color: 'blue',
label: 'Subject', label: 'Subject',
value: 'subject', value: MessageChannelVisibility.Subject,
position: 1, position: 1,
}, },
{ {
id: '9bf90844-93cf-4c0a-95e9-02f7d5fa397f', id: '9bf90844-93cf-4c0a-95e9-02f7d5fa397f',
color: 'orange', color: 'orange',
label: 'Share Everything', label: 'Share Everything',
value: 'share_everything', value: MessageChannelVisibility.ShareEverything,
position: 2, position: 2,
}, },
], ],

View File

@@ -1,7 +1,5 @@
import { import { CalendarChannelVisibility } from '~/generated/graphql';
TimelineCalendarEvent, import { TimelineCalendarEvent } from '~/generated-metadata/graphql';
TimelineCalendarEventVisibility,
} from '~/generated-metadata/graphql';
export const mockedTimelineCalendarEvents: TimelineCalendarEvent[] = [ export const mockedTimelineCalendarEvents: TimelineCalendarEvent[] = [
{ {
@@ -18,7 +16,7 @@ export const mockedTimelineCalendarEvents: TimelineCalendarEvent[] = [
}, },
conferenceSolution: 'GOOGLE_MEET', conferenceSolution: 'GOOGLE_MEET',
isCanceled: false, isCanceled: false,
visibility: TimelineCalendarEventVisibility.ShareEverything, visibility: CalendarChannelVisibility.ShareEverything,
isFullDay: false, isFullDay: false,
participants: [ participants: [
{ {
@@ -58,7 +56,7 @@ export const mockedTimelineCalendarEvents: TimelineCalendarEvent[] = [
}, },
conferenceSolution: 'GOOGLE_MEET', conferenceSolution: 'GOOGLE_MEET',
isCanceled: false, isCanceled: false,
visibility: TimelineCalendarEventVisibility.Metadata, visibility: CalendarChannelVisibility.Metadata,
participants: [ participants: [
{ {
__typename: 'TimelineCalendarEventParticipant', __typename: 'TimelineCalendarEventParticipant',
@@ -87,7 +85,7 @@ export const mockedTimelineCalendarEvents: TimelineCalendarEvent[] = [
}, },
conferenceSolution: 'GOOGLE_MEET', conferenceSolution: 'GOOGLE_MEET',
isCanceled: false, isCanceled: false,
visibility: TimelineCalendarEventVisibility.Metadata, visibility: CalendarChannelVisibility.Metadata,
participants: [ participants: [
{ {
__typename: 'TimelineCalendarEventParticipant', __typename: 'TimelineCalendarEventParticipant',

View File

@@ -10,6 +10,7 @@ type MockedUser = Pick<
| 'canImpersonate' | 'canImpersonate'
| '__typename' | '__typename'
| 'supportUserHash' | 'supportUserHash'
| 'state'
> & { > & {
workspaceMember: WorkspaceMember | null; workspaceMember: WorkspaceMember | null;
locale: string; locale: string;
@@ -92,6 +93,7 @@ export const mockedUsersData: Array<MockedUser> = [
defaultWorkspace: mockDefaultWorkspace, defaultWorkspace: mockDefaultWorkspace,
locale: 'en', locale: 'en',
workspaces: [{ workspace: mockDefaultWorkspace }], workspaces: [{ workspace: mockDefaultWorkspace }],
state: { skipSyncEmailOnboardingStep: true },
}, },
{ {
id: '7dfbc3f7-6e5e-4128-957e-8d86808cdf6c', id: '7dfbc3f7-6e5e-4128-957e-8d86808cdf6c',
@@ -114,6 +116,7 @@ export const mockedUsersData: Array<MockedUser> = [
defaultWorkspace: mockDefaultWorkspace, defaultWorkspace: mockDefaultWorkspace,
locale: 'en', locale: 'en',
workspaces: [{ workspace: mockDefaultWorkspace }], workspaces: [{ workspace: mockDefaultWorkspace }],
state: { skipSyncEmailOnboardingStep: true },
}, },
]; ];
@@ -140,6 +143,7 @@ export const mockedOnboardingUsersData: Array<MockedUser> = [
defaultWorkspace: mockDefaultWorkspace, defaultWorkspace: mockDefaultWorkspace,
locale: 'en', locale: 'en',
workspaces: [{ workspace: mockDefaultWorkspace }], workspaces: [{ workspace: mockDefaultWorkspace }],
state: { skipSyncEmailOnboardingStep: true },
}, },
{ {
id: '7dfbc3f7-6e5e-4128-957e-8d86808cdf6d', id: '7dfbc3f7-6e5e-4128-957e-8d86808cdf6d',
@@ -155,5 +159,6 @@ export const mockedOnboardingUsersData: Array<MockedUser> = [
}, },
locale: 'en', locale: 'en',
workspaces: [{ workspace: mockDefaultWorkspace }], workspaces: [{ workspace: mockDefaultWorkspace }],
state: { skipSyncEmailOnboardingStep: true },
}, },
]; ];

View File

@@ -15,10 +15,11 @@ import { StopDataSeedDemoWorkspaceCronCommand } from 'src/database/commands/data
import { WorkspaceAddTotalCountCommand } from 'src/database/commands/workspace-add-total-count.command'; import { WorkspaceAddTotalCountCommand } from 'src/database/commands/workspace-add-total-count.command';
import { DataSeedDemoWorkspaceCommand } from 'src/database/commands/data-seed-demo-workspace/data-seed-demo-workspace-command'; import { DataSeedDemoWorkspaceCommand } from 'src/database/commands/data-seed-demo-workspace/data-seed-demo-workspace-command';
import { DataSeedDemoWorkspaceModule } from 'src/database/commands/data-seed-demo-workspace/data-seed-demo-workspace.module'; import { DataSeedDemoWorkspaceModule } from 'src/database/commands/data-seed-demo-workspace/data-seed-demo-workspace.module';
import { UpdateMessageChannelSyncStatusEnumCommand } from 'src/database/commands/0-20-update-message-channel-sync-status-enum.command';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { UpdateMessageChannelVisibilityEnumCommand } from 'src/database/commands/update-message-channel-visibility-enum.command';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { WorkspaceCacheVersionModule } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.module'; import { WorkspaceCacheVersionModule } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.module';
import { UpdateMessageChannelSyncStatusEnumCommand } from 'src/database/commands/0-20-update-message-channel-sync-status-enum.command';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
@Module({ @Module({
@@ -45,6 +46,7 @@ import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadat
ConfirmationQuestion, ConfirmationQuestion,
StartDataSeedDemoWorkspaceCronCommand, StartDataSeedDemoWorkspaceCronCommand,
StopDataSeedDemoWorkspaceCronCommand, StopDataSeedDemoWorkspaceCronCommand,
UpdateMessageChannelVisibilityEnumCommand,
UpdateMessageChannelSyncStatusEnumCommand, UpdateMessageChannelSyncStatusEnumCommand,
], ],
}) })

View File

@@ -0,0 +1,166 @@
import { InjectRepository } from '@nestjs/typeorm';
import { Logger } from '@nestjs/common';
import { Command, CommandRunner, Option } from 'nest-commander';
import { Repository } from 'typeorm';
import chalk from 'chalk';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { TypeORMService } from 'src/database/typeorm/typeorm.service';
import { MessageChannelVisibility } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { WorkspaceCacheVersionService } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.service';
interface UpdateMessageChannelVisibilityEnumCommandOptions {
workspaceId?: string;
}
@Command({
name: 'migrate-0.20:update-message-channel-visibility-enum',
description:
'Change the messageChannel visibility type and update records.visibility',
})
export class UpdateMessageChannelVisibilityEnumCommand extends CommandRunner {
private readonly logger = new Logger(
UpdateMessageChannelVisibilityEnumCommand.name,
);
constructor(
@InjectRepository(Workspace, 'core')
private readonly workspaceRepository: Repository<Workspace>,
@InjectRepository(FieldMetadataEntity, 'metadata')
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
private readonly typeORMService: TypeORMService,
private readonly dataSourceService: DataSourceService,
private readonly workspaceCacheVersionService: WorkspaceCacheVersionService,
) {
super();
}
@Option({
flags: '-w, --workspace-id [workspace_id]',
description: 'workspace id. Command runs on all workspaces if not provided',
required: false,
})
parseWorkspaceId(value: string): string {
return value;
}
async run(
_passedParam: string[],
options: UpdateMessageChannelVisibilityEnumCommandOptions,
): Promise<void> {
let workspaceIds: string[] = [];
if (options.workspaceId) {
workspaceIds = [options.workspaceId];
} else {
workspaceIds = (await this.workspaceRepository.find()).map(
(workspace) => workspace.id,
);
}
if (!workspaceIds.length) {
this.logger.log(chalk.yellow('No workspace found'));
return;
} else {
this.logger.log(
chalk.green(`Running command on ${workspaceIds.length} workspaces`),
);
}
for (const workspaceId of workspaceIds) {
const dataSourceMetadatas =
await this.dataSourceService.getDataSourcesMetadataFromWorkspaceId(
workspaceId,
);
for (const dataSourceMetadata of dataSourceMetadatas) {
const workspaceDataSource =
await this.typeORMService.connectToDataSource(dataSourceMetadata);
if (workspaceDataSource) {
const queryRunner = workspaceDataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
const newMessageChannelVisibilities = Object.values(
MessageChannelVisibility,
);
try {
await queryRunner.query(
`ALTER TYPE "${dataSourceMetadata.schema}"."messageChannel_visibility_enum" RENAME TO "messageChannel_visibility_enum_old"`,
);
await queryRunner.query(
`CREATE TYPE "${
dataSourceMetadata.schema
}"."messageChannel_visibility_enum" AS ENUM ('${newMessageChannelVisibilities.join(
"','",
)}')`,
);
await queryRunner.query(
`ALTER TABLE "${dataSourceMetadata.schema}"."messageChannel" ALTER COLUMN "visibility" DROP DEFAULT`,
);
await queryRunner.query(
`ALTER TABLE "${dataSourceMetadata.schema}"."messageChannel" ALTER COLUMN "visibility" TYPE text`,
);
for (const newMessageChannelVisibility of newMessageChannelVisibilities) {
await queryRunner.query(
`UPDATE "${
dataSourceMetadata.schema
}"."messageChannel" SET "visibility" = '${newMessageChannelVisibility}' WHERE "visibility" = '${newMessageChannelVisibility.toLowerCase()}'`,
);
}
await queryRunner.query(
`ALTER TABLE "${dataSourceMetadata.schema}"."messageChannel" ALTER COLUMN "visibility" TYPE "${dataSourceMetadata.schema}"."messageChannel_visibility_enum" USING "visibility"::text::"${dataSourceMetadata.schema}"."messageChannel_visibility_enum"`,
);
await queryRunner.query(
`ALTER TABLE "${dataSourceMetadata.schema}"."messageChannel" ALTER COLUMN "visibility" SET DEFAULT '${MessageChannelVisibility.SHARE_EVERYTHING}'`,
);
await queryRunner.query(
`DROP TYPE "${dataSourceMetadata.schema}"."messageChannel_visibility_enum_old"`,
);
await queryRunner.commitTransaction();
} catch (error) {
await queryRunner.rollbackTransaction();
this.logger.log(
chalk.red(`Running command on workspace ${workspaceId} failed`),
);
throw error;
} finally {
await queryRunner.release();
}
}
}
await this.workspaceCacheVersionService.incrementVersion(workspaceId);
const visibilityFieldsMetadata = await this.fieldMetadataRepository.find({
where: { name: 'visibility', workspaceId },
});
for (const visibilityFieldMetadata of visibilityFieldsMetadata) {
const newOptions = visibilityFieldMetadata.options.map((option) => {
return { ...option, value: option.value.toUpperCase() };
});
const newDefaultValue =
typeof visibilityFieldMetadata.defaultValue === 'string'
? visibilityFieldMetadata.defaultValue.toUpperCase()
: visibilityFieldMetadata.defaultValue;
await this.fieldMetadataRepository.update(visibilityFieldMetadata.id, {
defaultValue: newDefaultValue,
options: newOptions,
});
}
this.logger.log(
chalk.green(`Running command on workspace ${workspaceId} done`),
);
}
this.logger.log(chalk.green(`Command completed!`));
}
}

View File

@@ -1,6 +1,7 @@
import { EntityManager } from 'typeorm'; import { EntityManager } from 'typeorm';
import { DEV_SEED_CONNECTED_ACCOUNT_IDS } from 'src/database/typeorm-seeds/workspace/connected-account'; import { DEV_SEED_CONNECTED_ACCOUNT_IDS } from 'src/database/typeorm-seeds/workspace/connected-account';
import { CalendarChannelVisibility } from 'src/modules/calendar/standard-objects/calendar-channel.workspace-entity';
const tableName = 'calendarChannel'; const tableName = 'calendarChannel';
@@ -25,7 +26,7 @@ export const seedCalendarChannels = async (
id: '59efdefe-a40f-4faf-bb9f-c6f9945b8203', id: '59efdefe-a40f-4faf-bb9f-c6f9945b8203',
connectedAccountId: DEV_SEED_CONNECTED_ACCOUNT_IDS.TIM, connectedAccountId: DEV_SEED_CONNECTED_ACCOUNT_IDS.TIM,
handle: 'tim@apple.com', handle: 'tim@apple.com',
visibility: 'SHARE_EVERYTHING', visibility: CalendarChannelVisibility.SHARE_EVERYTHING,
isContactAutoCreationEnabled: true, isContactAutoCreationEnabled: true,
isSyncEnabled: true, isSyncEnabled: true,
}, },

View File

@@ -1,7 +1,10 @@
import { EntityManager } from 'typeorm'; import { EntityManager } from 'typeorm';
import { DEV_SEED_CONNECTED_ACCOUNT_IDS } from 'src/database/typeorm-seeds/workspace/connected-account'; import { DEV_SEED_CONNECTED_ACCOUNT_IDS } from 'src/database/typeorm-seeds/workspace/connected-account';
import { MessageChannelSyncStage } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; import {
MessageChannelSyncStage,
MessageChannelVisibility,
} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
const tableName = 'messageChannel'; const tableName = 'messageChannel';
@@ -41,8 +44,8 @@ export const seedMessageChannel = async (
type: 'email', type: 'email',
connectedAccountId: DEV_SEED_CONNECTED_ACCOUNT_IDS.TIM, connectedAccountId: DEV_SEED_CONNECTED_ACCOUNT_IDS.TIM,
handle: 'tim@apple.dev', handle: 'tim@apple.dev',
visibility: 'share_everything', visibility: MessageChannelVisibility.SHARE_EVERYTHING,
syncStage: MessageChannelSyncStage.FULL_MESSAGE_LIST_FETCH_PENDING, syncSubStatus: MessageChannelSyncStage.FULL_MESSAGE_LIST_FETCH_PENDING,
}, },
{ {
id: DEV_SEED_MESSAGE_CHANNEL_IDS.JONY, id: DEV_SEED_MESSAGE_CHANNEL_IDS.JONY,
@@ -53,8 +56,8 @@ export const seedMessageChannel = async (
type: 'email', type: 'email',
connectedAccountId: DEV_SEED_CONNECTED_ACCOUNT_IDS.JONY, connectedAccountId: DEV_SEED_CONNECTED_ACCOUNT_IDS.JONY,
handle: 'jony.ive@apple.dev', handle: 'jony.ive@apple.dev',
visibility: 'share_everything', visibility: MessageChannelVisibility.SHARE_EVERYTHING,
syncStage: MessageChannelSyncStage.FULL_MESSAGE_LIST_FETCH_PENDING, syncSubStatus: MessageChannelSyncStage.FULL_MESSAGE_LIST_FETCH_PENDING,
}, },
{ {
id: DEV_SEED_MESSAGE_CHANNEL_IDS.PHIL, id: DEV_SEED_MESSAGE_CHANNEL_IDS.PHIL,
@@ -65,8 +68,8 @@ export const seedMessageChannel = async (
type: 'email', type: 'email',
connectedAccountId: DEV_SEED_CONNECTED_ACCOUNT_IDS.PHIL, connectedAccountId: DEV_SEED_CONNECTED_ACCOUNT_IDS.PHIL,
handle: 'phil.schiler@apple.dev', handle: 'phil.schiler@apple.dev',
visibility: 'share_everything', visibility: MessageChannelVisibility.SHARE_EVERYTHING,
syncStage: MessageChannelSyncStage.FULL_MESSAGE_LIST_FETCH_PENDING, syncSubStatus: MessageChannelSyncStage.FULL_MESSAGE_LIST_FETCH_PENDING,
}, },
]) ])
.execute(); .execute();

View File

@@ -0,0 +1,27 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class AddKeyValuePairTable1717425967770 implements MigrationInterface {
name = 'AddKeyValuePairTable1717425967770';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TABLE "core"."keyValuePair" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "userId" uuid, "workspaceId" uuid, "key" text NOT NULL, "value" text, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP WITH TIME ZONE, CONSTRAINT "IndexOnKeyUserIdWorkspaceIdUnique" UNIQUE ("key", "userId", "workspaceId"), CONSTRAINT "PK_c5a1ca828435d3eaf8f9361ed4b" PRIMARY KEY ("id"))`,
);
await queryRunner.query(
`ALTER TABLE "core"."keyValuePair" ADD CONSTRAINT "FK_0dae35d1c0fbdda6495be4ae71a" FOREIGN KEY ("userId") REFERENCES "core"."user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "core"."keyValuePair" ADD CONSTRAINT "FK_c137e3d8b3980901e114941daa2" FOREIGN KEY ("workspaceId") REFERENCES "core"."workspace"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "core"."keyValuePair" DROP CONSTRAINT "FK_c137e3d8b3980901e114941daa2"`,
);
await queryRunner.query(
`ALTER TABLE "core"."keyValuePair" DROP CONSTRAINT "FK_0dae35d1c0fbdda6495be4ae71a"`,
);
await queryRunner.query(`DROP TABLE "core"."keyValuePair"`);
}
}

View File

@@ -11,6 +11,7 @@ import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-
import { BillingSubscription } from 'src/engine/core-modules/billing/entities/billing-subscription.entity'; import { BillingSubscription } from 'src/engine/core-modules/billing/entities/billing-subscription.entity';
import { BillingSubscriptionItem } from 'src/engine/core-modules/billing/entities/billing-subscription-item.entity'; import { BillingSubscriptionItem } from 'src/engine/core-modules/billing/entities/billing-subscription-item.entity';
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
import { KeyValuePair } from 'src/engine/core-modules/key-value-pair/key-value-pair.entity';
@Injectable() @Injectable()
export class TypeORMService implements OnModuleInit, OnModuleDestroy { export class TypeORMService implements OnModuleInit, OnModuleDestroy {
@@ -29,6 +30,7 @@ export class TypeORMService implements OnModuleInit, OnModuleDestroy {
Workspace, Workspace,
UserWorkspace, UserWorkspace,
AppToken, AppToken,
KeyValuePair,
FeatureFlagEntity, FeatureFlagEntity,
BillingSubscription, BillingSubscription,
BillingSubscriptionItem, BillingSubscriptionItem,

View File

@@ -27,6 +27,7 @@ import { ObjectMetadataRepositoryModule } from 'src/engine/object-metadata-repos
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
import { CalendarChannelWorkspaceEntity } from 'src/modules/calendar/standard-objects/calendar-channel.workspace-entity'; import { CalendarChannelWorkspaceEntity } from 'src/modules/calendar/standard-objects/calendar-channel.workspace-entity';
import { MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; import { MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
import { UserStateModule } from 'src/engine/core-modules/user-state/user-state.module';
import { AuthResolver } from './auth.resolver'; import { AuthResolver } from './auth.resolver';
@@ -63,6 +64,7 @@ const jwtModule = JwtModule.registerAsync({
]), ]),
HttpModule, HttpModule,
UserWorkspaceModule, UserWorkspaceModule,
UserStateModule,
], ],
controllers: [ controllers: [
GoogleAuthController, GoogleAuthController,

View File

@@ -15,6 +15,10 @@ import { GoogleAPIsRequest } from 'src/engine/core-modules/auth/strategies/googl
import { GoogleAPIsService } from 'src/engine/core-modules/auth/services/google-apis.service'; import { GoogleAPIsService } from 'src/engine/core-modules/auth/services/google-apis.service';
import { TokenService } from 'src/engine/core-modules/auth/services/token.service'; import { TokenService } from 'src/engine/core-modules/auth/services/token.service';
import { EnvironmentService } from 'src/engine/integrations/environment/environment.service'; import { EnvironmentService } from 'src/engine/integrations/environment/environment.service';
import { UserStateService } from 'src/engine/core-modules/user-state/user-state.service';
import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator';
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
import { WorkspaceMemberRepository } from 'src/modules/workspace-member/repositories/workspace-member.repository';
@Controller('auth/google-apis') @Controller('auth/google-apis')
export class GoogleAPIsAuthController { export class GoogleAPIsAuthController {
@@ -22,6 +26,9 @@ export class GoogleAPIsAuthController {
private readonly googleAPIsService: GoogleAPIsService, private readonly googleAPIsService: GoogleAPIsService,
private readonly tokenService: TokenService, private readonly tokenService: TokenService,
private readonly environmentService: EnvironmentService, private readonly environmentService: EnvironmentService,
private readonly userStateService: UserStateService,
@InjectObjectMetadataRepository(WorkspaceMemberWorkspaceEntity)
private readonly workspaceMemberService: WorkspaceMemberRepository,
) {} ) {}
@Get() @Get()
@@ -39,7 +46,15 @@ export class GoogleAPIsAuthController {
) { ) {
const { user } = req; const { user } = req;
const { email, accessToken, refreshToken, transientToken } = user; const {
email,
accessToken,
refreshToken,
transientToken,
redirectLocation,
calendarVisibility,
messageVisibility,
} = user;
const { workspaceMemberId, workspaceId } = const { workspaceMemberId, workspaceId } =
await this.tokenService.verifyTransientToken(transientToken); await this.tokenService.verifyTransientToken(transientToken);
@@ -62,10 +77,25 @@ export class GoogleAPIsAuthController {
workspaceId: workspaceId, workspaceId: workspaceId,
accessToken, accessToken,
refreshToken, refreshToken,
calendarVisibility,
messageVisibility,
}); });
const userId = (
await this.workspaceMemberService.find(workspaceMemberId, workspaceId)
)?.userId;
if (userId) {
await this.userStateService.skipSyncEmailOnboardingStep(
userId,
workspaceId,
);
}
return res.redirect( return res.redirect(
`${this.environmentService.get('FRONT_BASE_URL')}/settings/accounts`, `${this.environmentService.get('FRONT_BASE_URL')}${
redirectLocation || '/settings/accounts'
}`,
); );
} }
} }

View File

@@ -12,10 +12,26 @@ export class GoogleAPIsOauthGuard extends AuthGuard('google-apis') {
async canActivate(context: ExecutionContext) { async canActivate(context: ExecutionContext) {
const request = context.switchToHttp().getRequest(); const request = context.switchToHttp().getRequest();
const transientToken = request.query.transientToken; const transientToken = request.query.transientToken;
const redirectLocation = request.query.redirectLocation;
const calendarVisibility = request.query.calendarVisibility;
const messageVisibility = request.query.messageVisibility;
if (transientToken && typeof transientToken === 'string') { if (transientToken && typeof transientToken === 'string') {
request.params.transientToken = transientToken; request.params.transientToken = transientToken;
} }
if (redirectLocation && typeof redirectLocation === 'string') {
request.params.redirectLocation = redirectLocation;
}
if (calendarVisibility && typeof calendarVisibility === 'string') {
request.params.calendarVisibility = calendarVisibility;
}
if (messageVisibility && typeof messageVisibility === 'string') {
request.params.messageVisibility = messageVisibility;
}
const activate = (await super.canActivate(context)) as boolean; const activate = (await super.canActivate(context)) as boolean;
return activate; return activate;

View File

@@ -58,8 +58,16 @@ export class GoogleAPIsService {
workspaceId: string; workspaceId: string;
accessToken: string; accessToken: string;
refreshToken: string; refreshToken: string;
calendarVisibility: CalendarChannelVisibility | undefined;
messageVisibility: MessageChannelVisibility | undefined;
}) { }) {
const { handle, workspaceId, workspaceMemberId } = input; const {
handle,
workspaceId,
workspaceMemberId,
calendarVisibility,
messageVisibility,
} = input;
const dataSourceMetadata = const dataSourceMetadata =
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail( await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail(
@@ -104,7 +112,8 @@ export class GoogleAPIsService {
connectedAccountId: newOrExistingConnectedAccountId, connectedAccountId: newOrExistingConnectedAccountId,
type: MessageChannelType.EMAIL, type: MessageChannelType.EMAIL,
handle, handle,
visibility: MessageChannelVisibility.SHARE_EVERYTHING, visibility:
messageVisibility || MessageChannelVisibility.SHARE_EVERYTHING,
}, },
workspaceId, workspaceId,
manager, manager,
@@ -116,7 +125,9 @@ export class GoogleAPIsService {
id: v4(), id: v4(),
connectedAccountId: newOrExistingConnectedAccountId, connectedAccountId: newOrExistingConnectedAccountId,
handle, handle,
visibility: CalendarChannelVisibility.SHARE_EVERYTHING, visibility:
calendarVisibility ||
CalendarChannelVisibility.SHARE_EVERYTHING,
}, },
workspaceId, workspaceId,
manager, manager,

View File

@@ -5,6 +5,8 @@ import { Strategy, VerifyCallback } from 'passport-google-oauth20';
import { Request } from 'express'; import { Request } from 'express';
import { EnvironmentService } from 'src/engine/integrations/environment/environment.service'; import { EnvironmentService } from 'src/engine/integrations/environment/environment.service';
import { CalendarChannelVisibility } from 'src/modules/calendar/standard-objects/calendar-channel.workspace-entity';
import { MessageChannelVisibility } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
export type GoogleAPIsRequest = Omit< export type GoogleAPIsRequest = Omit<
Request, Request,
@@ -19,6 +21,9 @@ export type GoogleAPIsRequest = Omit<
accessToken: string; accessToken: string;
refreshToken: string; refreshToken: string;
transientToken: string; transientToken: string;
redirectLocation?: string;
calendarVisibility?: CalendarChannelVisibility;
messageVisibility?: MessageChannelVisibility;
}; };
}; };
@@ -64,6 +69,9 @@ export class GoogleAPIsStrategy extends PassportStrategy(
prompt: 'consent', prompt: 'consent',
state: JSON.stringify({ state: JSON.stringify({
transientToken: req.params.transientToken, transientToken: req.params.transientToken,
redirectLocation: req.params.redirectLocation,
calendarVisibility: req.params.calendarVisibility,
messageVisibility: req.params.messageVisibility,
}), }),
}; };
@@ -92,6 +100,9 @@ export class GoogleAPIsStrategy extends PassportStrategy(
accessToken, accessToken,
refreshToken, refreshToken,
transientToken: state.transientToken, transientToken: state.transientToken,
redirectLocation: state.redirectLocation,
calendarVisibility: state.calendarVisibility,
messageVisibility: state.messageVisibility,
}; };
done(null, user); done(null, user);

View File

@@ -2,15 +2,11 @@ import { ObjectType, Field, registerEnumType } from '@nestjs/graphql';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import { TimelineCalendarEventParticipant } from 'src/engine/core-modules/calendar/dtos/timeline-calendar-event-participant.dto'; import { TimelineCalendarEventParticipant } from 'src/engine/core-modules/calendar/dtos/timeline-calendar-event-participant.dto';
import { CalendarChannelVisibility } from 'src/modules/calendar/standard-objects/calendar-channel.workspace-entity';
export enum TimelineCalendarEventVisibility { registerEnumType(CalendarChannelVisibility, {
METADATA = 'METADATA', name: 'CalendarChannelVisibility',
SHARE_EVERYTHING = 'SHARE_EVERYTHING', description: 'Visibility of the calendar channel',
}
registerEnumType(TimelineCalendarEventVisibility, {
name: 'TimelineCalendarEventVisibility',
description: 'Visibility of the calendar event',
}); });
@ObjectType('LinkMetadata') @ObjectType('LinkMetadata')
@@ -57,6 +53,6 @@ export class TimelineCalendarEvent {
@Field(() => [TimelineCalendarEventParticipant]) @Field(() => [TimelineCalendarEventParticipant])
participants: TimelineCalendarEventParticipant[]; participants: TimelineCalendarEventParticipant[];
@Field(() => TimelineCalendarEventVisibility) @Field(() => CalendarChannelVisibility)
visibility: TimelineCalendarEventVisibility; visibility: CalendarChannelVisibility;
} }

View File

@@ -4,12 +4,12 @@ import { Any } from 'typeorm';
import omit from 'lodash.omit'; import omit from 'lodash.omit';
import { TIMELINE_CALENDAR_EVENTS_DEFAULT_PAGE_SIZE } from 'src/engine/core-modules/calendar/constants/calendar.constants'; import { TIMELINE_CALENDAR_EVENTS_DEFAULT_PAGE_SIZE } from 'src/engine/core-modules/calendar/constants/calendar.constants';
import { TimelineCalendarEventVisibility } from 'src/engine/core-modules/calendar/dtos/timeline-calendar-event.dto';
import { TimelineCalendarEventsWithTotal } from 'src/engine/core-modules/calendar/dtos/timeline-calendar-events-with-total.dto'; import { TimelineCalendarEventsWithTotal } from 'src/engine/core-modules/calendar/dtos/timeline-calendar-events-with-total.dto';
import { InjectWorkspaceRepository } from 'src/engine/twenty-orm/decorators/inject-workspace-repository.decorator'; import { InjectWorkspaceRepository } from 'src/engine/twenty-orm/decorators/inject-workspace-repository.decorator';
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository'; import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
import { CalendarEventWorkspaceEntity } from 'src/modules/calendar/standard-objects/calendar-event.workspace-entity'; import { CalendarEventWorkspaceEntity } from 'src/modules/calendar/standard-objects/calendar-event.workspace-entity';
import { PersonWorkspaceEntity } from 'src/modules/person/standard-objects/person.workspace-entity'; import { PersonWorkspaceEntity } from 'src/modules/person/standard-objects/person.workspace-entity';
import { CalendarChannelVisibility } from 'src/modules/calendar/standard-objects/calendar-channel.workspace-entity';
@Injectable() @Injectable()
export class TimelineCalendarEventService { export class TimelineCalendarEventService {
@@ -107,8 +107,8 @@ export class TimelineCalendarEventService {
const visibility = event.calendarChannelEventAssociations.some( const visibility = event.calendarChannelEventAssociations.some(
(association) => association.calendarChannel.visibility === 'METADATA', (association) => association.calendarChannel.visibility === 'METADATA',
) )
? TimelineCalendarEventVisibility.METADATA ? CalendarChannelVisibility.METADATA
: TimelineCalendarEventVisibility.SHARE_EVERYTHING; : CalendarChannelVisibility.SHARE_EVERYTHING;
return { return {
...omit(event, [ ...omit(event, [

View File

@@ -0,0 +1,62 @@
import { Field, ObjectType } from '@nestjs/graphql';
import {
Column,
CreateDateColumn,
Entity,
JoinColumn,
ManyToOne,
PrimaryGeneratedColumn,
Relation,
Unique,
UpdateDateColumn,
} from 'typeorm';
import { IDField } from '@ptc-org/nestjs-query-graphql';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import { User } from 'src/engine/core-modules/user/user.entity';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
@Entity({ name: 'keyValuePair', schema: 'core' })
@ObjectType('KeyValuePair')
@Unique('IndexOnKeyUserIdWorkspaceIdUnique', ['key', 'userId', 'workspaceId'])
export class KeyValuePair {
@IDField(() => UUIDScalarType)
@PrimaryGeneratedColumn('uuid')
id: string;
@ManyToOne(() => User, (user) => user.keyValuePairs, {
onDelete: 'CASCADE',
})
@JoinColumn({ name: 'userId' })
user: Relation<User>;
@Column({ nullable: true })
userId: string;
@ManyToOne(() => Workspace, (workspace) => workspace.keyValuePairs, {
onDelete: 'CASCADE',
})
@JoinColumn({ name: 'workspaceId' })
workspace: Relation<Workspace>;
@Column({ nullable: true })
workspaceId: string;
@Field(() => String)
@Column({ nullable: false, type: 'text' })
key: string;
@Field(() => String, { nullable: true })
@Column({ nullable: true, type: 'text' })
value: string;
@CreateDateColumn({ type: 'timestamptz' })
createdAt: Date;
@UpdateDateColumn({ type: 'timestamptz' })
updatedAt: Date;
@Column({ nullable: true, type: 'timestamptz' })
deletedAt: Date | null;
}

View File

@@ -0,0 +1,22 @@
import { Module } from '@nestjs/common';
import { NestjsQueryGraphQLModule } from '@ptc-org/nestjs-query-graphql';
import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm';
import { KeyValuePairService } from 'src/engine/core-modules/key-value-pair/key-value-pair.service';
import { KeyValuePair } from 'src/engine/core-modules/key-value-pair/key-value-pair.entity';
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
@Module({
imports: [
NestjsQueryGraphQLModule.forFeature({
imports: [
NestjsQueryTypeOrmModule.forFeature([KeyValuePair], 'core'),
TypeORMModule,
],
}),
],
exports: [KeyValuePairService],
providers: [KeyValuePairService],
})
export class KeyValuePairModule {}

View File

@@ -0,0 +1,55 @@
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { KeyValuePair } from 'src/engine/core-modules/key-value-pair/key-value-pair.entity';
import { UserStates } from 'src/engine/core-modules/user-state/enums/user-states.enum';
import { UserStateEmailSyncValues } from 'src/engine/core-modules/user-state/enums/user-state-email-sync-values.enum';
export enum KeyValueTypes {
USER_STATE = 'USER_STATE',
}
type KeyValuePairs = {
[KeyValueTypes.USER_STATE]: {
[UserStates.SYNC_EMAIL_ONBOARDING_STEP]: UserStateEmailSyncValues;
};
};
export class KeyValuePairService<TYPE extends keyof KeyValuePairs> {
constructor(
@InjectRepository(KeyValuePair, 'core')
private readonly keyValuePairRepository: Repository<KeyValuePair>,
) {}
async get<K extends keyof KeyValuePairs[TYPE]>(
userId: string,
workspaceId: string,
key: K,
) {
return await this.keyValuePairRepository.findOne({
where: {
userId,
workspaceId,
key: key as string,
},
});
}
async set<K extends keyof KeyValuePairs[TYPE]>(
userId: string,
workspaceId: string,
key: K,
value: KeyValuePairs[TYPE][K],
) {
await this.keyValuePairRepository.upsert(
{
userId,
workspaceId,
key: key as string,
value: value as string,
},
{ conflictPaths: ['userId', 'workspaceId', 'key'] },
);
}
}

View File

@@ -1,7 +1,13 @@
import { ObjectType, Field } from '@nestjs/graphql'; import { ObjectType, Field, registerEnumType } from '@nestjs/graphql';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import { TimelineThreadParticipant } from 'src/engine/core-modules/messaging/dtos/timeline-thread-participant.dto'; import { TimelineThreadParticipant } from 'src/engine/core-modules/messaging/dtos/timeline-thread-participant.dto';
import { MessageChannelVisibility } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
registerEnumType(MessageChannelVisibility, {
name: 'MessageChannelVisibility',
description: 'Visibility of the message channel',
});
@ObjectType('TimelineThread') @ObjectType('TimelineThread')
export class TimelineThread { export class TimelineThread {
@@ -11,8 +17,8 @@ export class TimelineThread {
@Field() @Field()
read: boolean; read: boolean;
@Field() @Field(() => MessageChannelVisibility)
visibility: string; visibility: MessageChannelVisibility;
@Field() @Field()
firstParticipant: TimelineThreadParticipant; firstParticipant: TimelineThreadParticipant;

View File

@@ -3,6 +3,7 @@ import { Injectable } from '@nestjs/common';
import { TIMELINE_THREADS_DEFAULT_PAGE_SIZE } from 'src/engine/core-modules/messaging/constants/messaging.constants'; import { TIMELINE_THREADS_DEFAULT_PAGE_SIZE } from 'src/engine/core-modules/messaging/constants/messaging.constants';
import { TimelineThreadsWithTotal } from 'src/engine/core-modules/messaging/dtos/timeline-threads-with-total.dto'; import { TimelineThreadsWithTotal } from 'src/engine/core-modules/messaging/dtos/timeline-threads-with-total.dto';
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service'; import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
import { MessageChannelVisibility } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
type TimelineThreadParticipant = { type TimelineThreadParticipant = {
personId: string; personId: string;
@@ -352,7 +353,7 @@ export class TimelineMessagingService {
const threadVisibility: const threadVisibility:
| { | {
id: string; id: string;
visibility: 'metadata' | 'subject' | 'share_everything'; visibility: MessageChannelVisibility;
}[] }[]
| undefined = await this.workspaceDataSourceService.executeRawQuery( | undefined = await this.workspaceDataSourceService.executeRawQuery(
` `
@@ -372,11 +373,11 @@ export class TimelineMessagingService {
workspaceId, workspaceId,
); );
const visibilityValues = ['metadata', 'subject', 'share_everything']; const visibilityValues = Object.values(MessageChannelVisibility);
const threadVisibilityByThreadIdForWhichWorkspaceMemberIsNotInParticipants: const threadVisibilityByThreadIdForWhichWorkspaceMemberIsNotInParticipants:
| { | {
[key: string]: 'metadata' | 'subject' | 'share_everything'; [key: string]: MessageChannelVisibility;
} }
| undefined = threadVisibility?.reduce( | undefined = threadVisibility?.reduce(
(threadVisibilityAcc, threadVisibility) => { (threadVisibilityAcc, threadVisibility) => {
@@ -385,7 +386,8 @@ export class TimelineMessagingService {
Math.max( Math.max(
visibilityValues.indexOf(threadVisibility.visibility), visibilityValues.indexOf(threadVisibility.visibility),
visibilityValues.indexOf( visibilityValues.indexOf(
threadVisibilityAcc[threadVisibility.id] ?? 'metadata', threadVisibilityAcc[threadVisibility.id] ??
MessageChannelVisibility.METADATA,
), ),
) )
]; ];
@@ -396,7 +398,7 @@ export class TimelineMessagingService {
); );
const threadVisibilityByThreadId: { const threadVisibilityByThreadId: {
[key: string]: 'metadata' | 'subject' | 'share_everything'; [key: string]: MessageChannelVisibility;
} = messageThreadIds.reduce((threadVisibilityAcc, messageThreadId) => { } = messageThreadIds.reduce((threadVisibilityAcc, messageThreadId) => {
// If the workspace member is not in the participants of the thread, use the visibility value from the query // If the workspace member is not in the participants of the thread, use the visibility value from the query
threadVisibilityAcc[messageThreadId] = threadVisibilityAcc[messageThreadId] =
@@ -405,8 +407,8 @@ export class TimelineMessagingService {
) )
? threadVisibilityByThreadIdForWhichWorkspaceMemberIsNotInParticipants?.[ ? threadVisibilityByThreadIdForWhichWorkspaceMemberIsNotInParticipants?.[
messageThreadId messageThreadId
] ?? 'metadata' ] ?? MessageChannelVisibility.METADATA
: 'share_everything'; : MessageChannelVisibility.SHARE_EVERYTHING;
return threadVisibilityAcc; return threadVisibilityAcc;
}, {}); }, {});
@@ -461,7 +463,9 @@ export class TimelineMessagingService {
lastTwoParticipants, lastTwoParticipants,
lastMessageReceivedAt: thread.lastMessageReceivedAt, lastMessageReceivedAt: thread.lastMessageReceivedAt,
lastMessageBody: thread.lastMessageBody, lastMessageBody: thread.lastMessageBody,
visibility: threadVisibilityByThreadId?.[messageThreadId] ?? 'metadata', visibility:
threadVisibilityByThreadId?.[messageThreadId] ??
MessageChannelVisibility.METADATA,
subject: threadSubject, subject: threadSubject,
numberOfMessagesInThread: numberOfMessages, numberOfMessagesInThread: numberOfMessages,
participantCount: threadActiveParticipants.length, participantCount: threadActiveParticipants.length,

View File

@@ -0,0 +1,5 @@
import { UserState } from 'src/engine/core-modules/user-state/dtos/user-state.dto';
export const DEFAULT_USER_STATE: UserState = {
skipSyncEmailOnboardingStep: true,
};

View File

@@ -0,0 +1,9 @@
import { Field, ObjectType } from '@nestjs/graphql';
@ObjectType()
export class SkipSyncEmailOnboardingStep {
@Field(() => Boolean, {
description: 'Boolean that confirms query was dispatched',
})
success: boolean;
}

View File

@@ -0,0 +1,7 @@
import { Field, ObjectType } from '@nestjs/graphql';
@ObjectType('UserState')
export class UserState {
@Field(() => Boolean, { nullable: true })
skipSyncEmailOnboardingStep: boolean | null;
}

View File

@@ -0,0 +1,3 @@
export enum UserStateEmailSyncValues {
SKIPPED = 'SKIPPED',
}

View File

@@ -0,0 +1,3 @@
export enum UserStates {
SYNC_EMAIL_ONBOARDING_STEP = 'SYNC_EMAIL_ONBOARDING_STEP',
}

View File

@@ -0,0 +1,13 @@
import { Module } from '@nestjs/common';
import { UserStateService } from 'src/engine/core-modules/user-state/user-state.service';
import { UserStateResolver } from 'src/engine/core-modules/user-state/user-state.resolver';
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
import { KeyValuePairModule } from 'src/engine/core-modules/key-value-pair/key-value-pair.module';
@Module({
imports: [DataSourceModule, KeyValuePairModule],
exports: [UserStateService],
providers: [UserStateService, UserStateResolver],
})
export class UserStateModule {}

View File

@@ -0,0 +1,28 @@
import { UseGuards } from '@nestjs/common';
import { Mutation, Resolver } from '@nestjs/graphql';
import { JwtAuthGuard } from 'src/engine/guards/jwt.auth.guard';
import { UserState } from 'src/engine/core-modules/user-state/dtos/user-state.dto';
import { AuthUser } from 'src/engine/decorators/auth/auth-user.decorator';
import { User } from 'src/engine/core-modules/user/user.entity';
import { UserStateService } from 'src/engine/core-modules/user-state/user-state.service';
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { SkipSyncEmailOnboardingStep } from 'src/engine/core-modules/user-state/dtos/skip-sync-email.entity-onboarding-step';
@UseGuards(JwtAuthGuard)
@Resolver(() => UserState)
export class UserStateResolver {
constructor(private readonly userStateService: UserStateService) {}
@Mutation(() => SkipSyncEmailOnboardingStep)
async skipSyncEmailOnboardingStep(
@AuthUser() user: User,
@AuthWorkspace() workspace: Workspace,
): Promise<SkipSyncEmailOnboardingStep> {
return await this.userStateService.skipSyncEmailOnboardingStep(
user.id,
workspace.id,
);
}
}

View File

@@ -0,0 +1,64 @@
import { Injectable } from '@nestjs/common';
import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator';
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
import { ConnectedAccountRepository } from 'src/modules/connected-account/repositories/connected-account.repository';
import { User } from 'src/engine/core-modules/user/user.entity';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { UserState } from 'src/engine/core-modules/user-state/dtos/user-state.dto';
import {
KeyValuePairService,
KeyValueTypes,
} from 'src/engine/core-modules/key-value-pair/key-value-pair.service';
import { UserStates } from 'src/engine/core-modules/user-state/enums/user-states.enum';
import { UserStateEmailSyncValues } from 'src/engine/core-modules/user-state/enums/user-state-email-sync-values.enum';
import { SkipSyncEmailOnboardingStep } from 'src/engine/core-modules/user-state/dtos/skip-sync-email.entity-onboarding-step';
@Injectable()
export class UserStateService {
constructor(
@InjectObjectMetadataRepository(ConnectedAccountWorkspaceEntity)
private readonly connectedAccountRepository: ConnectedAccountRepository,
private readonly keyValuePairService: KeyValuePairService<KeyValueTypes.USER_STATE>,
) {}
async getUserState(user: User, workspace: Workspace): Promise<UserState> {
const connectedAccounts =
await this.connectedAccountRepository.getAllByUserId(
user.id,
workspace.id,
);
if (connectedAccounts?.length) {
return {
skipSyncEmailOnboardingStep: true,
};
}
const skipSyncEmail = await this.keyValuePairService.get(
user.id,
workspace.id,
UserStates.SYNC_EMAIL_ONBOARDING_STEP,
);
return {
skipSyncEmailOnboardingStep:
!!skipSyncEmail &&
skipSyncEmail.value === UserStateEmailSyncValues.SKIPPED,
};
}
async skipSyncEmailOnboardingStep(
userId: string,
workspaceId: string,
): Promise<SkipSyncEmailOnboardingStep> {
await this.keyValuePairService.set(
userId,
workspaceId,
UserStates.SYNC_EMAIL_ONBOARDING_STEP,
UserStateEmailSyncValues.SKIPPED,
);
return { success: true };
}
}

View File

@@ -2,14 +2,13 @@ import { Test, TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm'; import { getRepositoryToken } from '@nestjs/typeorm';
import { EventEmitter2 } from '@nestjs/event-emitter'; import { EventEmitter2 } from '@nestjs/event-emitter';
import { UserService } from 'src/engine/core-modules/user/services/user.service';
import { User } from 'src/engine/core-modules/user/user.entity'; import { User } from 'src/engine/core-modules/user/user.entity';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service'; import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { TypeORMService } from 'src/database/typeorm/typeorm.service'; import { TypeORMService } from 'src/database/typeorm/typeorm.service';
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
import { WorkspaceService } from 'src/engine/core-modules/workspace/services/workspace.service'; import { WorkspaceService } from 'src/engine/core-modules/workspace/services/workspace.service';
import { UserService } from './user.service';
describe('UserService', () => { describe('UserService', () => {
let service: UserService; let service: UserService;

View File

@@ -17,6 +17,8 @@ import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { WorkspaceMember } from 'src/engine/core-modules/user/dtos/workspace-member.dto'; import { WorkspaceMember } from 'src/engine/core-modules/user/dtos/workspace-member.dto';
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import { KeyValuePair } from 'src/engine/core-modules/key-value-pair/key-value-pair.entity';
import { UserState } from 'src/engine/core-modules/user-state/dtos/user-state.dto';
@Entity({ name: 'user', schema: 'core' }) @Entity({ name: 'user', schema: 'core' })
@ObjectType('User') @ObjectType('User')
@@ -100,10 +102,18 @@ export class User {
}) })
appTokens: Relation<AppToken[]>; appTokens: Relation<AppToken[]>;
@OneToMany(() => KeyValuePair, (keyValuePair) => keyValuePair.user, {
cascade: true,
})
keyValuePairs: Relation<KeyValuePair[]>;
@Field(() => WorkspaceMember, { nullable: true }) @Field(() => WorkspaceMember, { nullable: true })
workspaceMember: Relation<WorkspaceMember>; workspaceMember: Relation<WorkspaceMember>;
@Field(() => [UserWorkspace]) @Field(() => [UserWorkspace])
@OneToMany(() => UserWorkspace, (userWorkspace) => userWorkspace.user) @OneToMany(() => UserWorkspace, (userWorkspace) => userWorkspace.user)
workspaces: Relation<UserWorkspace[]>; workspaces: Relation<UserWorkspace[]>;
@Field(() => UserState, { nullable: false })
state: UserState;
} }

View File

@@ -11,6 +11,7 @@ import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-s
import { TypeORMModule } from 'src/database/typeorm/typeorm.module'; import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
import { FileUploadModule } from 'src/engine/core-modules/file/file-upload/file-upload.module'; import { FileUploadModule } from 'src/engine/core-modules/file/file-upload/file-upload.module';
import { WorkspaceModule } from 'src/engine/core-modules/workspace/workspace.module'; import { WorkspaceModule } from 'src/engine/core-modules/workspace/workspace.module';
import { UserStateModule } from 'src/engine/core-modules/user-state/user-state.module';
import { userAutoResolverOpts } from './user.auto-resolver-opts'; import { userAutoResolverOpts } from './user.auto-resolver-opts';
@@ -27,6 +28,7 @@ import { UserService } from './services/user.service';
}), }),
DataSourceModule, DataSourceModule,
FileUploadModule, FileUploadModule,
UserStateModule,
WorkspaceModule, WorkspaceModule,
], ],
exports: [UserService], exports: [UserService],

View File

@@ -1,10 +1,10 @@
import { import {
Resolver,
Query,
Args, Args,
Parent,
ResolveField,
Mutation, Mutation,
Parent,
Query,
ResolveField,
Resolver,
} from '@nestjs/graphql'; } from '@nestjs/graphql';
import { UseGuards } from '@nestjs/common'; import { UseGuards } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
@@ -17,6 +17,7 @@ import { Repository } from 'typeorm';
import { SupportDriver } from 'src/engine/integrations/environment/interfaces/support.interface'; import { SupportDriver } from 'src/engine/integrations/environment/interfaces/support.interface';
import { FileFolder } from 'src/engine/core-modules/file/interfaces/file-folder.interface'; import { FileFolder } from 'src/engine/core-modules/file/interfaces/file-folder.interface';
import { UserService } from 'src/engine/core-modules/user/services/user.service';
import { AuthUser } from 'src/engine/decorators/auth/auth-user.decorator'; import { AuthUser } from 'src/engine/decorators/auth/auth-user.decorator';
import { EnvironmentService } from 'src/engine/integrations/environment/environment.service'; import { EnvironmentService } from 'src/engine/integrations/environment/environment.service';
import { streamToBuffer } from 'src/utils/stream-to-buffer'; import { streamToBuffer } from 'src/utils/stream-to-buffer';
@@ -26,8 +27,11 @@ import { DemoEnvGuard } from 'src/engine/guards/demo.env.guard';
import { JwtAuthGuard } from 'src/engine/guards/jwt.auth.guard'; import { JwtAuthGuard } from 'src/engine/guards/jwt.auth.guard';
import { User } from 'src/engine/core-modules/user/user.entity'; import { User } from 'src/engine/core-modules/user/user.entity';
import { WorkspaceMember } from 'src/engine/core-modules/user/dtos/workspace-member.dto'; import { WorkspaceMember } from 'src/engine/core-modules/user/dtos/workspace-member.dto';
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
import { UserService } from './services/user.service'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { UserState } from 'src/engine/core-modules/user-state/dtos/user-state.dto';
import { UserStateService } from 'src/engine/core-modules/user-state/user-state.service';
import { DEFAULT_USER_STATE } from 'src/engine/core-modules/user-state/constants/default-user-state';
const getHMACKey = (email?: string, key?: string | null) => { const getHMACKey = (email?: string, key?: string | null) => {
if (!email || !key) return null; if (!email || !key) return null;
@@ -43,6 +47,7 @@ export class UserResolver {
constructor( constructor(
@InjectRepository(User, 'core') @InjectRepository(User, 'core')
private readonly userRepository: Repository<User>, private readonly userRepository: Repository<User>,
private readonly userStateService: UserStateService,
private readonly userService: UserService, private readonly userService: UserService,
private readonly environmentService: EnvironmentService, private readonly environmentService: EnvironmentService,
private readonly fileUploadService: FileUploadService, private readonly fileUploadService: FileUploadService,
@@ -113,4 +118,16 @@ export class UserResolver {
// Proceed with user deletion // Proceed with user deletion
return this.userService.deleteUser(userId); return this.userService.deleteUser(userId);
} }
@ResolveField(() => UserState)
async state(
@Parent() user: User,
@AuthWorkspace() workspace: Workspace,
): Promise<UserState> {
if (!user || !workspace) {
return DEFAULT_USER_STATE;
}
return this.userStateService.getUserState(user, workspace);
}
} }

View File

@@ -18,6 +18,7 @@ import { BillingSubscription } from 'src/engine/core-modules/billing/entities/bi
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
import { AppToken } from 'src/engine/core-modules/app-token/app-token.entity'; import { AppToken } from 'src/engine/core-modules/app-token/app-token.entity';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import { KeyValuePair } from 'src/engine/core-modules/key-value-pair/key-value-pair.entity';
@Entity({ name: 'workspace', schema: 'core' }) @Entity({ name: 'workspace', schema: 'core' })
@ObjectType('Workspace') @ObjectType('Workspace')
@@ -63,6 +64,11 @@ export class Workspace {
}) })
appTokens: Relation<AppToken[]>; appTokens: Relation<AppToken[]>;
@OneToMany(() => KeyValuePair, (keyValuePair) => keyValuePair.workspace, {
cascade: true,
})
keyValuePairs: Relation<KeyValuePair[]>;
@OneToMany(() => User, (user) => user.defaultWorkspace) @OneToMany(() => User, (user) => user.defaultWorkspace)
users: Relation<User[]>; users: Relation<User[]>;

View File

@@ -29,6 +29,15 @@ export class WorkspaceDataSourceService {
return dataSource; return dataSource;
} }
public async checkSchemaExists(workspaceId: string) {
const dataSource =
await this.dataSourceService.getDataSourcesMetadataFromWorkspaceId(
workspaceId,
);
return dataSource.length > 0;
}
public async connectedToWorkspaceDataSourceAndReturnMetadata( public async connectedToWorkspaceDataSourceAndReturnMetadata(
workspaceId: string, workspaceId: string,
): Promise<{ dataSource: DataSource; dataSourceMetadata: DataSourceEntity }> { ): Promise<{ dataSource: DataSource; dataSourceMetadata: DataSourceEntity }> {

View File

@@ -62,6 +62,41 @@ export class ConnectedAccountRepository {
return connectedAccounts; return connectedAccounts;
} }
public async getAllByUserId(
userId: string,
workspaceId: string,
transactionManager?: EntityManager,
): Promise<ObjectRecord<ConnectedAccountWorkspaceEntity>[] | undefined> {
const schemaExists =
await this.workspaceDataSourceService.checkSchemaExists(workspaceId);
if (!schemaExists) {
return;
}
const dataSourceSchema =
this.workspaceDataSourceService.getSchemaName(workspaceId);
const workspaceMember = (
await this.workspaceDataSourceService.executeRawQuery(
`SELECT * FROM ${dataSourceSchema}."workspaceMember" WHERE "userId" = $1`,
[userId],
workspaceId,
transactionManager,
)
)?.[0];
if (!workspaceMember) {
return;
}
return await this.getAllByWorkspaceMemberId(
workspaceMember.id,
workspaceId,
transactionManager,
);
}
public async getAllByHandleAndWorkspaceMemberId( public async getAllByHandleAndWorkspaceMemberId(
handle: string, handle: string,
workspaceMemberId: string, workspaceMemberId: string,

View File

@@ -41,9 +41,9 @@ export enum MessageChannelSyncStage {
} }
export enum MessageChannelVisibility { export enum MessageChannelVisibility {
METADATA = 'metadata', METADATA = 'METADATA',
SUBJECT = 'subject', SUBJECT = 'SUBJECT',
SHARE_EVERYTHING = 'share_everything', SHARE_EVERYTHING = 'SHARE_EVERYTHING',
} }
export enum MessageChannelType { export enum MessageChannelType {

View File

@@ -28,6 +28,20 @@ export class WorkspaceMemberRepository {
return result; return result;
} }
public async find(workspaceMemberId: string, workspaceId: string) {
const dataSourceSchema =
this.workspaceDataSourceService.getSchemaName(workspaceId);
const workspaceMembers =
await this.workspaceDataSourceService.executeRawQuery(
`SELECT * FROM ${dataSourceSchema}."workspaceMember" WHERE "id" = $1`,
[workspaceMemberId],
workspaceId,
);
return workspaceMembers?.[0];
}
public async getByIdOrFail( public async getByIdOrFail(
userId: string, userId: string,
workspaceId: string, workspaceId: string,

View File

@@ -34,8 +34,8 @@
--twentycrm-gray-0-14: #ffffff23; --twentycrm-gray-0-14: #ffffff23;
/* Blues */ /* Blues */
--twentycrm-blue-accent-90: #141a25, --twentycrm-blue-accent-90: #141a25;
--twentycrm-blue-accent-10: #f5f9fd, --twentycrm-blue-accent-10: #f5f9fd;
} }
:root.dark { :root.dark {

View File

@@ -18,7 +18,7 @@ export const MyComponent = () => {
<Button <Button
className className
Icon={null} Icon={null}
title="Click Me" title="Title"
fullWidth={false} fullWidth={false}
variant="primary" variant="primary"
size="medium" size="medium"
@@ -140,7 +140,7 @@ export const MyComponent = () => {
<FloatingButton <FloatingButton
className className
Icon={IconSearch} Icon={IconSearch}
title="Click Me" title="Title"
size="medium" size="medium"
position="standalone" position="standalone"
applyShadow={true} applyShadow={true}
@@ -322,7 +322,7 @@ export const MyComponent = () => {
return <LightButton return <LightButton
className className
icon={null} icon={null}
title="Click Me" title="Title"
accent="secondary" accent="secondary"
active={false} active={false}
disabled={false} disabled={false}
@@ -367,7 +367,7 @@ export const MyComponent = () => {
className className
testId="test1" testId="test1"
Icon={IconSearch} Icon={IconSearch}
title="Click Me" title="Title"
size="small" size="small"
accent="secondary" accent="secondary"
active={true} active={true}