From 4b0b5e77b0374440416be0de30a5be5f968fccc4 Mon Sep 17 00:00:00 2001 From: Mike Allison Date: Tue, 3 Feb 2026 20:24:39 +0000 Subject: [PATCH 01/18] fix: make user email field nullable in schema and migration --- .../20250201120000_make_user_email_nullable/migration.sql | 2 ++ libs/prisma/users/db/schema.prisma | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 libs/prisma/users/db/migrations/20250201120000_make_user_email_nullable/migration.sql diff --git a/libs/prisma/users/db/migrations/20250201120000_make_user_email_nullable/migration.sql b/libs/prisma/users/db/migrations/20250201120000_make_user_email_nullable/migration.sql new file mode 100644 index 00000000000..f5908591047 --- /dev/null +++ b/libs/prisma/users/db/migrations/20250201120000_make_user_email_nullable/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "User" ALTER COLUMN "email" DROP NOT NULL; diff --git a/libs/prisma/users/db/schema.prisma b/libs/prisma/users/db/schema.prisma index 7647ef9a255..44820f4e454 100644 --- a/libs/prisma/users/db/schema.prisma +++ b/libs/prisma/users/db/schema.prisma @@ -29,7 +29,7 @@ model User { userId String @unique firstName String lastName String? - email String @unique + email String? @unique imageUrl String? createdAt DateTime @default(now()) superAdmin Boolean @default(false) From abac660c8c7479cdd02efa072bc9a7af85949a94 Mon Sep 17 00:00:00 2001 From: Mike Allison Date: Tue, 3 Feb 2026 20:24:59 +0000 Subject: [PATCH 02/18] refactor: rename User type to AuthenticatedUser and update related schema references - Removed the User type in favor of AuthenticatedUser across multiple schemas. - Updated GraphQL queries and mutations to reflect the new type. - Adjusted resolver implementations to return AuthenticatedUser instead of User. - Introduced AnonymousUser type for handling unauthenticated users. - Ensured backward compatibility by updating union types and references in generated files. --- apis/api-gateway/schema.graphql | 38 +++++++------ apis/api-journeys-modern/schema.graphql | 20 +++---- .../src/schema/integration/google/google.ts | 4 +- .../src/schema/user/index.ts | 2 +- .../src/schema/user/user.ts | 10 ++-- .../src/schema/userJourney/userJourney.ts | 4 +- .../src/schema/userTeam/userTeam.ts | 4 +- apis/api-journeys/schema.graphql | 10 ++-- .../api-journeys/src/__generated__/graphql.ts | 52 +++++++++++------- .../src/app/__generated__/graphql.ts | 8 +-- .../modules/integration/google/google.graphql | 2 +- .../integration/google/google.resolver.ts | 4 +- .../modules/userJourney/userJourney.graphql | 4 +- .../userJourney/userJourney.resolver.spec.ts | 2 +- .../userJourney/userJourney.resolver.ts | 2 +- .../src/app/modules/userTeam/userTeam.graphql | 4 +- .../userTeam/userTeam.resolver.spec.ts | 2 +- .../app/modules/userTeam/userTeam.resolver.ts | 2 +- apis/api-languages/schema.graphql | 16 +++--- .../src/schema/user/user.spec.ts | 6 +- apis/api-languages/src/schema/user/user.ts | 38 +++++++------ apis/api-media/schema.graphql | 18 +++--- .../api-media/src/schema/playlist/playlist.ts | 4 +- apis/api-media/src/schema/user/index.ts | 2 +- apis/api-media/src/schema/user/user.spec.ts | 6 +- apis/api-media/src/schema/user/user.ts | 6 +- apis/api-users/schema.graphql | 34 +++++++----- apis/api-users/src/schema/builder.ts | 18 +++--- .../src/schema/user/objects/index.ts | 8 ++- .../api-users/src/schema/user/objects/user.ts | 44 ++++++++++++++- apis/api-users/src/schema/user/user.spec.ts | 19 ++++--- apis/api-users/src/schema/user/user.ts | 30 ++++++---- .../__generated__/CreateJourney.ts | 2 +- .../__generated__/GetAdminJourney.ts | 2 +- .../GetAdminJourneyWithPlausibleToken.ts | 2 +- .../__generated__/GetAdminJourneys.ts | 2 +- .../__generated__/GetCurrentUser.ts | 11 +++- .../__generated__/GetIntegration.ts | 2 +- .../__generated__/GetJourney.ts | 2 +- .../GetJourneyWithPermissions.ts | 4 +- .../__generated__/GetJourneyWithUserRoles.ts | 2 +- .../__generated__/GetJourneys.ts | 2 +- .../GetLastActiveTeamIdAndTeams.ts | 2 +- apps/journeys-admin/__generated__/GetMe.ts | 11 +++- .../__generated__/GetPublisherTemplate.ts | 2 +- .../__generated__/GetUserTeamsAndInvites.ts | 2 +- .../__generated__/JourneyFields.ts | 2 +- .../__generated__/TeamCreate.ts | 2 +- .../__generated__/UserTeamUpdate.ts | 2 +- .../__generated__/ValidateEmail.ts | 2 +- apps/journeys-admin/pages/templates/index.tsx | 2 +- apps/journeys-admin/pages/users/verify.tsx | 2 +- .../AccessAvatars/AccessAvatars.stories.tsx | 6 +- .../src/components/AccessAvatars/data.ts | 14 ++--- .../components/AccessDialog/AccessDialog.tsx | 4 +- .../AccessDialog/UserList/UserList.spec.tsx | 4 +- .../UserListItem/UserListItem.spec.tsx | 8 +-- .../src/components/Avatar/Avatar.spec.tsx | 2 +- .../src/components/Avatar/Avatar.stories.tsx | 4 +- .../JourneyAppearance/Host/Host.spec.tsx | 2 +- .../JourneyAppearance/Host/Host.stories.tsx | 2 +- .../JourneyAppearance/Host/Host.tsx | 2 +- .../Host/HostSelection/HostSelection.spec.tsx | 2 +- .../HostSelection/HostSelection.stories.tsx | 2 +- .../EmailVerification/EmailVerification.tsx | 2 +- .../ActiveJourneyListData.ts | 6 +- .../DefaultMenu/DefaultMenu.tsx | 20 +++++-- .../components/JourneyList/journeyListData.ts | 14 ++--- .../OnboardingPanel/OnboardingPanel.spec.tsx | 2 +- .../NavigationDrawer.spec.tsx | 2 +- .../NavigationDrawer.stories.tsx | 2 +- .../UserNavigation/UserMenu/UserMenu.spec.tsx | 8 +-- .../UserNavigation/UserMenu/UserMenu.tsx | 4 +- .../UserNavigation/UserNavigation.tsx | 55 +++++++++++-------- .../CustomDomainDialog/CustomDomainDialog.tsx | 9 ++- .../Team/TeamAvatars/TeamAvatars.spec.tsx | 20 +++---- .../Team/TeamAvatars/TeamAvatars.stories.tsx | 18 +++--- .../TeamManageDialog.spec.tsx | 2 +- .../TeamManageWrapper.spec.tsx | 2 +- .../TeamManageWrapper/TeamManageWrapper.tsx | 9 ++- .../UserTeamInviteList.spec.tsx | 8 +-- .../UserTeamList/UserTeamList.spec.tsx | 8 +-- .../UserTeamListItem.spec.tsx | 4 +- .../TeamUpdateDialog/TeamUpdateDialog.tsx | 9 ++- .../UserTeamInviteForm.spec.tsx | 2 +- .../checkConditionalRedirect.ts | 12 ++-- .../useCurrentUserLazyQuery.mock.ts | 2 +- .../useCurrentUserLazyQuery.ts | 14 ++++- apps/journeys/__generated__/GetJourney.ts | 2 +- apps/journeys/__generated__/GetJourneys.ts | 2 +- .../GetLastActiveTeamIdAndTeams.ts | 2 +- apps/journeys/__generated__/JourneyFields.ts | 2 +- apps/resources/__generated__/GetJourney.ts | 2 +- apps/resources/__generated__/GetJourneys.ts | 2 +- .../GetLastActiveTeamIdAndTeams.ts | 2 +- apps/resources/__generated__/JourneyFields.ts | 2 +- apps/watch/__generated__/GetJourney.ts | 2 +- apps/watch/__generated__/GetJourneys.ts | 2 +- .../GetLastActiveTeamIdAndTeams.ts | 2 +- apps/watch/__generated__/JourneyFields.ts | 2 +- .../TeamProvider/TeamProvider.mock.ts | 4 +- .../GetLastActiveTeamIdAndTeams.ts | 2 +- .../ui/src/components/TemplateView/data.ts | 6 +- .../__generated__/JourneyFields.ts | 2 +- .../__generated__/GetJourney.ts | 2 +- .../__generated__/GetJourneys.ts | 2 +- .../gql/src/__generated__/graphql-env.d.ts | 16 +++--- 107 files changed, 487 insertions(+), 340 deletions(-) diff --git a/apis/api-gateway/schema.graphql b/apis/api-gateway/schema.graphql index 8f4e6cd71e3..75acd26a9c3 100644 --- a/apis/api-gateway/schema.graphql +++ b/apis/api-gateway/schema.graphql @@ -552,7 +552,7 @@ type Mutation @join__type(graph: API_ANALYTICS) @join__type(graph: API_JOURNEYS videoEditionDelete(id: ID!) : VideoEdition! @join__field(graph: API_MEDIA) userImpersonate(email: String!) : String @join__field(graph: API_USERS) createVerificationRequest(input: CreateVerificationRequestInput) : Boolean @join__field(graph: API_USERS) - validateEmail(email: String!, token: String!) : User @join__field(graph: API_USERS) + validateEmail(email: String!, token: String!) : AuthenticatedUser @join__field(graph: API_USERS) } type MutationSiteCreateSuccess @join__type(graph: API_ANALYTICS) { @@ -948,8 +948,8 @@ type Query @join__type(graph: API_ANALYTICS) @join__type(graph: API_JOURNEYS) arclightApiKeys: [ArclightApiKey!]! @join__field(graph: API_MEDIA) arclightApiKeyByKey(key: String!) : ArclightApiKey @join__field(graph: API_MEDIA) me(input: MeInput) : User @join__field(graph: API_USERS) - user(id: ID!) : User @join__field(graph: API_USERS) - userByEmail(email: String!) : User @join__field(graph: API_USERS) + user(id: ID!) : AuthenticatedUser @join__field(graph: API_USERS) + userByEmail(email: String!) : AuthenticatedUser @join__field(graph: API_USERS) } type ButtonBlockSettings @join__type(graph: API_JOURNEYS) @join__type(graph: API_JOURNEYS_MODERN) { @@ -1837,7 +1837,7 @@ type IntegrationGoogle implements Integration @join__type(graph: API_JOURNEYS) id: ID! team: Team! type: IntegrationType! - user: User + user: AuthenticatedUser accountEmail: String } @@ -1892,7 +1892,7 @@ type UserJourney @join__type(graph: API_JOURNEYS, key: "id") @join__type(graph: userId: ID! journeyId: ID! role: UserJourneyRole! - user: User + user: AuthenticatedUser """ Date time of when the journey was first opened """ @@ -1976,7 +1976,7 @@ type JourneyNotification @join__type(graph: API_JOURNEYS) @join__type(graph: AP type UserTeam @join__type(graph: API_JOURNEYS, key: "id") @join__type(graph: API_JOURNEYS_MODERN) { id: ID! journeyNotification(journeyId: ID!) : JourneyNotification - user: User! + user: AuthenticatedUser! role: UserTeamRole! createdAt: DateTime! updatedAt: DateTime! @@ -2181,7 +2181,7 @@ type UserInvite @join__type(graph: API_JOURNEYS, key: "id") @join__type(graph: removedAt: DateTime } -type User @join__type(graph: API_JOURNEYS, key: "id", extension: true) @join__type(graph: API_JOURNEYS_MODERN, key: "id", extension: true) @join__type(graph: API_LANGUAGES, key: "id", extension: true) @join__type(graph: API_MEDIA, key: "id", extension: true) @join__type(graph: API_USERS, key: "id") { +type AuthenticatedUser @join__type(graph: API_JOURNEYS, key: "id", extension: true) @join__type(graph: API_JOURNEYS_MODERN, key: "id", extension: true) @join__type(graph: API_LANGUAGES, key: "id", extension: true) @join__type(graph: API_MEDIA, key: "id", extension: true) @join__type(graph: API_USERS, key: "id") { id: ID! languageUserRoles: [LanguageRole!]! @join__field(graph: API_LANGUAGES) mediaUserRoles: [MediaRole!]! @join__field(graph: API_MEDIA) @@ -2890,7 +2890,7 @@ type Playlist @join__type(graph: API_MEDIA) { createdAt: DateTime! updatedAt: DateTime! slug: String! - owner: User! + owner: AuthenticatedUser! items: [PlaylistItem!]! } @@ -3257,6 +3257,10 @@ type ZodFieldError @join__type(graph: API_MEDIA) { path: [String!]! } +type AnonymousUser @join__type(graph: API_USERS) { + id: ID! +} + interface BaseError @join__type(graph: API_ANALYTICS) @join__type(graph: API_MEDIA) { message: String } @@ -3337,6 +3341,8 @@ union QueryShortLinkResult @join__type(graph: API_MEDIA) @join__unionMember(gra union QueryYoutubeClosedCaptionLanguagesResult @join__type(graph: API_MEDIA) @join__unionMember(graph: API_MEDIA, member: "Error") @join__unionMember(graph: API_MEDIA, member: "ZodError") @join__unionMember(graph: API_MEDIA, member: "QueryYoutubeClosedCaptionLanguagesSuccess") = Error | ZodError | QueryYoutubeClosedCaptionLanguagesSuccess +union User @join__type(graph: API_USERS) @join__unionMember(graph: API_USERS, member: "AuthenticatedUser") @join__unionMember(graph: API_USERS, member: "AnonymousUser") = AuthenticatedUser | AnonymousUser + enum ThemeMode @join__type(graph: API_JOURNEYS) @join__type(graph: API_JOURNEYS_MODERN) { dark @join__enumValue(graph: API_JOURNEYS) @join__enumValue(graph: API_JOURNEYS_MODERN) light @join__enumValue(graph: API_JOURNEYS) @join__enumValue(graph: API_JOURNEYS_MODERN) @@ -3728,21 +3734,25 @@ enum PlausibleEvent @join__type(graph: API_JOURNEYS_MODERN) { journeyResponses @join__enumValue(graph: API_JOURNEYS_MODERN) } +enum LanguageRole @join__type(graph: API_LANGUAGES) { + publisher @join__enumValue(graph: API_LANGUAGES) +} + enum LanguageIdType @join__type(graph: API_LANGUAGES) { databaseId @join__enumValue(graph: API_LANGUAGES) bcp47 @join__enumValue(graph: API_LANGUAGES) } -enum LanguageRole @join__type(graph: API_LANGUAGES) { - publisher @join__enumValue(graph: API_LANGUAGES) -} - enum DefaultPlatform @join__type(graph: API_MEDIA) { ios @join__enumValue(graph: API_MEDIA) android @join__enumValue(graph: API_MEDIA) web @join__enumValue(graph: API_MEDIA) } +enum MediaRole @join__type(graph: API_MEDIA) { + publisher @join__enumValue(graph: API_MEDIA) +} + enum ImageAspectRatio @join__type(graph: API_MEDIA) { hd @join__enumValue(graph: API_MEDIA) banner @join__enumValue(graph: API_MEDIA) @@ -3754,10 +3764,6 @@ enum MaxResolutionTier @join__type(graph: API_MEDIA) { uhd @join__enumValue(graph: API_MEDIA) } -enum MediaRole @join__type(graph: API_MEDIA) { - publisher @join__enumValue(graph: API_MEDIA) -} - enum SegmindModel @join__type(graph: API_MEDIA) { sdxl1__0_txt2img @join__enumValue(graph: API_MEDIA) kandinsky2__2_txt2img @join__enumValue(graph: API_MEDIA) diff --git a/apis/api-journeys-modern/schema.graphql b/apis/api-journeys-modern/schema.graphql index cdefbabbfdb..1866b22fec7 100644 --- a/apis/api-journeys-modern/schema.graphql +++ b/apis/api-journeys-modern/schema.graphql @@ -7,6 +7,13 @@ interface Action { parentBlock: Block! } +type AuthenticatedUser + @key(fields: "id") + @extends +{ + id: ID! @external +} + interface Block { id: ID! journeyId: ID! @@ -697,7 +704,7 @@ type IntegrationGoogle implements Integration type: IntegrationType! team: Team! accountEmail: String - user: User + user: AuthenticatedUser } input IntegrationGoogleCreateInput { @@ -2250,13 +2257,6 @@ enum TypographyVariant { overline } -type User - @key(fields: "id") - @extends -{ - id: ID! @external -} - type UserAgent @shareable { @@ -2290,7 +2290,7 @@ type UserJourney role: UserJourneyRole! openedAt: DateTime journey: Journey - user: User + user: AuthenticatedUser journeyNotification: JourneyNotification } @@ -2316,7 +2316,7 @@ type UserTeam role: UserTeamRole! createdAt: DateTime! updatedAt: DateTime! - user: User! + user: AuthenticatedUser! journeyNotification(journeyId: ID!): JourneyNotification } diff --git a/apis/api-journeys-modern/src/schema/integration/google/google.ts b/apis/api-journeys-modern/src/schema/integration/google/google.ts index eac4baa7f79..10d1cb4d8b6 100644 --- a/apis/api-journeys-modern/src/schema/integration/google/google.ts +++ b/apis/api-journeys-modern/src/schema/integration/google/google.ts @@ -1,5 +1,5 @@ import { builder } from '../../builder' -import { UserRef } from '../../user' +import { AuthenticatedUserRef } from '../../user' import { IntegrationRef } from '../integration' export const IntegrationGoogleRef = builder.prismaObject('Integration', { @@ -11,7 +11,7 @@ export const IntegrationGoogleRef = builder.prismaObject('Integration', { accountEmail: t.exposeString('accountEmail', { nullable: true }), team: t.relation('team', { nullable: false }), user: t.field({ - type: UserRef, + type: AuthenticatedUserRef, nullable: true, resolve: async (integration) => { if (integration.userId == null) return null diff --git a/apis/api-journeys-modern/src/schema/user/index.ts b/apis/api-journeys-modern/src/schema/user/index.ts index ddac8a72157..25df7fc062e 100644 --- a/apis/api-journeys-modern/src/schema/user/index.ts +++ b/apis/api-journeys-modern/src/schema/user/index.ts @@ -1,3 +1,3 @@ import './user' -export { UserRef } from './user' +export { AuthenticatedUserRef } from './user' diff --git a/apis/api-journeys-modern/src/schema/user/user.ts b/apis/api-journeys-modern/src/schema/user/user.ts index 30b98bee598..29c08ed40d3 100644 --- a/apis/api-journeys-modern/src/schema/user/user.ts +++ b/apis/api-journeys-modern/src/schema/user/user.ts @@ -1,13 +1,13 @@ import { builder } from '../builder' -// Define the federated User type reference - this should only be defined once -export const UserRef = builder.externalRef( - 'User', +// Define the federated AuthenticatedUser type reference - this should only be defined once +export const AuthenticatedUserRef = builder.externalRef( + 'AuthenticatedUser', builder.selection<{ id: string }>('id') ) -// Implement the external fields for the User type -UserRef.implement({ +// Implement the external fields for the AuthenticatedUser type +AuthenticatedUserRef.implement({ externalFields: (t) => ({ id: t.id({ nullable: false }) }), fields: (t) => ({ // No additional fields needed - this is just the external reference diff --git a/apis/api-journeys-modern/src/schema/userJourney/userJourney.ts b/apis/api-journeys-modern/src/schema/userJourney/userJourney.ts index 83327b91235..9a667705319 100644 --- a/apis/api-journeys-modern/src/schema/userJourney/userJourney.ts +++ b/apis/api-journeys-modern/src/schema/userJourney/userJourney.ts @@ -1,5 +1,5 @@ import { builder } from '../builder' -import { UserRef } from '../user' +import { AuthenticatedUserRef } from '../user' import { UserJourneyRole } from './enums/userJourneyRole' @@ -17,7 +17,7 @@ export const UserJourneyRef = builder.prismaObject('UserJourney', { openedAt: t.expose('openedAt', { type: 'DateTime', nullable: true }), journey: t.relation('journey'), user: t.field({ - type: UserRef, + type: AuthenticatedUserRef, resolve: (userJourney) => ({ id: userJourney.userId }) diff --git a/apis/api-journeys-modern/src/schema/userTeam/userTeam.ts b/apis/api-journeys-modern/src/schema/userTeam/userTeam.ts index b153645ece1..aa2b4a16592 100644 --- a/apis/api-journeys-modern/src/schema/userTeam/userTeam.ts +++ b/apis/api-journeys-modern/src/schema/userTeam/userTeam.ts @@ -1,6 +1,6 @@ import { builder } from '../builder' import { JourneyNotificationRef } from '../journeyNotification' -import { UserRef } from '../user' +import { AuthenticatedUserRef } from '../user' import { UserTeamRole } from './enums' @@ -17,7 +17,7 @@ export const UserTeamRef = builder.prismaObject('UserTeam', { updatedAt: t.expose('updatedAt', { type: 'DateTime', nullable: false }), user: t.field({ nullable: false, - type: UserRef, + type: AuthenticatedUserRef, resolve: (userTeam) => ({ id: userTeam.userId }) diff --git a/apis/api-journeys/schema.graphql b/apis/api-journeys/schema.graphql index d86f741d92c..0b652b5cd35 100644 --- a/apis/api-journeys/schema.graphql +++ b/apis/api-journeys/schema.graphql @@ -2106,7 +2106,7 @@ type IntegrationGoogle implements Integration id: ID! team: Team! type: IntegrationType! - user: User + user: AuthenticatedUser accountEmail: String } @@ -2302,7 +2302,7 @@ type UserJourney userId: ID! journeyId: ID! role: UserJourneyRole! - user: User + user: AuthenticatedUser """Date time of when the journey was first opened""" openedAt: DateTime @@ -2426,7 +2426,7 @@ type UserTeam { journeyNotification(journeyId: ID!): JourneyNotification id: ID! - user: User! + user: AuthenticatedUser! role: UserTeamRole! createdAt: DateTime! updatedAt: DateTime! @@ -2716,7 +2716,7 @@ enum UserJourneyRole { owner } -extend type User +extend type AuthenticatedUser @key(fields: "id") { id: ID! @external @@ -3048,4 +3048,4 @@ type _Service { sdl: String } -union _Entity = Journey | JourneyProfile | JourneyVisitor | Language | ShortLink | Tag | Team | User | UserInvite | UserJourney | UserRole | UserTeam | Video | VideoBlock | Visitor +union _Entity = AuthenticatedUser | Journey | JourneyProfile | JourneyVisitor | Language | ShortLink | Tag | Team | UserInvite | UserJourney | UserRole | UserTeam | Video | VideoBlock | Visitor diff --git a/apis/api-journeys/src/__generated__/graphql.ts b/apis/api-journeys/src/__generated__/graphql.ts index 6ae8342f590..cf9517eff62 100644 --- a/apis/api-journeys/src/__generated__/graphql.ts +++ b/apis/api-journeys/src/__generated__/graphql.ts @@ -34,6 +34,11 @@ export type Action = { parentBlockId: Scalars['ID']['output']; }; +export type AnonymousUser = { + __typename?: 'AnonymousUser'; + id: Scalars['ID']['output']; +}; + export type ArclightApiKey = { __typename?: 'ArclightApiKey'; defaultPlatform: DefaultPlatform; @@ -52,6 +57,19 @@ export type AudioPreview = { value: Scalars['String']['output']; }; +export type AuthenticatedUser = { + __typename?: 'AuthenticatedUser'; + email: Scalars['String']['output']; + emailVerified: Scalars['Boolean']['output']; + firstName: Scalars['String']['output']; + id: Scalars['ID']['output']; + imageUrl?: Maybe; + languageUserRoles: Array; + lastName?: Maybe; + mediaUserRoles: Array; + superAdmin?: Maybe; +}; + export type BaseError = { message?: Maybe; }; @@ -994,7 +1012,7 @@ export type IntegrationGoogle = Integration & { id: Scalars['ID']['output']; team: Team; type: IntegrationType; - user?: Maybe; + user?: Maybe; }; export type IntegrationGoogleCreateInput = { @@ -1817,6 +1835,8 @@ export type Mutation = { deleteMuxVideo: Scalars['Boolean']['output']; enableMuxDownload?: Maybe; fixVideoLanguages: Scalars['Boolean']['output']; + /** Triggers a backfill of the Google Sheets sync. Clears existing data and re-exports all events. */ + googleSheetsSyncBackfill: GoogleSheetsSync; googleSheetsSyncCreate: GoogleSheetsSync; googleSheetsSyncDelete: GoogleSheetsSync; hostCreate: Host; @@ -1940,7 +1960,7 @@ export type Mutation = { userTeamInviteCreate?: Maybe; userTeamInviteRemove: UserTeamInvite; userTeamUpdate: UserTeam; - validateEmail?: Maybe; + validateEmail?: Maybe; videoBlockCreate: VideoBlock; videoBlockUpdate: VideoBlock; videoCollapseEventCreate: VideoCollapseEvent; @@ -2290,6 +2310,11 @@ export type MutationFixVideoLanguagesArgs = { }; +export type MutationGoogleSheetsSyncBackfillArgs = { + id: Scalars['ID']['input']; +}; + + export type MutationGoogleSheetsSyncCreateArgs = { input: CreateGoogleSheetsSyncInput; }; @@ -3570,7 +3595,7 @@ export type Playlist = { note?: Maybe; noteSharedAt?: Maybe; noteUpdatedAt?: Maybe; - owner: User; + owner: AuthenticatedUser; sharedAt?: Maybe; slug: Scalars['String']['output']; updatedAt: Scalars['DateTime']['output']; @@ -3767,8 +3792,8 @@ export type Query = { teams: Array; templateFamilyStatsAggregate?: Maybe; templateFamilyStatsBreakdown?: Maybe>; - user?: Maybe; - userByEmail?: Maybe; + user?: Maybe; + userByEmail?: Maybe; userInvites?: Maybe>; userTeam: UserTeam; userTeamInvites: Array; @@ -5094,18 +5119,7 @@ export type UnsplashUserLinks = { self: Scalars['String']['output']; }; -export type User = { - __typename?: 'User'; - email: Scalars['String']['output']; - emailVerified: Scalars['Boolean']['output']; - firstName: Scalars['String']['output']; - id: Scalars['ID']['output']; - imageUrl?: Maybe; - languageUserRoles: Array; - lastName?: Maybe; - mediaUserRoles: Array; - superAdmin?: Maybe; -}; +export type User = AnonymousUser | AuthenticatedUser; /** These types are a subset provided by the @types/ua-parser-js library. */ export type UserAgent = { @@ -5138,7 +5152,7 @@ export type UserJourney = { /** Date time of when the journey was first opened */ openedAt?: Maybe; role: UserJourneyRole; - user?: Maybe; + user?: Maybe; userId: Scalars['ID']['output']; }; @@ -5162,7 +5176,7 @@ export type UserTeam = { journeyNotification?: Maybe; role: UserTeamRole; updatedAt: Scalars['DateTime']['output']; - user: User; + user: AuthenticatedUser; }; diff --git a/apis/api-journeys/src/app/__generated__/graphql.ts b/apis/api-journeys/src/app/__generated__/graphql.ts index 3a791603c5b..1c2e2174c0d 100644 --- a/apis/api-journeys/src/app/__generated__/graphql.ts +++ b/apis/api-journeys/src/app/__generated__/graphql.ts @@ -1705,7 +1705,7 @@ export class IntegrationGoogle implements Integration { id: string; team: Team; type: IntegrationType; - user?: Nullable; + user?: Nullable; accountEmail?: Nullable; } @@ -1742,7 +1742,7 @@ export class UserJourney { userId: string; journeyId: string; role: UserJourneyRole; - user?: Nullable; + user?: Nullable; openedAt?: Nullable; } @@ -1814,7 +1814,7 @@ export class UserTeam { __typename?: 'UserTeam'; journeyNotification?: Nullable; id: string; - user: User; + user: AuthenticatedUser; role: UserTeamRole; createdAt: DateTime; updatedAt: DateTime; @@ -2026,7 +2026,7 @@ export class ShortLink { id: string; } -export class User { +export class AuthenticatedUser { id: string; } diff --git a/apis/api-journeys/src/app/modules/integration/google/google.graphql b/apis/api-journeys/src/app/modules/integration/google/google.graphql index 9fb929288dc..461229120b8 100644 --- a/apis/api-journeys/src/app/modules/integration/google/google.graphql +++ b/apis/api-journeys/src/app/modules/integration/google/google.graphql @@ -2,6 +2,6 @@ type IntegrationGoogle implements Integration @shareable { id: ID! team: Team! type: IntegrationType! - user: User + user: AuthenticatedUser accountEmail: String } diff --git a/apis/api-journeys/src/app/modules/integration/google/google.resolver.ts b/apis/api-journeys/src/app/modules/integration/google/google.resolver.ts index deb0ac95e8b..82d863ef5db 100644 --- a/apis/api-journeys/src/app/modules/integration/google/google.resolver.ts +++ b/apis/api-journeys/src/app/modules/integration/google/google.resolver.ts @@ -7,8 +7,8 @@ export class IntegrationGoogleResolver { @ResolveField() user( @Parent() integration: Integration - ): { __typename: 'User'; id: string } | null { + ): { __typename: 'AuthenticatedUser'; id: string } | null { if (integration.userId == null) return null - return { __typename: 'User', id: integration.userId } + return { __typename: 'AuthenticatedUser', id: integration.userId } } } diff --git a/apis/api-journeys/src/app/modules/userJourney/userJourney.graphql b/apis/api-journeys/src/app/modules/userJourney/userJourney.graphql index 00ad8492a97..058e7a66941 100644 --- a/apis/api-journeys/src/app/modules/userJourney/userJourney.graphql +++ b/apis/api-journeys/src/app/modules/userJourney/userJourney.graphql @@ -13,13 +13,13 @@ type UserJourney @key(fields: "id") @shareable { userId: ID! journeyId: ID! role: UserJourneyRole! - user: User + user: AuthenticatedUser """ Date time of when the journey was first opened """ openedAt: DateTime } -extend type User @key(fields: "id") { +extend type AuthenticatedUser @key(fields: "id") { id: ID! @external } diff --git a/apis/api-journeys/src/app/modules/userJourney/userJourney.resolver.spec.ts b/apis/api-journeys/src/app/modules/userJourney/userJourney.resolver.spec.ts index dd0a9beeb63..5a6cd437139 100644 --- a/apis/api-journeys/src/app/modules/userJourney/userJourney.resolver.spec.ts +++ b/apis/api-journeys/src/app/modules/userJourney/userJourney.resolver.spec.ts @@ -258,7 +258,7 @@ describe('UserJourneyResolver', () => { describe('user', () => { it('returns user reference', async () => { expect(await resolver.user(userJourney)).toEqual({ - __typename: 'User', + __typename: 'AuthenticatedUser', id: 'userId' }) }) diff --git a/apis/api-journeys/src/app/modules/userJourney/userJourney.resolver.ts b/apis/api-journeys/src/app/modules/userJourney/userJourney.resolver.ts index e7236f2f839..9723bdb1bef 100644 --- a/apis/api-journeys/src/app/modules/userJourney/userJourney.resolver.ts +++ b/apis/api-journeys/src/app/modules/userJourney/userJourney.resolver.ts @@ -242,7 +242,7 @@ export class UserJourneyResolver { async user( @Parent() userJourney: UserJourney ): Promise<{ __typename: string; id: string }> { - return { __typename: 'User', id: userJourney.userId } + return { __typename: 'AuthenticatedUser', id: userJourney.userId } } @ResolveField('journeyNotification') diff --git a/apis/api-journeys/src/app/modules/userTeam/userTeam.graphql b/apis/api-journeys/src/app/modules/userTeam/userTeam.graphql index 296542f6398..f0db8bbd28a 100644 --- a/apis/api-journeys/src/app/modules/userTeam/userTeam.graphql +++ b/apis/api-journeys/src/app/modules/userTeam/userTeam.graphql @@ -1,4 +1,4 @@ -extend type User @key(fields: "id") { +extend type AuthenticatedUser @key(fields: "id") { id: ID! @external } @@ -9,7 +9,7 @@ enum UserTeamRole { type UserTeam @key(fields: "id") @shareable { id: ID! - user: User! + user: AuthenticatedUser! role: UserTeamRole! createdAt: DateTime! updatedAt: DateTime! diff --git a/apis/api-journeys/src/app/modules/userTeam/userTeam.resolver.spec.ts b/apis/api-journeys/src/app/modules/userTeam/userTeam.resolver.spec.ts index 642d0ddf010..00a7784d114 100644 --- a/apis/api-journeys/src/app/modules/userTeam/userTeam.resolver.spec.ts +++ b/apis/api-journeys/src/app/modules/userTeam/userTeam.resolver.spec.ts @@ -303,7 +303,7 @@ describe('UserTeamResolver', () => { userId: 'userId' } as unknown as UserTeam) ).resolves.toEqual({ - __typename: 'User', + __typename: 'AuthenticatedUser', id: 'userId' }) }) diff --git a/apis/api-journeys/src/app/modules/userTeam/userTeam.resolver.ts b/apis/api-journeys/src/app/modules/userTeam/userTeam.resolver.ts index 5d31c1a562f..901ed599287 100644 --- a/apis/api-journeys/src/app/modules/userTeam/userTeam.resolver.ts +++ b/apis/api-journeys/src/app/modules/userTeam/userTeam.resolver.ts @@ -127,7 +127,7 @@ export class UserTeamResolver { async user( @Parent() userTeam: UserTeam ): Promise<{ __typename: string; id: string }> { - return { __typename: 'User', id: userTeam.userId } + return { __typename: 'AuthenticatedUser', id: userTeam.userId } } @ResolveField('journeyNotification') diff --git a/apis/api-languages/schema.graphql b/apis/api-languages/schema.graphql index cff25a5040e..e8710a0e50d 100644 --- a/apis/api-languages/schema.graphql +++ b/apis/api-languages/schema.graphql @@ -13,6 +13,14 @@ type AudioPreview codec: String! } +type AuthenticatedUser + @key(fields: "id") + @extends +{ + id: ID! @external + languageUserRoles: [LanguageRole!]! +} + type Continent { id: ID! name(languageId: ID, primary: Boolean): [ContinentName!]! @@ -122,12 +130,4 @@ type Query { languagesCount(where: LanguagesFilter, term: String): Int! country(id: ID!): Country countries(term: String, ids: [ID!]): [Country!]! -} - -type User - @key(fields: "id") - @extends -{ - id: ID! @external - languageUserRoles: [LanguageRole!]! } \ No newline at end of file diff --git a/apis/api-languages/src/schema/user/user.spec.ts b/apis/api-languages/src/schema/user/user.spec.ts index 03ee759248f..c3eae349df8 100644 --- a/apis/api-languages/src/schema/user/user.spec.ts +++ b/apis/api-languages/src/schema/user/user.spec.ts @@ -13,8 +13,10 @@ describe('user', () => { const VIDEO_ROLES = graphql(` query VideoRoles { - _entities(representations: [{ __typename: "User", id: "id" }]) { - ... on User { + _entities( + representations: [{ __typename: "AuthenticatedUser", id: "id" }] + ) { + ... on AuthenticatedUser { id languageUserRoles } diff --git a/apis/api-languages/src/schema/user/user.ts b/apis/api-languages/src/schema/user/user.ts index 642944b581f..2378fd1ca64 100644 --- a/apis/api-languages/src/schema/user/user.ts +++ b/apis/api-languages/src/schema/user/user.ts @@ -4,23 +4,25 @@ import { builder } from '../builder' import { LanguageRole } from './enums/languageRole' -builder.externalRef('User', builder.selection<{ id: string }>('id')).implement({ - externalFields: (t) => ({ - id: t.id({ nullable: false }) - }), - fields: (t) => ({ - languageUserRoles: t.field({ - type: [LanguageRole], - nullable: false, - resolve: async (data) => { - return ( - ( - await prisma.userLanguageRole.findUnique({ - where: { userId: data.id } - }) - )?.roles ?? [] - ) - } +builder + .externalRef('AuthenticatedUser', builder.selection<{ id: string }>('id')) + .implement({ + externalFields: (t) => ({ + id: t.id({ nullable: false }) + }), + fields: (t) => ({ + languageUserRoles: t.field({ + type: [LanguageRole], + nullable: false, + resolve: async (data) => { + return ( + ( + await prisma.userLanguageRole.findUnique({ + where: { userId: data.id } + }) + )?.roles ?? [] + ) + } + }) }) }) -}) diff --git a/apis/api-media/schema.graphql b/apis/api-media/schema.graphql index 5111a0c6174..92675da72f1 100644 --- a/apis/api-media/schema.graphql +++ b/apis/api-media/schema.graphql @@ -9,6 +9,14 @@ type ArclightApiKey defaultPlatform: DefaultPlatform! } +type AuthenticatedUser + @key(fields: "id") + @extends +{ + id: ID! @external + mediaUserRoles: [MediaRole!]! +} + interface BaseError { message: String } @@ -648,7 +656,7 @@ type Playlist { createdAt: DateTime! updatedAt: DateTime! slug: String! - owner: User! + owner: AuthenticatedUser! items: [PlaylistItem!]! } @@ -1048,14 +1056,6 @@ type UnsplashUserLinks { self: String! } -type User - @key(fields: "id") - @extends -{ - id: ID! @external - mediaUserRoles: [MediaRole!]! -} - type Video @key(fields: "id primaryLanguageId") @shareable diff --git a/apis/api-media/src/schema/playlist/playlist.ts b/apis/api-media/src/schema/playlist/playlist.ts index a2eb218f250..9a230b15aac 100644 --- a/apis/api-media/src/schema/playlist/playlist.ts +++ b/apis/api-media/src/schema/playlist/playlist.ts @@ -6,7 +6,7 @@ import { Prisma, prisma } from '@core/prisma/media/client' import { builder } from '../builder' import { IdType, IdTypeShape } from '../enums/idType' import { NotFoundError } from '../error/NotFoundError' -import { UserRef } from '../user' +import { AuthenticatedUserRef } from '../user' import { PlaylistCreateInput } from './inputs/playlistCreate' import { PlaylistUpdateInput } from './inputs/playlistUpdate' @@ -52,7 +52,7 @@ export const Playlist = builder.prismaObject('Playlist', { slug: t.expose('slug', { type: 'String', nullable: false }), owner: t.field({ nullable: false, - type: UserRef, + type: AuthenticatedUserRef, resolve: async (parent) => ({ id: parent.ownerId }) }), items: t.relation('items', { diff --git a/apis/api-media/src/schema/user/index.ts b/apis/api-media/src/schema/user/index.ts index cacb05ea5af..6bcc03363bf 100644 --- a/apis/api-media/src/schema/user/index.ts +++ b/apis/api-media/src/schema/user/index.ts @@ -1,4 +1,4 @@ import './enums' import './user' -export { UserRef } from './user' +export { AuthenticatedUserRef } from './user' diff --git a/apis/api-media/src/schema/user/user.spec.ts b/apis/api-media/src/schema/user/user.spec.ts index 7ae9de04755..cd7c2c11f84 100644 --- a/apis/api-media/src/schema/user/user.spec.ts +++ b/apis/api-media/src/schema/user/user.spec.ts @@ -13,8 +13,10 @@ describe('user', () => { const VIDEO_ROLES = graphql(` query VideoRoles { - _entities(representations: [{ __typename: "User", id: "id" }]) { - ... on User { + _entities( + representations: [{ __typename: "AuthenticatedUser", id: "id" }] + ) { + ... on AuthenticatedUser { id mediaUserRoles } diff --git a/apis/api-media/src/schema/user/user.ts b/apis/api-media/src/schema/user/user.ts index f27b1297abc..41e73ffc63a 100644 --- a/apis/api-media/src/schema/user/user.ts +++ b/apis/api-media/src/schema/user/user.ts @@ -4,12 +4,12 @@ import { builder } from '../builder' import { MediaRole } from './enums/mediaRole' -export const UserRef = builder.externalRef( - 'User', +export const AuthenticatedUserRef = builder.externalRef( + 'AuthenticatedUser', builder.selection<{ id: string }>('id') ) -UserRef.implement({ +AuthenticatedUserRef.implement({ externalFields: (t) => ({ id: t.id({ nullable: false }) }), diff --git a/apis/api-users/schema.graphql b/apis/api-users/schema.graphql index e8efcd93300..e5573b7a13a 100644 --- a/apis/api-users/schema.graphql +++ b/apis/api-users/schema.graphql @@ -1,6 +1,22 @@ extend schema @link(url: "https://specs.apollo.dev/federation/v2.6", import: ["@key"]) +type AnonymousUser { + id: ID! +} + +type AuthenticatedUser + @key(fields: "id") +{ + id: ID! + firstName: String! + lastName: String + email: String! + imageUrl: String + superAdmin: Boolean + emailVerified: Boolean! +} + input CreateVerificationRequestInput { redirect: String } @@ -12,23 +28,13 @@ input MeInput { type Mutation { userImpersonate(email: String!): String createVerificationRequest(input: CreateVerificationRequestInput): Boolean - validateEmail(email: String!, token: String!): User + validateEmail(email: String!, token: String!): AuthenticatedUser } type Query { me(input: MeInput): User - user(id: ID!): User - userByEmail(email: String!): User + user(id: ID!): AuthenticatedUser + userByEmail(email: String!): AuthenticatedUser } -type User - @key(fields: "id") -{ - id: ID! - firstName: String! - lastName: String - email: String! - imageUrl: String - superAdmin: Boolean - emailVerified: Boolean! -} \ No newline at end of file +union User = AuthenticatedUser | AnonymousUser \ No newline at end of file diff --git a/apis/api-users/src/schema/builder.ts b/apis/api-users/src/schema/builder.ts index 47632c6ca59..c4223b53921 100644 --- a/apis/api-users/src/schema/builder.ts +++ b/apis/api-users/src/schema/builder.ts @@ -47,11 +47,13 @@ export const builder = new SchemaBuilder<{ } AuthScopes: { isAuthenticated: boolean + isAnonymous: boolean isSuperAdmin: boolean isValidInterop: boolean } AuthContexts: { isAuthenticated: Extract + isAnonymous: Extract isSuperAdmin: Extract isValidInterop: Extract } @@ -68,25 +70,27 @@ export const builder = new SchemaBuilder<{ authScopes: async (context: Context) => { switch (context.type) { case 'authenticated': + // eslint-disable-next-line no-case-declarations -- This is intentional + const user = await prisma.user.findUnique({ + where: { userId: context.currentUser.id } + }) return { - isAuthenticated: true, - isSuperAdmin: - ( - await prisma.user.findUnique({ - where: { userId: context.currentUser.id } - }) - )?.superAdmin ?? false, + isAuthenticated: user?.email != null, + isAnonymous: user?.email == null, + isSuperAdmin: user?.superAdmin ?? false, isValidInterop: false } case 'interop': return { isAuthenticated: false, + isAnonymous: false, isSuperAdmin: false, isValidInterop: true } default: return { isAuthenticated: false, + isAnonymous: false, isSuperAdmin: false, isValidInterop: false } diff --git a/apis/api-users/src/schema/user/objects/index.ts b/apis/api-users/src/schema/user/objects/index.ts index 88377789070..56c0793fbb8 100644 --- a/apis/api-users/src/schema/user/objects/index.ts +++ b/apis/api-users/src/schema/user/objects/index.ts @@ -1,3 +1,9 @@ import './user' -export { User } from './user' +export { + AuthenticatedUser, + AnonymousUser, + User, + isAuthenticatedUser +} from './user' +export type { UserShape } from './user' diff --git a/apis/api-users/src/schema/user/objects/user.ts b/apis/api-users/src/schema/user/objects/user.ts index adc67c1c1ed..37aa4cd166b 100644 --- a/apis/api-users/src/schema/user/objects/user.ts +++ b/apis/api-users/src/schema/user/objects/user.ts @@ -1,6 +1,17 @@ +import { User as PrismaUser } from '@core/prisma/users/client' + import { builder } from '../../builder' -export const User = builder.prismaObject('User', { +// Type for anonymous user shape +interface AnonymousUserShape { + id: string +} + +// Union member type +export type UserShape = PrismaUser | AnonymousUserShape + +export const AuthenticatedUser = builder.prismaObject('User', { + name: 'AuthenticatedUser', fields: (t) => ({ id: t.exposeID('id', { nullable: false }), firstName: t.field({ @@ -18,9 +29,38 @@ export const User = builder.prismaObject('User', { } }), lastName: t.exposeString('lastName'), - email: t.exposeString('email', { nullable: false }), + email: t.field({ + type: 'String', + nullable: false, + resolve: (user) => user.email ?? '' + }), imageUrl: t.exposeString('imageUrl'), superAdmin: t.exposeBoolean('superAdmin'), emailVerified: t.exposeBoolean('emailVerified', { nullable: false }) }) }) + +const AnonymousUserRef = builder.objectRef('AnonymousUser') + +export const AnonymousUser = builder.objectType(AnonymousUserRef, { + fields: (t) => ({ + id: t.exposeID('id', { nullable: false }) + }) +}) + +export const User = builder.unionType('User', { + types: [AuthenticatedUser, AnonymousUser], + resolveType: (user) => { + if ('email' in user && user.email != null) { + return AuthenticatedUser + } + return AnonymousUser + } +}) + +// Type guard for use in resolvers +export function isAuthenticatedUser( + user: PrismaUser | AnonymousUserShape +): user is PrismaUser { + return 'email' in user && user.email != null +} diff --git a/apis/api-users/src/schema/user/user.spec.ts b/apis/api-users/src/schema/user/user.spec.ts index 681b82aeca0..a28aa1f0a1c 100644 --- a/apis/api-users/src/schema/user/user.spec.ts +++ b/apis/api-users/src/schema/user/user.spec.ts @@ -61,13 +61,18 @@ describe('api-users', () => { const ME_QUERY = graphql(` query Me { me { - id - firstName - lastName - email - imageUrl - superAdmin - emailVerified + ... on AuthenticatedUser { + id + firstName + lastName + email + imageUrl + superAdmin + emailVerified + } + ... on AnonymousUser { + id + } } } `) diff --git a/apis/api-users/src/schema/user/user.ts b/apis/api-users/src/schema/user/user.ts index 3b04e525fc0..a0077b095fd 100644 --- a/apis/api-users/src/schema/user/user.ts +++ b/apis/api-users/src/schema/user/user.ts @@ -7,11 +7,11 @@ import { builder } from '../builder' import { findOrFetchUser } from './findOrFetchUser' import { CreateVerificationRequestInput, MeInput } from './inputs' -import { User } from './objects' +import { AuthenticatedUser, User } from './objects' import { validateEmail } from './validateEmail' import { verifyUser } from './verifyUser' -builder.asEntity(User, { +builder.asEntity(AuthenticatedUser, { key: builder.selection<{ id: string }>('id'), resolveReference: async ({ id }) => { try { @@ -47,8 +47,8 @@ builder.asEntity(User, { }) builder.queryFields((t) => ({ - me: t.withAuth({ isAuthenticated: true }).prismaField({ - type: 'User', + me: t.withAuth({ $any: { isAuthenticated: true, isAnonymous: true } }).field({ + type: User, nullable: true, args: { input: t.arg({ @@ -56,16 +56,24 @@ builder.queryFields((t) => ({ required: false }) }, - resolve: async (query, _parent, { input }, ctx) => { - return await findOrFetchUser( - query, + resolve: async (_parent, { input }, ctx) => { + const user = await findOrFetchUser( + {}, ctx.currentUser.id, input?.redirect ?? undefined ) + if (user == null) return null + + // Return appropriate type based on whether user has email + if (user.email != null) { + return user + } + // Anonymous user - only return id + return { id: user.id } } }), user: t.withAuth({ isValidInterop: true }).prismaField({ - type: 'User', + type: AuthenticatedUser, nullable: true, args: { id: t.arg.id({ required: true }) @@ -77,7 +85,7 @@ builder.queryFields((t) => ({ }) }), userByEmail: t.withAuth({ isValidInterop: true }).prismaField({ - type: 'User', + type: AuthenticatedUser, nullable: true, args: { email: t.arg.string({ required: true }) @@ -135,7 +143,7 @@ builder.mutationFields((t) => ({ } }), validateEmail: t.field({ - type: User, + type: AuthenticatedUser, args: { email: t.arg.string({ required: true }), token: t.arg.string({ required: true }) @@ -152,7 +160,7 @@ builder.mutationFields((t) => ({ extensions: { code: 'NOT_FOUND' } }) - const validatedEmail = await validateEmail(user.userId, user.email, token) + const validatedEmail = await validateEmail(user.userId, email, token) if (!validatedEmail) throw new GraphQLError('Invalid token', { extensions: { code: 'FORBIDDEN' } diff --git a/apps/journeys-admin/__generated__/CreateJourney.ts b/apps/journeys-admin/__generated__/CreateJourney.ts index ab0de3c690a..b770c2831a7 100644 --- a/apps/journeys-admin/__generated__/CreateJourney.ts +++ b/apps/journeys-admin/__generated__/CreateJourney.ts @@ -22,7 +22,7 @@ export interface CreateJourney_journeyCreate_language { } export interface CreateJourney_journeyCreate_userJourneys_user { - __typename: "User"; + __typename: "AuthenticatedUser"; id: string; firstName: string; lastName: string | null; diff --git a/apps/journeys-admin/__generated__/GetAdminJourney.ts b/apps/journeys-admin/__generated__/GetAdminJourney.ts index e9f42c6f046..5acadc99fa3 100644 --- a/apps/journeys-admin/__generated__/GetAdminJourney.ts +++ b/apps/journeys-admin/__generated__/GetAdminJourney.ts @@ -667,7 +667,7 @@ export interface GetAdminJourney_journey_creatorImageBlock { } export interface GetAdminJourney_journey_userJourneys_user { - __typename: "User"; + __typename: "AuthenticatedUser"; id: string; firstName: string; lastName: string | null; diff --git a/apps/journeys-admin/__generated__/GetAdminJourneyWithPlausibleToken.ts b/apps/journeys-admin/__generated__/GetAdminJourneyWithPlausibleToken.ts index 527951e0503..1f5fc16fa52 100644 --- a/apps/journeys-admin/__generated__/GetAdminJourneyWithPlausibleToken.ts +++ b/apps/journeys-admin/__generated__/GetAdminJourneyWithPlausibleToken.ts @@ -667,7 +667,7 @@ export interface GetAdminJourneyWithPlausibleToken_journey_creatorImageBlock { } export interface GetAdminJourneyWithPlausibleToken_journey_userJourneys_user { - __typename: "User"; + __typename: "AuthenticatedUser"; id: string; firstName: string; lastName: string | null; diff --git a/apps/journeys-admin/__generated__/GetAdminJourneys.ts b/apps/journeys-admin/__generated__/GetAdminJourneys.ts index da9104ed839..014c09aa5b4 100644 --- a/apps/journeys-admin/__generated__/GetAdminJourneys.ts +++ b/apps/journeys-admin/__generated__/GetAdminJourneys.ts @@ -22,7 +22,7 @@ export interface GetAdminJourneys_journeys_language { } export interface GetAdminJourneys_journeys_userJourneys_user { - __typename: "User"; + __typename: "AuthenticatedUser"; id: string; firstName: string; lastName: string | null; diff --git a/apps/journeys-admin/__generated__/GetCurrentUser.ts b/apps/journeys-admin/__generated__/GetCurrentUser.ts index a5dc3ae8fb3..bd9dbac32ca 100644 --- a/apps/journeys-admin/__generated__/GetCurrentUser.ts +++ b/apps/journeys-admin/__generated__/GetCurrentUser.ts @@ -7,12 +7,19 @@ // GraphQL query operation: GetCurrentUser // ==================================================== -export interface GetCurrentUser_me { - __typename: "User"; +export interface GetCurrentUser_me_AuthenticatedUser { + __typename: "AuthenticatedUser"; id: string; email: string; } +export interface GetCurrentUser_me_AnonymousUser { + __typename: "AnonymousUser"; + id: string; +} + +export type GetCurrentUser_me = GetCurrentUser_me_AuthenticatedUser | GetCurrentUser_me_AnonymousUser; + export interface GetCurrentUser { me: GetCurrentUser_me | null; } diff --git a/apps/journeys-admin/__generated__/GetIntegration.ts b/apps/journeys-admin/__generated__/GetIntegration.ts index cf18665550d..adcebe2dedf 100644 --- a/apps/journeys-admin/__generated__/GetIntegration.ts +++ b/apps/journeys-admin/__generated__/GetIntegration.ts @@ -36,7 +36,7 @@ export interface GetIntegration_integrations_IntegrationGoogle_team { } export interface GetIntegration_integrations_IntegrationGoogle_user { - __typename: "User"; + __typename: "AuthenticatedUser"; id: string; email: string; } diff --git a/apps/journeys-admin/__generated__/GetJourney.ts b/apps/journeys-admin/__generated__/GetJourney.ts index b6b660e62b8..91902e97140 100644 --- a/apps/journeys-admin/__generated__/GetJourney.ts +++ b/apps/journeys-admin/__generated__/GetJourney.ts @@ -667,7 +667,7 @@ export interface GetJourney_journey_creatorImageBlock { } export interface GetJourney_journey_userJourneys_user { - __typename: "User"; + __typename: "AuthenticatedUser"; id: string; firstName: string; lastName: string | null; diff --git a/apps/journeys-admin/__generated__/GetJourneyWithPermissions.ts b/apps/journeys-admin/__generated__/GetJourneyWithPermissions.ts index b90d98d0951..954099df370 100644 --- a/apps/journeys-admin/__generated__/GetJourneyWithPermissions.ts +++ b/apps/journeys-admin/__generated__/GetJourneyWithPermissions.ts @@ -10,7 +10,7 @@ import { UserTeamRole, UserJourneyRole } from "./globalTypes"; // ==================================================== export interface GetJourneyWithPermissions_journey_team_userTeams_user { - __typename: "User"; + __typename: "AuthenticatedUser"; email: string; firstName: string; id: string; @@ -39,7 +39,7 @@ export interface GetJourneyWithPermissions_journey_team { } export interface GetJourneyWithPermissions_journey_userJourneys_user { - __typename: "User"; + __typename: "AuthenticatedUser"; id: string; firstName: string; lastName: string | null; diff --git a/apps/journeys-admin/__generated__/GetJourneyWithUserRoles.ts b/apps/journeys-admin/__generated__/GetJourneyWithUserRoles.ts index 6afdac52f88..c32e7659d40 100644 --- a/apps/journeys-admin/__generated__/GetJourneyWithUserRoles.ts +++ b/apps/journeys-admin/__generated__/GetJourneyWithUserRoles.ts @@ -10,7 +10,7 @@ import { UserJourneyRole } from "./globalTypes"; // ==================================================== export interface GetJourneyWithUserRoles_adminJourney_userJourneys_user { - __typename: "User"; + __typename: "AuthenticatedUser"; id: string; email: string; } diff --git a/apps/journeys-admin/__generated__/GetJourneys.ts b/apps/journeys-admin/__generated__/GetJourneys.ts index 67e0d5cfb78..486737ad7a6 100644 --- a/apps/journeys-admin/__generated__/GetJourneys.ts +++ b/apps/journeys-admin/__generated__/GetJourneys.ts @@ -31,7 +31,7 @@ export interface GetJourneys_journeys_journeyCustomizationFields { } export interface GetJourneys_journeys_userJourneys_user { - __typename: "User"; + __typename: "AuthenticatedUser"; id: string; firstName: string; lastName: string | null; diff --git a/apps/journeys-admin/__generated__/GetLastActiveTeamIdAndTeams.ts b/apps/journeys-admin/__generated__/GetLastActiveTeamIdAndTeams.ts index 134d854294c..f151d41d194 100644 --- a/apps/journeys-admin/__generated__/GetLastActiveTeamIdAndTeams.ts +++ b/apps/journeys-admin/__generated__/GetLastActiveTeamIdAndTeams.ts @@ -16,7 +16,7 @@ export interface GetLastActiveTeamIdAndTeams_getJourneyProfile { } export interface GetLastActiveTeamIdAndTeams_teams_userTeams_user { - __typename: "User"; + __typename: "AuthenticatedUser"; id: string; firstName: string; lastName: string | null; diff --git a/apps/journeys-admin/__generated__/GetMe.ts b/apps/journeys-admin/__generated__/GetMe.ts index e13fe28cfec..8127e590049 100644 --- a/apps/journeys-admin/__generated__/GetMe.ts +++ b/apps/journeys-admin/__generated__/GetMe.ts @@ -9,8 +9,8 @@ import { MeInput } from "./globalTypes"; // GraphQL query operation: GetMe // ==================================================== -export interface GetMe_me { - __typename: "User"; +export interface GetMe_me_AuthenticatedUser { + __typename: "AuthenticatedUser"; id: string; firstName: string; lastName: string | null; @@ -20,6 +20,13 @@ export interface GetMe_me { emailVerified: boolean; } +export interface GetMe_me_AnonymousUser { + __typename: "AnonymousUser"; + id: string; +} + +export type GetMe_me = GetMe_me_AuthenticatedUser | GetMe_me_AnonymousUser; + export interface GetMe { me: GetMe_me | null; } diff --git a/apps/journeys-admin/__generated__/GetPublisherTemplate.ts b/apps/journeys-admin/__generated__/GetPublisherTemplate.ts index fa137c6cf3f..883ba964a83 100644 --- a/apps/journeys-admin/__generated__/GetPublisherTemplate.ts +++ b/apps/journeys-admin/__generated__/GetPublisherTemplate.ts @@ -667,7 +667,7 @@ export interface GetPublisherTemplate_publisherTemplate_creatorImageBlock { } export interface GetPublisherTemplate_publisherTemplate_userJourneys_user { - __typename: "User"; + __typename: "AuthenticatedUser"; id: string; firstName: string; lastName: string | null; diff --git a/apps/journeys-admin/__generated__/GetUserTeamsAndInvites.ts b/apps/journeys-admin/__generated__/GetUserTeamsAndInvites.ts index 991c9dbcf6c..f654360e3d6 100644 --- a/apps/journeys-admin/__generated__/GetUserTeamsAndInvites.ts +++ b/apps/journeys-admin/__generated__/GetUserTeamsAndInvites.ts @@ -10,7 +10,7 @@ import { UserTeamFilterInput, UserTeamRole } from "./globalTypes"; // ==================================================== export interface GetUserTeamsAndInvites_userTeams_user { - __typename: "User"; + __typename: "AuthenticatedUser"; email: string; firstName: string; id: string; diff --git a/apps/journeys-admin/__generated__/JourneyFields.ts b/apps/journeys-admin/__generated__/JourneyFields.ts index e4ab828d645..57a5d7ba854 100644 --- a/apps/journeys-admin/__generated__/JourneyFields.ts +++ b/apps/journeys-admin/__generated__/JourneyFields.ts @@ -667,7 +667,7 @@ export interface JourneyFields_creatorImageBlock { } export interface JourneyFields_userJourneys_user { - __typename: "User"; + __typename: "AuthenticatedUser"; id: string; firstName: string; lastName: string | null; diff --git a/apps/journeys-admin/__generated__/TeamCreate.ts b/apps/journeys-admin/__generated__/TeamCreate.ts index 9f96c886156..0d4547f6e67 100644 --- a/apps/journeys-admin/__generated__/TeamCreate.ts +++ b/apps/journeys-admin/__generated__/TeamCreate.ts @@ -10,7 +10,7 @@ import { TeamCreateInput, UserTeamRole } from "./globalTypes"; // ==================================================== export interface TeamCreate_teamCreate_userTeams_user { - __typename: "User"; + __typename: "AuthenticatedUser"; id: string; firstName: string; lastName: string | null; diff --git a/apps/journeys-admin/__generated__/UserTeamUpdate.ts b/apps/journeys-admin/__generated__/UserTeamUpdate.ts index c0d0baef764..786fe3a4e07 100644 --- a/apps/journeys-admin/__generated__/UserTeamUpdate.ts +++ b/apps/journeys-admin/__generated__/UserTeamUpdate.ts @@ -10,7 +10,7 @@ import { UserTeamUpdateInput, UserTeamRole } from "./globalTypes"; // ==================================================== export interface UserTeamUpdate_userTeamUpdate_user { - __typename: "User"; + __typename: "AuthenticatedUser"; id: string; } diff --git a/apps/journeys-admin/__generated__/ValidateEmail.ts b/apps/journeys-admin/__generated__/ValidateEmail.ts index 268e2f2c1e5..4134ca0e524 100644 --- a/apps/journeys-admin/__generated__/ValidateEmail.ts +++ b/apps/journeys-admin/__generated__/ValidateEmail.ts @@ -8,7 +8,7 @@ // ==================================================== export interface ValidateEmail_validateEmail { - __typename: "User"; + __typename: "AuthenticatedUser"; id: string; emailVerified: boolean; } diff --git a/apps/journeys-admin/pages/templates/index.tsx b/apps/journeys-admin/pages/templates/index.tsx index 00ae2733d9b..0abd4c3785c 100644 --- a/apps/journeys-admin/pages/templates/index.tsx +++ b/apps/journeys-admin/pages/templates/index.tsx @@ -34,7 +34,7 @@ function TemplateIndexPage(): ReactElement { const router = useRouter() const { data } = useQuery(GET_ME) const { query } = useTeam() - if (data?.me?.id != null && !data?.me?.emailVerified) { + if (data?.me?.__typename === 'AuthenticatedUser' && data?.me?.id != null && !data?.me?.emailVerified) { void router.push('/users/verify?redirect=/templates') } diff --git a/apps/journeys-admin/pages/users/verify.tsx b/apps/journeys-admin/pages/users/verify.tsx index 3f50e3e703b..7495dd36615 100644 --- a/apps/journeys-admin/pages/users/verify.tsx +++ b/apps/journeys-admin/pages/users/verify.tsx @@ -234,7 +234,7 @@ export const getServerSideProps = withUserTokenSSR({ query: GET_ME, variables: { input: { redirect: query.redirect ?? undefined } } }) - if (apiUser.data?.me?.emailVerified ?? false) { + if (apiUser.data?.me?.__typename === 'AuthenticatedUser' && (apiUser.data?.me?.emailVerified ?? false)) { return { redirect: { permanent: false, diff --git a/apps/journeys-admin/src/components/AccessAvatars/AccessAvatars.stories.tsx b/apps/journeys-admin/src/components/AccessAvatars/AccessAvatars.stories.tsx index e3bc6ddd1e4..5b2c7d06417 100644 --- a/apps/journeys-admin/src/components/AccessAvatars/AccessAvatars.stories.tsx +++ b/apps/journeys-admin/src/components/AccessAvatars/AccessAvatars.stories.tsx @@ -36,7 +36,7 @@ const noImageUserJourneys: UserJourney[] = [ { ...userJourney1, user: { - __typename: 'User', + __typename: 'AuthenticatedUser', id: '1', firstName: 'Amin', lastName: 'One', @@ -46,7 +46,7 @@ const noImageUserJourneys: UserJourney[] = [ { ...userJourney2, user: { - __typename: 'User', + __typename: 'AuthenticatedUser', id: '2', firstName: 'Horace', lastName: 'Two', @@ -56,7 +56,7 @@ const noImageUserJourneys: UserJourney[] = [ { ...userJourney3, user: { - __typename: 'User', + __typename: 'AuthenticatedUser', id: '3', firstName: 'Coral', lastName: 'Three', diff --git a/apps/journeys-admin/src/components/AccessAvatars/data.ts b/apps/journeys-admin/src/components/AccessAvatars/data.ts index 3eb8c66ae4d..12b9c222758 100644 --- a/apps/journeys-admin/src/components/AccessAvatars/data.ts +++ b/apps/journeys-admin/src/components/AccessAvatars/data.ts @@ -7,7 +7,7 @@ export const userJourney1: UserJourney = { role: UserJourneyRole.owner, openedAt: null, user: { - __typename: 'User', + __typename: 'AuthenticatedUser', id: '1', firstName: 'Amin', lastName: 'One', @@ -21,7 +21,7 @@ export const userJourney2: UserJourney = { role: UserJourneyRole.owner, openedAt: null, user: { - __typename: 'User', + __typename: 'AuthenticatedUser', id: '2', firstName: 'Horace', lastName: 'Two', @@ -34,7 +34,7 @@ export const userJourney3: UserJourney = { role: UserJourneyRole.owner, openedAt: null, user: { - __typename: 'User', + __typename: 'AuthenticatedUser', id: '3', firstName: 'Coral', lastName: 'Three', @@ -47,7 +47,7 @@ export const userJourney4: UserJourney = { role: UserJourneyRole.owner, openedAt: null, user: { - __typename: 'User', + __typename: 'AuthenticatedUser', id: '4', firstName: 'Effie', lastName: 'Four', @@ -60,7 +60,7 @@ export const userJourney5: UserJourney = { role: UserJourneyRole.owner, openedAt: null, user: { - __typename: 'User', + __typename: 'AuthenticatedUser', id: '5', firstName: 'Janelle', lastName: 'Five', @@ -74,7 +74,7 @@ export const userJourney6: UserJourney = { role: UserJourneyRole.owner, openedAt: null, user: { - __typename: 'User', + __typename: 'AuthenticatedUser', id: '6', firstName: 'Drake', lastName: 'Six', @@ -88,7 +88,7 @@ export const userJourney7: UserJourney = { role: UserJourneyRole.inviteRequested, openedAt: null, user: { - __typename: 'User', + __typename: 'AuthenticatedUser', id: '6', firstName: 'Drake', lastName: 'Six', diff --git a/apps/journeys-admin/src/components/AccessDialog/AccessDialog.tsx b/apps/journeys-admin/src/components/AccessDialog/AccessDialog.tsx index 3f6b7b791f9..1346e5d05f1 100644 --- a/apps/journeys-admin/src/components/AccessDialog/AccessDialog.tsx +++ b/apps/journeys-admin/src/components/AccessDialog/AccessDialog.tsx @@ -93,13 +93,13 @@ export function AccessDialog({ const currentUserJourney = useMemo(() => { return data?.journey?.userJourneys?.find( - (userJourney) => userJourney.user?.email === user.email + (userJourney) => userJourney.user?.__typename === 'AuthenticatedUser' && user.__typename === 'AuthenticatedUser' && userJourney.user?.email === user.email ) }, [data?.journey?.userJourneys, user]) const currentUserTeam: UserTeam | undefined = useMemo(() => { return data?.journey?.team?.userTeams.find(({ user: { email } }) => { - return email === user?.email + return user?.__typename === 'AuthenticatedUser' && email === user.email }) }, [data?.journey?.team?.userTeams, user]) diff --git a/apps/journeys-admin/src/components/AccessDialog/UserList/UserList.spec.tsx b/apps/journeys-admin/src/components/AccessDialog/UserList/UserList.spec.tsx index d3140bb234b..af35b812480 100644 --- a/apps/journeys-admin/src/components/AccessDialog/UserList/UserList.spec.tsx +++ b/apps/journeys-admin/src/components/AccessDialog/UserList/UserList.spec.tsx @@ -14,7 +14,7 @@ describe('UserList', () => { __typename: 'UserJourney', role: UserJourneyRole.editor, user: { - __typename: 'User', + __typename: 'AuthenticatedUser', id: 'user1.id', firstName: 'firstName1', lastName: 'lastName1', @@ -29,7 +29,7 @@ describe('UserList', () => { __typename: 'UserJourney', role: UserJourneyRole.owner, user: { - __typename: 'User', + __typename: 'AuthenticatedUser', id: 'user2.id', firstName: 'firstName2', lastName: 'lastName2', diff --git a/apps/journeys-admin/src/components/AccessDialog/UserList/UserListItem/UserListItem.spec.tsx b/apps/journeys-admin/src/components/AccessDialog/UserList/UserListItem/UserListItem.spec.tsx index eaf4b40cbf7..d449e131b94 100644 --- a/apps/journeys-admin/src/components/AccessDialog/UserList/UserListItem/UserListItem.spec.tsx +++ b/apps/journeys-admin/src/components/AccessDialog/UserList/UserListItem/UserListItem.spec.tsx @@ -18,7 +18,7 @@ const owner: UserJourney = { id: 'userJourneyOwner.id', role: UserJourneyRole.owner, user: { - __typename: 'User', + __typename: 'AuthenticatedUser', id: 'owner.id', firstName: 'ownerFirstName', lastName: 'ownerLastName', @@ -33,7 +33,7 @@ const editor: UserJourney = { id: 'userJourneyEditor.id', role: UserJourneyRole.editor, user: { - __typename: 'User', + __typename: 'AuthenticatedUser', id: 'editor.id', firstName: 'editorFirstName', lastName: 'editorLastName', @@ -48,7 +48,7 @@ const editor2: UserJourney = { id: 'userJourneyEditor2.id', role: UserJourneyRole.editor, user: { - __typename: 'User', + __typename: 'AuthenticatedUser', id: 'editor2.id', firstName: 'editorFirstName', lastName: 'editorLastName', @@ -63,7 +63,7 @@ const userRequest: UserJourney = { id: 'userJourneyRequest.id', role: UserJourneyRole.inviteRequested, user: { - __typename: 'User', + __typename: 'AuthenticatedUser', id: 'request.id', firstName: 'requestFirstName', lastName: 'requestLastName', diff --git a/apps/journeys-admin/src/components/Avatar/Avatar.spec.tsx b/apps/journeys-admin/src/components/Avatar/Avatar.spec.tsx index 7ee66730961..a9242556097 100644 --- a/apps/journeys-admin/src/components/Avatar/Avatar.spec.tsx +++ b/apps/journeys-admin/src/components/Avatar/Avatar.spec.tsx @@ -6,7 +6,7 @@ import { Avatar } from '.' describe('Avatar', () => { const apiUser: ApiUser = { - __typename: 'User', + __typename: 'AuthenticatedUser', id: '1', firstName: 'Person', lastName: 'One', diff --git a/apps/journeys-admin/src/components/Avatar/Avatar.stories.tsx b/apps/journeys-admin/src/components/Avatar/Avatar.stories.tsx index 4691b03ed90..a4c14032ef6 100644 --- a/apps/journeys-admin/src/components/Avatar/Avatar.stories.tsx +++ b/apps/journeys-admin/src/components/Avatar/Avatar.stories.tsx @@ -20,7 +20,7 @@ const Template: StoryObj = { { __typename: 'UserTeam', role: UserTeamRole.manager, user: { - __typename: 'User', + __typename: 'AuthenticatedUser', email: user1.email, firstName: 'User', id: user1.id, diff --git a/apps/journeys-admin/src/components/Editor/Slider/Settings/CanvasDetails/JourneyAppearance/Host/Host.stories.tsx b/apps/journeys-admin/src/components/Editor/Slider/Settings/CanvasDetails/JourneyAppearance/Host/Host.stories.tsx index 3ba95f49c69..69224cb9c22 100644 --- a/apps/journeys-admin/src/components/Editor/Slider/Settings/CanvasDetails/JourneyAppearance/Host/Host.stories.tsx +++ b/apps/journeys-admin/src/components/Editor/Slider/Settings/CanvasDetails/JourneyAppearance/Host/Host.stories.tsx @@ -42,7 +42,7 @@ const userTeam: UserTeam = { __typename: 'UserTeam', role: UserTeamRole.manager, user: { - __typename: 'User', + __typename: 'AuthenticatedUser', email: user.email, firstName: 'User', id: user.id, diff --git a/apps/journeys-admin/src/components/Editor/Slider/Settings/CanvasDetails/JourneyAppearance/Host/Host.tsx b/apps/journeys-admin/src/components/Editor/Slider/Settings/CanvasDetails/JourneyAppearance/Host/Host.tsx index e70fae4fa84..32debc30799 100644 --- a/apps/journeys-admin/src/components/Editor/Slider/Settings/CanvasDetails/JourneyAppearance/Host/Host.tsx +++ b/apps/journeys-admin/src/components/Editor/Slider/Settings/CanvasDetails/JourneyAppearance/Host/Host.tsx @@ -75,7 +75,7 @@ export function Host(): ReactElement { const userInTeam = data == null || data.userTeams.length === 0 || journey?.team == null ? false - : data.userTeams.find((userTeam) => userTeam.user.email === user.email) != + : data.userTeams.find((userTeam) => userTeam.user?.__typename === 'AuthenticatedUser' && user.__typename === 'AuthenticatedUser' && userTeam.user.email === user.email) != null // Fetch all hosts made for a team diff --git a/apps/journeys-admin/src/components/Editor/Slider/Settings/CanvasDetails/JourneyAppearance/Host/HostSelection/HostSelection.spec.tsx b/apps/journeys-admin/src/components/Editor/Slider/Settings/CanvasDetails/JourneyAppearance/Host/HostSelection/HostSelection.spec.tsx index 3f4b7c793b3..68adb4eb638 100644 --- a/apps/journeys-admin/src/components/Editor/Slider/Settings/CanvasDetails/JourneyAppearance/Host/HostSelection/HostSelection.spec.tsx +++ b/apps/journeys-admin/src/components/Editor/Slider/Settings/CanvasDetails/JourneyAppearance/Host/HostSelection/HostSelection.spec.tsx @@ -25,7 +25,7 @@ describe('HostSelection', () => { __typename: 'UserTeam', role: UserTeamRole.manager, user: { - __typename: 'User', + __typename: 'AuthenticatedUser', email: user.email, firstName: 'User', id: user.id, diff --git a/apps/journeys-admin/src/components/Editor/Slider/Settings/CanvasDetails/JourneyAppearance/Host/HostSelection/HostSelection.stories.tsx b/apps/journeys-admin/src/components/Editor/Slider/Settings/CanvasDetails/JourneyAppearance/Host/HostSelection/HostSelection.stories.tsx index dce98f5a2d7..16260f0a548 100644 --- a/apps/journeys-admin/src/components/Editor/Slider/Settings/CanvasDetails/JourneyAppearance/Host/HostSelection/HostSelection.stories.tsx +++ b/apps/journeys-admin/src/components/Editor/Slider/Settings/CanvasDetails/JourneyAppearance/Host/HostSelection/HostSelection.stories.tsx @@ -36,7 +36,7 @@ const userTeam: UserTeam = { __typename: 'UserTeam', role: UserTeamRole.manager, user: { - __typename: 'User', + __typename: 'AuthenticatedUser', email: user.email, firstName: 'User', id: user.id, diff --git a/apps/journeys-admin/src/components/EmailVerification/EmailVerification.tsx b/apps/journeys-admin/src/components/EmailVerification/EmailVerification.tsx index d26a9fd52d1..5add6f25ff8 100644 --- a/apps/journeys-admin/src/components/EmailVerification/EmailVerification.tsx +++ b/apps/journeys-admin/src/components/EmailVerification/EmailVerification.tsx @@ -43,7 +43,7 @@ export function EmailVerification({ } return ( owner?.user?.email === currentUser?.email, - [currentUser?.email, owner?.user?.email] + () => owner?.user?.email === currentUserEmail, + [currentUserEmail, owner?.user?.email] ) useEffect(() => { @@ -155,15 +161,15 @@ export function DefaultMenu({ // Determine the current user's role in the team const teamRole = useMemo(() => { - if (activeTeam?.userTeams == null || currentUser?.email == null) + if (activeTeam?.userTeams == null || currentUserEmail == null) return undefined const userTeam = activeTeam.userTeams.find( - (userTeam) => userTeam.user.email === currentUser.email + (userTeam) => userTeam.user.email === currentUserEmail ) return userTeam?.role - }, [activeTeam?.userTeams, currentUser?.email]) + }, [activeTeam?.userTeams, currentUserEmail]) const isPublisher = userRoleData?.getUserRole?.roles?.includes(Role.publisher) === true @@ -178,6 +184,10 @@ export function DefaultMenu({ const isLocalTemplate = journey?.template === true && journey?.team?.id !== 'jfp-team' + if (isAnonymousUser) { + return <> + } + return ( <> = __typename: 'UserJourney', id: 'user-journey-id', user: { - __typename: 'User', + __typename: 'AuthenticatedUser', id: 'user-id1', firstName: 'Admin', lastName: 'One', diff --git a/apps/journeys-admin/src/components/PageWrapper/NavigationDrawer/NavigationDrawer.spec.tsx b/apps/journeys-admin/src/components/PageWrapper/NavigationDrawer/NavigationDrawer.spec.tsx index 423ba27727b..faf522d035d 100644 --- a/apps/journeys-admin/src/components/PageWrapper/NavigationDrawer/NavigationDrawer.spec.tsx +++ b/apps/journeys-admin/src/components/PageWrapper/NavigationDrawer/NavigationDrawer.spec.tsx @@ -100,7 +100,7 @@ describe('NavigationDrawer', () => { email: 'amin@email.com', superAdmin: true, emailVerified: true, - __typename: 'User' + __typename: 'AuthenticatedUser' } } } diff --git a/apps/journeys-admin/src/components/PageWrapper/NavigationDrawer/NavigationDrawer.stories.tsx b/apps/journeys-admin/src/components/PageWrapper/NavigationDrawer/NavigationDrawer.stories.tsx index 8930c946431..fe8d90ef096 100644 --- a/apps/journeys-admin/src/components/PageWrapper/NavigationDrawer/NavigationDrawer.stories.tsx +++ b/apps/journeys-admin/src/components/PageWrapper/NavigationDrawer/NavigationDrawer.stories.tsx @@ -58,7 +58,7 @@ const getMeMock: MockedResponse = { email: 'amin@email.com', superAdmin: true, emailVerified: true, - __typename: 'User' + __typename: 'AuthenticatedUser' } } } diff --git a/apps/journeys-admin/src/components/PageWrapper/NavigationDrawer/UserNavigation/UserMenu/UserMenu.spec.tsx b/apps/journeys-admin/src/components/PageWrapper/NavigationDrawer/UserNavigation/UserMenu/UserMenu.spec.tsx index f83aed61ca6..bfe154d8260 100644 --- a/apps/journeys-admin/src/components/PageWrapper/NavigationDrawer/UserNavigation/UserMenu/UserMenu.spec.tsx +++ b/apps/journeys-admin/src/components/PageWrapper/NavigationDrawer/UserNavigation/UserMenu/UserMenu.spec.tsx @@ -43,7 +43,7 @@ describe('UserMenu', () => { { { { void diff --git a/apps/journeys-admin/src/components/PageWrapper/NavigationDrawer/UserNavigation/UserNavigation.tsx b/apps/journeys-admin/src/components/PageWrapper/NavigationDrawer/UserNavigation/UserNavigation.tsx index 8e04420fa80..1731938458b 100644 --- a/apps/journeys-admin/src/components/PageWrapper/NavigationDrawer/UserNavigation/UserNavigation.tsx +++ b/apps/journeys-admin/src/components/PageWrapper/NavigationDrawer/UserNavigation/UserNavigation.tsx @@ -43,13 +43,18 @@ const UserMenu = dynamic( export const GET_ME = gql` query GetMe($input: MeInput) { me(input: $input) { - id - firstName - lastName - email - imageUrl - superAdmin - emailVerified + ... on AuthenticatedUser { + id + firstName + lastName + email + imageUrl + superAdmin + emailVerified + } + ... on AnonymousUser { + id + } } } ` @@ -130,7 +135,7 @@ export function UserNavigation({ /> )} - {data.me.superAdmin === true && ( + {data.me?.__typename === 'AuthenticatedUser' && data.me.superAdmin === true && ( )} - - - + + + + - - - - {profileAnchorEl !== undefined && ( + + )} + {profileAnchorEl !== undefined && data.me?.__typename === 'AuthenticatedUser' && ( { - return activeTeam?.userTeams?.find(({ user: { email } }) => { - return email === currentUser?.email - })?.role + if (currentUser?.__typename === 'AuthenticatedUser') { + return activeTeam?.userTeams?.find(({ user: { email } }) => { + return email === currentUser?.email + })?.role + } + return undefined }, [activeTeam, currentUser]) return ( diff --git a/apps/journeys-admin/src/components/Team/TeamAvatars/TeamAvatars.spec.tsx b/apps/journeys-admin/src/components/Team/TeamAvatars/TeamAvatars.spec.tsx index 226a2b727d8..34c78e00283 100644 --- a/apps/journeys-admin/src/components/Team/TeamAvatars/TeamAvatars.spec.tsx +++ b/apps/journeys-admin/src/components/Team/TeamAvatars/TeamAvatars.spec.tsx @@ -12,7 +12,7 @@ describe('TeamAvatars', () => { __typename: 'UserTeam', id: 'userTeamId1', user: { - __typename: 'User', + __typename: 'AuthenticatedUser', id: 'userId', firstName: 'Joe', lastName: 'Bloggs', @@ -25,7 +25,7 @@ describe('TeamAvatars', () => { __typename: 'UserTeam', id: 'userTeamId1', user: { - __typename: 'User', + __typename: 'AuthenticatedUser', id: 'userId', firstName: 'Mike', lastName: 'The Guy', @@ -38,7 +38,7 @@ describe('TeamAvatars', () => { __typename: 'UserTeam', id: 'userTeamId1', user: { - __typename: 'User', + __typename: 'AuthenticatedUser', id: 'userId', firstName: 'Bob', lastName: 'The Builder', @@ -54,7 +54,7 @@ describe('TeamAvatars', () => { __typename: 'UserTeam', id: 'userTeamId1', user: { - __typename: 'User', + __typename: 'AuthenticatedUser', id: 'userId', firstName: 'Joe', lastName: 'Bloggs', @@ -67,7 +67,7 @@ describe('TeamAvatars', () => { __typename: 'UserTeam', id: 'userTeamId1', user: { - __typename: 'User', + __typename: 'AuthenticatedUser', id: 'userId', firstName: 'Mike', lastName: 'The Guy', @@ -80,7 +80,7 @@ describe('TeamAvatars', () => { __typename: 'UserTeam', id: 'userTeamId1', user: { - __typename: 'User', + __typename: 'AuthenticatedUser', id: 'userId', firstName: 'Bob', lastName: 'The Builder', @@ -93,7 +93,7 @@ describe('TeamAvatars', () => { __typename: 'UserTeam', id: 'userTeamId1', user: { - __typename: 'User', + __typename: 'AuthenticatedUser', id: 'userId', firstName: 'Hello', lastName: 'Kitty', @@ -106,7 +106,7 @@ describe('TeamAvatars', () => { __typename: 'UserTeam', id: 'userTeamId1', user: { - __typename: 'User', + __typename: 'AuthenticatedUser', id: 'userId', firstName: 'Serena', lastName: 'Williams', @@ -119,7 +119,7 @@ describe('TeamAvatars', () => { __typename: 'UserTeam', id: 'userTeamId1', user: { - __typename: 'User', + __typename: 'AuthenticatedUser', id: 'userId', firstName: 'Jonathan', lastName: 'G', @@ -132,7 +132,7 @@ describe('TeamAvatars', () => { __typename: 'UserTeam', id: 'userTeamId1', user: { - __typename: 'User', + __typename: 'AuthenticatedUser', id: 'userId', firstName: 'Meme', lastName: 'Guy', diff --git a/apps/journeys-admin/src/components/Team/TeamAvatars/TeamAvatars.stories.tsx b/apps/journeys-admin/src/components/Team/TeamAvatars/TeamAvatars.stories.tsx index 827dcd71c5a..de6b2ac36e3 100644 --- a/apps/journeys-admin/src/components/Team/TeamAvatars/TeamAvatars.stories.tsx +++ b/apps/journeys-admin/src/components/Team/TeamAvatars/TeamAvatars.stories.tsx @@ -19,7 +19,7 @@ const userTeam: UserTeams[] = [ __typename: 'UserTeam', id: 'userTeamId1', user: { - __typename: 'User', + __typename: 'AuthenticatedUser', id: 'userId', firstName: 'Joe', lastName: 'Bloggs', @@ -32,7 +32,7 @@ const userTeam: UserTeams[] = [ __typename: 'UserTeam', id: 'userTeamId1', user: { - __typename: 'User', + __typename: 'AuthenticatedUser', id: 'userId', firstName: 'Mike', lastName: 'The Guy', @@ -45,7 +45,7 @@ const userTeam: UserTeams[] = [ __typename: 'UserTeam', id: 'userTeamId1', user: { - __typename: 'User', + __typename: 'AuthenticatedUser', id: 'userId', firstName: 'Bob', lastName: 'The Builder', @@ -61,7 +61,7 @@ const userTeamOverflow: UserTeams[] = [ __typename: 'UserTeam', id: 'userTeamId1', user: { - __typename: 'User', + __typename: 'AuthenticatedUser', id: 'userId', firstName: 'Joe', lastName: 'Bloggs', @@ -74,7 +74,7 @@ const userTeamOverflow: UserTeams[] = [ __typename: 'UserTeam', id: 'userTeamId1', user: { - __typename: 'User', + __typename: 'AuthenticatedUser', id: 'userId', firstName: 'Mike', lastName: 'The Guy', @@ -87,7 +87,7 @@ const userTeamOverflow: UserTeams[] = [ __typename: 'UserTeam', id: 'userTeamId1', user: { - __typename: 'User', + __typename: 'AuthenticatedUser', id: 'userId', firstName: 'Bob', lastName: 'The Builder', @@ -100,7 +100,7 @@ const userTeamOverflow: UserTeams[] = [ __typename: 'UserTeam', id: 'userTeamId1', user: { - __typename: 'User', + __typename: 'AuthenticatedUser', id: 'userId', firstName: 'Hello', lastName: 'Kitty', @@ -113,7 +113,7 @@ const userTeamOverflow: UserTeams[] = [ __typename: 'UserTeam', id: 'userTeamId1', user: { - __typename: 'User', + __typename: 'AuthenticatedUser', id: 'userId', firstName: 'Serena', lastName: 'Williams', @@ -126,7 +126,7 @@ const userTeamOverflow: UserTeams[] = [ __typename: 'UserTeam', id: 'userTeamId1', user: { - __typename: 'User', + __typename: 'AuthenticatedUser', id: 'userId', firstName: 'Jonathan', lastName: 'G', diff --git a/apps/journeys-admin/src/components/Team/TeamManageDialog/TeamManageDialog.spec.tsx b/apps/journeys-admin/src/components/Team/TeamManageDialog/TeamManageDialog.spec.tsx index 2343a63890f..9fad643f6a5 100644 --- a/apps/journeys-admin/src/components/Team/TeamManageDialog/TeamManageDialog.spec.tsx +++ b/apps/journeys-admin/src/components/Team/TeamManageDialog/TeamManageDialog.spec.tsx @@ -55,7 +55,7 @@ describe('TeamManageDialog', () => { id: 'userTeamId', role: UserTeamRole.manager, user: { - __typename: 'User', + __typename: 'AuthenticatedUser', email: 'miguelohara@example.com', firstName: 'Miguel', id: 'userId', diff --git a/apps/journeys-admin/src/components/Team/TeamManageDialog/TeamManageWrapper/TeamManageWrapper.spec.tsx b/apps/journeys-admin/src/components/Team/TeamManageDialog/TeamManageWrapper/TeamManageWrapper.spec.tsx index 2151eb98d0e..4753dde6c6b 100644 --- a/apps/journeys-admin/src/components/Team/TeamManageDialog/TeamManageWrapper/TeamManageWrapper.spec.tsx +++ b/apps/journeys-admin/src/components/Team/TeamManageDialog/TeamManageWrapper/TeamManageWrapper.spec.tsx @@ -42,7 +42,7 @@ describe('TeamMembersList', () => { id: 'userTeamId', role: UserTeamRole.manager, user: { - __typename: 'User', + __typename: 'AuthenticatedUser', email: 'miguelohara@example.com', firstName: 'Miguel', id: 'userId', diff --git a/apps/journeys-admin/src/components/Team/TeamManageDialog/TeamManageWrapper/TeamManageWrapper.tsx b/apps/journeys-admin/src/components/Team/TeamManageDialog/TeamManageWrapper/TeamManageWrapper.tsx index b7b6a90749f..b02db3d5564 100644 --- a/apps/journeys-admin/src/components/Team/TeamManageDialog/TeamManageWrapper/TeamManageWrapper.tsx +++ b/apps/journeys-admin/src/components/Team/TeamManageDialog/TeamManageWrapper/TeamManageWrapper.tsx @@ -48,9 +48,12 @@ export function TeamManageWrapper({ }, [activeTeam, getUserTeamsAndInvites]) const currentUserTeam: UserTeam | undefined = useMemo(() => { - return data?.userTeams?.find(({ user: { email } }) => { - return email === currentUser?.email - }) + if (currentUser?.__typename === 'AuthenticatedUser') { + return data?.userTeams?.find(({ user: { email } }) => { + return email === currentUser.email + }) + } + return undefined }, [data, currentUser]) return ( diff --git a/apps/journeys-admin/src/components/Team/TeamManageDialog/UserTeamInviteList/UserTeamInviteList.spec.tsx b/apps/journeys-admin/src/components/Team/TeamManageDialog/UserTeamInviteList/UserTeamInviteList.spec.tsx index 5b4a2511483..376b4a5d49e 100644 --- a/apps/journeys-admin/src/components/Team/TeamManageDialog/UserTeamInviteList/UserTeamInviteList.spec.tsx +++ b/apps/journeys-admin/src/components/Team/TeamManageDialog/UserTeamInviteList/UserTeamInviteList.spec.tsx @@ -16,7 +16,7 @@ describe('UserTeamInviteList', () => { id: 'userTeamId', role: UserTeamRole.manager, user: { - __typename: 'User', + __typename: 'AuthenticatedUser', email: 'miguelohara@example.com', firstName: 'Miguel', id: 'userId', @@ -29,7 +29,7 @@ describe('UserTeamInviteList', () => { id: 'userTeamId2', role: UserTeamRole.member, user: { - __typename: 'User', + __typename: 'AuthenticatedUser', email: 'hobiebrown@example.com', firstName: 'Hobie', id: 'userId2', @@ -65,7 +65,7 @@ describe('UserTeamInviteList', () => { id: 'userTeamId', role: UserTeamRole.manager, user: { - __typename: 'User', + __typename: 'AuthenticatedUser', email: 'miguelohara@example.com', firstName: 'Miguel', id: 'userId', @@ -79,7 +79,7 @@ describe('UserTeamInviteList', () => { id: 'userTeamId2', role: UserTeamRole.member, user: { - __typename: 'User', + __typename: 'AuthenticatedUser', email: 'hobiebrown@example.com', firstName: 'Hobie', id: 'userId2', diff --git a/apps/journeys-admin/src/components/Team/TeamManageDialog/UserTeamList/UserTeamList.spec.tsx b/apps/journeys-admin/src/components/Team/TeamManageDialog/UserTeamList/UserTeamList.spec.tsx index 422a07b2379..215e9c8ed19 100644 --- a/apps/journeys-admin/src/components/Team/TeamManageDialog/UserTeamList/UserTeamList.spec.tsx +++ b/apps/journeys-admin/src/components/Team/TeamManageDialog/UserTeamList/UserTeamList.spec.tsx @@ -19,7 +19,7 @@ describe('UserTeamList', () => { id: 'userTeamId', role: UserTeamRole.manager, user: { - __typename: 'User', + __typename: 'AuthenticatedUser', email: 'miguelohara@example.com', firstName: 'Miguel', id: 'userId', @@ -32,7 +32,7 @@ describe('UserTeamList', () => { id: 'userTeamId2', role: UserTeamRole.member, user: { - __typename: 'User', + __typename: 'AuthenticatedUser', email: 'hobiebrown@example.com', firstName: 'Hobie', id: 'userId2', @@ -55,7 +55,7 @@ describe('UserTeamList', () => { id: 'userTeamId', role: UserTeamRole.manager, user: { - __typename: 'User', + __typename: 'AuthenticatedUser', email: 'miguelohara@example.com', firstName: 'Miguel', id: 'userId', @@ -69,7 +69,7 @@ describe('UserTeamList', () => { id: 'userTeamId2', role: UserTeamRole.member, user: { - __typename: 'User', + __typename: 'AuthenticatedUser', email: 'hobiebrown@example.com', firstName: 'Hobie', id: 'userId2', diff --git a/apps/journeys-admin/src/components/Team/TeamManageDialog/UserTeamList/UserTeamListItem/UserTeamListItem.spec.tsx b/apps/journeys-admin/src/components/Team/TeamManageDialog/UserTeamList/UserTeamListItem/UserTeamListItem.spec.tsx index 34b9171239a..d014459c845 100644 --- a/apps/journeys-admin/src/components/Team/TeamManageDialog/UserTeamList/UserTeamListItem/UserTeamListItem.spec.tsx +++ b/apps/journeys-admin/src/components/Team/TeamManageDialog/UserTeamList/UserTeamListItem/UserTeamListItem.spec.tsx @@ -12,7 +12,7 @@ describe('UserTeamListItem', () => { id: 'userTeamId', role: UserTeamRole.manager, user: { - __typename: 'User', + __typename: 'AuthenticatedUser', email: 'miguelohara@example.com', firstName: 'Miguel', id: 'userId', @@ -51,7 +51,7 @@ describe('UserTeamListItem', () => { id: 'userTeamId', role: UserTeamRole.manager, user: { - __typename: 'User', + __typename: 'AuthenticatedUser', email: 'miguelohara@example.com', firstName: 'Miguel', id: 'userId', diff --git a/apps/journeys-admin/src/components/Team/TeamUpdateDialog/TeamUpdateDialog.tsx b/apps/journeys-admin/src/components/Team/TeamUpdateDialog/TeamUpdateDialog.tsx index 9c7154acce5..ac32c267efd 100644 --- a/apps/journeys-admin/src/components/Team/TeamUpdateDialog/TeamUpdateDialog.tsx +++ b/apps/journeys-admin/src/components/Team/TeamUpdateDialog/TeamUpdateDialog.tsx @@ -54,9 +54,12 @@ export function TeamUpdateDialog({ }, [loadUser]) const currentUserTeamRole: UserTeamRole | undefined = useMemo(() => { - return activeTeam?.userTeams?.find(({ user: { email } }) => { - return email === currentUser?.email - })?.role + if (currentUser?.__typename === 'AuthenticatedUser') { + return activeTeam?.userTeams?.find(({ user: { email } }) => { + return email === currentUser.email + })?.role + } + return undefined }, [activeTeam, currentUser]) async function handleSubmit( diff --git a/apps/journeys-admin/src/components/Team/UserTeamInviteForm/UserTeamInviteForm.spec.tsx b/apps/journeys-admin/src/components/Team/UserTeamInviteForm/UserTeamInviteForm.spec.tsx index 9c719489413..043dda5d2ab 100644 --- a/apps/journeys-admin/src/components/Team/UserTeamInviteForm/UserTeamInviteForm.spec.tsx +++ b/apps/journeys-admin/src/components/Team/UserTeamInviteForm/UserTeamInviteForm.spec.tsx @@ -92,7 +92,7 @@ describe('UserTeamInviteForm', () => { __typename: 'UserTeam', role: UserTeamRole.manager, user: { - __typename: 'User', + __typename: 'AuthenticatedUser', email: 'siyangguccigang@example.com', firstName: 'Siyang', id: 'userId', diff --git a/apps/journeys-admin/src/libs/checkConditionalRedirect/checkConditionalRedirect.ts b/apps/journeys-admin/src/libs/checkConditionalRedirect/checkConditionalRedirect.ts index d0747d76416..a52ef64ef00 100644 --- a/apps/journeys-admin/src/libs/checkConditionalRedirect/checkConditionalRedirect.ts +++ b/apps/journeys-admin/src/libs/checkConditionalRedirect/checkConditionalRedirect.ts @@ -52,11 +52,13 @@ export async function checkConditionalRedirect({ variables: { input: { redirect } } }) - if (!(me.me?.emailVerified ?? false)) { - if (resolvedUrl.startsWith('/users/verify')) return - return { - destination: `/users/verify${redirect}`, - permanent: false + if (me.me?.__typename === 'AuthenticatedUser') { + if (!(me.me?.emailVerified ?? false)) { + if (resolvedUrl.startsWith('/users/verify')) return + return { + destination: `/users/verify${redirect}`, + permanent: false + } } } diff --git a/apps/journeys-admin/src/libs/useCurrentUserLazyQuery/useCurrentUserLazyQuery.mock.ts b/apps/journeys-admin/src/libs/useCurrentUserLazyQuery/useCurrentUserLazyQuery.mock.ts index dd8bfad7514..951a6295037 100644 --- a/apps/journeys-admin/src/libs/useCurrentUserLazyQuery/useCurrentUserLazyQuery.mock.ts +++ b/apps/journeys-admin/src/libs/useCurrentUserLazyQuery/useCurrentUserLazyQuery.mock.ts @@ -11,7 +11,7 @@ export const mockUseCurrentUserLazyQuery: MockedResponse = { result: { data: { me: { - __typename: 'User', + __typename: 'AuthenticatedUser', id: 'user.id', email: 'test@email.com' } diff --git a/apps/journeys-admin/src/libs/useCurrentUserLazyQuery/useCurrentUserLazyQuery.ts b/apps/journeys-admin/src/libs/useCurrentUserLazyQuery/useCurrentUserLazyQuery.ts index f78251e6b17..35a48d2ddf4 100644 --- a/apps/journeys-admin/src/libs/useCurrentUserLazyQuery/useCurrentUserLazyQuery.ts +++ b/apps/journeys-admin/src/libs/useCurrentUserLazyQuery/useCurrentUserLazyQuery.ts @@ -13,8 +13,13 @@ import { export const GET_CURRENT_USER = gql` query GetCurrentUser { me { - id - email + ... on AuthenticatedUser { + id + email + } + ... on AnonymousUser { + id + } } } ` @@ -29,5 +34,8 @@ export function useCurrentUserLazyQuery(): { return { loadUser, data: data.me } } - return { loadUser, data: { __typename: 'User', id: '', email: '' } } + return { + loadUser, + data: { __typename: 'AuthenticatedUser', id: '', email: '' } + } } diff --git a/apps/journeys/__generated__/GetJourney.ts b/apps/journeys/__generated__/GetJourney.ts index b6b660e62b8..91902e97140 100644 --- a/apps/journeys/__generated__/GetJourney.ts +++ b/apps/journeys/__generated__/GetJourney.ts @@ -667,7 +667,7 @@ export interface GetJourney_journey_creatorImageBlock { } export interface GetJourney_journey_userJourneys_user { - __typename: "User"; + __typename: "AuthenticatedUser"; id: string; firstName: string; lastName: string | null; diff --git a/apps/journeys/__generated__/GetJourneys.ts b/apps/journeys/__generated__/GetJourneys.ts index 67e0d5cfb78..486737ad7a6 100644 --- a/apps/journeys/__generated__/GetJourneys.ts +++ b/apps/journeys/__generated__/GetJourneys.ts @@ -31,7 +31,7 @@ export interface GetJourneys_journeys_journeyCustomizationFields { } export interface GetJourneys_journeys_userJourneys_user { - __typename: "User"; + __typename: "AuthenticatedUser"; id: string; firstName: string; lastName: string | null; diff --git a/apps/journeys/__generated__/GetLastActiveTeamIdAndTeams.ts b/apps/journeys/__generated__/GetLastActiveTeamIdAndTeams.ts index 134d854294c..f151d41d194 100644 --- a/apps/journeys/__generated__/GetLastActiveTeamIdAndTeams.ts +++ b/apps/journeys/__generated__/GetLastActiveTeamIdAndTeams.ts @@ -16,7 +16,7 @@ export interface GetLastActiveTeamIdAndTeams_getJourneyProfile { } export interface GetLastActiveTeamIdAndTeams_teams_userTeams_user { - __typename: "User"; + __typename: "AuthenticatedUser"; id: string; firstName: string; lastName: string | null; diff --git a/apps/journeys/__generated__/JourneyFields.ts b/apps/journeys/__generated__/JourneyFields.ts index e4ab828d645..57a5d7ba854 100644 --- a/apps/journeys/__generated__/JourneyFields.ts +++ b/apps/journeys/__generated__/JourneyFields.ts @@ -667,7 +667,7 @@ export interface JourneyFields_creatorImageBlock { } export interface JourneyFields_userJourneys_user { - __typename: "User"; + __typename: "AuthenticatedUser"; id: string; firstName: string; lastName: string | null; diff --git a/apps/resources/__generated__/GetJourney.ts b/apps/resources/__generated__/GetJourney.ts index b6b660e62b8..91902e97140 100644 --- a/apps/resources/__generated__/GetJourney.ts +++ b/apps/resources/__generated__/GetJourney.ts @@ -667,7 +667,7 @@ export interface GetJourney_journey_creatorImageBlock { } export interface GetJourney_journey_userJourneys_user { - __typename: "User"; + __typename: "AuthenticatedUser"; id: string; firstName: string; lastName: string | null; diff --git a/apps/resources/__generated__/GetJourneys.ts b/apps/resources/__generated__/GetJourneys.ts index 67e0d5cfb78..486737ad7a6 100644 --- a/apps/resources/__generated__/GetJourneys.ts +++ b/apps/resources/__generated__/GetJourneys.ts @@ -31,7 +31,7 @@ export interface GetJourneys_journeys_journeyCustomizationFields { } export interface GetJourneys_journeys_userJourneys_user { - __typename: "User"; + __typename: "AuthenticatedUser"; id: string; firstName: string; lastName: string | null; diff --git a/apps/resources/__generated__/GetLastActiveTeamIdAndTeams.ts b/apps/resources/__generated__/GetLastActiveTeamIdAndTeams.ts index 134d854294c..f151d41d194 100644 --- a/apps/resources/__generated__/GetLastActiveTeamIdAndTeams.ts +++ b/apps/resources/__generated__/GetLastActiveTeamIdAndTeams.ts @@ -16,7 +16,7 @@ export interface GetLastActiveTeamIdAndTeams_getJourneyProfile { } export interface GetLastActiveTeamIdAndTeams_teams_userTeams_user { - __typename: "User"; + __typename: "AuthenticatedUser"; id: string; firstName: string; lastName: string | null; diff --git a/apps/resources/__generated__/JourneyFields.ts b/apps/resources/__generated__/JourneyFields.ts index e4ab828d645..57a5d7ba854 100644 --- a/apps/resources/__generated__/JourneyFields.ts +++ b/apps/resources/__generated__/JourneyFields.ts @@ -667,7 +667,7 @@ export interface JourneyFields_creatorImageBlock { } export interface JourneyFields_userJourneys_user { - __typename: "User"; + __typename: "AuthenticatedUser"; id: string; firstName: string; lastName: string | null; diff --git a/apps/watch/__generated__/GetJourney.ts b/apps/watch/__generated__/GetJourney.ts index b6b660e62b8..91902e97140 100644 --- a/apps/watch/__generated__/GetJourney.ts +++ b/apps/watch/__generated__/GetJourney.ts @@ -667,7 +667,7 @@ export interface GetJourney_journey_creatorImageBlock { } export interface GetJourney_journey_userJourneys_user { - __typename: "User"; + __typename: "AuthenticatedUser"; id: string; firstName: string; lastName: string | null; diff --git a/apps/watch/__generated__/GetJourneys.ts b/apps/watch/__generated__/GetJourneys.ts index 67e0d5cfb78..486737ad7a6 100644 --- a/apps/watch/__generated__/GetJourneys.ts +++ b/apps/watch/__generated__/GetJourneys.ts @@ -31,7 +31,7 @@ export interface GetJourneys_journeys_journeyCustomizationFields { } export interface GetJourneys_journeys_userJourneys_user { - __typename: "User"; + __typename: "AuthenticatedUser"; id: string; firstName: string; lastName: string | null; diff --git a/apps/watch/__generated__/GetLastActiveTeamIdAndTeams.ts b/apps/watch/__generated__/GetLastActiveTeamIdAndTeams.ts index 134d854294c..f151d41d194 100644 --- a/apps/watch/__generated__/GetLastActiveTeamIdAndTeams.ts +++ b/apps/watch/__generated__/GetLastActiveTeamIdAndTeams.ts @@ -16,7 +16,7 @@ export interface GetLastActiveTeamIdAndTeams_getJourneyProfile { } export interface GetLastActiveTeamIdAndTeams_teams_userTeams_user { - __typename: "User"; + __typename: "AuthenticatedUser"; id: string; firstName: string; lastName: string | null; diff --git a/apps/watch/__generated__/JourneyFields.ts b/apps/watch/__generated__/JourneyFields.ts index e4ab828d645..57a5d7ba854 100644 --- a/apps/watch/__generated__/JourneyFields.ts +++ b/apps/watch/__generated__/JourneyFields.ts @@ -667,7 +667,7 @@ export interface JourneyFields_creatorImageBlock { } export interface JourneyFields_userJourneys_user { - __typename: "User"; + __typename: "AuthenticatedUser"; id: string; firstName: string; lastName: string | null; diff --git a/libs/journeys/ui/src/components/TeamProvider/TeamProvider.mock.ts b/libs/journeys/ui/src/components/TeamProvider/TeamProvider.mock.ts index 7e4a81408cd..5602762603c 100644 --- a/libs/journeys/ui/src/components/TeamProvider/TeamProvider.mock.ts +++ b/libs/journeys/ui/src/components/TeamProvider/TeamProvider.mock.ts @@ -23,7 +23,7 @@ export const getLastActiveTeamIdAndTeamsMock: MockedResponse Date: Tue, 3 Feb 2026 20:47:46 +0000 Subject: [PATCH 03/18] test: update User type to AuthenticatedUser in useJourneyQuery tests --- .../ui/src/libs/useJourneyQuery/useJourneyQuery.spec.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/journeys/ui/src/libs/useJourneyQuery/useJourneyQuery.spec.tsx b/libs/journeys/ui/src/libs/useJourneyQuery/useJourneyQuery.spec.tsx index 45720777f92..4d28081526f 100644 --- a/libs/journeys/ui/src/libs/useJourneyQuery/useJourneyQuery.spec.tsx +++ b/libs/journeys/ui/src/libs/useJourneyQuery/useJourneyQuery.spec.tsx @@ -62,7 +62,7 @@ describe('useJourneyQuery', () => { role: UserJourneyRole.owner, openedAt: null, user: { - __typename: 'User', + __typename: 'AuthenticatedUser', id: 'user-id1', firstName: 'Amin', lastName: 'One', From fc5e6c80bdd03d41b4ff326916394ec42728f881 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Tue, 3 Feb 2026 20:57:05 +0000 Subject: [PATCH 04/18] fix: lint issues --- apps/journeys-admin/pages/templates/index.tsx | 6 ++- apps/journeys-admin/pages/users/verify.tsx | 5 +- .../components/AccessDialog/AccessDialog.tsx | 5 +- .../JourneyAppearance/Host/Host.tsx | 8 +++- .../EmailVerification/EmailVerification.tsx | 4 +- .../UserNavigation/UserNavigation.tsx | 48 ++++++++++--------- 6 files changed, 47 insertions(+), 29 deletions(-) diff --git a/apps/journeys-admin/pages/templates/index.tsx b/apps/journeys-admin/pages/templates/index.tsx index 0abd4c3785c..e0c96fc31b5 100644 --- a/apps/journeys-admin/pages/templates/index.tsx +++ b/apps/journeys-admin/pages/templates/index.tsx @@ -34,7 +34,11 @@ function TemplateIndexPage(): ReactElement { const router = useRouter() const { data } = useQuery(GET_ME) const { query } = useTeam() - if (data?.me?.__typename === 'AuthenticatedUser' && data?.me?.id != null && !data?.me?.emailVerified) { + if ( + data?.me?.__typename === 'AuthenticatedUser' && + data?.me?.id != null && + !data?.me?.emailVerified + ) { void router.push('/users/verify?redirect=/templates') } diff --git a/apps/journeys-admin/pages/users/verify.tsx b/apps/journeys-admin/pages/users/verify.tsx index 7495dd36615..82df0d0811e 100644 --- a/apps/journeys-admin/pages/users/verify.tsx +++ b/apps/journeys-admin/pages/users/verify.tsx @@ -234,7 +234,10 @@ export const getServerSideProps = withUserTokenSSR({ query: GET_ME, variables: { input: { redirect: query.redirect ?? undefined } } }) - if (apiUser.data?.me?.__typename === 'AuthenticatedUser' && (apiUser.data?.me?.emailVerified ?? false)) { + if ( + apiUser.data?.me?.__typename === 'AuthenticatedUser' && + (apiUser.data?.me?.emailVerified ?? false) + ) { return { redirect: { permanent: false, diff --git a/apps/journeys-admin/src/components/AccessDialog/AccessDialog.tsx b/apps/journeys-admin/src/components/AccessDialog/AccessDialog.tsx index 1346e5d05f1..571b754a259 100644 --- a/apps/journeys-admin/src/components/AccessDialog/AccessDialog.tsx +++ b/apps/journeys-admin/src/components/AccessDialog/AccessDialog.tsx @@ -93,7 +93,10 @@ export function AccessDialog({ const currentUserJourney = useMemo(() => { return data?.journey?.userJourneys?.find( - (userJourney) => userJourney.user?.__typename === 'AuthenticatedUser' && user.__typename === 'AuthenticatedUser' && userJourney.user?.email === user.email + (userJourney) => + userJourney.user?.__typename === 'AuthenticatedUser' && + user.__typename === 'AuthenticatedUser' && + userJourney.user?.email === user.email ) }, [data?.journey?.userJourneys, user]) diff --git a/apps/journeys-admin/src/components/Editor/Slider/Settings/CanvasDetails/JourneyAppearance/Host/Host.tsx b/apps/journeys-admin/src/components/Editor/Slider/Settings/CanvasDetails/JourneyAppearance/Host/Host.tsx index 32debc30799..be3f3a80bf2 100644 --- a/apps/journeys-admin/src/components/Editor/Slider/Settings/CanvasDetails/JourneyAppearance/Host/Host.tsx +++ b/apps/journeys-admin/src/components/Editor/Slider/Settings/CanvasDetails/JourneyAppearance/Host/Host.tsx @@ -75,8 +75,12 @@ export function Host(): ReactElement { const userInTeam = data == null || data.userTeams.length === 0 || journey?.team == null ? false - : data.userTeams.find((userTeam) => userTeam.user?.__typename === 'AuthenticatedUser' && user.__typename === 'AuthenticatedUser' && userTeam.user.email === user.email) != - null + : data.userTeams.find( + (userTeam) => + userTeam.user?.__typename === 'AuthenticatedUser' && + user.__typename === 'AuthenticatedUser' && + userTeam.user.email === user.email + ) != null // Fetch all hosts made for a team const [getAllTeamHosts, { data: teamHosts }] = useLazyQuery< diff --git a/apps/journeys-admin/src/components/EmailVerification/EmailVerification.tsx b/apps/journeys-admin/src/components/EmailVerification/EmailVerification.tsx index 5add6f25ff8..deb0992338e 100644 --- a/apps/journeys-admin/src/components/EmailVerification/EmailVerification.tsx +++ b/apps/journeys-admin/src/components/EmailVerification/EmailVerification.tsx @@ -43,7 +43,9 @@ export function EmailVerification({ } return ( )} - {data.me?.__typename === 'AuthenticatedUser' && data.me.superAdmin === true && ( - - - - - - - )} + {data.me?.__typename === 'AuthenticatedUser' && + data.me.superAdmin === true && ( + + + + + + + )} {data.me?.__typename === 'AuthenticatedUser' && ( )} - {profileAnchorEl !== undefined && data.me?.__typename === 'AuthenticatedUser' && ( - - )} + {profileAnchorEl !== undefined && + data.me?.__typename === 'AuthenticatedUser' && ( + + )} {impersonateOpen != null && ( Date: Tue, 3 Feb 2026 21:15:32 +0000 Subject: [PATCH 05/18] test: update User type to AuthenticatedUser in various component tests --- .../components/AccessDialog/AccessDialog.spec.tsx | 10 +++++----- .../JourneyAppearance/Host/Host.spec.tsx | 2 +- .../DefaultMenu/DefaultMenu.spec.tsx | 14 +++++++------- .../UserNavigation/UserNavigation.spec.tsx | 3 +++ .../TeamManageDialog/TeamManageDialog.spec.tsx | 2 +- .../TeamManageWrapper/TeamManageWrapper.spec.tsx | 2 +- .../checkConditionalRedirect.spec.tsx | 5 +++-- .../useCurrentUser.spec.tsx | 9 ++++++--- 8 files changed, 27 insertions(+), 20 deletions(-) diff --git a/apps/journeys-admin/src/components/AccessDialog/AccessDialog.spec.tsx b/apps/journeys-admin/src/components/AccessDialog/AccessDialog.spec.tsx index dff5a14963d..01e0d21e117 100644 --- a/apps/journeys-admin/src/components/AccessDialog/AccessDialog.spec.tsx +++ b/apps/journeys-admin/src/components/AccessDialog/AccessDialog.spec.tsx @@ -18,7 +18,7 @@ jest.mock('../../libs/useCurrentUserLazyQuery', () => ({ useCurrentUserLazyQuery: jest.fn().mockReturnValue({ loadUser: jest.fn(), data: { - __typename: 'User', + __typename: 'AuthenticatedUser', ...user1 } }) @@ -45,7 +45,7 @@ const mocks = [ id: 'userTeamId', role: 'manager', user: { - __typename: 'User', + __typename: 'AuthenticatedUser', email: 'kujojotaro@example.com', firstName: 'Jotaro', id: 'userId', @@ -63,7 +63,7 @@ const mocks = [ id: 'userTeamId1', role: 'member', user: { - __typename: 'User', + __typename: 'AuthenticatedUser', email: 'josukehigashikata@example.com', firstName: 'Josuke', id: 'userId1', @@ -80,7 +80,7 @@ const mocks = [ id: 'userTeamId2', role: 'member', user: { - __typename: 'User', + __typename: 'AuthenticatedUser', email: 'KoichiHirose@example.com', firstName: 'Koichi', id: 'userId2', @@ -147,7 +147,7 @@ const mocks = [ id: 'userJourneyId4', role: 'editor', user: { - __typename: 'User', + __typename: 'AuthenticatedUser', email: 'kujojotaro@example.com', firstName: 'Jotaro', id: 'userId', diff --git a/apps/journeys-admin/src/components/Editor/Slider/Settings/CanvasDetails/JourneyAppearance/Host/Host.spec.tsx b/apps/journeys-admin/src/components/Editor/Slider/Settings/CanvasDetails/JourneyAppearance/Host/Host.spec.tsx index 8d3a517306b..dfdcd71cb01 100644 --- a/apps/journeys-admin/src/components/Editor/Slider/Settings/CanvasDetails/JourneyAppearance/Host/Host.spec.tsx +++ b/apps/journeys-admin/src/components/Editor/Slider/Settings/CanvasDetails/JourneyAppearance/Host/Host.spec.tsx @@ -21,7 +21,7 @@ import { ThemeProvider } from '../../../../../../ThemeProvider' import { GET_ALL_TEAM_HOSTS, Host } from './Host' -const user1 = { id: 'userId', email: 'admin@email.com' } +const user1 = { id: 'userId', email: 'admin@email.com', __typename: 'AuthenticatedUser' } jest.mock('../../../../../../../libs/useCurrentUserLazyQuery', () => ({ __esModule: true, diff --git a/apps/journeys-admin/src/components/JourneyList/JourneyCard/JourneyCardMenu/DefaultMenu/DefaultMenu.spec.tsx b/apps/journeys-admin/src/components/JourneyList/JourneyCard/JourneyCardMenu/DefaultMenu/DefaultMenu.spec.tsx index b44f1d34b14..11bb795963a 100644 --- a/apps/journeys-admin/src/components/JourneyList/JourneyCard/JourneyCardMenu/DefaultMenu/DefaultMenu.spec.tsx +++ b/apps/journeys-admin/src/components/JourneyList/JourneyCard/JourneyCardMenu/DefaultMenu/DefaultMenu.spec.tsx @@ -71,7 +71,7 @@ const currentUserMock = { result: { data: { me: { - __typename: 'User', + __typename: 'AuthenticatedUser', id: 'current-user-id', email: 'current@example.com', lastName: 'userLastName', @@ -105,7 +105,7 @@ const baseTeamMock = { lastName: 'userLastName', firstName: 'userFirstName', imageUrl: 'https://example.com/image.jpg', - __typename: 'User' + __typename: 'AuthenticatedUser' }, __typename: 'UserTeam' } @@ -559,7 +559,7 @@ describe('DefaultMenu', () => { id: 'userJourney1.id', role: UserJourneyRole.owner, user: { - __typename: 'User', + __typename: 'AuthenticatedUser', id: 'current-user-id', email: 'current@example.com' } @@ -623,7 +623,7 @@ describe('DefaultMenu', () => { id: 'userJourney1.id', role: UserJourneyRole.editor, user: { - __typename: 'User', + __typename: 'AuthenticatedUser', id: 'current-user-id' } } @@ -686,7 +686,7 @@ describe('DefaultMenu', () => { id: 'userJourney1.id', role: UserJourneyRole.editor, user: { - __typename: 'User', + __typename: 'AuthenticatedUser', id: 'current-user-id' } } @@ -751,7 +751,7 @@ describe('DefaultMenu', () => { id: 'userJourney1.id', role: UserJourneyRole.editor, user: { - __typename: 'User', + __typename: 'AuthenticatedUser', id: 'current-user-id' } } @@ -998,7 +998,7 @@ describe('DefaultMenu', () => { id: 'userJourney1.id', role: UserJourneyRole.editor, user: { - __typename: 'User', + __typename: 'AuthenticatedUser', id: 'current-user-id' } } diff --git a/apps/journeys-admin/src/components/PageWrapper/NavigationDrawer/UserNavigation/UserNavigation.spec.tsx b/apps/journeys-admin/src/components/PageWrapper/NavigationDrawer/UserNavigation/UserNavigation.spec.tsx index 51aa8b4bcb5..b74f48312c7 100644 --- a/apps/journeys-admin/src/components/PageWrapper/NavigationDrawer/UserNavigation/UserNavigation.spec.tsx +++ b/apps/journeys-admin/src/components/PageWrapper/NavigationDrawer/UserNavigation/UserNavigation.spec.tsx @@ -70,6 +70,7 @@ jest.mock('../../../../libs/useAdminJourneysSuspenseQuery', () => ({ describe('UserNavigation', () => { const user = { + __typename: 'AuthenticatedUser', id: 'userId', displayName: 'Amin One', photoURL: 'https://bit.ly/3Gth4Yf', @@ -84,6 +85,7 @@ describe('UserNavigation', () => { mockUseSuspenseQuery.mockReturnValue({ data: { me: { + __typename: 'AuthenticatedUser', id: 'userId', firstName: 'Amin', lastName: 'One', @@ -165,6 +167,7 @@ describe('UserNavigation', () => { mockUseSuspenseQuery.mockReturnValueOnce({ data: { me: { + __typename: 'AuthenticatedUser', id: 'userId', firstName: 'Amin', lastName: 'One', diff --git a/apps/journeys-admin/src/components/Team/TeamManageDialog/TeamManageDialog.spec.tsx b/apps/journeys-admin/src/components/Team/TeamManageDialog/TeamManageDialog.spec.tsx index 9fad643f6a5..f6282a8ffa4 100644 --- a/apps/journeys-admin/src/components/Team/TeamManageDialog/TeamManageDialog.spec.tsx +++ b/apps/journeys-admin/src/components/Team/TeamManageDialog/TeamManageDialog.spec.tsx @@ -24,7 +24,7 @@ jest.mock('apps/journeys-admin/src/libs/useCurrentUserLazyQuery', () => ({ useCurrentUserLazyQuery: jest.fn().mockReturnValue({ loadUser: jest.fn(), data: { - __typename: 'User', + __typename: 'AuthenticatedUser', id: 'userId', email: 'miguelohara@example.com' } diff --git a/apps/journeys-admin/src/components/Team/TeamManageDialog/TeamManageWrapper/TeamManageWrapper.spec.tsx b/apps/journeys-admin/src/components/Team/TeamManageDialog/TeamManageWrapper/TeamManageWrapper.spec.tsx index 4753dde6c6b..ce58fd1cbcb 100644 --- a/apps/journeys-admin/src/components/Team/TeamManageDialog/TeamManageWrapper/TeamManageWrapper.spec.tsx +++ b/apps/journeys-admin/src/components/Team/TeamManageDialog/TeamManageWrapper/TeamManageWrapper.spec.tsx @@ -23,7 +23,7 @@ jest.mock('../../../../libs/useCurrentUserLazyQuery', () => ({ } }) })) -const user1 = { id: 'userId', email: 'miguelohara@example.com' } +const user1 = { id: 'userId', email: 'miguelohara@example.com', __typename: 'AuthenticatedUser' } describe('TeamMembersList', () => { const getUserTeamMock1: MockedResponse = { diff --git a/apps/journeys-admin/src/libs/checkConditionalRedirect/checkConditionalRedirect.spec.tsx b/apps/journeys-admin/src/libs/checkConditionalRedirect/checkConditionalRedirect.spec.tsx index fcf5a81ea1c..18919d8f8a4 100644 --- a/apps/journeys-admin/src/libs/checkConditionalRedirect/checkConditionalRedirect.spec.tsx +++ b/apps/journeys-admin/src/libs/checkConditionalRedirect/checkConditionalRedirect.spec.tsx @@ -9,6 +9,7 @@ import { checkConditionalRedirect } from '.' const meData = { me: { + __typename: 'AuthenticatedUser', emailVerified: true } } @@ -38,7 +39,7 @@ describe('checkConditionalRedirect', () => { query: jest .fn() .mockResolvedValue({ data }) - .mockResolvedValueOnce({ data: { me: { emailVerified: false } } }) + .mockResolvedValueOnce({ data: { me: { emailVerified: false, __typename: 'AuthenticatedUser' } } }) } as unknown as ApolloClient expect( await checkConditionalRedirect({ @@ -57,7 +58,7 @@ describe('checkConditionalRedirect', () => { query: jest .fn() .mockResolvedValue({ data }) - .mockResolvedValueOnce({ data: { me: { emailVerified: false } } }) + .mockResolvedValueOnce({ data: { me: { emailVerified: false, __typename: 'AuthenticatedUser' } } }) } as unknown as ApolloClient expect( await checkConditionalRedirect({ apolloClient, resolvedUrl: '/' }) diff --git a/apps/journeys-admin/src/libs/useCurrentUserLazyQuery/useCurrentUser.spec.tsx b/apps/journeys-admin/src/libs/useCurrentUserLazyQuery/useCurrentUser.spec.tsx index ea0121e0f28..ea5c33845bf 100644 --- a/apps/journeys-admin/src/libs/useCurrentUserLazyQuery/useCurrentUser.spec.tsx +++ b/apps/journeys-admin/src/libs/useCurrentUserLazyQuery/useCurrentUser.spec.tsx @@ -20,7 +20,8 @@ describe('useCurrentUserLazyQuery', () => { data: { me: { id: 'user.id', - email: 'test@email.com' + email: 'test@email.com', + __typename: 'AuthenticatedUser' } } } @@ -38,6 +39,7 @@ describe('useCurrentUserLazyQuery', () => { await waitFor(() => expect(result.current.data).toEqual({ + __typename: 'AuthenticatedUser', id: 'user.id', email: 'test@email.com' }) @@ -57,7 +59,8 @@ describe('useCurrentUserLazyQuery', () => { data: { me: { id: 'user.id', - email: 'test@email.com' + email: 'test@email.com', + __typename: 'AuthenticatedUser' } } } @@ -70,7 +73,7 @@ describe('useCurrentUserLazyQuery', () => { }) expect(result.current.data).toEqual({ - __typename: 'User', + __typename: 'AuthenticatedUser', id: '', email: '' }) From b0ef999b3c770464eb85deafc9f19bcd574cf313 Mon Sep 17 00:00:00 2001 From: Mike Allison Date: Tue, 3 Feb 2026 21:29:42 +0000 Subject: [PATCH 06/18] test: remove User type from mock data in TeamManageWrapper tests --- .../TeamManageWrapper/TeamManageWrapper.spec.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/journeys-admin/src/components/Team/TeamManageDialog/TeamManageWrapper/TeamManageWrapper.spec.tsx b/apps/journeys-admin/src/components/Team/TeamManageDialog/TeamManageWrapper/TeamManageWrapper.spec.tsx index ce58fd1cbcb..b9dcc829438 100644 --- a/apps/journeys-admin/src/components/Team/TeamManageDialog/TeamManageWrapper/TeamManageWrapper.spec.tsx +++ b/apps/journeys-admin/src/components/Team/TeamManageDialog/TeamManageWrapper/TeamManageWrapper.spec.tsx @@ -18,7 +18,6 @@ jest.mock('../../../../libs/useCurrentUserLazyQuery', () => ({ useCurrentUserLazyQuery: jest.fn().mockReturnValue({ loadUser: jest.fn(), data: { - __typename: 'User', ...user1 } }) From 69c8b205898d66fd479dd3333b112bbdce85d273 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Tue, 3 Feb 2026 21:35:02 +0000 Subject: [PATCH 07/18] fix: lint issues --- .../JourneyAppearance/Host/Host.spec.tsx | 6 +++++- .../TeamManageWrapper/TeamManageWrapper.spec.tsx | 6 +++++- .../checkConditionalRedirect.spec.tsx | 12 ++++++++++-- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/apps/journeys-admin/src/components/Editor/Slider/Settings/CanvasDetails/JourneyAppearance/Host/Host.spec.tsx b/apps/journeys-admin/src/components/Editor/Slider/Settings/CanvasDetails/JourneyAppearance/Host/Host.spec.tsx index dfdcd71cb01..984dca9856b 100644 --- a/apps/journeys-admin/src/components/Editor/Slider/Settings/CanvasDetails/JourneyAppearance/Host/Host.spec.tsx +++ b/apps/journeys-admin/src/components/Editor/Slider/Settings/CanvasDetails/JourneyAppearance/Host/Host.spec.tsx @@ -21,7 +21,11 @@ import { ThemeProvider } from '../../../../../../ThemeProvider' import { GET_ALL_TEAM_HOSTS, Host } from './Host' -const user1 = { id: 'userId', email: 'admin@email.com', __typename: 'AuthenticatedUser' } +const user1 = { + id: 'userId', + email: 'admin@email.com', + __typename: 'AuthenticatedUser' +} jest.mock('../../../../../../../libs/useCurrentUserLazyQuery', () => ({ __esModule: true, diff --git a/apps/journeys-admin/src/components/Team/TeamManageDialog/TeamManageWrapper/TeamManageWrapper.spec.tsx b/apps/journeys-admin/src/components/Team/TeamManageDialog/TeamManageWrapper/TeamManageWrapper.spec.tsx index b9dcc829438..dd592e7a9fc 100644 --- a/apps/journeys-admin/src/components/Team/TeamManageDialog/TeamManageWrapper/TeamManageWrapper.spec.tsx +++ b/apps/journeys-admin/src/components/Team/TeamManageDialog/TeamManageWrapper/TeamManageWrapper.spec.tsx @@ -22,7 +22,11 @@ jest.mock('../../../../libs/useCurrentUserLazyQuery', () => ({ } }) })) -const user1 = { id: 'userId', email: 'miguelohara@example.com', __typename: 'AuthenticatedUser' } +const user1 = { + id: 'userId', + email: 'miguelohara@example.com', + __typename: 'AuthenticatedUser' +} describe('TeamMembersList', () => { const getUserTeamMock1: MockedResponse = { diff --git a/apps/journeys-admin/src/libs/checkConditionalRedirect/checkConditionalRedirect.spec.tsx b/apps/journeys-admin/src/libs/checkConditionalRedirect/checkConditionalRedirect.spec.tsx index 18919d8f8a4..359d4847536 100644 --- a/apps/journeys-admin/src/libs/checkConditionalRedirect/checkConditionalRedirect.spec.tsx +++ b/apps/journeys-admin/src/libs/checkConditionalRedirect/checkConditionalRedirect.spec.tsx @@ -39,7 +39,11 @@ describe('checkConditionalRedirect', () => { query: jest .fn() .mockResolvedValue({ data }) - .mockResolvedValueOnce({ data: { me: { emailVerified: false, __typename: 'AuthenticatedUser' } } }) + .mockResolvedValueOnce({ + data: { + me: { emailVerified: false, __typename: 'AuthenticatedUser' } + } + }) } as unknown as ApolloClient expect( await checkConditionalRedirect({ @@ -58,7 +62,11 @@ describe('checkConditionalRedirect', () => { query: jest .fn() .mockResolvedValue({ data }) - .mockResolvedValueOnce({ data: { me: { emailVerified: false, __typename: 'AuthenticatedUser' } } }) + .mockResolvedValueOnce({ + data: { + me: { emailVerified: false, __typename: 'AuthenticatedUser' } + } + }) } as unknown as ApolloClient expect( await checkConditionalRedirect({ apolloClient, resolvedUrl: '/' }) From fc7326b11c198ad6dd2f51bafca5a0ba3f3de3c4 Mon Sep 17 00:00:00 2001 From: Mike Allison Date: Wed, 4 Feb 2026 19:29:18 +0000 Subject: [PATCH 08/18] test: add test for creating anonymous user with null email in findOrFetchUser --- .../src/schema/user/findOrFetchUser.spec.ts | 38 +++++++++++++++++++ .../src/schema/user/findOrFetchUser.ts | 2 +- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/apis/api-users/src/schema/user/findOrFetchUser.spec.ts b/apis/api-users/src/schema/user/findOrFetchUser.spec.ts index 578ac30459e..e3dce69fb6d 100644 --- a/apis/api-users/src/schema/user/findOrFetchUser.spec.ts +++ b/apis/api-users/src/schema/user/findOrFetchUser.spec.ts @@ -67,4 +67,42 @@ describe('findOrFetchUser', () => { undefined ) }) + + it('should create anonymous user with null email', async () => { + const { auth } = jest.requireMock('@core/yoga/firebaseClient') + auth.getUser.mockReturnValueOnce({ + id: '1', + userId: '1', + createdAt: new Date('2021-01-01T00:00:00.000Z'), + displayName: 'Anonymous', + email: undefined, + photoURL: 'https://bit.ly/3Gth4', + emailVerified: false + }) + jest.mocked(verifyUser).mockClear() + + const anonymousUser = { + ...user, + email: null, + firstName: 'Anonymous', + lastName: '' + } + prismaMock.user.findUnique.mockResolvedValueOnce(null) + prismaMock.user.create.mockResolvedValueOnce(anonymousUser) + + const data = await findOrFetchUser({}, 'userId', undefined) + + expect(data).toEqual(anonymousUser) + expect(prismaMock.user.create).toHaveBeenCalledWith({ + data: { + email: null, + emailVerified: false, + firstName: 'Anonymous', + imageUrl: 'https://bit.ly/3Gth4', + lastName: '', + userId: 'userId' + } + }) + expect(verifyUser).not.toHaveBeenCalled() + }) }) diff --git a/apis/api-users/src/schema/user/findOrFetchUser.ts b/apis/api-users/src/schema/user/findOrFetchUser.ts index 4547c686b55..339e29c40b8 100644 --- a/apis/api-users/src/schema/user/findOrFetchUser.ts +++ b/apis/api-users/src/schema/user/findOrFetchUser.ts @@ -64,7 +64,7 @@ export async function findOrFetchUser( userId, firstName, lastName, - email: email ?? '', + email: email ?? null, imageUrl, emailVerified } From 588cad856390d172fd2e2e3278624f4dc16f479d Mon Sep 17 00:00:00 2001 From: Mike Allison Date: Wed, 4 Feb 2026 22:12:32 +0000 Subject: [PATCH 09/18] refactor: update User type to include AnonymousUser and adjust related schema references - Introduced AnonymousUser type to handle unauthenticated users across multiple schemas. - Updated User type to be a union of AuthenticatedUser and AnonymousUser. - Adjusted resolver implementations and references in user-related schemas to accommodate the new structure. - Ensured compatibility with existing queries and mutations by updating relevant types. --- apis/api-gateway/schema.graphql | 14 ++++++------ apis/api-journeys-modern/schema.graphql | 10 ++++++++- .../src/schema/user/index.ts | 2 +- .../src/schema/user/user.ts | 22 +++++++++++++++++++ .../src/schema/userTeam/userTeam.ts | 4 ++-- apis/api-users/schema.graphql | 6 +++-- .../api-users/src/schema/user/objects/user.ts | 1 + .../GetJourneyWithPermissions.ts | 9 +++++++- .../GetLastActiveTeamIdAndTeams.ts | 9 +++++++- .../__generated__/GetUserTeamsAndInvites.ts | 9 +++++++- .../__generated__/TeamCreate.ts | 9 +++++++- .../__generated__/UserTeamUpdate.ts | 9 +++++++- .../components/AccessDialog/AccessDialog.tsx | 15 ++++++++----- .../UserTeamListItem/UserTeamListItem.tsx | 7 +++++- .../useTeamCreateMutation.tsx | 15 ++++++++----- .../useUserTeamsAndInvitesQuery.ts | 15 ++++++++----- .../GetLastActiveTeamIdAndTeams.ts | 9 +++++++- .../GetLastActiveTeamIdAndTeams.ts | 9 +++++++- .../GetLastActiveTeamIdAndTeams.ts | 9 +++++++- .../components/TeamProvider/TeamProvider.tsx | 13 +++++++---- .../GetLastActiveTeamIdAndTeams.ts | 9 +++++++- .../gql/src/__generated__/graphql-env.d.ts | 2 +- 22 files changed, 164 insertions(+), 43 deletions(-) diff --git a/apis/api-gateway/schema.graphql b/apis/api-gateway/schema.graphql index 75acd26a9c3..233b494c480 100644 --- a/apis/api-gateway/schema.graphql +++ b/apis/api-gateway/schema.graphql @@ -1976,7 +1976,7 @@ type JourneyNotification @join__type(graph: API_JOURNEYS) @join__type(graph: AP type UserTeam @join__type(graph: API_JOURNEYS, key: "id") @join__type(graph: API_JOURNEYS_MODERN) { id: ID! journeyNotification(journeyId: ID!) : JourneyNotification - user: AuthenticatedUser! + user: User! @join__field(graph: API_JOURNEYS, type: "AuthenticatedUser!") @join__field(graph: API_JOURNEYS_MODERN, type: "User!") role: UserTeamRole! createdAt: DateTime! updatedAt: DateTime! @@ -2380,6 +2380,10 @@ type VisitorsConnection @join__type(graph: API_JOURNEYS) { pageInfo: PageInfo! } +type AnonymousUser @join__type(graph: API_JOURNEYS_MODERN) @join__type(graph: API_USERS) { + id: ID! +} + type GoogleSheetsSync @join__type(graph: API_JOURNEYS_MODERN) { id: ID! teamId: ID! @@ -3257,10 +3261,6 @@ type ZodFieldError @join__type(graph: API_MEDIA) { path: [String!]! } -type AnonymousUser @join__type(graph: API_USERS) { - id: ID! -} - interface BaseError @join__type(graph: API_ANALYTICS) @join__type(graph: API_MEDIA) { message: String } @@ -3300,6 +3300,8 @@ union MutationSiteCreateResult @join__type(graph: API_ANALYTICS) @join__unionMe union MediaVideo @join__type(graph: API_JOURNEYS_MODERN) @join__unionMember(graph: API_JOURNEYS_MODERN, member: "MuxVideo") @join__unionMember(graph: API_JOURNEYS_MODERN, member: "Video") @join__unionMember(graph: API_JOURNEYS_MODERN, member: "YouTube") = MuxVideo | Video | YouTube +union User @join__type(graph: API_JOURNEYS_MODERN) @join__type(graph: API_USERS) @join__unionMember(graph: API_JOURNEYS_MODERN, member: "AuthenticatedUser") @join__unionMember(graph: API_JOURNEYS_MODERN, member: "AnonymousUser") @join__unionMember(graph: API_USERS, member: "AuthenticatedUser") @join__unionMember(graph: API_USERS, member: "AnonymousUser") = AuthenticatedUser | AnonymousUser + union MutationPlaylistCreateResult @join__type(graph: API_MEDIA) @join__unionMember(graph: API_MEDIA, member: "ZodError") @join__unionMember(graph: API_MEDIA, member: "MutationPlaylistCreateSuccess") = ZodError | MutationPlaylistCreateSuccess union MutationPlaylistDeleteResult @join__type(graph: API_MEDIA) @join__unionMember(graph: API_MEDIA, member: "NotFoundError") @join__unionMember(graph: API_MEDIA, member: "MutationPlaylistDeleteSuccess") = NotFoundError | MutationPlaylistDeleteSuccess @@ -3341,8 +3343,6 @@ union QueryShortLinkResult @join__type(graph: API_MEDIA) @join__unionMember(gra union QueryYoutubeClosedCaptionLanguagesResult @join__type(graph: API_MEDIA) @join__unionMember(graph: API_MEDIA, member: "Error") @join__unionMember(graph: API_MEDIA, member: "ZodError") @join__unionMember(graph: API_MEDIA, member: "QueryYoutubeClosedCaptionLanguagesSuccess") = Error | ZodError | QueryYoutubeClosedCaptionLanguagesSuccess -union User @join__type(graph: API_USERS) @join__unionMember(graph: API_USERS, member: "AuthenticatedUser") @join__unionMember(graph: API_USERS, member: "AnonymousUser") = AuthenticatedUser | AnonymousUser - enum ThemeMode @join__type(graph: API_JOURNEYS) @join__type(graph: API_JOURNEYS_MODERN) { dark @join__enumValue(graph: API_JOURNEYS) @join__enumValue(graph: API_JOURNEYS_MODERN) light @join__enumValue(graph: API_JOURNEYS) @join__enumValue(graph: API_JOURNEYS_MODERN) diff --git a/apis/api-journeys-modern/schema.graphql b/apis/api-journeys-modern/schema.graphql index 1866b22fec7..63f093f4ce7 100644 --- a/apis/api-journeys-modern/schema.graphql +++ b/apis/api-journeys-modern/schema.graphql @@ -7,6 +7,12 @@ interface Action { parentBlock: Block! } +type AnonymousUser + @shareable +{ + id: ID! +} + type AuthenticatedUser @key(fields: "id") @extends @@ -2257,6 +2263,8 @@ enum TypographyVariant { overline } +union User = AuthenticatedUser | AnonymousUser + type UserAgent @shareable { @@ -2316,7 +2324,7 @@ type UserTeam role: UserTeamRole! createdAt: DateTime! updatedAt: DateTime! - user: AuthenticatedUser! + user: User! journeyNotification(journeyId: ID!): JourneyNotification } diff --git a/apis/api-journeys-modern/src/schema/user/index.ts b/apis/api-journeys-modern/src/schema/user/index.ts index 25df7fc062e..c1c134bb17e 100644 --- a/apis/api-journeys-modern/src/schema/user/index.ts +++ b/apis/api-journeys-modern/src/schema/user/index.ts @@ -1,3 +1,3 @@ import './user' -export { AuthenticatedUserRef } from './user' +export { AnonymousUserRef, AuthenticatedUserRef, UserRef } from './user' diff --git a/apis/api-journeys-modern/src/schema/user/user.ts b/apis/api-journeys-modern/src/schema/user/user.ts index 29c08ed40d3..087c68f88ae 100644 --- a/apis/api-journeys-modern/src/schema/user/user.ts +++ b/apis/api-journeys-modern/src/schema/user/user.ts @@ -13,3 +13,25 @@ AuthenticatedUserRef.implement({ // No additional fields needed - this is just the external reference }) }) + +// AnonymousUser is not a federation entity (no @key in api-users), so we define it locally +// to match the api-users schema for the User union +// Marked as shareable since it's also defined in api-users +export const AnonymousUserRef = builder.objectRef<{ id: string }>('AnonymousUser') + +AnonymousUserRef.implement({ + shareable: true, + fields: (t) => ({ + id: t.exposeID('id', { nullable: false }) + }) +}) + +// Define the User union type to match api-users schema +export const UserRef = builder.unionType('User', { + types: [AuthenticatedUserRef, AnonymousUserRef], + resolveType: (user) => { + // In practice, UserTeam members are always authenticated users + // but the union type allows for both possibilities + return 'AuthenticatedUser' + } +}) diff --git a/apis/api-journeys-modern/src/schema/userTeam/userTeam.ts b/apis/api-journeys-modern/src/schema/userTeam/userTeam.ts index aa2b4a16592..b153645ece1 100644 --- a/apis/api-journeys-modern/src/schema/userTeam/userTeam.ts +++ b/apis/api-journeys-modern/src/schema/userTeam/userTeam.ts @@ -1,6 +1,6 @@ import { builder } from '../builder' import { JourneyNotificationRef } from '../journeyNotification' -import { AuthenticatedUserRef } from '../user' +import { UserRef } from '../user' import { UserTeamRole } from './enums' @@ -17,7 +17,7 @@ export const UserTeamRef = builder.prismaObject('UserTeam', { updatedAt: t.expose('updatedAt', { type: 'DateTime', nullable: false }), user: t.field({ nullable: false, - type: AuthenticatedUserRef, + type: UserRef, resolve: (userTeam) => ({ id: userTeam.userId }) diff --git a/apis/api-users/schema.graphql b/apis/api-users/schema.graphql index e5573b7a13a..2ecc09c1355 100644 --- a/apis/api-users/schema.graphql +++ b/apis/api-users/schema.graphql @@ -1,7 +1,9 @@ extend schema - @link(url: "https://specs.apollo.dev/federation/v2.6", import: ["@key"]) + @link(url: "https://specs.apollo.dev/federation/v2.6", import: ["@key", "@shareable"]) -type AnonymousUser { +type AnonymousUser + @shareable +{ id: ID! } diff --git a/apis/api-users/src/schema/user/objects/user.ts b/apis/api-users/src/schema/user/objects/user.ts index 37aa4cd166b..3c96f48d62d 100644 --- a/apis/api-users/src/schema/user/objects/user.ts +++ b/apis/api-users/src/schema/user/objects/user.ts @@ -43,6 +43,7 @@ export const AuthenticatedUser = builder.prismaObject('User', { const AnonymousUserRef = builder.objectRef('AnonymousUser') export const AnonymousUser = builder.objectType(AnonymousUserRef, { + shareable: true, fields: (t) => ({ id: t.exposeID('id', { nullable: false }) }) diff --git a/apps/journeys-admin/__generated__/GetJourneyWithPermissions.ts b/apps/journeys-admin/__generated__/GetJourneyWithPermissions.ts index 954099df370..bdb90716946 100644 --- a/apps/journeys-admin/__generated__/GetJourneyWithPermissions.ts +++ b/apps/journeys-admin/__generated__/GetJourneyWithPermissions.ts @@ -9,7 +9,7 @@ import { UserTeamRole, UserJourneyRole } from "./globalTypes"; // GraphQL query operation: GetJourneyWithPermissions // ==================================================== -export interface GetJourneyWithPermissions_journey_team_userTeams_user { +export interface GetJourneyWithPermissions_journey_team_userTeams_user_AuthenticatedUser { __typename: "AuthenticatedUser"; email: string; firstName: string; @@ -18,6 +18,13 @@ export interface GetJourneyWithPermissions_journey_team_userTeams_user { lastName: string | null; } +export interface GetJourneyWithPermissions_journey_team_userTeams_user_AnonymousUser { + __typename: "AnonymousUser"; + id: string; +} + +export type GetJourneyWithPermissions_journey_team_userTeams_user = GetJourneyWithPermissions_journey_team_userTeams_user_AuthenticatedUser | GetJourneyWithPermissions_journey_team_userTeams_user_AnonymousUser; + export interface GetJourneyWithPermissions_journey_team_userTeams_journeyNotification { __typename: "JourneyNotification"; id: string; diff --git a/apps/journeys-admin/__generated__/GetLastActiveTeamIdAndTeams.ts b/apps/journeys-admin/__generated__/GetLastActiveTeamIdAndTeams.ts index f151d41d194..7df3c60dcdf 100644 --- a/apps/journeys-admin/__generated__/GetLastActiveTeamIdAndTeams.ts +++ b/apps/journeys-admin/__generated__/GetLastActiveTeamIdAndTeams.ts @@ -15,7 +15,7 @@ export interface GetLastActiveTeamIdAndTeams_getJourneyProfile { lastActiveTeamId: string | null; } -export interface GetLastActiveTeamIdAndTeams_teams_userTeams_user { +export interface GetLastActiveTeamIdAndTeams_teams_userTeams_user_AuthenticatedUser { __typename: "AuthenticatedUser"; id: string; firstName: string; @@ -24,6 +24,13 @@ export interface GetLastActiveTeamIdAndTeams_teams_userTeams_user { email: string; } +export interface GetLastActiveTeamIdAndTeams_teams_userTeams_user_AnonymousUser { + __typename: "AnonymousUser"; + id: string; +} + +export type GetLastActiveTeamIdAndTeams_teams_userTeams_user = GetLastActiveTeamIdAndTeams_teams_userTeams_user_AuthenticatedUser | GetLastActiveTeamIdAndTeams_teams_userTeams_user_AnonymousUser; + export interface GetLastActiveTeamIdAndTeams_teams_userTeams { __typename: "UserTeam"; id: string; diff --git a/apps/journeys-admin/__generated__/GetUserTeamsAndInvites.ts b/apps/journeys-admin/__generated__/GetUserTeamsAndInvites.ts index f654360e3d6..e892faa2710 100644 --- a/apps/journeys-admin/__generated__/GetUserTeamsAndInvites.ts +++ b/apps/journeys-admin/__generated__/GetUserTeamsAndInvites.ts @@ -9,7 +9,7 @@ import { UserTeamFilterInput, UserTeamRole } from "./globalTypes"; // GraphQL query operation: GetUserTeamsAndInvites // ==================================================== -export interface GetUserTeamsAndInvites_userTeams_user { +export interface GetUserTeamsAndInvites_userTeams_user_AuthenticatedUser { __typename: "AuthenticatedUser"; email: string; firstName: string; @@ -18,6 +18,13 @@ export interface GetUserTeamsAndInvites_userTeams_user { lastName: string | null; } +export interface GetUserTeamsAndInvites_userTeams_user_AnonymousUser { + __typename: "AnonymousUser"; + id: string; +} + +export type GetUserTeamsAndInvites_userTeams_user = GetUserTeamsAndInvites_userTeams_user_AuthenticatedUser | GetUserTeamsAndInvites_userTeams_user_AnonymousUser; + export interface GetUserTeamsAndInvites_userTeams { __typename: "UserTeam"; id: string; diff --git a/apps/journeys-admin/__generated__/TeamCreate.ts b/apps/journeys-admin/__generated__/TeamCreate.ts index 0d4547f6e67..fe2a241b49e 100644 --- a/apps/journeys-admin/__generated__/TeamCreate.ts +++ b/apps/journeys-admin/__generated__/TeamCreate.ts @@ -9,7 +9,7 @@ import { TeamCreateInput, UserTeamRole } from "./globalTypes"; // GraphQL mutation operation: TeamCreate // ==================================================== -export interface TeamCreate_teamCreate_userTeams_user { +export interface TeamCreate_teamCreate_userTeams_user_AuthenticatedUser { __typename: "AuthenticatedUser"; id: string; firstName: string; @@ -18,6 +18,13 @@ export interface TeamCreate_teamCreate_userTeams_user { email: string; } +export interface TeamCreate_teamCreate_userTeams_user_AnonymousUser { + __typename: "AnonymousUser"; + id: string; +} + +export type TeamCreate_teamCreate_userTeams_user = TeamCreate_teamCreate_userTeams_user_AuthenticatedUser | TeamCreate_teamCreate_userTeams_user_AnonymousUser; + export interface TeamCreate_teamCreate_userTeams { __typename: "UserTeam"; id: string; diff --git a/apps/journeys-admin/__generated__/UserTeamUpdate.ts b/apps/journeys-admin/__generated__/UserTeamUpdate.ts index 786fe3a4e07..59590db4f58 100644 --- a/apps/journeys-admin/__generated__/UserTeamUpdate.ts +++ b/apps/journeys-admin/__generated__/UserTeamUpdate.ts @@ -9,11 +9,18 @@ import { UserTeamUpdateInput, UserTeamRole } from "./globalTypes"; // GraphQL mutation operation: UserTeamUpdate // ==================================================== -export interface UserTeamUpdate_userTeamUpdate_user { +export interface UserTeamUpdate_userTeamUpdate_user_AuthenticatedUser { __typename: "AuthenticatedUser"; id: string; } +export interface UserTeamUpdate_userTeamUpdate_user_AnonymousUser { + __typename: "AnonymousUser"; + id: string; +} + +export type UserTeamUpdate_userTeamUpdate_user = UserTeamUpdate_userTeamUpdate_user_AuthenticatedUser | UserTeamUpdate_userTeamUpdate_user_AnonymousUser; + export interface UserTeamUpdate_userTeamUpdate { __typename: "UserTeam"; role: UserTeamRole; diff --git a/apps/journeys-admin/src/components/AccessDialog/AccessDialog.tsx b/apps/journeys-admin/src/components/AccessDialog/AccessDialog.tsx index 571b754a259..4d375722f7a 100644 --- a/apps/journeys-admin/src/components/AccessDialog/AccessDialog.tsx +++ b/apps/journeys-admin/src/components/AccessDialog/AccessDialog.tsx @@ -36,11 +36,16 @@ export const GET_JOURNEY_WITH_PERMISSIONS = gql` id role user { - email - firstName - id - imageUrl - lastName + ... on AuthenticatedUser { + email + firstName + id + imageUrl + lastName + } + ... on AnonymousUser { + id + } } journeyNotification(journeyId: $id) { id diff --git a/apps/journeys-admin/src/components/Team/TeamManageDialog/UserTeamList/UserTeamListItem/UserTeamListItem.tsx b/apps/journeys-admin/src/components/Team/TeamManageDialog/UserTeamList/UserTeamListItem/UserTeamListItem.tsx index c8d23cab35a..33153851bcd 100644 --- a/apps/journeys-admin/src/components/Team/TeamManageDialog/UserTeamList/UserTeamListItem/UserTeamListItem.tsx +++ b/apps/journeys-admin/src/components/Team/TeamManageDialog/UserTeamList/UserTeamListItem/UserTeamListItem.tsx @@ -35,7 +35,12 @@ export const USER_TEAM_UPDATE = gql` role id user { - id + ... on AuthenticatedUser { + id + } + ... on AnonymousUser { + id + } } } } diff --git a/apps/journeys-admin/src/libs/useTeamCreateMutation/useTeamCreateMutation.tsx b/apps/journeys-admin/src/libs/useTeamCreateMutation/useTeamCreateMutation.tsx index 18205ba7f4a..fb2e66796ec 100644 --- a/apps/journeys-admin/src/libs/useTeamCreateMutation/useTeamCreateMutation.tsx +++ b/apps/journeys-admin/src/libs/useTeamCreateMutation/useTeamCreateMutation.tsx @@ -21,11 +21,16 @@ export const TEAM_CREATE = gql` userTeams { id user { - id - firstName - lastName - imageUrl - email + ... on AuthenticatedUser { + id + firstName + lastName + imageUrl + email + } + ... on AnonymousUser { + id + } } role } diff --git a/apps/journeys-admin/src/libs/useUserTeamsAndInvitesQuery/useUserTeamsAndInvitesQuery.ts b/apps/journeys-admin/src/libs/useUserTeamsAndInvitesQuery/useUserTeamsAndInvitesQuery.ts index 5c072b4e80d..85184de00bb 100644 --- a/apps/journeys-admin/src/libs/useUserTeamsAndInvitesQuery/useUserTeamsAndInvitesQuery.ts +++ b/apps/journeys-admin/src/libs/useUserTeamsAndInvitesQuery/useUserTeamsAndInvitesQuery.ts @@ -12,11 +12,16 @@ export const GET_USER_TEAMS_AND_INVITES = gql` id role user { - email - firstName - id - imageUrl - lastName + ... on AuthenticatedUser { + email + firstName + id + imageUrl + lastName + } + ... on AnonymousUser { + id + } } } userTeamInvites(teamId: $teamId) { diff --git a/apps/journeys/__generated__/GetLastActiveTeamIdAndTeams.ts b/apps/journeys/__generated__/GetLastActiveTeamIdAndTeams.ts index f151d41d194..7df3c60dcdf 100644 --- a/apps/journeys/__generated__/GetLastActiveTeamIdAndTeams.ts +++ b/apps/journeys/__generated__/GetLastActiveTeamIdAndTeams.ts @@ -15,7 +15,7 @@ export interface GetLastActiveTeamIdAndTeams_getJourneyProfile { lastActiveTeamId: string | null; } -export interface GetLastActiveTeamIdAndTeams_teams_userTeams_user { +export interface GetLastActiveTeamIdAndTeams_teams_userTeams_user_AuthenticatedUser { __typename: "AuthenticatedUser"; id: string; firstName: string; @@ -24,6 +24,13 @@ export interface GetLastActiveTeamIdAndTeams_teams_userTeams_user { email: string; } +export interface GetLastActiveTeamIdAndTeams_teams_userTeams_user_AnonymousUser { + __typename: "AnonymousUser"; + id: string; +} + +export type GetLastActiveTeamIdAndTeams_teams_userTeams_user = GetLastActiveTeamIdAndTeams_teams_userTeams_user_AuthenticatedUser | GetLastActiveTeamIdAndTeams_teams_userTeams_user_AnonymousUser; + export interface GetLastActiveTeamIdAndTeams_teams_userTeams { __typename: "UserTeam"; id: string; diff --git a/apps/resources/__generated__/GetLastActiveTeamIdAndTeams.ts b/apps/resources/__generated__/GetLastActiveTeamIdAndTeams.ts index f151d41d194..7df3c60dcdf 100644 --- a/apps/resources/__generated__/GetLastActiveTeamIdAndTeams.ts +++ b/apps/resources/__generated__/GetLastActiveTeamIdAndTeams.ts @@ -15,7 +15,7 @@ export interface GetLastActiveTeamIdAndTeams_getJourneyProfile { lastActiveTeamId: string | null; } -export interface GetLastActiveTeamIdAndTeams_teams_userTeams_user { +export interface GetLastActiveTeamIdAndTeams_teams_userTeams_user_AuthenticatedUser { __typename: "AuthenticatedUser"; id: string; firstName: string; @@ -24,6 +24,13 @@ export interface GetLastActiveTeamIdAndTeams_teams_userTeams_user { email: string; } +export interface GetLastActiveTeamIdAndTeams_teams_userTeams_user_AnonymousUser { + __typename: "AnonymousUser"; + id: string; +} + +export type GetLastActiveTeamIdAndTeams_teams_userTeams_user = GetLastActiveTeamIdAndTeams_teams_userTeams_user_AuthenticatedUser | GetLastActiveTeamIdAndTeams_teams_userTeams_user_AnonymousUser; + export interface GetLastActiveTeamIdAndTeams_teams_userTeams { __typename: "UserTeam"; id: string; diff --git a/apps/watch/__generated__/GetLastActiveTeamIdAndTeams.ts b/apps/watch/__generated__/GetLastActiveTeamIdAndTeams.ts index f151d41d194..7df3c60dcdf 100644 --- a/apps/watch/__generated__/GetLastActiveTeamIdAndTeams.ts +++ b/apps/watch/__generated__/GetLastActiveTeamIdAndTeams.ts @@ -15,7 +15,7 @@ export interface GetLastActiveTeamIdAndTeams_getJourneyProfile { lastActiveTeamId: string | null; } -export interface GetLastActiveTeamIdAndTeams_teams_userTeams_user { +export interface GetLastActiveTeamIdAndTeams_teams_userTeams_user_AuthenticatedUser { __typename: "AuthenticatedUser"; id: string; firstName: string; @@ -24,6 +24,13 @@ export interface GetLastActiveTeamIdAndTeams_teams_userTeams_user { email: string; } +export interface GetLastActiveTeamIdAndTeams_teams_userTeams_user_AnonymousUser { + __typename: "AnonymousUser"; + id: string; +} + +export type GetLastActiveTeamIdAndTeams_teams_userTeams_user = GetLastActiveTeamIdAndTeams_teams_userTeams_user_AuthenticatedUser | GetLastActiveTeamIdAndTeams_teams_userTeams_user_AnonymousUser; + export interface GetLastActiveTeamIdAndTeams_teams_userTeams { __typename: "UserTeam"; id: string; diff --git a/libs/journeys/ui/src/components/TeamProvider/TeamProvider.tsx b/libs/journeys/ui/src/components/TeamProvider/TeamProvider.tsx index 81cf6b763e4..047783260f8 100644 --- a/libs/journeys/ui/src/components/TeamProvider/TeamProvider.tsx +++ b/libs/journeys/ui/src/components/TeamProvider/TeamProvider.tsx @@ -46,11 +46,16 @@ export const GET_LAST_ACTIVE_TEAM_ID_AND_TEAMS = gql` userTeams { id user { + ... on AuthenticatedUser { id - firstName - lastName - imageUrl - email + firstName + lastName + imageUrl + email + } + ... on AnonymousUser { + id + } } role } diff --git a/libs/journeys/ui/src/components/TeamProvider/__generated__/GetLastActiveTeamIdAndTeams.ts b/libs/journeys/ui/src/components/TeamProvider/__generated__/GetLastActiveTeamIdAndTeams.ts index 5fe4cc5bbf0..ff42a53b1e2 100644 --- a/libs/journeys/ui/src/components/TeamProvider/__generated__/GetLastActiveTeamIdAndTeams.ts +++ b/libs/journeys/ui/src/components/TeamProvider/__generated__/GetLastActiveTeamIdAndTeams.ts @@ -15,7 +15,7 @@ export interface GetLastActiveTeamIdAndTeams_getJourneyProfile { lastActiveTeamId: string | null; } -export interface GetLastActiveTeamIdAndTeams_teams_userTeams_user { +export interface GetLastActiveTeamIdAndTeams_teams_userTeams_user_AuthenticatedUser { __typename: "AuthenticatedUser"; id: string; firstName: string; @@ -24,6 +24,13 @@ export interface GetLastActiveTeamIdAndTeams_teams_userTeams_user { email: string; } +export interface GetLastActiveTeamIdAndTeams_teams_userTeams_user_AnonymousUser { + __typename: "AnonymousUser"; + id: string; +} + +export type GetLastActiveTeamIdAndTeams_teams_userTeams_user = GetLastActiveTeamIdAndTeams_teams_userTeams_user_AuthenticatedUser | GetLastActiveTeamIdAndTeams_teams_userTeams_user_AnonymousUser; + export interface GetLastActiveTeamIdAndTeams_teams_userTeams { __typename: "UserTeam"; id: string; diff --git a/libs/shared/gql/src/__generated__/graphql-env.d.ts b/libs/shared/gql/src/__generated__/graphql-env.d.ts index b4bd04ac57e..843b5c05a3c 100644 --- a/libs/shared/gql/src/__generated__/graphql-env.d.ts +++ b/libs/shared/gql/src/__generated__/graphql-env.d.ts @@ -353,7 +353,7 @@ export type introspection_types = { 'UserJourney': { kind: 'OBJECT'; name: 'UserJourney'; fields: { 'id': { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; } }; 'journey': { name: 'journey'; type: { kind: 'OBJECT'; name: 'Journey'; ofType: null; } }; 'journeyId': { name: 'journeyId'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; } }; 'journeyNotification': { name: 'journeyNotification'; type: { kind: 'OBJECT'; name: 'JourneyNotification'; ofType: null; } }; 'openedAt': { name: 'openedAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; } }; 'role': { name: 'role'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'ENUM'; name: 'UserJourneyRole'; ofType: null; }; } }; 'user': { name: 'user'; type: { kind: 'OBJECT'; name: 'AuthenticatedUser'; ofType: null; } }; 'userId': { name: 'userId'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; } }; }; }; 'UserJourneyRole': { name: 'UserJourneyRole'; enumValues: 'inviteRequested' | 'editor' | 'owner'; }; 'UserRole': { kind: 'OBJECT'; name: 'UserRole'; fields: { 'id': { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; } }; 'roles': { name: 'roles'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'ENUM'; name: 'Role'; ofType: null; }; }; } }; 'userId': { name: 'userId'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; } }; }; }; - 'UserTeam': { kind: 'OBJECT'; name: 'UserTeam'; fields: { 'createdAt': { name: 'createdAt'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; }; } }; 'id': { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; } }; 'journeyNotification': { name: 'journeyNotification'; type: { kind: 'OBJECT'; name: 'JourneyNotification'; ofType: null; } }; 'role': { name: 'role'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'ENUM'; name: 'UserTeamRole'; ofType: null; }; } }; 'updatedAt': { name: 'updatedAt'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; }; } }; 'user': { name: 'user'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'AuthenticatedUser'; ofType: null; }; } }; }; }; + 'UserTeam': { kind: 'OBJECT'; name: 'UserTeam'; fields: { 'createdAt': { name: 'createdAt'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; }; } }; 'id': { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; } }; 'journeyNotification': { name: 'journeyNotification'; type: { kind: 'OBJECT'; name: 'JourneyNotification'; ofType: null; } }; 'role': { name: 'role'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'ENUM'; name: 'UserTeamRole'; ofType: null; }; } }; 'updatedAt': { name: 'updatedAt'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; }; } }; 'user': { name: 'user'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'UNION'; name: 'User'; ofType: null; }; } }; }; }; 'UserTeamFilterInput': { kind: 'INPUT_OBJECT'; name: 'UserTeamFilterInput'; isOneOf: false; inputFields: [{ name: 'role'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'ENUM'; name: 'UserTeamRole'; ofType: null; }; }; }; defaultValue: null }]; }; 'UserTeamInvite': { kind: 'OBJECT'; name: 'UserTeamInvite'; fields: { 'email': { name: 'email'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'id': { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; } }; 'teamId': { name: 'teamId'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; } }; }; }; 'UserTeamInviteCreateInput': { kind: 'INPUT_OBJECT'; name: 'UserTeamInviteCreateInput'; isOneOf: false; inputFields: [{ name: 'email'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }]; }; From 5ec41460e64d45220b6702e40ea2c70437546270 Mon Sep 17 00:00:00 2001 From: Mike Allison Date: Wed, 4 Feb 2026 22:33:19 +0000 Subject: [PATCH 10/18] refactor: update user handling to ensure only AuthenticatedUser types are processed - Modified various components to check for AuthenticatedUser type before accessing user properties. - Updated user-related logic in JourneyVisitorsPage, AccessDialog, and Team components to enhance type safety. - Ensured consistent handling of user data across the application by filtering out non-authenticated users. --- .../journeys/[journeyId]/reports/visitors.tsx | 4 ++- .../components/AccessDialog/AccessDialog.tsx | 8 ++++-- .../DefaultMenu/DefaultMenu.tsx | 4 ++- .../CustomDomainDialog/CustomDomainDialog.tsx | 7 +++-- .../Team/TeamAvatars/TeamAvatars.tsx | 8 +++--- .../TeamManageWrapper/TeamManageWrapper.tsx | 7 +++-- .../UserTeamList/UserTeamList.tsx | 11 +++++++- .../UserTeamListItem/UserTeamListItem.tsx | 27 +++++++++++-------- .../TeamUpdateDialog/TeamUpdateDialog.tsx | 7 +++-- .../useUserTeamsAndInvitesLazyQuery.ts | 8 +++++- .../useUserTeamsAndInvitesQuery.ts | 10 ++++--- 11 files changed, 72 insertions(+), 29 deletions(-) diff --git a/apps/journeys-admin/pages/journeys/[journeyId]/reports/visitors.tsx b/apps/journeys-admin/pages/journeys/[journeyId]/reports/visitors.tsx index f325a0cb0c5..1eb560ca5ff 100644 --- a/apps/journeys-admin/pages/journeys/[journeyId]/reports/visitors.tsx +++ b/apps/journeys-admin/pages/journeys/[journeyId]/reports/visitors.tsx @@ -198,7 +198,9 @@ function JourneyVisitorsPage({ () => !!journey?.team && !!userTeamsData?.userTeams?.some( - (userTeam) => userTeam.user.email === user.email + (userTeam) => + userTeam.user.__typename === 'AuthenticatedUser' && + userTeam.user.email === user.email ), [journey?.team, userTeamsData?.userTeams, user.email] ) diff --git a/apps/journeys-admin/src/components/AccessDialog/AccessDialog.tsx b/apps/journeys-admin/src/components/AccessDialog/AccessDialog.tsx index 4d375722f7a..f707de3e268 100644 --- a/apps/journeys-admin/src/components/AccessDialog/AccessDialog.tsx +++ b/apps/journeys-admin/src/components/AccessDialog/AccessDialog.tsx @@ -106,8 +106,12 @@ export function AccessDialog({ }, [data?.journey?.userJourneys, user]) const currentUserTeam: UserTeam | undefined = useMemo(() => { - return data?.journey?.team?.userTeams.find(({ user: { email } }) => { - return user?.__typename === 'AuthenticatedUser' && email === user.email + return data?.journey?.team?.userTeams.find(({ user: teamUser }) => { + return ( + user?.__typename === 'AuthenticatedUser' && + teamUser.__typename === 'AuthenticatedUser' && + teamUser.email === user.email + ) }) }, [data?.journey?.team?.userTeams, user]) diff --git a/apps/journeys-admin/src/components/JourneyList/JourneyCard/JourneyCardMenu/DefaultMenu/DefaultMenu.tsx b/apps/journeys-admin/src/components/JourneyList/JourneyCard/JourneyCardMenu/DefaultMenu/DefaultMenu.tsx index 2dc6ee04ab0..346247c3448 100644 --- a/apps/journeys-admin/src/components/JourneyList/JourneyCard/JourneyCardMenu/DefaultMenu/DefaultMenu.tsx +++ b/apps/journeys-admin/src/components/JourneyList/JourneyCard/JourneyCardMenu/DefaultMenu/DefaultMenu.tsx @@ -165,7 +165,9 @@ export function DefaultMenu({ return undefined const userTeam = activeTeam.userTeams.find( - (userTeam) => userTeam.user.email === currentUserEmail + (userTeam) => + userTeam.user.__typename === 'AuthenticatedUser' && + userTeam.user.email === currentUserEmail ) return userTeam?.role diff --git a/apps/journeys-admin/src/components/Team/CustomDomainDialog/CustomDomainDialog.tsx b/apps/journeys-admin/src/components/Team/CustomDomainDialog/CustomDomainDialog.tsx index 25cf3bf38b3..f0501344f6f 100644 --- a/apps/journeys-admin/src/components/Team/CustomDomainDialog/CustomDomainDialog.tsx +++ b/apps/journeys-admin/src/components/Team/CustomDomainDialog/CustomDomainDialog.tsx @@ -51,8 +51,11 @@ export function CustomDomainDialog({ const customDomain = data?.customDomains[0] const currentUserTeamRole: UserTeamRole | undefined = useMemo(() => { if (currentUser?.__typename === 'AuthenticatedUser') { - return activeTeam?.userTeams?.find(({ user: { email } }) => { - return email === currentUser?.email + return activeTeam?.userTeams?.find(({ user }) => { + return ( + user.__typename === 'AuthenticatedUser' && + user.email === currentUser?.email + ) })?.role } return undefined diff --git a/apps/journeys-admin/src/components/Team/TeamAvatars/TeamAvatars.tsx b/apps/journeys-admin/src/components/Team/TeamAvatars/TeamAvatars.tsx index 8f7b9d077f5..4ca624a51b3 100644 --- a/apps/journeys-admin/src/components/Team/TeamAvatars/TeamAvatars.tsx +++ b/apps/journeys-admin/src/components/Team/TeamAvatars/TeamAvatars.tsx @@ -49,9 +49,11 @@ export function TeamAvatars({ } }} > - {take(userTeams, 5).map(({ user }) => ( - - ))} + {take(userTeams, 5).map(({ user }) => + user.__typename === 'AuthenticatedUser' ? ( + + ) : null + )} {onClick != null && ( { if (currentUser?.__typename === 'AuthenticatedUser') { - return data?.userTeams?.find(({ user: { email } }) => { - return email === currentUser.email + return data?.userTeams?.find(({ user }) => { + return ( + user.__typename === 'AuthenticatedUser' && + user.email === currentUser.email + ) }) } return undefined diff --git a/apps/journeys-admin/src/components/Team/TeamManageDialog/UserTeamList/UserTeamList.tsx b/apps/journeys-admin/src/components/Team/TeamManageDialog/UserTeamList/UserTeamList.tsx index 1056a6046e7..1bbe2af83e2 100644 --- a/apps/journeys-admin/src/components/Team/TeamManageDialog/UserTeamList/UserTeamList.tsx +++ b/apps/journeys-admin/src/components/Team/TeamManageDialog/UserTeamList/UserTeamList.tsx @@ -73,13 +73,22 @@ export function UserTeamList({ {sortedUserTeams.length > 0 && currentUserTeam != null && ( <> {sortedUserTeams.map((userTeam) => { + const currentUserEmail = + currentUserTeam.user.__typename === 'AuthenticatedUser' + ? currentUserTeam.user.email + : undefined + const userTeamEmail = + userTeam.user.__typename === 'AuthenticatedUser' + ? userTeam.user.email + : undefined return ( (USER_TEAM_UPDATE) const { id, email, displayName, imageUrl, role, userId } = useMemo(() => { + const user = listItem?.user + const isAuthenticated = user?.__typename === 'AuthenticatedUser' return { id: listItem.id, - email: listItem?.user?.email, - displayName: compact([ - listItem?.user?.firstName, - listItem?.user?.lastName - ]).join(' '), - userId: listItem?.user?.id, - imageUrl: listItem?.user?.imageUrl, + email: isAuthenticated ? user.email : undefined, + displayName: isAuthenticated + ? compact([user.firstName, user.lastName]).join(' ') + : '', + userId: user?.id, + imageUrl: isAuthenticated ? user.imageUrl : undefined, role: listItem.role } }, [listItem]) @@ -96,10 +97,10 @@ export function UserTeamListItem({ <> - - {displayName != null + + {displayName != null && displayName !== '' ? displayName.charAt(0)?.toUpperCase() - : email.charAt(0).toUpperCase()} + : email?.charAt(0)?.toUpperCase() ?? '?'} @@ -124,7 +125,11 @@ export function UserTeamListItem({ {journeyId != null && ( { if (currentUser?.__typename === 'AuthenticatedUser') { - return activeTeam?.userTeams?.find(({ user: { email } }) => { - return email === currentUser.email + return activeTeam?.userTeams?.find(({ user }) => { + return ( + user.__typename === 'AuthenticatedUser' && + user.email === currentUser.email + ) })?.role } return undefined diff --git a/apps/journeys-admin/src/libs/useUserTeamsAndInvitesLazyQuery/useUserTeamsAndInvitesLazyQuery.ts b/apps/journeys-admin/src/libs/useUserTeamsAndInvitesLazyQuery/useUserTeamsAndInvitesLazyQuery.ts index 8df5175e943..c1a8b36f4b2 100644 --- a/apps/journeys-admin/src/libs/useUserTeamsAndInvitesLazyQuery/useUserTeamsAndInvitesLazyQuery.ts +++ b/apps/journeys-admin/src/libs/useUserTeamsAndInvitesLazyQuery/useUserTeamsAndInvitesLazyQuery.ts @@ -21,7 +21,13 @@ export function useUserTeamsAndInvitesLazyQuery(): { >(GET_USER_TEAMS_AND_INVITES, { onCompleted: ({ userTeams, userTeamInvites }) => { setEmails([ - ...userTeams.map(({ user: { email } }) => email.toLowerCase()), + ...userTeams + .filter(({ user }) => user.__typename === 'AuthenticatedUser') + .map(({ user }) => + user.__typename === 'AuthenticatedUser' + ? user.email.toLowerCase() + : '' + ), ...userTeamInvites.map(({ email }) => email.toLowerCase()) ]) } diff --git a/apps/journeys-admin/src/libs/useUserTeamsAndInvitesQuery/useUserTeamsAndInvitesQuery.ts b/apps/journeys-admin/src/libs/useUserTeamsAndInvitesQuery/useUserTeamsAndInvitesQuery.ts index 85184de00bb..5544209eb77 100644 --- a/apps/journeys-admin/src/libs/useUserTeamsAndInvitesQuery/useUserTeamsAndInvitesQuery.ts +++ b/apps/journeys-admin/src/libs/useUserTeamsAndInvitesQuery/useUserTeamsAndInvitesQuery.ts @@ -46,9 +46,13 @@ export function useUserTeamsAndInvitesQuery( const emails = useMemo(() => { return [ - ...(query.data?.userTeams.map(({ user: { email } }) => - email.toLowerCase() - ) ?? []), + ...(query.data?.userTeams + .filter(({ user }) => user.__typename === 'AuthenticatedUser') + .map(({ user }) => + user.__typename === 'AuthenticatedUser' + ? user.email.toLowerCase() + : '' + ) ?? []), ...(query.data?.userTeamInvites.map(({ email }) => email.toLowerCase()) ?? []) ] From 8578b805d1fbd2d7d47e34fac9a9691d586e8ca7 Mon Sep 17 00:00:00 2001 From: Mike Allison Date: Wed, 4 Feb 2026 23:00:59 +0000 Subject: [PATCH 11/18] test: update User type to AuthenticatedUser in various component tests - Changed instances of User type to AuthenticatedUser in JourneyAppearance, JourneyCardMenu, TeamOnboarding, UserTeamInviteForm, and useUserTeamsAndInvitesLazyQuery tests. - Ensured consistency in user type handling across multiple test files. --- .../JourneyAppearance/JourneyAppearance.spec.tsx | 6 +++--- .../JourneyCard/JourneyCardMenu/JourneyCardMenu.spec.tsx | 2 +- .../components/Team/TeamOnboarding/TeamOnboarding.spec.tsx | 2 +- .../Team/UserTeamInviteForm/UserTeamInviteForm.spec.tsx | 2 +- .../useUserTeamsAndInvitesLazyQuery.spec.tsx | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/journeys-admin/src/components/Editor/Slider/Settings/CanvasDetails/JourneyAppearance/JourneyAppearance.spec.tsx b/apps/journeys-admin/src/components/Editor/Slider/Settings/CanvasDetails/JourneyAppearance/JourneyAppearance.spec.tsx index dcdfb166d52..56777190f19 100644 --- a/apps/journeys-admin/src/components/Editor/Slider/Settings/CanvasDetails/JourneyAppearance/JourneyAppearance.spec.tsx +++ b/apps/journeys-admin/src/components/Editor/Slider/Settings/CanvasDetails/JourneyAppearance/JourneyAppearance.spec.tsx @@ -47,7 +47,7 @@ describe('JourneyAppearance', () => { request: { query: GET_CURRENT_USER }, result: { data: { - me: { __typename: 'User', id: 'u1', email: 'u1@example.com' } + me: { __typename: 'AuthenticatedUser', id: 'u1', email: 'u1@example.com' } } } }, @@ -102,7 +102,7 @@ describe('JourneyAppearance', () => { request: { query: GET_CURRENT_USER }, result: { data: { - me: { __typename: 'User', id: 'u1', email: 'u1@example.com' } + me: { __typename: 'AuthenticatedUser', id: 'u1', email: 'u1@example.com' } } } }, @@ -160,7 +160,7 @@ describe('JourneyAppearance', () => { request: { query: GET_CURRENT_USER }, result: { data: { - me: { __typename: 'User', id: 'u1', email: 'u1@example.com' } + me: { __typename: 'AuthenticatedUser', id: 'u1', email: 'u1@example.com' } } } }, diff --git a/apps/journeys-admin/src/components/JourneyList/JourneyCard/JourneyCardMenu/JourneyCardMenu.spec.tsx b/apps/journeys-admin/src/components/JourneyList/JourneyCard/JourneyCardMenu/JourneyCardMenu.spec.tsx index a0eba9d39dc..d82d73d2bdc 100644 --- a/apps/journeys-admin/src/components/JourneyList/JourneyCard/JourneyCardMenu/JourneyCardMenu.spec.tsx +++ b/apps/journeys-admin/src/components/JourneyList/JourneyCard/JourneyCardMenu/JourneyCardMenu.spec.tsx @@ -43,7 +43,7 @@ const teamMock = { lastName: 'User', imageUrl: null, email: 'test@example.com', - __typename: 'User' + __typename: 'AuthenticatedUser' }, role: UserTeamRole.manager, __typename: 'UserTeam' diff --git a/apps/journeys-admin/src/components/Team/TeamOnboarding/TeamOnboarding.spec.tsx b/apps/journeys-admin/src/components/Team/TeamOnboarding/TeamOnboarding.spec.tsx index 64366e1ee51..15862511afb 100644 --- a/apps/journeys-admin/src/components/Team/TeamOnboarding/TeamOnboarding.spec.tsx +++ b/apps/journeys-admin/src/components/Team/TeamOnboarding/TeamOnboarding.spec.tsx @@ -30,7 +30,7 @@ jest.mock('apps/journeys-admin/src/libs/useCurrentUserLazyQuery', () => ({ useCurrentUserLazyQuery: jest.fn().mockReturnValue({ loadUser: jest.fn(), data: { - __typename: 'User', + __typename: 'AuthenticatedUser', id: 'userId', email: 'siyangguccigang@example.com' } diff --git a/apps/journeys-admin/src/components/Team/UserTeamInviteForm/UserTeamInviteForm.spec.tsx b/apps/journeys-admin/src/components/Team/UserTeamInviteForm/UserTeamInviteForm.spec.tsx index 043dda5d2ab..0b4088d85a5 100644 --- a/apps/journeys-admin/src/components/Team/UserTeamInviteForm/UserTeamInviteForm.spec.tsx +++ b/apps/journeys-admin/src/components/Team/UserTeamInviteForm/UserTeamInviteForm.spec.tsx @@ -23,7 +23,7 @@ jest.mock('../../../libs/useCurrentUserLazyQuery', () => ({ useCurrentUserLazyQuery: jest.fn().mockReturnValue({ loadUser: jest.fn(), data: { - __typename: 'User', + __typename: 'AuthenticatedUser', ...user1 } }) diff --git a/apps/journeys-admin/src/libs/useUserTeamsAndInvitesLazyQuery/useUserTeamsAndInvitesLazyQuery.spec.tsx b/apps/journeys-admin/src/libs/useUserTeamsAndInvitesLazyQuery/useUserTeamsAndInvitesLazyQuery.spec.tsx index 096ed38f368..aef3046c524 100644 --- a/apps/journeys-admin/src/libs/useUserTeamsAndInvitesLazyQuery/useUserTeamsAndInvitesLazyQuery.spec.tsx +++ b/apps/journeys-admin/src/libs/useUserTeamsAndInvitesLazyQuery/useUserTeamsAndInvitesLazyQuery.spec.tsx @@ -16,7 +16,7 @@ describe('useUserTeamsAndInvitesLazyQuery', () => { id: 'ut1', role: UserTeamRole.manager, user: { - __typename: 'User', + __typename: 'AuthenticatedUser', email: 'userTeam1@example.com', firstName: 'User', lastName: 'One', @@ -29,7 +29,7 @@ describe('useUserTeamsAndInvitesLazyQuery', () => { id: 'ut2', role: UserTeamRole.member, user: { - __typename: 'User', + __typename: 'AuthenticatedUser', email: 'userTeam2@example.com', firstName: 'User', lastName: 'Two', From 85eb72a1597dc5f9c77e26b878bea8265087d3a5 Mon Sep 17 00:00:00 2001 From: Mike Allison Date: Thu, 5 Feb 2026 01:21:01 +0000 Subject: [PATCH 12/18] refactor: update User type to include AnonymousUser and adjust related schema references - Changed the User type in UserTeam to reference the new User union, which includes both AuthenticatedUser and AnonymousUser. - Introduced AnonymousUser type in multiple schemas to handle unauthenticated users. - Updated generated TypeScript files to reflect the changes in the GraphQL schema. - Ensured consistency across the application by updating user-related logic to accommodate the new structure. --- apis/api-gateway/schema.graphql | 14 +++++++------- apis/api-journeys/schema.graphql | 10 +++++++++- apis/api-journeys/src/__generated__/graphql.ts | 2 +- apis/api-journeys/src/app/__generated__/graphql.ts | 8 +++++++- .../src/app/modules/userTeam/userTeam.graphql | 8 +++++++- 5 files changed, 31 insertions(+), 11 deletions(-) diff --git a/apis/api-gateway/schema.graphql b/apis/api-gateway/schema.graphql index 233b494c480..f85a3774b90 100644 --- a/apis/api-gateway/schema.graphql +++ b/apis/api-gateway/schema.graphql @@ -1976,7 +1976,7 @@ type JourneyNotification @join__type(graph: API_JOURNEYS) @join__type(graph: AP type UserTeam @join__type(graph: API_JOURNEYS, key: "id") @join__type(graph: API_JOURNEYS_MODERN) { id: ID! journeyNotification(journeyId: ID!) : JourneyNotification - user: User! @join__field(graph: API_JOURNEYS, type: "AuthenticatedUser!") @join__field(graph: API_JOURNEYS_MODERN, type: "User!") + user: User! role: UserTeamRole! createdAt: DateTime! updatedAt: DateTime! @@ -2199,6 +2199,10 @@ type UserRole @join__type(graph: API_JOURNEYS, key: "id") @join__type(graph: AP roles: [Role!] } +type AnonymousUser @join__type(graph: API_JOURNEYS) @join__type(graph: API_JOURNEYS_MODERN) @join__type(graph: API_USERS) { + id: ID! +} + type UserTeamInvite @join__type(graph: API_JOURNEYS) @join__type(graph: API_JOURNEYS_MODERN) { id: ID! teamId: ID! @@ -2380,10 +2384,6 @@ type VisitorsConnection @join__type(graph: API_JOURNEYS) { pageInfo: PageInfo! } -type AnonymousUser @join__type(graph: API_JOURNEYS_MODERN) @join__type(graph: API_USERS) { - id: ID! -} - type GoogleSheetsSync @join__type(graph: API_JOURNEYS_MODERN) { id: ID! teamId: ID! @@ -3298,9 +3298,9 @@ interface Node @join__type(graph: API_JOURNEYS_MODERN) { union MutationSiteCreateResult @join__type(graph: API_ANALYTICS) @join__unionMember(graph: API_ANALYTICS, member: "Error") @join__unionMember(graph: API_ANALYTICS, member: "MutationSiteCreateSuccess") = Error | MutationSiteCreateSuccess -union MediaVideo @join__type(graph: API_JOURNEYS_MODERN) @join__unionMember(graph: API_JOURNEYS_MODERN, member: "MuxVideo") @join__unionMember(graph: API_JOURNEYS_MODERN, member: "Video") @join__unionMember(graph: API_JOURNEYS_MODERN, member: "YouTube") = MuxVideo | Video | YouTube +union User @join__type(graph: API_JOURNEYS) @join__type(graph: API_JOURNEYS_MODERN) @join__type(graph: API_USERS) @join__unionMember(graph: API_JOURNEYS, member: "AuthenticatedUser") @join__unionMember(graph: API_JOURNEYS, member: "AnonymousUser") @join__unionMember(graph: API_JOURNEYS_MODERN, member: "AuthenticatedUser") @join__unionMember(graph: API_JOURNEYS_MODERN, member: "AnonymousUser") @join__unionMember(graph: API_USERS, member: "AuthenticatedUser") @join__unionMember(graph: API_USERS, member: "AnonymousUser") = AuthenticatedUser | AnonymousUser -union User @join__type(graph: API_JOURNEYS_MODERN) @join__type(graph: API_USERS) @join__unionMember(graph: API_JOURNEYS_MODERN, member: "AuthenticatedUser") @join__unionMember(graph: API_JOURNEYS_MODERN, member: "AnonymousUser") @join__unionMember(graph: API_USERS, member: "AuthenticatedUser") @join__unionMember(graph: API_USERS, member: "AnonymousUser") = AuthenticatedUser | AnonymousUser +union MediaVideo @join__type(graph: API_JOURNEYS_MODERN) @join__unionMember(graph: API_JOURNEYS_MODERN, member: "MuxVideo") @join__unionMember(graph: API_JOURNEYS_MODERN, member: "Video") @join__unionMember(graph: API_JOURNEYS_MODERN, member: "YouTube") = MuxVideo | Video | YouTube union MutationPlaylistCreateResult @join__type(graph: API_MEDIA) @join__unionMember(graph: API_MEDIA, member: "ZodError") @join__unionMember(graph: API_MEDIA, member: "MutationPlaylistCreateSuccess") = ZodError | MutationPlaylistCreateSuccess diff --git a/apis/api-journeys/schema.graphql b/apis/api-journeys/schema.graphql index 0b652b5cd35..341ac8762b5 100644 --- a/apis/api-journeys/schema.graphql +++ b/apis/api-journeys/schema.graphql @@ -2426,7 +2426,7 @@ type UserTeam { journeyNotification(journeyId: ID!): JourneyNotification id: ID! - user: AuthenticatedUser! + user: User! role: UserTeamRole! createdAt: DateTime! updatedAt: DateTime! @@ -2739,6 +2739,14 @@ enum Role { publisher } +type AnonymousUser + @shareable +{ + id: ID! +} + +union User = AuthenticatedUser | AnonymousUser + enum UserTeamRole { manager member diff --git a/apis/api-journeys/src/__generated__/graphql.ts b/apis/api-journeys/src/__generated__/graphql.ts index cf9517eff62..730e3d7f362 100644 --- a/apis/api-journeys/src/__generated__/graphql.ts +++ b/apis/api-journeys/src/__generated__/graphql.ts @@ -5176,7 +5176,7 @@ export type UserTeam = { journeyNotification?: Maybe; role: UserTeamRole; updatedAt: Scalars['DateTime']['output']; - user: AuthenticatedUser; + user: User; }; diff --git a/apis/api-journeys/src/app/__generated__/graphql.ts b/apis/api-journeys/src/app/__generated__/graphql.ts index 1c2e2174c0d..715f77db41d 100644 --- a/apis/api-journeys/src/app/__generated__/graphql.ts +++ b/apis/api-journeys/src/app/__generated__/graphql.ts @@ -1814,7 +1814,7 @@ export class UserTeam { __typename?: 'UserTeam'; journeyNotification?: Nullable; id: string; - user: AuthenticatedUser; + user: User; role: UserTeamRole; createdAt: DateTime; updatedAt: DateTime; @@ -1932,6 +1932,11 @@ export class UserRole { roles?: Nullable; } +export class AnonymousUser { + __typename?: 'AnonymousUser'; + id: string; +} + export class UserTeamInvite { __typename?: 'UserTeamInvite'; id: string; @@ -2038,4 +2043,5 @@ export class ISchema { Mutation: IMutation; } +export type User = AuthenticatedUser | AnonymousUser; type Nullable = T | null; diff --git a/apis/api-journeys/src/app/modules/userTeam/userTeam.graphql b/apis/api-journeys/src/app/modules/userTeam/userTeam.graphql index f0db8bbd28a..0c41ea32e94 100644 --- a/apis/api-journeys/src/app/modules/userTeam/userTeam.graphql +++ b/apis/api-journeys/src/app/modules/userTeam/userTeam.graphql @@ -2,6 +2,12 @@ extend type AuthenticatedUser @key(fields: "id") { id: ID! @external } +type AnonymousUser @shareable { + id: ID! +} + +union User = AuthenticatedUser | AnonymousUser + enum UserTeamRole { manager member @@ -9,7 +15,7 @@ enum UserTeamRole { type UserTeam @key(fields: "id") @shareable { id: ID! - user: AuthenticatedUser! + user: User! role: UserTeamRole! createdAt: DateTime! updatedAt: DateTime! From d408c98519cabb238c8fc2091c033a25ea9d0b3b Mon Sep 17 00:00:00 2001 From: Mike Allison Date: Thu, 5 Feb 2026 02:36:55 +0000 Subject: [PATCH 13/18] refactor: enhance type safety for user checks in AccessDialog and Host components - Updated user type checks in AccessDialog and Host components to use optional chaining for safer access to user properties. - Ensured consistent handling of AuthenticatedUser type across components to prevent potential runtime errors. --- .../src/components/AccessDialog/AccessDialog.tsx | 2 +- .../CanvasDetails/JourneyAppearance/Host/Host.tsx | 2 +- .../useCurrentUserLazyQuery/useCurrentUserLazyQuery.ts | 8 ++------ 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/apps/journeys-admin/src/components/AccessDialog/AccessDialog.tsx b/apps/journeys-admin/src/components/AccessDialog/AccessDialog.tsx index f707de3e268..b888c5bada3 100644 --- a/apps/journeys-admin/src/components/AccessDialog/AccessDialog.tsx +++ b/apps/journeys-admin/src/components/AccessDialog/AccessDialog.tsx @@ -100,7 +100,7 @@ export function AccessDialog({ return data?.journey?.userJourneys?.find( (userJourney) => userJourney.user?.__typename === 'AuthenticatedUser' && - user.__typename === 'AuthenticatedUser' && + user?.__typename === 'AuthenticatedUser' && userJourney.user?.email === user.email ) }, [data?.journey?.userJourneys, user]) diff --git a/apps/journeys-admin/src/components/Editor/Slider/Settings/CanvasDetails/JourneyAppearance/Host/Host.tsx b/apps/journeys-admin/src/components/Editor/Slider/Settings/CanvasDetails/JourneyAppearance/Host/Host.tsx index be3f3a80bf2..3af251efa0e 100644 --- a/apps/journeys-admin/src/components/Editor/Slider/Settings/CanvasDetails/JourneyAppearance/Host/Host.tsx +++ b/apps/journeys-admin/src/components/Editor/Slider/Settings/CanvasDetails/JourneyAppearance/Host/Host.tsx @@ -78,7 +78,7 @@ export function Host(): ReactElement { : data.userTeams.find( (userTeam) => userTeam.user?.__typename === 'AuthenticatedUser' && - user.__typename === 'AuthenticatedUser' && + user?.__typename === 'AuthenticatedUser' && userTeam.user.email === user.email ) != null diff --git a/apps/journeys-admin/src/libs/useCurrentUserLazyQuery/useCurrentUserLazyQuery.ts b/apps/journeys-admin/src/libs/useCurrentUserLazyQuery/useCurrentUserLazyQuery.ts index 35a48d2ddf4..06acd4e5281 100644 --- a/apps/journeys-admin/src/libs/useCurrentUserLazyQuery/useCurrentUserLazyQuery.ts +++ b/apps/journeys-admin/src/libs/useCurrentUserLazyQuery/useCurrentUserLazyQuery.ts @@ -26,16 +26,12 @@ export const GET_CURRENT_USER = gql` export function useCurrentUserLazyQuery(): { loadUser: LazyQueryExecFunction - data: ApiUser + data?: ApiUser | null } { const [loadUser, { data }] = useLazyQuery(GET_CURRENT_USER) - if (data?.me != null) { - return { loadUser, data: data.me } - } - return { loadUser, - data: { __typename: 'AuthenticatedUser', id: '', email: '' } + data: data?.me } } From 5d2ddfdd3fbc3caa51694061d4b43a302b49f29b Mon Sep 17 00:00:00 2001 From: Mike Allison Date: Fri, 6 Feb 2026 21:29:32 +0000 Subject: [PATCH 14/18] refactor: simplify user sign-in handling in UseThisTemplateButton - Removed conditional check for 'menu-item' variant and signed-in status in handleCheckSignIn function. - Streamlined the logic to always set loading state and call handleCustomizeNavigation, improving code clarity. --- .../UseThisTemplateButton/UseThisTemplateButton.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/libs/journeys/ui/src/components/TemplateView/UseThisTemplateButton/UseThisTemplateButton.tsx b/libs/journeys/ui/src/components/TemplateView/UseThisTemplateButton/UseThisTemplateButton.tsx index 7b16534ee41..9f2d29d3c23 100644 --- a/libs/journeys/ui/src/components/TemplateView/UseThisTemplateButton/UseThisTemplateButton.tsx +++ b/libs/journeys/ui/src/components/TemplateView/UseThisTemplateButton/UseThisTemplateButton.tsx @@ -36,12 +36,12 @@ export function UseThisTemplateButton({ const handleCheckSignIn = async (): Promise => { // For menu-item variant, assume user is signed in - if (variant === 'menu-item' || signedIn) { - setLoading(true) - await handleCustomizeNavigation() - } else { - setOpenAccountDialog(true) - } + // if (variant === 'menu-item' || signedIn) { + setLoading(true) + await handleCustomizeNavigation() + // } else { + // setOpenAccountDialog(true) + // } } const handleSignIn = (login: boolean): void => { From a4eae68e34654199f0ab6baf15b2a5fff74633e7 Mon Sep 17 00:00:00 2001 From: Mike Allison Date: Fri, 6 Feb 2026 21:29:38 +0000 Subject: [PATCH 15/18] refactor: restructure UserRole handling and related schemas - Removed UserRole type and its associated queries from the journeys schema, streamlining user role management. - Introduced a new UserRole type in the modern journeys schema to enhance clarity and maintainability. - Updated references to UserRole across various components and modules, ensuring consistency in user role handling. - Adjusted the team creation logic to accommodate guest users, improving the user experience for unauthenticated access. --- apis/api-gateway/schema.graphql | 26 +- apis/api-journeys-modern/schema.graphql | 2 + .../api-journeys-modern/src/schema/builder.ts | 1 + .../getJourneyProfile.query.spec.ts | 129 ++++++++++ .../journeyProfile/getJourneyProfile.query.ts | 18 ++ .../src/schema/journeyProfile/index.ts | 1 + .../schema/userRole/getUserRole.query.spec.ts | 154 ++++++++++++ .../src/schema/userRole/getUserRole.query.ts | 32 +++ .../src/schema/userRole/index.ts | 2 + .../db/seeds/playwrightUserAccess.ts | 5 +- apis/api-journeys/schema.graphql | 20 +- .../api-journeys/src/__generated__/graphql.ts | 4 - .../src/app/__generated__/graphql.ts | 15 -- apis/api-journeys/src/app/app.module.ts | 2 - .../journeyProfile/journeyProfile.graphql | 4 - .../journeyProfile/journeyProfile.resolver.ts | 6 +- .../src/app/modules/userRole/userRole.graphql | 17 -- .../app/modules/userRole/userRole.module.ts | 14 -- .../userRole/userRole.resolver.spec.ts | 53 ---- .../app/modules/userRole/userRole.resolver.ts | 20 -- .../modules/userRole/userRole.service.spec.ts | 64 ----- .../app/modules/userRole/userRole.service.ts | 26 -- .../src/schema/user/findOrFetchUser.ts | 30 ++- apis/api-users/src/schema/user/user.ts | 3 +- .../pages/templates/[journeyId]/customize.tsx | 2 +- .../Screens/LanguageScreen/LanguageScreen.tsx | 238 ++++++++++++++++-- .../src/libs/useTeamCreateMutation/index.ts | 5 +- .../useTeamCreateMutation.tsx | 61 +++++ 28 files changed, 656 insertions(+), 298 deletions(-) create mode 100644 apis/api-journeys-modern/src/schema/journeyProfile/getJourneyProfile.query.spec.ts create mode 100644 apis/api-journeys-modern/src/schema/journeyProfile/getJourneyProfile.query.ts create mode 100644 apis/api-journeys-modern/src/schema/userRole/getUserRole.query.spec.ts create mode 100644 apis/api-journeys-modern/src/schema/userRole/getUserRole.query.ts delete mode 100644 apis/api-journeys/src/app/modules/userRole/userRole.graphql delete mode 100644 apis/api-journeys/src/app/modules/userRole/userRole.module.ts delete mode 100644 apis/api-journeys/src/app/modules/userRole/userRole.resolver.spec.ts delete mode 100644 apis/api-journeys/src/app/modules/userRole/userRole.resolver.ts delete mode 100644 apis/api-journeys/src/app/modules/userRole/userRole.service.spec.ts delete mode 100644 apis/api-journeys/src/app/modules/userRole/userRole.service.ts diff --git a/apis/api-gateway/schema.graphql b/apis/api-gateway/schema.graphql index f85a3774b90..de970a39123 100644 --- a/apis/api-gateway/schema.graphql +++ b/apis/api-gateway/schema.graphql @@ -767,7 +767,6 @@ type Query @join__type(graph: API_ANALYTICS) @join__type(graph: API_JOURNEYS) teams: [Team!]! @join__field(graph: API_JOURNEYS) team(id: ID!) : Team! @join__field(graph: API_JOURNEYS) userInvites(journeyId: ID!) : [UserInvite!] @join__field(graph: API_JOURNEYS) - getUserRole: UserRole @join__field(graph: API_JOURNEYS) userTeams(teamId: ID!, where: UserTeamFilterInput) : [UserTeam!]! @join__field(graph: API_JOURNEYS) userTeam(id: ID!) : UserTeam! @join__field(graph: API_JOURNEYS) userTeamInvites(teamId: ID!) : [UserTeamInvite!]! @join__field(graph: API_JOURNEYS) @@ -849,6 +848,7 @@ type Query @join__type(graph: API_ANALYTICS) @join__type(graph: API_JOURNEYS) """ status: [JourneyStatus!] ): [TemplateFamilyStatsBreakdownResponse!] @join__field(graph: API_JOURNEYS_MODERN) + getUserRole: UserRole @join__field(graph: API_JOURNEYS_MODERN) language(id: ID!, idType: LanguageIdType = databaseId) : Language @join__field(graph: API_LANGUAGES) languages(offset: Int, limit: Int, where: LanguagesFilter, term: String) : [Language!]! @join__field(graph: API_LANGUAGES) languagesCount(where: LanguagesFilter, term: String) : Int! @join__field(graph: API_LANGUAGES) @@ -2193,12 +2193,6 @@ type AuthenticatedUser @join__type(graph: API_JOURNEYS, key: "id", extension: tr emailVerified: Boolean! @join__field(graph: API_USERS) } -type UserRole @join__type(graph: API_JOURNEYS, key: "id") @join__type(graph: API_JOURNEYS_MODERN, key: "id") { - id: ID! - userId: ID! - roles: [Role!] -} - type AnonymousUser @join__type(graph: API_JOURNEYS) @join__type(graph: API_JOURNEYS_MODERN) @join__type(graph: API_USERS) { id: ID! } @@ -2558,6 +2552,12 @@ type TemplateFamilyStatsEventResponse @join__type(graph: API_JOURNEYS_MODERN) { visitors: Int! } +type UserRole @join__type(graph: API_JOURNEYS_MODERN, key: "id") { + id: ID! + userId: ID! + roles: [Role!] +} + type YouTube @join__type(graph: API_JOURNEYS_MODERN, key: "id primaryLanguageId", extension: true) { id: ID! primaryLanguageId: ID @@ -3634,14 +3634,6 @@ enum UserTeamRole @join__type(graph: API_JOURNEYS) @join__type(graph: API_JOURN member @join__enumValue(graph: API_JOURNEYS) @join__enumValue(graph: API_JOURNEYS_MODERN) } -enum Role @join__type(graph: API_JOURNEYS) @join__type(graph: API_JOURNEYS_MODERN) { - """ - User can create templates and - add them to template library - """ - publisher @join__enumValue(graph: API_JOURNEYS) @join__enumValue(graph: API_JOURNEYS_MODERN) -} - enum DeviceType @join__type(graph: API_JOURNEYS) @join__type(graph: API_JOURNEYS_MODERN) { console @join__enumValue(graph: API_JOURNEYS) @join__enumValue(graph: API_JOURNEYS_MODERN) mobile @join__enumValue(graph: API_JOURNEYS) @join__enumValue(graph: API_JOURNEYS_MODERN) @@ -3734,6 +3726,10 @@ enum PlausibleEvent @join__type(graph: API_JOURNEYS_MODERN) { journeyResponses @join__enumValue(graph: API_JOURNEYS_MODERN) } +enum Role @join__type(graph: API_JOURNEYS_MODERN) { + publisher @join__enumValue(graph: API_JOURNEYS_MODERN) +} + enum LanguageRole @join__type(graph: API_LANGUAGES) { publisher @join__enumValue(graph: API_LANGUAGES) } diff --git a/apis/api-journeys-modern/schema.graphql b/apis/api-journeys-modern/schema.graphql index 63f093f4ce7..63fccfb264a 100644 --- a/apis/api-journeys-modern/schema.graphql +++ b/apis/api-journeys-modern/schema.graphql @@ -1715,6 +1715,7 @@ type Query { journeySimpleGet(id: ID!): Json googleSheetsSyncs(filter: GoogleSheetsSyncsFilter!): [GoogleSheetsSync!]! integrationGooglePickerToken(integrationId: ID!): String! + getJourneyProfile: JourneyProfile """ Returns a CSV formatted string with journey visitor export data including headers and visitor data with event information @@ -1773,6 +1774,7 @@ type Query { """ status: [JourneyStatus!] ): [TemplateFamilyStatsBreakdownResponse!] + getUserRole: UserRole } type RadioOptionBlock implements Block diff --git a/apis/api-journeys-modern/src/schema/builder.ts b/apis/api-journeys-modern/src/schema/builder.ts index d0da16b5f8f..ab2a778dc0d 100644 --- a/apis/api-journeys-modern/src/schema/builder.ts +++ b/apis/api-journeys-modern/src/schema/builder.ts @@ -35,6 +35,7 @@ export const builder = new SchemaBuilder<{ AuthScopes: AuthScopes AuthContexts: { isAuthenticated: Extract + isAnonymous: Extract isInTeam: Extract isIntegrationOwner: Extract isTeamManager: Extract diff --git a/apis/api-journeys-modern/src/schema/journeyProfile/getJourneyProfile.query.spec.ts b/apis/api-journeys-modern/src/schema/journeyProfile/getJourneyProfile.query.spec.ts new file mode 100644 index 00000000000..b8b5bfd5153 --- /dev/null +++ b/apis/api-journeys-modern/src/schema/journeyProfile/getJourneyProfile.query.spec.ts @@ -0,0 +1,129 @@ +import { getUserFromPayload } from '@core/yoga/firebaseClient' + +import { getClient } from '../../../test/client' +import { prismaMock } from '../../../test/prismaMock' +import { graphql } from '../../lib/graphql/subgraphGraphql' + +jest.mock('@core/yoga/firebaseClient', () => ({ + getUserFromPayload: jest.fn() +})) + +const mockGetUserFromPayload = getUserFromPayload as jest.MockedFunction< + typeof getUserFromPayload +> + +describe('getJourneyProfile', () => { + const mockUser = { + id: 'userId', + email: 'test@example.com', + emailVerified: true, + firstName: 'Test', + lastName: 'User', + imageUrl: null, + roles: [] + } + + const authClient = getClient({ + headers: { authorization: 'token' }, + context: { currentUser: mockUser } + }) + + const GET_JOURNEY_PROFILE_QUERY = graphql(` + query GetJourneyProfile { + getJourneyProfile { + id + userId + acceptedTermsAt + lastActiveTeamId + journeyFlowBackButtonClicked + plausibleJourneyFlowViewed + plausibleDashboardViewed + } + } + `) + + beforeEach(() => { + jest.clearAllMocks() + mockGetUserFromPayload.mockReturnValue(mockUser) + prismaMock.userRole.findUnique.mockResolvedValue({ + id: 'userRoleId', + userId: mockUser.id, + roles: [] + }) + }) + + it('should return journey profile when user is authenticated', async () => { + const profile = { + id: 'profileId', + userId: 'userId', + acceptedTermsAt: new Date('2021-02-18T00:00:00.000Z'), + lastActiveTeamId: null, + journeyFlowBackButtonClicked: null, + plausibleJourneyFlowViewed: null, + plausibleDashboardViewed: null + } + + prismaMock.journeyProfile.findUnique.mockResolvedValue(profile) + + const result = await authClient({ + document: GET_JOURNEY_PROFILE_QUERY + }) + + expect(result).toEqual({ + data: { + getJourneyProfile: { + id: 'profileId', + userId: 'userId', + acceptedTermsAt: '2021-02-18T00:00:00.000Z', + lastActiveTeamId: null, + journeyFlowBackButtonClicked: null, + plausibleJourneyFlowViewed: null, + plausibleDashboardViewed: null + } + } + }) + + expect(prismaMock.journeyProfile.findUnique).toHaveBeenCalledWith( + expect.objectContaining({ + where: { userId: 'userId' } + }) + ) + }) + + it('should return null when profile does not exist', async () => { + prismaMock.journeyProfile.findUnique.mockResolvedValue(null) + + const result = await authClient({ + document: GET_JOURNEY_PROFILE_QUERY + }) + + expect(result).toEqual({ + data: { + getJourneyProfile: null + } + }) + }) + + it('should throw error when user is not authenticated', async () => { + mockGetUserFromPayload.mockReturnValue(null) + const unauthClient = getClient({ + headers: { authorization: 'token' }, + context: { currentUser: null } + }) + + const result = await unauthClient({ + document: GET_JOURNEY_PROFILE_QUERY + }) + + expect(result).toEqual({ + data: { + getJourneyProfile: null + }, + errors: [ + expect.objectContaining({ + message: expect.stringContaining('Not authorized') + }) + ] + }) + }) +}) diff --git a/apis/api-journeys-modern/src/schema/journeyProfile/getJourneyProfile.query.ts b/apis/api-journeys-modern/src/schema/journeyProfile/getJourneyProfile.query.ts new file mode 100644 index 00000000000..2e823775f2e --- /dev/null +++ b/apis/api-journeys-modern/src/schema/journeyProfile/getJourneyProfile.query.ts @@ -0,0 +1,18 @@ +import { prisma } from '@core/prisma/journeys/client' + +import { builder } from '../builder' + +import { JourneyProfileRef } from './journeyProfile' + +builder.queryField('getJourneyProfile', (t) => + t.withAuth({ isAuthenticated: true }).prismaField({ + type: JourneyProfileRef, + nullable: true, + resolve: async (query, _parent, _args, context) => { + return await prisma.journeyProfile.findUnique({ + ...query, + where: { userId: context.user.id } + }) + } + }) +) diff --git a/apis/api-journeys-modern/src/schema/journeyProfile/index.ts b/apis/api-journeys-modern/src/schema/journeyProfile/index.ts index ac0a9fa73d9..cde31650979 100644 --- a/apis/api-journeys-modern/src/schema/journeyProfile/index.ts +++ b/apis/api-journeys-modern/src/schema/journeyProfile/index.ts @@ -1,3 +1,4 @@ +import './getJourneyProfile.query' import './journeyProfile' import './inputs' diff --git a/apis/api-journeys-modern/src/schema/userRole/getUserRole.query.spec.ts b/apis/api-journeys-modern/src/schema/userRole/getUserRole.query.spec.ts new file mode 100644 index 00000000000..b94d2f90568 --- /dev/null +++ b/apis/api-journeys-modern/src/schema/userRole/getUserRole.query.spec.ts @@ -0,0 +1,154 @@ +import { getUserFromPayload } from '@core/yoga/firebaseClient' + +import { getClient } from '../../../test/client' +import { prismaMock } from '../../../test/prismaMock' +import { graphql } from '../../lib/graphql/subgraphGraphql' + +jest.mock('@core/yoga/firebaseClient', () => ({ + getUserFromPayload: jest.fn() +})) + +const mockGetUserFromPayload = getUserFromPayload as jest.MockedFunction< + typeof getUserFromPayload +> + +describe('getUserRole', () => { + const mockUser = { + id: 'userId', + email: 'test@example.com', + emailVerified: true, + firstName: 'Test', + lastName: 'User', + imageUrl: null, + roles: [] + } + + const authClient = getClient({ + headers: { authorization: 'token' }, + context: { currentUser: mockUser } + }) + + const GET_USER_ROLE_QUERY = graphql(` + query GetUserRole { + getUserRole { + id + userId + roles + } + } + `) + + beforeEach(() => { + jest.clearAllMocks() + mockGetUserFromPayload.mockReturnValue(mockUser) + prismaMock.userRole.findUnique.mockResolvedValue({ + id: 'userRoleId', + userId: mockUser.id, + roles: [] + }) + }) + + it('should return user role when user is authenticated', async () => { + const userRole = { + id: 'userRoleId', + userId: 'userId', + roles: [] + } + + prismaMock.userRole.upsert.mockResolvedValue(userRole) + + const result = await authClient({ + document: GET_USER_ROLE_QUERY + }) + + expect(result).toEqual({ + data: { + getUserRole: { + id: 'userRoleId', + userId: 'userId', + roles: [] + } + } + }) + + expect(prismaMock.userRole.upsert).toHaveBeenCalledWith({ + where: { userId: 'userId' }, + create: { userId: 'userId' }, + update: {} + }) + }) + + it('should return user role with roles', async () => { + const userRole = { + id: 'userRoleId', + userId: 'userId', + roles: ['publisher'] + } + + prismaMock.userRole.upsert.mockResolvedValue(userRole as any) + + const result = await authClient({ + document: GET_USER_ROLE_QUERY + }) + + expect(result).toEqual({ + data: { + getUserRole: { + id: 'userRoleId', + userId: 'userId', + roles: ['publisher'] + } + } + }) + }) + + it('should retry on unique constraint violation', async () => { + const userRole = { + id: 'userRoleId', + userId: 'userId', + roles: [] + } + + prismaMock.userRole.upsert.mockRejectedValueOnce({ code: 'P2002' }) + prismaMock.userRole.upsert.mockResolvedValue(userRole) + + const result = await authClient({ + document: GET_USER_ROLE_QUERY + }) + + expect(result).toEqual({ + data: { + getUserRole: { + id: 'userRoleId', + userId: 'userId', + roles: [] + } + } + }) + + expect(prismaMock.userRole.upsert).toHaveBeenCalledTimes(2) + }) + + it('should throw error when user is not authenticated', async () => { + mockGetUserFromPayload.mockReturnValue(null) + const unauthClient = getClient({ + headers: { authorization: 'token' }, + context: { currentUser: null } + }) + + const result = await unauthClient({ + document: GET_USER_ROLE_QUERY + }) + + expect(result).toEqual({ + data: { + getUserRole: null + }, + errors: [ + expect.objectContaining({ + message: expect.stringContaining('Not authorized') + }) + ] + }) + }) +}) diff --git a/apis/api-journeys-modern/src/schema/userRole/getUserRole.query.ts b/apis/api-journeys-modern/src/schema/userRole/getUserRole.query.ts new file mode 100644 index 00000000000..69d7e45d184 --- /dev/null +++ b/apis/api-journeys-modern/src/schema/userRole/getUserRole.query.ts @@ -0,0 +1,32 @@ +import { prisma } from '@core/prisma/journeys/client' + +import { builder } from '../builder' + +import { UserRoleRef } from './userRole' + +const ERROR_PSQL_UNIQUE_CONSTRAINT_VIOLATED = 'P2002' + +async function getUserRoleByUserId(userId: string) { + try { + return await prisma.userRole.upsert({ + where: { userId }, + create: { userId }, + update: {} + }) + } catch (err: any) { + if (err.code !== ERROR_PSQL_UNIQUE_CONSTRAINT_VIOLATED) { + throw err + } + } + return await getUserRoleByUserId(userId) +} + +builder.queryField('getUserRole', (t) => + t.withAuth({ $any: { isAuthenticated: true, isAnonymous: true } }).prismaField({ + type: UserRoleRef, + nullable: true, + resolve: async (query, _parent, _args, context) => { + return await getUserRoleByUserId(context.user.id) + } + }) +) diff --git a/apis/api-journeys-modern/src/schema/userRole/index.ts b/apis/api-journeys-modern/src/schema/userRole/index.ts index 5427355d269..d55ecc70a55 100644 --- a/apis/api-journeys-modern/src/schema/userRole/index.ts +++ b/apis/api-journeys-modern/src/schema/userRole/index.ts @@ -1 +1,3 @@ +import './getUserRole.query' + export { UserRoleRef } from './userRole' diff --git a/apis/api-journeys/db/seeds/playwrightUserAccess.ts b/apis/api-journeys/db/seeds/playwrightUserAccess.ts index 9e939a6c6d3..c3d664e312e 100644 --- a/apis/api-journeys/db/seeds/playwrightUserAccess.ts +++ b/apis/api-journeys/db/seeds/playwrightUserAccess.ts @@ -2,7 +2,6 @@ import { v4 as uuidv4 } from 'uuid' import { JourneyStatus, - Role, ThemeMode, ThemeName, UserTeamRole @@ -52,6 +51,10 @@ const PLAYWRIGHT_USER_DATA = [ } ] +enum Role { + publisher = 'publisher' +} + interface UserAccessData { journeyData: { id: string diff --git a/apis/api-journeys/schema.graphql b/apis/api-journeys/schema.graphql index 341ac8762b5..d1eae1d2d85 100644 --- a/apis/api-journeys/schema.graphql +++ b/apis/api-journeys/schema.graphql @@ -279,7 +279,6 @@ extend type Query { teams: [Team!]! team(id: ID!): Team! userInvites(journeyId: ID!): [UserInvite!] - getUserRole: UserRole userTeams(teamId: ID!, where: UserTeamFilterInput): [UserTeam!]! userTeam(id: ID!): UserTeam! userTeamInvites(teamId: ID!): [UserTeamInvite!]! @@ -2722,23 +2721,6 @@ extend type AuthenticatedUser id: ID! @external } -type UserRole - @key(fields: "id") - @shareable -{ - id: ID! - userId: ID! - roles: [Role!] -} - -enum Role { - """ - User can create templates and - add them to template library - """ - publisher -} - type AnonymousUser @shareable { @@ -3056,4 +3038,4 @@ type _Service { sdl: String } -union _Entity = AuthenticatedUser | Journey | JourneyProfile | JourneyVisitor | Language | ShortLink | Tag | Team | UserInvite | UserJourney | UserRole | UserTeam | Video | VideoBlock | Visitor +union _Entity = AuthenticatedUser | Journey | JourneyProfile | JourneyVisitor | Language | ShortLink | Tag | Team | UserInvite | UserJourney | UserTeam | Video | VideoBlock | Visitor diff --git a/apis/api-journeys/src/__generated__/graphql.ts b/apis/api-journeys/src/__generated__/graphql.ts index 730e3d7f362..c536beb7e19 100644 --- a/apis/api-journeys/src/__generated__/graphql.ts +++ b/apis/api-journeys/src/__generated__/graphql.ts @@ -4420,10 +4420,6 @@ export enum RedirectType { } export enum Role { - /** - * User can create templates and - * add them to template library - */ Publisher = 'publisher' } diff --git a/apis/api-journeys/src/app/__generated__/graphql.ts b/apis/api-journeys/src/app/__generated__/graphql.ts index 715f77db41d..3bfdb3b31d6 100644 --- a/apis/api-journeys/src/app/__generated__/graphql.ts +++ b/apis/api-journeys/src/app/__generated__/graphql.ts @@ -272,10 +272,6 @@ export enum UserJourneyRole { owner = "owner" } -export enum Role { - publisher = "publisher" -} - export enum UserTeamRole { manager = "manager", member = "member" @@ -1019,8 +1015,6 @@ export abstract class IQuery { abstract journeyEventsCount(journeyId: string, filter?: Nullable): number | Promise; - abstract getJourneyProfile(): Nullable | Promise>; - abstract journeyTheme(journeyId: string): Nullable | Promise>; abstract journeyVisitorsConnection(filter: JourneyVisitorFilter, first?: Nullable, after?: Nullable, sort?: Nullable): JourneyVisitorsConnection | Promise; @@ -1039,8 +1033,6 @@ export abstract class IQuery { abstract userInvites(journeyId: string): Nullable | Promise>; - abstract getUserRole(): Nullable | Promise>; - abstract userTeams(teamId: string, where?: Nullable): UserTeam[] | Promise; abstract userTeam(id: string): UserTeam | Promise; @@ -1925,13 +1917,6 @@ export class UserInvite { removedAt?: Nullable; } -export class UserRole { - __typename?: 'UserRole'; - id: string; - userId: string; - roles?: Nullable; -} - export class AnonymousUser { __typename?: 'AnonymousUser'; id: string; diff --git a/apis/api-journeys/src/app/app.module.ts b/apis/api-journeys/src/app/app.module.ts index 04609c0b357..63bfb3c6c10 100644 --- a/apis/api-journeys/src/app/app.module.ts +++ b/apis/api-journeys/src/app/app.module.ts @@ -33,7 +33,6 @@ import { QrCodeModule } from './modules/qrCode/qrCode.module' import { TeamModule } from './modules/team/team.module' import { UserInviteModule } from './modules/userInvite/userInvite.module' import { UserJourneyModule } from './modules/userJourney/userJourney.module' -import { UserRoleModule } from './modules/userRole/userRole.module' import { UserTeamModule } from './modules/userTeam/userTeam.module' import { UserTeamInviteModule } from './modules/userTeamInvite/userTeamInvite.module' import { VisitorModule } from './modules/visitor/visitor.module' @@ -62,7 +61,6 @@ import { VisitorModule } from './modules/visitor/visitor.module' TeamModule, UserJourneyModule, UserInviteModule, - UserRoleModule, UserTeamModule, UserTeamInviteModule, VisitorModule, diff --git a/apis/api-journeys/src/app/modules/journeyProfile/journeyProfile.graphql b/apis/api-journeys/src/app/modules/journeyProfile/journeyProfile.graphql index b0a21fc8854..0cca5290db3 100644 --- a/apis/api-journeys/src/app/modules/journeyProfile/journeyProfile.graphql +++ b/apis/api-journeys/src/app/modules/journeyProfile/journeyProfile.graphql @@ -8,10 +8,6 @@ type JourneyProfile @key(fields: "id") @shareable { plausibleDashboardViewed: Boolean } -extend type Query { - getJourneyProfile: JourneyProfile -} - input JourneyProfileUpdateInput { lastActiveTeamId: String journeyFlowBackButtonClicked: Boolean diff --git a/apis/api-journeys/src/app/modules/journeyProfile/journeyProfile.resolver.ts b/apis/api-journeys/src/app/modules/journeyProfile/journeyProfile.resolver.ts index 71da6d56140..38a3d8e9bb4 100644 --- a/apis/api-journeys/src/app/modules/journeyProfile/journeyProfile.resolver.ts +++ b/apis/api-journeys/src/app/modules/journeyProfile/journeyProfile.resolver.ts @@ -18,11 +18,7 @@ export class JourneyProfileResolver { private readonly mailChimpService: MailChimpService ) {} - @Query() - @UseGuards(AppCaslGuard) - async getJourneyProfile( - @CurrentUserId() userId: string - ): Promise { + async getJourneyProfile(userId: string): Promise { return await this.prismaService.journeyProfile.findUnique({ where: { userId } }) diff --git a/apis/api-journeys/src/app/modules/userRole/userRole.graphql b/apis/api-journeys/src/app/modules/userRole/userRole.graphql deleted file mode 100644 index e9e438c98d3..00000000000 --- a/apis/api-journeys/src/app/modules/userRole/userRole.graphql +++ /dev/null @@ -1,17 +0,0 @@ -type UserRole @key(fields: "id") @shareable { - id: ID! - userId: ID! - roles: [Role!] -} - -enum Role { - """ - User can create templates and - add them to template library - """ - publisher -} - -extend type Query { - getUserRole: UserRole -} diff --git a/apis/api-journeys/src/app/modules/userRole/userRole.module.ts b/apis/api-journeys/src/app/modules/userRole/userRole.module.ts deleted file mode 100644 index c42dfdc28ba..00000000000 --- a/apis/api-journeys/src/app/modules/userRole/userRole.module.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Global, Module } from '@nestjs/common' - -import { PrismaService } from '../../lib/prisma.service' - -import { UserRoleResolver } from './userRole.resolver' -import { UserRoleService } from './userRole.service' - -@Global() -@Module({ - imports: [], - providers: [UserRoleService, UserRoleResolver, PrismaService], - exports: [UserRoleService] -}) -export class UserRoleModule {} diff --git a/apis/api-journeys/src/app/modules/userRole/userRole.resolver.spec.ts b/apis/api-journeys/src/app/modules/userRole/userRole.resolver.spec.ts deleted file mode 100644 index 57c685a309f..00000000000 --- a/apis/api-journeys/src/app/modules/userRole/userRole.resolver.spec.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing' - -import { Role } from '../../__generated__/graphql' - -import { UserRoleResolver } from './userRole.resolver' -import { UserRoleService } from './userRole.service' - -describe('UserRoleResolver', () => { - let resolver: UserRoleResolver - - it('should return user', async () => { - const user = { - id: '1', - userId: 'userId' - } - - const userRoleService = { - provide: UserRoleService, - useFactory: () => ({ - getUserRoleById: jest.fn(() => user) - }) - } - - const module: TestingModule = await Test.createTestingModule({ - providers: [UserRoleResolver, userRoleService] - }).compile() - resolver = module.get(UserRoleResolver) - - expect(await resolver.getUserRole('userId')).toEqual(user) - }) - - it('should return user with roles', async () => { - const user = { - id: '1', - userId: 'userId', - roles: [Role.publisher] - } - - const userRoleService = { - provide: UserRoleService, - useFactory: () => ({ - getUserRoleById: jest.fn(() => user) - }) - } - - const module: TestingModule = await Test.createTestingModule({ - providers: [UserRoleResolver, userRoleService] - }).compile() - resolver = module.get(UserRoleResolver) - - expect(await resolver.getUserRole('userId')).toEqual(user) - }) -}) diff --git a/apis/api-journeys/src/app/modules/userRole/userRole.resolver.ts b/apis/api-journeys/src/app/modules/userRole/userRole.resolver.ts deleted file mode 100644 index a774ba7e1dd..00000000000 --- a/apis/api-journeys/src/app/modules/userRole/userRole.resolver.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { UseGuards } from '@nestjs/common' -import { Query, Resolver } from '@nestjs/graphql' - -import { UserRole } from '@core/prisma/journeys/client' - -import { CurrentUserId } from '../../lib/decorators/CurrentUserId' -import { GqlAuthGuard } from '../../lib/GqlAuthGuard' - -import { UserRoleService } from './userRole.service' - -@Resolver('UserRole') -export class UserRoleResolver { - constructor(private readonly userRoleService: UserRoleService) {} - - @Query() - @UseGuards(GqlAuthGuard) - async getUserRole(@CurrentUserId() userId: string): Promise { - return await this.userRoleService.getUserRoleById(userId) - } -} diff --git a/apis/api-journeys/src/app/modules/userRole/userRole.service.spec.ts b/apis/api-journeys/src/app/modules/userRole/userRole.service.spec.ts deleted file mode 100644 index 316a1db6ddd..00000000000 --- a/apis/api-journeys/src/app/modules/userRole/userRole.service.spec.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing' -import { DeepMockProxy, mockDeep } from 'jest-mock-extended' - -import { Role } from '../../__generated__/graphql' -import { PrismaService } from '../../lib/prisma.service' -import { ERROR_PSQL_UNIQUE_CONSTRAINT_VIOLATED } from '../../lib/prismaErrors' - -import { UserRoleService } from './userRole.service' - -describe('userRoleService', () => { - let service: UserRoleService, prismaService: DeepMockProxy - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [ - UserRoleService, - { - provide: PrismaService, - useValue: mockDeep() - } - ] - }).compile() - - service = module.get(UserRoleService) - prismaService = module.get( - PrismaService - ) as DeepMockProxy - }) - - afterAll(() => { - jest.resetAllMocks() - }) - - const user = { - id: '1', - userId: 'userId', - roles: [Role.publisher] - } - - describe('getUserRoleById', () => { - it('should return a user role if exists', async () => { - prismaService.userRole.upsert.mockResolvedValue(user) - expect(await service.getUserRoleById('1')).toEqual(user) - expect(prismaService.userRole.upsert).toHaveBeenCalledWith({ - where: { userId: '1' }, - update: {}, - create: { userId: '1' } - }) - }) - - it('should retry if error', async () => { - prismaService.userRole.upsert.mockRejectedValueOnce({ - code: ERROR_PSQL_UNIQUE_CONSTRAINT_VIOLATED - }) - prismaService.userRole.upsert.mockResolvedValue(user) - expect(await service.getUserRoleById('1')).toEqual(user) - expect(prismaService.userRole.upsert).toHaveBeenCalledWith({ - where: { userId: '1' }, - update: {}, - create: { userId: '1' } - }) - }) - }) -}) diff --git a/apis/api-journeys/src/app/modules/userRole/userRole.service.ts b/apis/api-journeys/src/app/modules/userRole/userRole.service.ts deleted file mode 100644 index ff7e5320dfd..00000000000 --- a/apis/api-journeys/src/app/modules/userRole/userRole.service.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Injectable } from '@nestjs/common' - -import { UserRole } from '@core/prisma/journeys/client' - -import { PrismaService } from '../../lib/prisma.service' -import { ERROR_PSQL_UNIQUE_CONSTRAINT_VIOLATED } from '../../lib/prismaErrors' - -@Injectable() -export class UserRoleService { - constructor(private readonly prismaService: PrismaService) {} - - async getUserRoleById(userId: string): Promise { - try { - return await this.prismaService.userRole.upsert({ - where: { userId }, - create: { userId }, - update: {} - }) - } catch (err) { - if (err.code !== ERROR_PSQL_UNIQUE_CONSTRAINT_VIOLATED) { - throw err - } - } - return await this.getUserRoleById(userId) - } -} diff --git a/apis/api-users/src/schema/user/findOrFetchUser.ts b/apis/api-users/src/schema/user/findOrFetchUser.ts index 339e29c40b8..38d5d656395 100644 --- a/apis/api-users/src/schema/user/findOrFetchUser.ts +++ b/apis/api-users/src/schema/user/findOrFetchUser.ts @@ -17,7 +17,7 @@ export async function findOrFetchUser( if (existingUser != null && existingUser.emailVerified == null) { const user = await prisma.user.update({ where: { - id: userId + userId }, data: { emailVerified: false @@ -70,26 +70,30 @@ export async function findOrFetchUser( } let user: User | null = null - let retry = 0 let userCreated = false - // this function can run in parallel as such it is possible for multiple - // calls to reach this point and try to create the same user - // due to the earlier firebase async call. + // This function can run in parallel; multiple calls may try to create the same user. try { user = await prisma.user.create({ data }) userCreated = true - } catch (e) { - do { - user = await prisma.user.update({ - where: { - id: userId - }, + } catch { + // Create failed (e.g. P2002 unique constraint from concurrent request). Use existing row or retry create once. + user = await prisma.user.findUnique({ + where: { userId } + }) + if (user == null) { + try { + user = await prisma.user.create({ data }) - retry++ - } while (user == null && retry < 3) + userCreated = true + } catch { + user = await prisma.user.findUnique({ + where: { userId } + }) + } + } } // after user create so it is only sent once if (email != null && userCreated && !emailVerified) diff --git a/apis/api-users/src/schema/user/user.ts b/apis/api-users/src/schema/user/user.ts index a0077b095fd..9923abfa7e3 100644 --- a/apis/api-users/src/schema/user/user.ts +++ b/apis/api-users/src/schema/user/user.ts @@ -15,9 +15,8 @@ builder.asEntity(AuthenticatedUser, { key: builder.selection<{ id: string }>('id'), resolveReference: async ({ id }) => { try { - const user = await prisma.user.findUnique({ where: { userId: id } }) + const user = await findOrFetchUser({}, id, undefined) - // Handle cases where user doesn't exist if (user == null) { console.warn(`Federation: User not found for userId: ${id}`) return null diff --git a/apps/journeys-admin/pages/templates/[journeyId]/customize.tsx b/apps/journeys-admin/pages/templates/[journeyId]/customize.tsx index ca099c44110..e8ef22bbf54 100644 --- a/apps/journeys-admin/pages/templates/[journeyId]/customize.tsx +++ b/apps/journeys-admin/pages/templates/[journeyId]/customize.tsx @@ -111,5 +111,5 @@ export const getServerSideProps = withUserTokenSSR()(async ({ export default withUser({ // TODO: remove this after anon user is implemented - whenUnauthedBeforeInit: AuthAction.REDIRECT_TO_LOGIN + // whenUnauthedBeforeInit: AuthAction.REDIRECT_TO_LOGIN })(CustomizePage) diff --git a/apps/journeys-admin/src/components/TemplateCustomization/MultiStepForm/Screens/LanguageScreen/LanguageScreen.tsx b/apps/journeys-admin/src/components/TemplateCustomization/MultiStepForm/Screens/LanguageScreen/LanguageScreen.tsx index ce20b29bad7..68fce0427f3 100644 --- a/apps/journeys-admin/src/components/TemplateCustomization/MultiStepForm/Screens/LanguageScreen/LanguageScreen.tsx +++ b/apps/journeys-admin/src/components/TemplateCustomization/MultiStepForm/Screens/LanguageScreen/LanguageScreen.tsx @@ -1,12 +1,15 @@ +import { gql, useMutation } from '@apollo/client' import FormControl from '@mui/material/FormControl' import Stack from '@mui/material/Stack' import Typography from '@mui/material/Typography' +import { getApp } from 'firebase/app' +import { getAuth, signInAnonymously } from 'firebase/auth' import { Form, Formik, FormikValues } from 'formik' import { useRouter } from 'next/router' import { useUser } from 'next-firebase-auth' import { useTranslation } from 'next-i18next' import { useSnackbar } from 'notistack' -import { ReactElement, useState } from 'react' +import { ReactElement, useEffect, useState } from 'react' import { object, string } from 'yup' import { useJourney } from '@core/journeys/ui/JourneyProvider' @@ -15,13 +18,29 @@ import { SocialImage } from '@core/journeys/ui/TemplateView/TemplateViewHeader/S import { useJourneyDuplicateMutation } from '@core/journeys/ui/useJourneyDuplicateMutation' import { LanguageAutocomplete } from '@core/shared/ui/LanguageAutocomplete' +import { JourneyProfileCreate } from '../../../../../../__generated__/JourneyProfileCreate' +import { useCurrentUserLazyQuery } from '../../../../../libs/useCurrentUserLazyQuery' import { useGetChildTemplateJourneyLanguages } from '../../../../../libs/useGetChildTemplateJourneyLanguages' import { useGetParentTemplateJourneyLanguages } from '../../../../../libs/useGetParentTemplateJourneyLanguages' +import { + useTeamCreateMutation, + useTeamCreateMutationGuest +} from '../../../../../libs/useTeamCreateMutation' import { CustomizationScreen } from '../../../utils/getCustomizeFlowConfig' import { CustomizeFlowNextButton } from '../../CustomizeFlowNextButton' import { JourneyCustomizeTeamSelect } from './JourneyCustomizeTeamSelect' +// const JOURNEY_PROFILE_CREATE = gql` +// mutation JourneyProfileCreate { +// journeyProfileCreate { +// id +// userId +// acceptedTermsAt +// } +// } +// ` + interface LanguageScreenProps { handleNext: () => void handleScreenNavigation: (screen: CustomizationScreen) => void @@ -42,6 +61,14 @@ export function LanguageScreen({ const isSignedIn = user?.email != null && user?.id != null const { query } = useTeam() + useEffect(() => { + //TODO: delete this effect + const firebaseUserId = user?.id ?? null + const isAnonymous = user?.firebaseUser?.isAnonymous ?? false + console.log('[LanguageScreen] Firebase user id:', firebaseUserId) + console.log('[LanguageScreen] Is anonymous user:', isAnonymous) + }, [user?.id, user?.firebaseUser?.isAnonymous]) + const isParentTemplate = journey?.fromTemplateId == null const { @@ -94,7 +121,7 @@ export function LanguageScreen({ } const validationSchema = object({ - teamSelect: string().required() + teamSelect: isSignedIn ? string().required() : string() }) const initialValues = { @@ -107,44 +134,211 @@ export function LanguageScreen({ } const [journeyDuplicate] = useJourneyDuplicateMutation() + const { loadUser } = useCurrentUserLazyQuery() + // const [journeyProfileCreate] = useMutation( + // JOURNEY_PROFILE_CREATE + // ) + const [teamCreate] = useTeamCreateMutation() + const [teamCreateGuest] = useTeamCreateMutationGuest() const FORM_SM_BREAKPOINT_WIDTH = '390px' + async function createGuestUser(): Promise<{ teamId: string } | null> { + try { + console.log('[createGuestUser] 1. start', { + isAnonymous: user?.firebaseUser?.isAnonymous ?? false + }) + const isAnonymous = user?.firebaseUser?.isAnonymous ?? false + if (!isAnonymous) { + console.log('[createGuestUser] 2. calling signInAnonymously') + await signInAnonymously(getAuth(getApp())) + console.log('[createGuestUser] 3. signInAnonymously done') + } else { + console.log('[createGuestUser] 2. already anonymous, skip signInAnonymously') + } + + const teamName = t('My Team') + + let meResult: Awaited> | null = null + try { + console.log('[createGuestUser] 4. calling loadUser') + meResult = await loadUser() + console.log('[createGuestUser] 5. loadUser done', { + hasMe: meResult?.data?.me != null, + __typename: meResult?.data?.me?.__typename + }) + } catch (e) { + console.error('[createGuestUser] loadUser failed:', e) + } + + // let profileResult: Awaited> | null = + // null + // try { + // profileResult = await journeyProfileCreate() + // } catch (e) { + // console.error('[createGuestUser] journeyProfileCreate failed:', e) + // } + + let teamResult: Awaited> | null = null + try { + console.log('[createGuestUser] 6. calling teamCreate', { teamName }) + teamResult = await teamCreateGuest({ + variables: { + input: { title: teamName, publicTitle: teamName } + } + }) + console.log('[createGuestUser] 7. teamCreate done', { + teamId: teamResult?.data?.teamCreate?.id + }) + } catch (e) { + const err = e as { + graphQLErrors?: Array<{ message: string; extensions?: unknown }> + networkError?: unknown + message?: string + cause?: unknown + } + console.error('[createGuestUser] teamCreate failed:', err?.message ?? e) + if (err?.graphQLErrors?.length) { + err.graphQLErrors.forEach((g, i) => { + console.error( + `[createGuestUser] graphQLErrors[${i}]:`, + g.message, + g.extensions + ) + }) + } + if (err?.networkError) { + console.error('[createGuestUser] networkError:', err.networkError) + } + if (err?.cause != null) { + console.error('[createGuestUser] cause:', err.cause) + } + return null + } + + if (teamResult?.data?.teamCreate == null) { + console.log('[createGuestUser] 8. returning null (team missing)', { + hasMe: meResult?.data?.me != null, + hasTeam: false + }) + return null + } + + // Refetch me after teamCreate (user was created in resolveReference); optional for guest flow + if (meResult?.data?.me == null) { + try { + await loadUser() + } catch { + // ignore + } + } + + // Guest flow success: we have a team (user exists in api-users via resolveReference) + console.log('[createGuestUser] 9. success', { + teamId: teamResult.data.teamCreate.id + }) + return { teamId: teamResult.data.teamCreate.id } + } catch (e) { + console.error('[createGuestUser] unexpected error:', e) + return null + } + } + + async function duplicateJourneyAndRedirect( + journeyId: string, + teamId: string + ): Promise { + const { data } = await journeyDuplicate({ + variables: { id: journeyId, teamId, forceNonTemplate: true } + }) + if (data?.journeyDuplicate == null) return false + + await router.push( + `/templates/${data.journeyDuplicate.id}/customize`, + undefined, + { shallow: true } + ) + return true + } + async function handleSubmit(values: FormikValues) { setLoading(true) if (journey == null) { setLoading(false) return } + + const journeyId = + languagesJourneyMap?.[values.languageSelect?.id] ?? journey?.id + if (journeyId == null) { + enqueueSnackbar( + t('Unable to continue as guest. Please try again or sign in.'), + { variant: 'error' } + ) + setLoading(false) + return + } + if (isSignedIn) { - const { teamSelect: teamId } = values - const { - languageSelect: { id: languageId } - } = values - const journeyId = languagesJourneyMap?.[languageId] ?? journey.id - const { data: duplicateData } = await journeyDuplicate({ - variables: { id: journeyId, teamId, forceNonTemplate: true } - }) - if (duplicateData?.journeyDuplicate == null) { + const teamId = values.teamSelect as string + const success = await duplicateJourneyAndRedirect(journeyId, teamId) + if (!success) { enqueueSnackbar( t( 'Failed to duplicate journey to team, please refresh the page and try again' ), - { - variant: 'error' + { variant: 'error' } + ) + } else { + handleNext() + } + setLoading(false) + return + } else { + try { + const guestResult = await createGuestUser() + if (guestResult == null) { + enqueueSnackbar( + t('Unable to continue as guest. Please try again or sign in.'), + { variant: 'error' } + ) + setLoading(false) + return + } + + try { + const success = await duplicateJourneyAndRedirect( + journeyId, + guestResult.teamId + ) + if (!success) { + enqueueSnackbar( + t( + 'Failed to duplicate journey to team, please refresh the page and try again' + ), + { variant: 'error' } + ) + } else { + handleNext() } + } catch (e) { + console.error('[LanguageScreen] duplicateJourneyAndRedirect error:', e) + enqueueSnackbar( + t( + 'Failed to duplicate journey to team, please refresh the page and try again' + ), + { variant: 'error' } + ) + } + } catch (e) { + console.error('[LanguageScreen] createGuestUser error:', e) + enqueueSnackbar( + t('Unable to continue as guest. Please try again or sign in.'), + { variant: 'error' } ) + } finally { setLoading(false) - - return } - await router.push( - `/templates/${duplicateData.journeyDuplicate.id}/customize`, - undefined, - { shallow: true } - ) - handleNext() - setLoading(false) } } diff --git a/apps/journeys-admin/src/libs/useTeamCreateMutation/index.ts b/apps/journeys-admin/src/libs/useTeamCreateMutation/index.ts index 38ddb0db05b..01b7632c399 100644 --- a/apps/journeys-admin/src/libs/useTeamCreateMutation/index.ts +++ b/apps/journeys-admin/src/libs/useTeamCreateMutation/index.ts @@ -1 +1,4 @@ -export { useTeamCreateMutation } from './useTeamCreateMutation' +export { + useTeamCreateMutation, + useTeamCreateMutationGuest +} from './useTeamCreateMutation' diff --git a/apps/journeys-admin/src/libs/useTeamCreateMutation/useTeamCreateMutation.tsx b/apps/journeys-admin/src/libs/useTeamCreateMutation/useTeamCreateMutation.tsx index fb2e66796ec..8a27f4257fa 100644 --- a/apps/journeys-admin/src/libs/useTeamCreateMutation/useTeamCreateMutation.tsx +++ b/apps/journeys-admin/src/libs/useTeamCreateMutation/useTeamCreateMutation.tsx @@ -42,6 +42,33 @@ export const TEAM_CREATE = gql` } ` +/** Minimal selection for guest flow: only user.id to avoid User entity field resolution errors in federation. */ +export const TEAM_CREATE_GUEST = gql` + mutation TeamCreateGuest($input: TeamCreateInput!) { + teamCreate(input: $input) { + id + title + publicTitle + userTeams { + id + user { + ... on AuthenticatedUser { + id + } + ... on AnonymousUser { + id + } + } + role + } + customDomains { + id + name + } + } + } +` + export function useTeamCreateMutation( options?: MutationHookOptions ): MutationTuple { @@ -76,3 +103,37 @@ export function useTeamCreateMutation( return mutation } + +/** Use for guest flow: only requests user.id to avoid federation User field resolution errors. */ +export function useTeamCreateMutationGuest( + options?: MutationHookOptions +): MutationTuple { + const { setActiveTeam } = useTeam() + return useMutation(TEAM_CREATE_GUEST, { + update(cache, { data }) { + if (data?.teamCreate != null) { + cache.modify({ + fields: { + teams(existingTeams = []) { + const newTeamRef = cache.writeFragment({ + data: data.teamCreate, + fragment: gql` + fragment NewTeam on Team { + id + } + ` + }) + return [...existingTeams, newTeamRef] + } + } + }) + } + }, + onCompleted(data) { + if (data?.teamCreate != null) { + setActiveTeam(data.teamCreate) + } + }, + ...options + }) +} From da5cfa25dc48ceaa01368bae860cf9be00201086 Mon Sep 17 00:00:00 2001 From: Mike Allison Date: Fri, 6 Feb 2026 22:29:14 +0000 Subject: [PATCH 16/18] refactor: move getJourneyProfile query to modern journeys schema - Removed getJourneyProfile query from the legacy journeys schema to streamline the API. - Added getJourneyProfile query to the modern journeys schema, ensuring consistency in data retrieval across different schemas. --- apis/api-gateway/schema.graphql | 2 +- apis/api-journeys/schema.graphql | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/apis/api-gateway/schema.graphql b/apis/api-gateway/schema.graphql index de970a39123..8e010e3065c 100644 --- a/apis/api-gateway/schema.graphql +++ b/apis/api-gateway/schema.graphql @@ -734,7 +734,6 @@ type Query @join__type(graph: API_ANALYTICS) @join__type(graph: API_JOURNEYS) journeyCollections(teamId: ID!) : [JourneyCollection]! @join__field(graph: API_JOURNEYS) journeyEventsConnection(journeyId: ID!, filter: JourneyEventsFilter, first: Int, after: String) : JourneyEventsConnection! @join__field(graph: API_JOURNEYS) journeyEventsCount(journeyId: ID!, filter: JourneyEventsFilter) : Int! @join__field(graph: API_JOURNEYS) - getJourneyProfile: JourneyProfile @join__field(graph: API_JOURNEYS) journeyTheme(journeyId: ID!) : JourneyTheme @join__field(graph: API_JOURNEYS) """ Get a list of Visitor Information by Journey @@ -796,6 +795,7 @@ type Query @join__type(graph: API_ANALYTICS) @join__type(graph: API_JOURNEYS) journeySimpleGet(id: ID!) : Json @join__field(graph: API_JOURNEYS_MODERN) googleSheetsSyncs(filter: GoogleSheetsSyncsFilter!) : [GoogleSheetsSync!]! @join__field(graph: API_JOURNEYS_MODERN) integrationGooglePickerToken(integrationId: ID!) : String! @join__field(graph: API_JOURNEYS_MODERN) + getJourneyProfile: JourneyProfile @join__field(graph: API_JOURNEYS_MODERN) """ Returns a CSV formatted string with journey visitor export data including headers and visitor data with event information """ diff --git a/apis/api-journeys/schema.graphql b/apis/api-journeys/schema.graphql index d1eae1d2d85..f9e550079a8 100644 --- a/apis/api-journeys/schema.graphql +++ b/apis/api-journeys/schema.graphql @@ -253,7 +253,6 @@ extend type Query { journeyCollections(teamId: ID!): [JourneyCollection]! journeyEventsConnection(journeyId: ID!, filter: JourneyEventsFilter, first: Int, after: String): JourneyEventsConnection! journeyEventsCount(journeyId: ID!, filter: JourneyEventsFilter): Int! - getJourneyProfile: JourneyProfile journeyTheme(journeyId: ID!): JourneyTheme """Get a list of Visitor Information by Journey""" From 09b28adc81ef6b8e26e48a3df894912c6fb000ec Mon Sep 17 00:00:00 2001 From: Mike Allison Date: Fri, 6 Feb 2026 22:38:29 +0000 Subject: [PATCH 17/18] feat: add quickStartTemplate seed to database seeding process - Included quickStartTemplate in the main seeding function to enhance initial data setup. - This addition supports improved onboarding and user experience by providing a quick start option. --- apis/api-journeys/db/seed.ts | 2 + .../db/seeds/quickStartTemplate.ts | 140 ++++++++++++++++++ 2 files changed, 142 insertions(+) create mode 100644 apis/api-journeys/db/seeds/quickStartTemplate.ts diff --git a/apis/api-journeys/db/seed.ts b/apis/api-journeys/db/seed.ts index 04d92b1c8e8..7c05c89a70a 100644 --- a/apis/api-journeys/db/seed.ts +++ b/apis/api-journeys/db/seed.ts @@ -10,6 +10,7 @@ import { nua9 } from './seeds/nua9' import { onboarding } from './seeds/onboarding' import { onboardingTemplates } from './seeds/onboardingTemplates' import { playwrightUserAccess } from './seeds/playwrightUserAccess' +import { quickStartTemplate } from './seeds/quickStartTemplate' async function main(): Promise { // this should be removed when the UI can support team management @@ -21,6 +22,7 @@ async function main(): Promise { await nua1() await onboarding() await onboardingTemplates() + await quickStartTemplate() await playwrightUserAccess() await formBlocksDelete() diff --git a/apis/api-journeys/db/seeds/quickStartTemplate.ts b/apis/api-journeys/db/seeds/quickStartTemplate.ts new file mode 100644 index 00000000000..2f5bdbbb03d --- /dev/null +++ b/apis/api-journeys/db/seeds/quickStartTemplate.ts @@ -0,0 +1,140 @@ +import { PrismaClient } from '.prisma/api-journeys-client' + +import { + JourneyStatus, + ThemeMode, + ThemeName +} from '../../src/app/__generated__/graphql' + +const prisma = new PrismaClient() + +const QUICK_START_TEMPLATE = { + id: 'b4a4e122-2b7f-4e6f-a2d1-81c1f792c92b', + slug: 'quick-start-template' +} + +const CUSTOMIZATION_DESCRIPTION = + 'Hi {{ name }}, welcome to your journey! We are glad you are here.' + +export async function quickStartTemplate(action?: 'reset'): Promise { + if (action === 'reset') { + const existingJourney = await prisma.journey.findUnique({ + where: { slug: QUICK_START_TEMPLATE.slug } + }) + if (existingJourney != null) { + await prisma.journey.delete({ where: { id: existingJourney.id } }) + } + } + + const existingJourney = await prisma.journey.findUnique({ + where: { slug: QUICK_START_TEMPLATE.slug } + }) + if (existingJourney != null) return + + const journey = await prisma.journey.create({ + data: { + id: QUICK_START_TEMPLATE.id, + title: 'Quick Start', + description: 'A customizable quick start template to get you going.', + languageId: '529', + themeMode: ThemeMode.dark, + themeName: ThemeName.base, + slug: QUICK_START_TEMPLATE.slug, + status: JourneyStatus.published, + template: true, + createdAt: new Date(), + publishedAt: new Date(), + teamId: 'jfp-team', + journeyCustomizationDescription: CUSTOMIZATION_DESCRIPTION + } + }) + + // Create customization fields parsed from the description + await prisma.journeyCustomizationField.create({ + data: { + journeyId: journey.id, + key: 'name', + value: null, + defaultValue: null + } + }) + + // Primary image + const primaryImageBlock = await prisma.block.create({ + data: { + journeyId: journey.id, + typename: 'ImageBlock', + src: 'https://imagedelivery.net/tMY86qEHFACTO8_0kAeRFA/e8692352-21c7-4f66-cb57-0298e86a3300/public', + alt: 'quick start primary', + width: 1152, + height: 768, + blurhash: 'UE9Qmr%MIpWCtmbH%Mxu_4xuWYoL-;oIWYt7', + parentOrder: 1 + } + }) + await prisma.journey.update({ + where: { id: journey.id }, + data: { primaryImageBlockId: primaryImageBlock.id } + }) + + // Step block + const step = await prisma.block.create({ + data: { + journeyId: journey.id, + typename: 'StepBlock', + locked: false, + parentOrder: 0 + } + }) + + // Card block + const card = await prisma.block.create({ + data: { + journeyId: journey.id, + typename: 'CardBlock', + parentBlockId: step.id, + fullscreen: false, + parentOrder: 0 + } + }) + + // Cover image for the card + const coverBlock = await prisma.block.create({ + data: { + journeyId: journey.id, + typename: 'ImageBlock', + src: 'https://imagedelivery.net/tMY86qEHFACTO8_0kAeRFA/ae95a856-1401-41e1-6f3e-7b4e6f707f00/public', + alt: 'quick start card cover', + width: 1152, + height: 768, + blurhash: 'UbLX6?~p9FtRkX.8ogD%IUj@M{adxaM_ofkW', + parentBlockId: card.id + } + }) + await prisma.block.update({ + where: { id: card.id }, + data: { coverBlockId: coverBlock.id } + }) + + // Typography blocks with customization placeholder + await prisma.block.createMany({ + data: [ + { + journeyId: journey.id, + typename: 'TypographyBlock', + parentBlockId: card.id, + content: 'Welcome, {{ name }}!', + variant: 'h3', + parentOrder: 0 + }, + { + journeyId: journey.id, + typename: 'TypographyBlock', + parentBlockId: card.id, + content: 'Start your journey here and explore what is possible.', + variant: 'body1', + parentOrder: 1 + } + ] + }) +} From a4014b85b1ea7124ac96f7652c25a645402dc0c1 Mon Sep 17 00:00:00 2001 From: Mike Allison Date: Sat, 7 Feb 2026 02:58:37 +0000 Subject: [PATCH 18/18] refactor: migrate adminJourneys query to modern journeys schema - Removed the adminJourneys query from the legacy journeys schema to streamline the API. - Added the adminJourneys query to the modern journeys schema, ensuring consistency in data retrieval. - Updated related resolver and generated TypeScript files to reflect the changes, enhancing maintainability. --- apis/api-gateway/schema.graphql | 21 +- apis/api-journeys-modern/schema.graphql | 1 + .../src/schema/authScopes.ts | 3 + .../journey/adminJourneys.query.spec.ts | 252 ++++++++++++++++++ .../src/schema/journey/adminJourneys.query.ts | 124 +++++++++ .../src/schema/journey/index.ts | 1 + apis/api-journeys/schema.graphql | 14 - .../src/app/__generated__/graphql.ts | 2 - .../src/app/modules/journey/journey.graphql | 15 -- .../modules/journey/journey.resolver.spec.ts | 150 ----------- .../app/modules/journey/journey.resolver.ts | 50 ---- .../LanguageScreen/LanguageScreen.spec.tsx | 20 -- .../Screens/LanguageScreen/LanguageScreen.tsx | 37 ++- .../src/libs/initAndAuthApp/initAndAuthApp.ts | 5 +- 14 files changed, 406 insertions(+), 289 deletions(-) create mode 100644 apis/api-journeys-modern/src/schema/journey/adminJourneys.query.spec.ts create mode 100644 apis/api-journeys-modern/src/schema/journey/adminJourneys.query.ts diff --git a/apis/api-gateway/schema.graphql b/apis/api-gateway/schema.graphql index 8e010e3065c..b18baf32469 100644 --- a/apis/api-gateway/schema.graphql +++ b/apis/api-gateway/schema.graphql @@ -711,21 +711,6 @@ type Query @join__type(graph: API_ANALYTICS) @join__type(graph: API_JOURNEYS) customDomains(teamId: ID!) : [CustomDomain!]! @join__field(graph: API_JOURNEYS) hosts(teamId: ID!) : [Host!]! @join__field(graph: API_JOURNEYS) integrations(teamId: ID!) : [Integration!]! @join__field(graph: API_JOURNEYS) - """ - returns all journeys that match the provided filters - If no team id is provided and template is not true then only returns journeys - where the user is not a member of a team but is an editor or owner of the - journey - """ - adminJourneys( - status: [JourneyStatus!] - template: Boolean - teamId: ID - """ - Use Last Active Team Id from JourneyProfile (if null will error) - """ - useLastActiveTeamId: Boolean - ): [Journey!]! @join__field(graph: API_JOURNEYS) adminJourneysReport(reportType: JourneysReportType!) : PowerBiEmbed @join__field(graph: API_JOURNEYS) adminJourney(id: ID!, idType: IdType) : Journey! @join__field(graph: API_JOURNEYS) journeys(where: JourneysFilter, options: JourneysQueryOptions) : [Journey!]! @join__field(graph: API_JOURNEYS) @@ -795,6 +780,12 @@ type Query @join__type(graph: API_ANALYTICS) @join__type(graph: API_JOURNEYS) journeySimpleGet(id: ID!) : Json @join__field(graph: API_JOURNEYS_MODERN) googleSheetsSyncs(filter: GoogleSheetsSyncsFilter!) : [GoogleSheetsSync!]! @join__field(graph: API_JOURNEYS_MODERN) integrationGooglePickerToken(integrationId: ID!) : String! @join__field(graph: API_JOURNEYS_MODERN) + adminJourneys( + status: [JourneyStatus!] + template: Boolean + teamId: ID + useLastActiveTeamId: Boolean + ): [Journey!]! @join__field(graph: API_JOURNEYS_MODERN) getJourneyProfile: JourneyProfile @join__field(graph: API_JOURNEYS_MODERN) """ Returns a CSV formatted string with journey visitor export data including headers and visitor data with event information diff --git a/apis/api-journeys-modern/schema.graphql b/apis/api-journeys-modern/schema.graphql index 63fccfb264a..b8308c9d1e5 100644 --- a/apis/api-journeys-modern/schema.graphql +++ b/apis/api-journeys-modern/schema.graphql @@ -1715,6 +1715,7 @@ type Query { journeySimpleGet(id: ID!): Json googleSheetsSyncs(filter: GoogleSheetsSyncsFilter!): [GoogleSheetsSync!]! integrationGooglePickerToken(integrationId: ID!): String! + adminJourneys(status: [JourneyStatus!], template: Boolean, teamId: ID, useLastActiveTeamId: Boolean): [Journey!]! getJourneyProfile: JourneyProfile """ diff --git a/apis/api-journeys-modern/src/schema/authScopes.ts b/apis/api-journeys-modern/src/schema/authScopes.ts index bf9982ad55c..9d7d4333058 100644 --- a/apis/api-journeys-modern/src/schema/authScopes.ts +++ b/apis/api-journeys-modern/src/schema/authScopes.ts @@ -76,6 +76,7 @@ async function isTeamManager({ export interface AuthScopes { isAuthenticated: boolean + isAnonymous: boolean isPublisher: boolean isValidInterop: boolean } @@ -83,6 +84,7 @@ export interface AuthScopes { export async function authScopes(context: Context) { const defaultScopes = { isAuthenticated: false, + isAnonymous: false, isPublisher: false, isValidInterop: false } @@ -91,6 +93,7 @@ export async function authScopes(context: Context) { return { ...defaultScopes, isAuthenticated: true, + isAnonymous: context.user.email == null, isPublisher: context.currentRoles.includes('publisher'), isInTeam: async (teamId: string) => await isInTeam({ context, teamId }), isIntegrationOwner: async (integrationId: string) => diff --git a/apis/api-journeys-modern/src/schema/journey/adminJourneys.query.spec.ts b/apis/api-journeys-modern/src/schema/journey/adminJourneys.query.spec.ts new file mode 100644 index 00000000000..9a5df4c8b0e --- /dev/null +++ b/apis/api-journeys-modern/src/schema/journey/adminJourneys.query.spec.ts @@ -0,0 +1,252 @@ +import { getUserFromPayload } from '@core/yoga/firebaseClient' + +import { getClient } from '../../../test/client' +import { prismaMock } from '../../../test/prismaMock' +import { graphql } from '../../lib/graphql/subgraphGraphql' + +jest.mock('@core/yoga/firebaseClient', () => ({ + getUserFromPayload: jest.fn() +})) + +const mockGetUserFromPayload = getUserFromPayload as jest.MockedFunction< + typeof getUserFromPayload +> + +describe('adminJourneys', () => { + const mockUser = { + id: 'userId', + email: 'test@example.com', + emailVerified: true, + firstName: 'Test', + lastName: 'User', + imageUrl: null, + roles: [] + } + + const mockAnonymousUser = { + id: 'anonUserId', + email: null, + emailVerified: false, + firstName: '', + imageUrl: null, + roles: [] + } + + const authClient = getClient({ + headers: { authorization: 'token' }, + context: { currentUser: mockUser } + }) + + const ADMIN_JOURNEYS_QUERY = graphql(` + query AdminJourneys( + $status: [JourneyStatus!] + $template: Boolean + $teamId: ID + $useLastActiveTeamId: Boolean + ) { + adminJourneys( + status: $status + template: $template + teamId: $teamId + useLastActiveTeamId: $useLastActiveTeamId + ) { + id + title + status + template + } + } + `) + + const mockJourney = { + id: 'journeyId', + title: 'Test Journey', + description: null, + slug: 'test-journey', + languageId: '529', + themeMode: 'dark', + themeName: 'base', + status: 'published', + template: false, + teamId: 'teamId', + createdAt: new Date(), + updatedAt: new Date(), + publishedAt: new Date(), + archivedAt: null, + trashedAt: null, + deletedAt: null, + featuredAt: null, + seoTitle: null, + seoDescription: null, + primaryImageBlockId: null, + creatorImageBlockId: null, + logoImageBlockId: null, + creatorDescription: null, + website: false, + showShareButton: null, + showLikeButton: null, + showDislikeButton: null, + displayTitle: null, + showHosts: null, + showChatButtons: null, + showReactionButtons: null, + showLogo: null, + showMenu: null, + showDisplayTitle: null, + showAssistant: null, + menuButtonIcon: null, + menuStepBlockId: null, + socialNodeX: null, + socialNodeY: null, + strategySlug: null, + plausibleToken: null, + templateSite: null, + fromTemplateId: null, + hostId: null, + journeyCustomizationDescription: null + } + + beforeEach(() => { + jest.clearAllMocks() + mockGetUserFromPayload.mockReturnValue(mockUser) + prismaMock.userRole.findUnique.mockResolvedValue({ + id: 'userRoleId', + userId: mockUser.id, + roles: [] + }) + }) + + it('should return journeys for authenticated user', async () => { + prismaMock.journey.findMany.mockResolvedValue([mockJourney as any]) + + const result = await authClient({ + document: ADMIN_JOURNEYS_QUERY + }) + + expect(result.data?.adminJourneys).toHaveLength(1) + expect(result.data?.adminJourneys[0]).toMatchObject({ + id: 'journeyId', + title: 'Test Journey', + status: 'published', + template: false + }) + }) + + it('should return journeys for anonymous user', async () => { + mockGetUserFromPayload.mockReturnValue(mockAnonymousUser) + prismaMock.userRole.findUnique.mockResolvedValue({ + id: 'userRoleId', + userId: mockAnonymousUser.id, + roles: [] + }) + + const anonClient = getClient({ + headers: { authorization: 'token' }, + context: { currentUser: mockAnonymousUser } + }) + + prismaMock.journey.findMany.mockResolvedValue([mockJourney as any]) + + const result = await anonClient({ + document: ADMIN_JOURNEYS_QUERY + }) + + expect(result.data?.adminJourneys).toHaveLength(1) + }) + + it('should handle useLastActiveTeamId gracefully when profile not found', async () => { + prismaMock.journeyProfile.findUnique.mockResolvedValue(null) + prismaMock.journey.findMany.mockResolvedValue([mockJourney as any]) + + const result = await authClient({ + document: ADMIN_JOURNEYS_QUERY, + variables: { useLastActiveTeamId: true } + }) + + // Should not throw, should return journeys + expect(result.errors).toBeUndefined() + expect(result.data?.adminJourneys).toBeDefined() + }) + + it('should filter by lastActiveTeamId when profile exists', async () => { + prismaMock.journeyProfile.findUnique.mockResolvedValue({ + id: 'profileId', + userId: 'userId', + lastActiveTeamId: 'teamId', + acceptedTermsAt: new Date(), + onboardingComplete: false + }) + prismaMock.journey.findMany.mockResolvedValue([mockJourney as any]) + + const result = await authClient({ + document: ADMIN_JOURNEYS_QUERY, + variables: { useLastActiveTeamId: true } + }) + + expect(result.data?.adminJourneys).toHaveLength(1) + expect(prismaMock.journey.findMany).toHaveBeenCalledWith( + expect.objectContaining({ + where: expect.objectContaining({ + AND: expect.arrayContaining([ + expect.objectContaining({ teamId: 'teamId' }) + ]) + }) + }) + ) + }) + + it('should filter by teamId', async () => { + prismaMock.journey.findMany.mockResolvedValue([mockJourney as any]) + + const result = await authClient({ + document: ADMIN_JOURNEYS_QUERY, + variables: { teamId: 'teamId' } + }) + + expect(result.data?.adminJourneys).toHaveLength(1) + expect(prismaMock.journey.findMany).toHaveBeenCalledWith( + expect.objectContaining({ + where: expect.objectContaining({ + AND: expect.arrayContaining([ + expect.objectContaining({ teamId: 'teamId' }) + ]) + }) + }) + ) + }) + + it('should filter by template', async () => { + prismaMock.journey.findMany.mockResolvedValue([mockJourney as any]) + + const result = await authClient({ + document: ADMIN_JOURNEYS_QUERY, + variables: { template: true } + }) + + expect(result.data?.adminJourneys).toHaveLength(1) + expect(prismaMock.journey.findMany).toHaveBeenCalledWith( + expect.objectContaining({ + where: expect.objectContaining({ + AND: expect.arrayContaining([ + expect.objectContaining({ template: true }) + ]) + }) + }) + ) + }) + + it('should reject unauthenticated users', async () => { + mockGetUserFromPayload.mockReturnValue(null) + const unauthClient = getClient({ + headers: { authorization: 'token' }, + context: { currentUser: null } + }) + + const result = await unauthClient({ + document: ADMIN_JOURNEYS_QUERY + }) + + expect(result.errors).toBeDefined() + expect(result.errors?.[0]?.message).toContain('Not authorized') + }) +}) diff --git a/apis/api-journeys-modern/src/schema/journey/adminJourneys.query.ts b/apis/api-journeys-modern/src/schema/journey/adminJourneys.query.ts new file mode 100644 index 00000000000..44bdc058261 --- /dev/null +++ b/apis/api-journeys-modern/src/schema/journey/adminJourneys.query.ts @@ -0,0 +1,124 @@ +import { + Prisma, + JourneyStatus as PrismaJourneyStatus, + UserJourneyRole, + UserTeamRole, + prisma +} from '@core/prisma/journeys/client' + +import { builder } from '../builder' + +import { JourneyStatus } from './enums/journeyStatus' +import { JourneyRef } from './journey' + +builder.queryField('adminJourneys', (t) => + t + .withAuth({ $any: { isAuthenticated: true, isAnonymous: true } }) + .prismaField({ + type: [JourneyRef], + nullable: false, + args: { + status: t.arg({ type: [JourneyStatus], required: false }), + template: t.arg.boolean({ required: false }), + teamId: t.arg.id({ required: false }), + useLastActiveTeamId: t.arg.boolean({ required: false }) + }, + resolve: async (query, _parent, args, context) => { + const userId = context.user.id + const filter: Prisma.JourneyWhereInput = {} + + if (args.useLastActiveTeamId === true) { + const profile = await prisma.journeyProfile.findUnique({ + where: { userId } + }) + if (profile?.lastActiveTeamId != null) { + filter.teamId = profile.lastActiveTeamId + } + } + + if (args.teamId != null) { + filter.teamId = args.teamId + } else if (args.template !== true && filter.teamId == null) { + // if not looking for templates then only return journeys where: + // 1. the user is an owner or editor + // 2. not a member of the team + filter.userJourneys = { + some: { + userId, + role: { in: [UserJourneyRole.owner, UserJourneyRole.editor] } + } + } + filter.team = { + userTeams: { + none: { + userId + } + } + } + } + + if (args.template != null) filter.template = args.template + if (args.status != null) { + filter.status = { in: args.status as PrismaJourneyStatus[] } + } + + // ACL: only return journeys the user has access to + const accessibleJourneys: Prisma.JourneyWhereInput = { + OR: [ + // user is a team manager + { + team: { + userTeams: { + some: { + userId, + role: UserTeamRole.manager + } + } + } + }, + // user is a team member + { + team: { + userTeams: { + some: { + userId, + role: UserTeamRole.member + } + } + } + }, + // user is a journey owner + { + userJourneys: { + some: { + userId, + role: UserJourneyRole.owner + } + } + }, + // user is a journey editor + { + userJourneys: { + some: { + userId, + role: UserJourneyRole.editor + } + } + }, + // published templates are readable by everyone + { + template: true, + status: PrismaJourneyStatus.published + } + ] + } + + return await prisma.journey.findMany({ + ...query, + where: { + AND: [accessibleJourneys, filter] + } + }) + } + }) +) diff --git a/apis/api-journeys-modern/src/schema/journey/index.ts b/apis/api-journeys-modern/src/schema/journey/index.ts index fafa8cd6405..e9bb8479599 100644 --- a/apis/api-journeys-modern/src/schema/journey/index.ts +++ b/apis/api-journeys-modern/src/schema/journey/index.ts @@ -1,3 +1,4 @@ +import './adminJourneys.query' import './journey' import './inputs' import './enums' diff --git a/apis/api-journeys/schema.graphql b/apis/api-journeys/schema.graphql index f9e550079a8..36c79df6a51 100644 --- a/apis/api-journeys/schema.graphql +++ b/apis/api-journeys/schema.graphql @@ -231,20 +231,6 @@ extend type Query { hosts(teamId: ID!): [Host!]! integrations(teamId: ID!): [Integration!]! - """ - returns all journeys that match the provided filters - If no team id is provided and template is not true then only returns journeys - where the user is not a member of a team but is an editor or owner of the - journey - """ - adminJourneys( - status: [JourneyStatus!] - template: Boolean - teamId: ID - - """Use Last Active Team Id from JourneyProfile (if null will error)""" - useLastActiveTeamId: Boolean - ): [Journey!]! adminJourneysReport(reportType: JourneysReportType!): PowerBiEmbed adminJourney(id: ID!, idType: IdType): Journey! journeys(where: JourneysFilter, options: JourneysQueryOptions): [Journey!]! diff --git a/apis/api-journeys/src/app/__generated__/graphql.ts b/apis/api-journeys/src/app/__generated__/graphql.ts index 3bfdb3b31d6..cd262e8432f 100644 --- a/apis/api-journeys/src/app/__generated__/graphql.ts +++ b/apis/api-journeys/src/app/__generated__/graphql.ts @@ -997,8 +997,6 @@ export abstract class IQuery { abstract integrations(teamId: string): Integration[] | Promise; - abstract adminJourneys(status?: Nullable, template?: Nullable, teamId?: Nullable, useLastActiveTeamId?: Nullable): Journey[] | Promise; - abstract adminJourneysReport(reportType: JourneysReportType): Nullable | Promise>; abstract adminJourney(id: string, idType?: Nullable): Journey | Promise; diff --git a/apis/api-journeys/src/app/modules/journey/journey.graphql b/apis/api-journeys/src/app/modules/journey/journey.graphql index ebc9fb460b9..07394b0ec31 100644 --- a/apis/api-journeys/src/app/modules/journey/journey.graphql +++ b/apis/api-journeys/src/app/modules/journey/journey.graphql @@ -154,21 +154,6 @@ type PowerBiEmbed @shareable { } extend type Query { - """ - returns all journeys that match the provided filters - If no team id is provided and template is not true then only returns journeys - where the user is not a member of a team but is an editor or owner of the - journey - """ - adminJourneys( - status: [JourneyStatus!] - template: Boolean - teamId: ID - """ - Use Last Active Team Id from JourneyProfile (if null will error) - """ - useLastActiveTeamId: Boolean - ): [Journey!]! adminJourneysReport(reportType: JourneysReportType!): PowerBiEmbed adminJourney(id: ID!, idType: IdType): Journey! journeys(where: JourneysFilter, options: JourneysQueryOptions): [Journey!]! diff --git a/apis/api-journeys/src/app/modules/journey/journey.resolver.spec.ts b/apis/api-journeys/src/app/modules/journey/journey.resolver.spec.ts index 1787f1ad227..67f7ef30abb 100644 --- a/apis/api-journeys/src/app/modules/journey/journey.resolver.spec.ts +++ b/apis/api-journeys/src/app/modules/journey/journey.resolver.spec.ts @@ -12,7 +12,6 @@ import { Host, Journey, JourneyCollection, - JourneyProfile, Prisma, Team, ThemeMode, @@ -360,155 +359,6 @@ describe('JourneyResolver', () => { }) }) - describe('adminJourneys', () => { - const journeysSharedWithMe: Prisma.JourneyWhereInput = { - userJourneys: { - some: { - userId: 'userId', - role: { in: [UserJourneyRole.owner, UserJourneyRole.editor] } - } - }, - team: { - userTeams: { - none: { - userId: 'userId' - } - } - } - } - - beforeEach(() => { - prismaService.journey.findMany.mockResolvedValueOnce([journey]) - }) - - it('should get journeys that are shared with me', async () => { - expect( - await resolver.adminJourneys('userId', accessibleJourneys) - ).toEqual([journey]) - expect(prismaService.journey.findMany).toHaveBeenCalledWith({ - where: { - AND: [accessibleJourneys, journeysSharedWithMe] - } - }) - }) - - it('should get filtered journeys', async () => { - expect( - await resolver.adminJourneys( - 'userId', - accessibleJourneys, - [JourneyStatus.archived], - false, - 'teamId' - ) - ).toEqual([journey]) - expect(prismaService.journey.findMany).toHaveBeenCalledWith({ - where: { - AND: [ - accessibleJourneys, - { - status: { in: [JourneyStatus.archived] }, - template: false, - teamId: 'teamId' - } - ] - } - }) - }) - - describe('status', () => { - it('should get journeys that are shared with me with status', async () => { - expect( - await resolver.adminJourneys('userId', accessibleJourneys, [ - JourneyStatus.draft - ]) - ).toEqual([journey]) - expect(prismaService.journey.findMany).toHaveBeenCalledWith({ - where: { - AND: [ - accessibleJourneys, - { ...journeysSharedWithMe, status: { in: [JourneyStatus.draft] } } - ] - } - }) - }) - }) - - describe('template', () => { - it('should get template journeys', async () => { - expect( - await resolver.adminJourneys( - 'userId', - accessibleJourneys, - undefined, - true - ) - ).toEqual([journey]) - expect(prismaService.journey.findMany).toHaveBeenCalledWith({ - where: { - AND: [accessibleJourneys, { template: true }] - } - }) - }) - }) - - describe('useLastActiveTeamId', () => { - it('should get journeys belonging to last active team', async () => { - prismaService.journeyProfile.findUnique.mockResolvedValue({ - lastActiveTeamId: 'teamId' - } as unknown as JourneyProfile) - expect( - await resolver.adminJourneys( - 'userId', - accessibleJourneys, - undefined, - undefined, - undefined, - true - ) - ).toEqual([journey]) - expect(prismaService.journey.findMany).toHaveBeenCalledWith({ - where: { - AND: [accessibleJourneys, { teamId: 'teamId' }] - } - }) - }) - - it('should throw error if profile not found', async () => { - prismaService.journeyProfile.findUnique.mockResolvedValue(null) - await expect( - resolver.adminJourneys( - 'userId', - accessibleJourneys, - undefined, - undefined, - undefined, - true - ) - ).rejects.toThrow('journey profile not found') - }) - }) - - describe('teamId', () => { - it('should get journeys belonging to team', async () => { - expect( - await resolver.adminJourneys( - 'userId', - accessibleJourneys, - undefined, - undefined, - 'teamId' - ) - ).toEqual([journey]) - expect(prismaService.journey.findMany).toHaveBeenCalledWith({ - where: { - AND: [accessibleJourneys, { teamId: 'teamId' }] - } - }) - }) - }) - }) - describe('adminJourney', () => { it('returns journey by slug', async () => { prismaService.journey.findUnique.mockResolvedValueOnce( diff --git a/apis/api-journeys/src/app/modules/journey/journey.resolver.ts b/apis/api-journeys/src/app/modules/journey/journey.resolver.ts index ee0d0d09563..96500671cce 100644 --- a/apis/api-journeys/src/app/modules/journey/journey.resolver.ts +++ b/apis/api-journeys/src/app/modules/journey/journey.resolver.ts @@ -129,56 +129,6 @@ export class JourneyResolver { } } - @Query() - @UseGuards(AppCaslGuard) - async adminJourneys( - @CurrentUserId() userId: string, - @CaslAccessible('Journey') accessibleJourneys: Prisma.JourneyWhereInput, - @Args('status') status?: JourneyStatus[], - @Args('template') template?: boolean, - @Args('teamId') teamId?: string, - @Args('useLastActiveTeamId') useLastActiveTeamId?: boolean - ): Promise { - const filter: Prisma.JourneyWhereInput = {} - if (useLastActiveTeamId === true) { - const profile = await this.prismaService.journeyProfile.findUnique({ - where: { userId } - }) - if (profile == null) - throw new GraphQLError('journey profile not found', { - extensions: { code: 'NOT_FOUND' } - }) - filter.teamId = profile.lastActiveTeamId ?? undefined - } - if (teamId != null) { - filter.teamId = teamId - } else if (template !== true && filter.teamId == null) { - // if not looking for templates then only return journeys where: - // 1. the user is an owner or editor - // 2. not a member of the team - filter.userJourneys = { - some: { - userId, - role: { in: [UserJourneyRole.owner, UserJourneyRole.editor] } - } - } - filter.team = { - userTeams: { - none: { - userId - } - } - } - } - if (template != null) filter.template = template - if (status != null) filter.status = { in: status } - return await this.prismaService.journey.findMany({ - where: { - AND: [accessibleJourneys, filter] - } - }) - } - @Query() @UseGuards(AppCaslGuard) async adminJourney( diff --git a/apps/journeys-admin/src/components/TemplateCustomization/MultiStepForm/Screens/LanguageScreen/LanguageScreen.spec.tsx b/apps/journeys-admin/src/components/TemplateCustomization/MultiStepForm/Screens/LanguageScreen/LanguageScreen.spec.tsx index 2a313241293..1a7aa25920e 100644 --- a/apps/journeys-admin/src/components/TemplateCustomization/MultiStepForm/Screens/LanguageScreen/LanguageScreen.spec.tsx +++ b/apps/journeys-admin/src/components/TemplateCustomization/MultiStepForm/Screens/LanguageScreen/LanguageScreen.spec.tsx @@ -157,11 +157,6 @@ describe('LanguageScreen', () => { ) - await waitFor(() => - expect(screen.getByRole('combobox', { name: 'Team' })).toHaveTextContent( - 'Team One' - ) - ) fireEvent.click(screen.getByTestId('CustomizeFlowNextButton')) await waitFor(() => expect(mockJourneyDuplicateMockResult).toHaveBeenCalled() @@ -266,12 +261,6 @@ describe('LanguageScreen', () => { await waitFor(() => expect(mockGetParentJourneysFromTemplateIdMockResult).toHaveBeenCalled() ) - await waitFor(() => - expect(screen.getByRole('combobox', { name: 'Team' })).toHaveTextContent( - 'Team One' - ) - ) - fireEvent.focus(screen.getByTestId('LanguageAutocompleteInput')) fireEvent.keyDown(screen.getByTestId('LanguageAutocompleteInput'), { key: 'ArrowDown' @@ -321,11 +310,6 @@ describe('LanguageScreen', () => { ) - await waitFor(() => - expect(screen.getByRole('combobox', { name: 'Team' })).toHaveTextContent( - 'Team One' - ) - ) fireEvent.click(screen.getByTestId('CustomizeFlowNextButton')) await waitFor(() => expect(mockJourneyDuplicateMockResult).toHaveBeenCalled() @@ -422,10 +406,6 @@ describe('LanguageScreen', () => { expect(screen.getAllByText('Select a language')).toHaveLength(2) expect(screen.getByTestId('LanguageAutocompleteInput')).toBeInTheDocument() - expect(screen.getAllByText('Select a team')).toHaveLength(2) - await waitFor(() => { - expect(screen.getByRole('combobox', { name: 'Team' })).toBeInTheDocument() - }) expect(screen.getByTestId('CustomizeFlowNextButton')).toBeInTheDocument() expect(screen.getByTestId('CustomizeFlowNextButton')).toHaveTextContent( diff --git a/apps/journeys-admin/src/components/TemplateCustomization/MultiStepForm/Screens/LanguageScreen/LanguageScreen.tsx b/apps/journeys-admin/src/components/TemplateCustomization/MultiStepForm/Screens/LanguageScreen/LanguageScreen.tsx index 68fce0427f3..18cee381add 100644 --- a/apps/journeys-admin/src/components/TemplateCustomization/MultiStepForm/Screens/LanguageScreen/LanguageScreen.tsx +++ b/apps/journeys-admin/src/components/TemplateCustomization/MultiStepForm/Screens/LanguageScreen/LanguageScreen.tsx @@ -10,7 +10,7 @@ import { useUser } from 'next-firebase-auth' import { useTranslation } from 'next-i18next' import { useSnackbar } from 'notistack' import { ReactElement, useEffect, useState } from 'react' -import { object, string } from 'yup' +import { object } from 'yup' import { useJourney } from '@core/journeys/ui/JourneyProvider' import { useTeam } from '@core/journeys/ui/TeamProvider' @@ -29,7 +29,6 @@ import { import { CustomizationScreen } from '../../../utils/getCustomizeFlowConfig' import { CustomizeFlowNextButton } from '../../CustomizeFlowNextButton' -import { JourneyCustomizeTeamSelect } from './JourneyCustomizeTeamSelect' // const JOURNEY_PROFILE_CREATE = gql` // mutation JourneyProfileCreate { @@ -120,12 +119,9 @@ export function LanguageScreen({ ...childJourneyLanguagesJourneyMap } - const validationSchema = object({ - teamSelect: isSignedIn ? string().required() : string() - }) + const validationSchema = object({}) const initialValues = { - teamSelect: query?.data?.getJourneyProfile?.lastActiveTeamId ?? '', languageSelect: { id: journey?.language?.id, localName: journey?.language?.name.find((name) => name.primary)?.value, @@ -280,7 +276,19 @@ export function LanguageScreen({ } if (isSignedIn) { - const teamId = values.teamSelect as string + const teams = query?.data?.teams ?? [] + const teamId = + query?.data?.getJourneyProfile?.lastActiveTeamId ?? teams[0]?.id + if (teamId == null) { + enqueueSnackbar( + t( + 'No team available. Please create a team first.' + ), + { variant: 'error' } + ) + setLoading(false) + return + } const success = await duplicateJourneyAndRedirect(journeyId, teamId) if (!success) { enqueueSnackbar( @@ -410,21 +418,6 @@ export function LanguageScreen({ }))} onChange={(value) => setFieldValue('languageSelect', value)} /> - - {t('Select a team')} - - - {t('Select a team')} - - {isSignedIn && } handleSubmit()} diff --git a/apps/journeys-admin/src/libs/initAndAuthApp/initAndAuthApp.ts b/apps/journeys-admin/src/libs/initAndAuthApp/initAndAuthApp.ts index 3a10c318ccd..9dd96768eb1 100644 --- a/apps/journeys-admin/src/libs/initAndAuthApp/initAndAuthApp.ts +++ b/apps/journeys-admin/src/libs/initAndAuthApp/initAndAuthApp.ts @@ -108,7 +108,10 @@ export async function initAndAuthApp({ }) : undefined - if (!(redirect?.destination.startsWith('/users/verify') ?? false)) + if ( + !(redirect?.destination.startsWith('/users/verify') ?? false) && + user?.email != null + ) await apolloClient.mutate({ mutation: ACCEPT_ALL_INVITES })