From becf8c1505d73a2d2f378e983b17680b3aa77474 Mon Sep 17 00:00:00 2001 From: Siyang Date: Tue, 3 Feb 2026 22:05:29 +0000 Subject: [PATCH 01/13] inital guest user create --- .devcontainer/docker-compose.yml | 23 ++-- .../pages/templates/[journeyId]/customize.tsx | 2 +- .../Screens/LanguageScreen/LanguageScreen.tsx | 122 +++++++++++++++++- 3 files changed, 133 insertions(+), 14 deletions(-) diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index 7c637e54ae4..d1a790da70c 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -4,7 +4,7 @@ services: volumes: - core-workspace:/workspaces - core-node-user:/home/node - entrypoint: ['/bin/bash', '-lc'] + entrypoint: ["/bin/bash", "-lc"] command: - | LOGFILE=/workspaces/bootstrap.log @@ -74,7 +74,10 @@ services: PUPPETEER_EXECUTABLE_PATH: /usr/bin/chromium PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: true extra_hosts: - - 'host.docker.internal:host-gateway' + - "host.docker.internal:host-gateway" + # Prevent ENOSPC: file watcher limit (Cursor + Nx + multiple dev servers exceed default) + sysctls: + - fs.inotify.max_user_watches=5242880 depends_on: bootstrap: condition: service_completed_successfully @@ -98,13 +101,13 @@ services: - 6379:6379 command: [ - 'redis-server', - '--appendonly', - 'no', - '--maxmemory', - '500mb', - '--maxmemory-policy', - 'noeviction' + "redis-server", + "--appendonly", + "no", + "--maxmemory", + "500mb", + "--maxmemory-policy", + "noeviction", ] volumes: - redis-data:/data @@ -120,7 +123,7 @@ services: SRH_TOKEN: example_token SRH_CONNECTION_STRING: redis://redis:6379 maildev: - image: 'maildev/maildev:latest' + image: "maildev/maildev:latest" restart: unless-stopped ports: - 1080:1080 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..f6b7b6ef564 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 { Form, Formik, FormikValues } from 'formik' +import { getApp } from 'firebase/app' +import { getAuth, signInAnonymously } from 'firebase/auth' 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,26 @@ 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 { useGetChildTemplateJourneyLanguages } from '../../../../../libs/useGetChildTemplateJourneyLanguages' import { useGetParentTemplateJourneyLanguages } from '../../../../../libs/useGetParentTemplateJourneyLanguages' +import { useCurrentUserLazyQuery } from '../../../../../libs/useCurrentUserLazyQuery' +import { useTeamCreateMutation } 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 +58,13 @@ export function LanguageScreen({ const isSignedIn = user?.email != null && user?.id != null const { query } = useTeam() + useEffect(() => { + 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 { @@ -107,10 +130,16 @@ export function LanguageScreen({ } const [journeyDuplicate] = useJourneyDuplicateMutation() + const { loadUser } = useCurrentUserLazyQuery() + const [journeyProfileCreate] = useMutation( + JOURNEY_PROFILE_CREATE + ) + const [teamCreate] = useTeamCreateMutation() const FORM_SM_BREAKPOINT_WIDTH = '390px' async function handleSubmit(values: FormikValues) { + console.log('isSignedIn', isSignedIn, loading) setLoading(true) if (journey == null) { setLoading(false) @@ -145,6 +174,90 @@ export function LanguageScreen({ ) handleNext() setLoading(false) + } else { + console.log('--------- HERE') + const isAnonymous = user?.firebaseUser?.isAnonymous ?? false + + try { + if (!isAnonymous) { + await signInAnonymously(getAuth(getApp())) + } + const result = await loadUser() + if (result?.data?.me == null) { + enqueueSnackbar( + t('Unable to continue as guest. Please try again or sign in.'), + { variant: 'error' } + ) + setLoading(false) + return + } + + const teamName = t('My Team') + + const [profileResult, teamResult] = await Promise.all([ + journeyProfileCreate(), + teamCreate({ + variables: { + input: { + title: teamName, + publicTitle: teamName + } + } + }) + ]) + + if ( + profileResult?.data?.journeyProfileCreate == null || + teamResult?.data?.teamCreate == null + ) { + enqueueSnackbar( + t('Unable to continue as guest. Please try again or sign in.'), + { variant: 'error' } + ) + setLoading(false) + return + } + + const teamId = teamResult.data.teamCreate.id + 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 + } + + const { data: duplicateData } = await journeyDuplicate({ + variables: { id: journeyId, teamId, forceNonTemplate: true } + }) + if (duplicateData?.journeyDuplicate == null) { + enqueueSnackbar( + t( + 'Failed to duplicate journey to team, please refresh the page and try again' + ), + { variant: 'error' } + ) + setLoading(false) + return + } + + // await router.push( + // `/templates/${duplicateData.journeyDuplicate.id}/customize`, + // undefined, + // { shallow: true } + // ) + } catch (error) { + enqueueSnackbar( + t('Unable to continue as guest. Please try again or sign in.'), + { variant: 'error' } + ) + } finally { + handleNext() + setLoading(false) + } } } @@ -232,8 +345,11 @@ export function LanguageScreen({ {isSignedIn && } handleSubmit()} + label={t('Next12')} + onClick={() => { + console.log('loading') + handleSubmit() + }} disabled={loading} ariaLabel={t('Next')} /> From ab81c719ce3d7bacbc59653ae283305112ed8d18 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Tue, 3 Feb 2026 22:10:53 +0000 Subject: [PATCH 02/13] fix: lint issues --- .devcontainer/docker-compose.yml | 20 +++++++++---------- .../Screens/LanguageScreen/LanguageScreen.tsx | 4 ++-- libs/locales/en/journeys-ui.json | 3 +++ 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index d1a790da70c..3315634127b 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -4,7 +4,7 @@ services: volumes: - core-workspace:/workspaces - core-node-user:/home/node - entrypoint: ["/bin/bash", "-lc"] + entrypoint: ['/bin/bash', '-lc'] command: - | LOGFILE=/workspaces/bootstrap.log @@ -74,7 +74,7 @@ services: PUPPETEER_EXECUTABLE_PATH: /usr/bin/chromium PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: true extra_hosts: - - "host.docker.internal:host-gateway" + - 'host.docker.internal:host-gateway' # Prevent ENOSPC: file watcher limit (Cursor + Nx + multiple dev servers exceed default) sysctls: - fs.inotify.max_user_watches=5242880 @@ -101,13 +101,13 @@ services: - 6379:6379 command: [ - "redis-server", - "--appendonly", - "no", - "--maxmemory", - "500mb", - "--maxmemory-policy", - "noeviction", + 'redis-server', + '--appendonly', + 'no', + '--maxmemory', + '500mb', + '--maxmemory-policy', + 'noeviction' ] volumes: - redis-data:/data @@ -123,7 +123,7 @@ services: SRH_TOKEN: example_token SRH_CONNECTION_STRING: redis://redis:6379 maildev: - image: "maildev/maildev:latest" + image: 'maildev/maildev:latest' restart: unless-stopped ports: - 1080:1080 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 f6b7b6ef564..fc4aa5e9b41 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 @@ -2,9 +2,9 @@ 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 { Form, Formik, FormikValues } from 'formik' 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' @@ -19,9 +19,9 @@ import { useJourneyDuplicateMutation } from '@core/journeys/ui/useJourneyDuplica 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 { useCurrentUserLazyQuery } from '../../../../../libs/useCurrentUserLazyQuery' import { useTeamCreateMutation } from '../../../../../libs/useTeamCreateMutation' import { CustomizationScreen } from '../../../utils/getCustomizeFlowConfig' import { CustomizeFlowNextButton } from '../../CustomizeFlowNextButton' diff --git a/libs/locales/en/journeys-ui.json b/libs/locales/en/journeys-ui.json index b059ab4592a..2d075c1d18c 100644 --- a/libs/locales/en/journeys-ui.json +++ b/libs/locales/en/journeys-ui.json @@ -1,8 +1,11 @@ { "Failed to duplicate journey to team, please refresh the page and try again": "Failed to duplicate journey to team, please refresh the page and try again", + "Unable to continue as guest. Please try again or sign in.": "Unable to continue as guest. Please try again or sign in.", + "My Team": "My Team", "Let's get started!": "Let's get started!", "A few quick edits and your template will be ready to share.": "A few quick edits and your template will be ready to share.", "Select a language": "Select a language", "Select a team": "Select a team", + "Next12": "Next12", "Next": "Next" } From fe19f37e5657b291c1ba2a64e79743583ef5c6ed Mon Sep 17 00:00:00 2001 From: Siyang Date: Tue, 3 Feb 2026 22:14:15 +0000 Subject: [PATCH 03/13] remove docker changes --- .devcontainer/docker-compose.yml | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index 3315634127b..6a879253d82 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -4,7 +4,7 @@ services: volumes: - core-workspace:/workspaces - core-node-user:/home/node - entrypoint: ['/bin/bash', '-lc'] + entrypoint: ["/bin/bash", "-lc"] command: - | LOGFILE=/workspaces/bootstrap.log @@ -74,10 +74,7 @@ services: PUPPETEER_EXECUTABLE_PATH: /usr/bin/chromium PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: true extra_hosts: - - 'host.docker.internal:host-gateway' - # Prevent ENOSPC: file watcher limit (Cursor + Nx + multiple dev servers exceed default) - sysctls: - - fs.inotify.max_user_watches=5242880 + - "host.docker.internal:host-gateway" depends_on: bootstrap: condition: service_completed_successfully @@ -101,13 +98,13 @@ services: - 6379:6379 command: [ - 'redis-server', - '--appendonly', - 'no', - '--maxmemory', - '500mb', - '--maxmemory-policy', - 'noeviction' + "redis-server", + "--appendonly", + "no", + "--maxmemory", + "500mb", + "--maxmemory-policy", + "noeviction", ] volumes: - redis-data:/data @@ -123,7 +120,7 @@ services: SRH_TOKEN: example_token SRH_CONNECTION_STRING: redis://redis:6379 maildev: - image: 'maildev/maildev:latest' + image: "maildev/maildev:latest" restart: unless-stopped ports: - 1080:1080 From cdd1c60d401175677564561f1c77f62bd1ec6c9e Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Tue, 3 Feb 2026 22:18:28 +0000 Subject: [PATCH 04/13] fix: lint issues --- .devcontainer/docker-compose.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index 6a879253d82..7c637e54ae4 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -4,7 +4,7 @@ services: volumes: - core-workspace:/workspaces - core-node-user:/home/node - entrypoint: ["/bin/bash", "-lc"] + entrypoint: ['/bin/bash', '-lc'] command: - | LOGFILE=/workspaces/bootstrap.log @@ -74,7 +74,7 @@ services: PUPPETEER_EXECUTABLE_PATH: /usr/bin/chromium PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: true extra_hosts: - - "host.docker.internal:host-gateway" + - 'host.docker.internal:host-gateway' depends_on: bootstrap: condition: service_completed_successfully @@ -98,13 +98,13 @@ services: - 6379:6379 command: [ - "redis-server", - "--appendonly", - "no", - "--maxmemory", - "500mb", - "--maxmemory-policy", - "noeviction", + 'redis-server', + '--appendonly', + 'no', + '--maxmemory', + '500mb', + '--maxmemory-policy', + 'noeviction' ] volumes: - redis-data:/data @@ -120,7 +120,7 @@ services: SRH_TOKEN: example_token SRH_CONNECTION_STRING: redis://redis:6379 maildev: - image: "maildev/maildev:latest" + image: 'maildev/maildev:latest' restart: unless-stopped ports: - 1080:1080 From 0a74f78b06ddeb8f98fd8542b761b6d057d2032f Mon Sep 17 00:00:00 2001 From: Siyang Date: Tue, 3 Feb 2026 22:40:45 +0000 Subject: [PATCH 05/13] refactor submit logic --- .../Screens/LanguageScreen/LanguageScreen.tsx | 165 ++++++++---------- 1 file changed, 77 insertions(+), 88 deletions(-) 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 fc4aa5e9b41..ec5d1cec75f 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 @@ -117,7 +117,7 @@ export function LanguageScreen({ } const validationSchema = object({ - teamSelect: string().required() + teamSelect: isSignedIn ? string().required() : string() }) const initialValues = { @@ -138,52 +138,88 @@ export function LanguageScreen({ const FORM_SM_BREAKPOINT_WIDTH = '390px' + async function createGuestUser(): Promise<{ teamId: string } | null> { + const isAnonymous = user?.firebaseUser?.isAnonymous ?? false + if (!isAnonymous) { + await signInAnonymously(getAuth(getApp())) + } + + const teamName = t('My Team') + const [meResult, profileResult, teamResult] = await Promise.all([ + loadUser(), + journeyProfileCreate(), + teamCreate({ + variables: { + input: { title: teamName, publicTitle: teamName } + } + }) + ]) + + if ( + meResult?.data?.me == null || + profileResult?.data?.journeyProfileCreate == null || + teamResult?.data?.teamCreate == null + ) { + return null + } + + return { teamId: teamResult.data.teamCreate.id } + } + + 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) { - console.log('isSignedIn', isSignedIn, loading) 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' } ) - setLoading(false) - - return + } else { + handleNext() } - await router.push( - `/templates/${duplicateData.journeyDuplicate.id}/customize`, - undefined, - { shallow: true } - ) - handleNext() setLoading(false) + return } else { - console.log('--------- HERE') - const isAnonymous = user?.firebaseUser?.isAnonymous ?? false - try { - if (!isAnonymous) { - await signInAnonymously(getAuth(getApp())) - } - const result = await loadUser() - if (result?.data?.me == null) { + const guestResult = await createGuestUser() + if (guestResult == null) { enqueueSnackbar( t('Unable to continue as guest. Please try again or sign in.'), { variant: 'error' } @@ -192,70 +228,26 @@ export function LanguageScreen({ return } - const teamName = t('My Team') - - const [profileResult, teamResult] = await Promise.all([ - journeyProfileCreate(), - teamCreate({ - variables: { - input: { - title: teamName, - publicTitle: teamName - } - } - }) - ]) - - if ( - profileResult?.data?.journeyProfileCreate == null || - teamResult?.data?.teamCreate == null - ) { - enqueueSnackbar( - t('Unable to continue as guest. Please try again or sign in.'), - { variant: 'error' } - ) - setLoading(false) - return - } - - const teamId = teamResult.data.teamCreate.id - 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 - } - - const { data: duplicateData } = await journeyDuplicate({ - variables: { id: journeyId, teamId, forceNonTemplate: true } - }) - if (duplicateData?.journeyDuplicate == null) { + 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' } ) - setLoading(false) - return + } else { + handleNext() } - - // await router.push( - // `/templates/${duplicateData.journeyDuplicate.id}/customize`, - // undefined, - // { shallow: true } - // ) - } catch (error) { + } catch { enqueueSnackbar( t('Unable to continue as guest. Please try again or sign in.'), { variant: 'error' } ) } finally { - handleNext() setLoading(false) } } @@ -345,11 +337,8 @@ export function LanguageScreen({ {isSignedIn && } { - console.log('loading') - handleSubmit() - }} + label={t('Next')} + onClick={() => handleSubmit()} disabled={loading} ariaLabel={t('Next')} /> From cdeaf6e14ee7dc860819e4afe4931dd22c578a9c Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Tue, 3 Feb 2026 22:44:27 +0000 Subject: [PATCH 06/13] fix: lint issues --- libs/locales/en/journeys-ui.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/libs/locales/en/journeys-ui.json b/libs/locales/en/journeys-ui.json index 2d075c1d18c..efc32e3c7b4 100644 --- a/libs/locales/en/journeys-ui.json +++ b/libs/locales/en/journeys-ui.json @@ -1,11 +1,10 @@ { - "Failed to duplicate journey to team, please refresh the page and try again": "Failed to duplicate journey to team, please refresh the page and try again", - "Unable to continue as guest. Please try again or sign in.": "Unable to continue as guest. Please try again or sign in.", "My Team": "My Team", + "Unable to continue as guest. Please try again or sign in.": "Unable to continue as guest. Please try again or sign in.", + "Failed to duplicate journey to team, please refresh the page and try again": "Failed to duplicate journey to team, please refresh the page and try again", "Let's get started!": "Let's get started!", "A few quick edits and your template will be ready to share.": "A few quick edits and your template will be ready to share.", "Select a language": "Select a language", "Select a team": "Select a team", - "Next12": "Next12", "Next": "Next" } From 5fca47673bb1baa1050bbfca3092f091f0b3a201 Mon Sep 17 00:00:00 2001 From: Siyang Date: Thu, 5 Feb 2026 05:08:05 +0000 Subject: [PATCH 07/13] create guest user --- .../Screens/LanguageScreen/LanguageScreen.tsx | 54 ++++++++++++------- .../UseThisTemplateButton.tsx | 12 ++--- 2 files changed, 42 insertions(+), 24 deletions(-) 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 ec5d1cec75f..0cf0ce6f660 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 @@ -28,15 +28,15 @@ import { CustomizeFlowNextButton } from '../../CustomizeFlowNextButton' import { JourneyCustomizeTeamSelect } from './JourneyCustomizeTeamSelect' -const JOURNEY_PROFILE_CREATE = gql` - mutation JourneyProfileCreate { - journeyProfileCreate { - id - userId - acceptedTermsAt - } - } -` +// const JOURNEY_PROFILE_CREATE = gql` +// mutation JourneyProfileCreate { +// journeyProfileCreate { +// id +// userId +// acceptedTermsAt +// } +// } +// ` interface LanguageScreenProps { handleNext: () => void @@ -59,6 +59,7 @@ export function LanguageScreen({ 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) @@ -131,9 +132,9 @@ export function LanguageScreen({ const [journeyDuplicate] = useJourneyDuplicateMutation() const { loadUser } = useCurrentUserLazyQuery() - const [journeyProfileCreate] = useMutation( - JOURNEY_PROFILE_CREATE - ) + // const [journeyProfileCreate] = useMutation( + // JOURNEY_PROFILE_CREATE + // ) const [teamCreate] = useTeamCreateMutation() const FORM_SM_BREAKPOINT_WIDTH = '390px' @@ -145,19 +146,36 @@ export function LanguageScreen({ } const teamName = t('My Team') - const [meResult, profileResult, teamResult] = await Promise.all([ - loadUser(), - journeyProfileCreate(), - teamCreate({ + + let meResult: Awaited> | null = null + try { + meResult = await loadUser() + } 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 { + teamResult = await teamCreate({ variables: { input: { title: teamName, publicTitle: teamName } } }) - ]) + } catch (e) { + console.error('[createGuestUser] teamCreate failed:', e) + } if ( meResult?.data?.me == null || - profileResult?.data?.journeyProfileCreate == null || + // profileResult?.data?.journeyProfileCreate == null || teamResult?.data?.teamCreate == null ) { return null 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 0ca209f7bb3318cf42f4285b0a0a52a047f28c52 Mon Sep 17 00:00:00 2001 From: Siyang Date: Thu, 5 Feb 2026 05:08:20 +0000 Subject: [PATCH 08/13] backend fix --- apis/api-gateway/schema.graphql | 2 +- .../modules/journey/journey.resolver.spec.ts | 13 +- .../app/modules/journey/journey.resolver.ts | 8 +- .../src/app/modules/team/team.resolver.ts | 10 +- apis/api-users/schema.graphql | 2 +- .../src/schema/user/findOrFetchUser.ts | 34 +++--- .../api-users/src/schema/user/objects/user.ts | 57 +++++++-- apis/api-users/src/schema/user/user.ts | 23 ++-- .../Screens/LanguageScreen/LanguageScreen.tsx | 115 ++++++++++++++---- .../src/libs/useTeamCreateMutation/index.ts | 5 +- .../useTeamCreateMutation.tsx | 61 ++++++++++ 11 files changed, 256 insertions(+), 74 deletions(-) diff --git a/apis/api-gateway/schema.graphql b/apis/api-gateway/schema.graphql index f85a3774b90..69ac3bf7718 100644 --- a/apis/api-gateway/schema.graphql +++ b/apis/api-gateway/schema.graphql @@ -2189,7 +2189,7 @@ type AuthenticatedUser @join__type(graph: API_JOURNEYS, key: "id", extension: tr lastName: String @join__field(graph: API_USERS) email: String! @join__field(graph: API_USERS) imageUrl: String @join__field(graph: API_USERS) - superAdmin: Boolean @join__field(graph: API_USERS) + superAdmin: Boolean! @join__field(graph: API_USERS) emailVerified: Boolean! @join__field(graph: API_USERS) } 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..f96714cb9d9 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 @@ -474,10 +474,10 @@ describe('JourneyResolver', () => { }) }) - it('should throw error if profile not found', async () => { + it('should not throw when profile not found (e.g. new guest); uses default filter', async () => { prismaService.journeyProfile.findUnique.mockResolvedValue(null) - await expect( - resolver.adminJourneys( + expect( + await resolver.adminJourneys( 'userId', accessibleJourneys, undefined, @@ -485,7 +485,12 @@ describe('JourneyResolver', () => { undefined, true ) - ).rejects.toThrow('journey profile not found') + ).toEqual([journey]) + expect(prismaService.journey.findMany).toHaveBeenCalledWith({ + where: { + AND: [accessibleJourneys, journeysSharedWithMe] + } + }) }) }) 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..e61cedddd65 100644 --- a/apis/api-journeys/src/app/modules/journey/journey.resolver.ts +++ b/apis/api-journeys/src/app/modules/journey/journey.resolver.ts @@ -144,11 +144,9 @@ export class JourneyResolver { 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 + // No profile (e.g. new guest) → no last active team; continue without throwing + if (profile != null) + filter.teamId = profile.lastActiveTeamId ?? undefined } if (teamId != null) { filter.teamId = teamId diff --git a/apis/api-journeys/src/app/modules/team/team.resolver.ts b/apis/api-journeys/src/app/modules/team/team.resolver.ts index f1a9ab397cf..bb1bf3f346a 100644 --- a/apis/api-journeys/src/app/modules/team/team.resolver.ts +++ b/apis/api-journeys/src/app/modules/team/team.resolver.ts @@ -1,5 +1,5 @@ import { subject } from '@casl/ability' -import { UseGuards } from '@nestjs/common' +import { Logger, UseGuards } from '@nestjs/common' import { Args, Mutation, @@ -32,6 +32,8 @@ import { PrismaService } from '../../lib/prisma.service' @Resolver('Team') export class TeamResolver { + private readonly logger = new Logger(TeamResolver.name) + constructor(private readonly prismaService: PrismaService) {} @Query() @@ -72,9 +74,13 @@ export class TeamResolver { @CurrentUserId() userId: string, @Args('input') data ): Promise { - return await this.prismaService.team.create({ + // Debug: trace how far teamCreate gets (guards run before this) + this.logger.log(`[teamCreate] 1. resolver entered userId=${userId}`) + const team = await this.prismaService.team.create({ data: { ...data, userTeams: { create: { userId, role: 'manager' } } } }) + this.logger.log(`[teamCreate] 2. prisma create done teamId=${team.id}`) + return team } @Mutation() diff --git a/apis/api-users/schema.graphql b/apis/api-users/schema.graphql index 2ecc09c1355..5e3b66f469f 100644 --- a/apis/api-users/schema.graphql +++ b/apis/api-users/schema.graphql @@ -15,7 +15,7 @@ type AuthenticatedUser lastName: String email: String! imageUrl: String - superAdmin: Boolean + superAdmin: Boolean! emailVerified: Boolean! } diff --git a/apis/api-users/src/schema/user/findOrFetchUser.ts b/apis/api-users/src/schema/user/findOrFetchUser.ts index 339e29c40b8..1dce0a2a72e 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 - }, - data - }) - retry++ - } while (user == null && retry < 3) + } 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 + }) + 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/objects/user.ts b/apis/api-users/src/schema/user/objects/user.ts index 3c96f48d62d..64f4606af63 100644 --- a/apis/api-users/src/schema/user/objects/user.ts +++ b/apis/api-users/src/schema/user/objects/user.ts @@ -13,30 +13,63 @@ export type UserShape = PrismaUser | AnonymousUserShape export const AuthenticatedUser = builder.prismaObject('User', { name: 'AuthenticatedUser', fields: (t) => ({ - id: t.exposeID('id', { nullable: false }), + id: t.field({ + type: 'ID', + nullable: false, + resolve: (user) => { + if (user == null) return '' + if ('userId' in user && user.userId != null) return user.userId + if ('id' in user && user.id != null) return String(user.id) + return '' + } + }), firstName: t.field({ type: 'String', nullable: false, resolve: (user) => { - // Additional safeguard for firstName field - if (!user.firstName || user.firstName.trim() === '') { - console.warn( - `User ${user.userId} has invalid firstName: "${user.firstName}", using fallback` - ) + const name = + user != null && 'firstName' in user ? user.firstName : undefined + if (name == null || (typeof name === 'string' && name.trim() === '')) { + if (user != null && 'userId' in user) { + console.warn( + `User ${(user as { userId: string }).userId} has invalid firstName: "${name}", using fallback` + ) + } return 'Unknown User' } - return user.firstName + return name } }), - lastName: t.exposeString('lastName'), + lastName: t.field({ + type: 'String', + nullable: true, + resolve: (user) => + user != null && 'lastName' in user ? user.lastName ?? null : null + }), email: t.field({ type: 'String', nullable: false, - resolve: (user) => user.email ?? '' + resolve: (user) => + user != null && 'email' in user ? (user.email ?? '') : '' }), - imageUrl: t.exposeString('imageUrl'), - superAdmin: t.exposeBoolean('superAdmin'), - emailVerified: t.exposeBoolean('emailVerified', { nullable: false }) + imageUrl: t.field({ + type: 'String', + nullable: true, + resolve: (user) => + user != null && 'imageUrl' in user ? user.imageUrl ?? null : null + }), + superAdmin: t.field({ + type: 'Boolean', + nullable: false, + resolve: (user) => + user != null && 'superAdmin' in user ? user.superAdmin === true : false + }), + emailVerified: t.field({ + type: 'Boolean', + nullable: false, + resolve: (user) => + user != null && 'emailVerified' in user ? user.emailVerified === true : false + }) }) }) diff --git a/apis/api-users/src/schema/user/user.ts b/apis/api-users/src/schema/user/user.ts index a0077b095fd..1e8cf83ffc2 100644 --- a/apis/api-users/src/schema/user/user.ts +++ b/apis/api-users/src/schema/user/user.ts @@ -15,27 +15,28 @@ builder.asEntity(AuthenticatedUser, { key: builder.selection<{ id: string }>('id'), resolveReference: async ({ id }) => { try { - const user = await prisma.user.findUnique({ where: { userId: id } }) + let user = await prisma.user.findUnique({ where: { userId: id } }) - // Handle cases where user doesn't exist + // Create user on first reference (e.g. after teamCreate before me was called) if (user == null) { - console.warn(`Federation: User not found for userId: ${id}`) - return null + user = await findOrFetchUser({}, id, undefined) + if (user == null) { + console.warn(`Federation: User not found for userId: ${id}`) + return null + } } - // Handle cases where firstName is null or empty (data integrity issue) - // This provides a fallback to prevent GraphQL federation errors + // Use userId (Firebase uid) as entity id so gateway re-resolution works + const entity = { ...user, id: user.userId } + if (user.firstName == null || user.firstName.trim() === '') { console.warn( `Federation: User ${id} has null/empty firstName, using fallback` ) - return { - ...user, - firstName: 'Unknown User' - } + return { ...entity, firstName: 'Unknown User' } } - return user + return entity } catch (error) { console.error( `Federation: Error resolving User entity for userId: ${id}`, 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 0cf0ce6f660..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 @@ -22,7 +22,10 @@ import { JourneyProfileCreate } from '../../../../../../__generated__/JourneyPro import { useCurrentUserLazyQuery } from '../../../../../libs/useCurrentUserLazyQuery' import { useGetChildTemplateJourneyLanguages } from '../../../../../libs/useGetChildTemplateJourneyLanguages' import { useGetParentTemplateJourneyLanguages } from '../../../../../libs/useGetParentTemplateJourneyLanguages' -import { useTeamCreateMutation } from '../../../../../libs/useTeamCreateMutation' +import { + useTeamCreateMutation, + useTeamCreateMutationGuest +} from '../../../../../libs/useTeamCreateMutation' import { CustomizationScreen } from '../../../utils/getCustomizeFlowConfig' import { CustomizeFlowNextButton } from '../../CustomizeFlowNextButton' @@ -136,20 +139,34 @@ export function LanguageScreen({ // JOURNEY_PROFILE_CREATE // ) const [teamCreate] = useTeamCreateMutation() + const [teamCreateGuest] = useTeamCreateMutationGuest() const FORM_SM_BREAKPOINT_WIDTH = '390px' async function createGuestUser(): Promise<{ teamId: string } | null> { - const isAnonymous = user?.firebaseUser?.isAnonymous ?? false - if (!isAnonymous) { - await signInAnonymously(getAuth(getApp())) - } + 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') + 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) } @@ -164,24 +181,67 @@ export function LanguageScreen({ let teamResult: Awaited> | null = null try { - teamResult = await teamCreate({ + 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) { - console.error('[createGuestUser] teamCreate failed:', 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 ( - meResult?.data?.me == null || - // profileResult?.data?.journeyProfileCreate == null || - teamResult?.data?.teamCreate == null - ) { + if (teamResult?.data?.teamCreate == null) { + console.log('[createGuestUser] 8. returning null (team missing)', { + hasMe: meResult?.data?.me != null, + hasTeam: false + }) return null } - return { teamId: teamResult.data.teamCreate.id } + // 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( @@ -246,21 +306,32 @@ export function LanguageScreen({ return } - const success = await duplicateJourneyAndRedirect( - journeyId, - guestResult.teamId - ) - if (!success) { + 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' } ) - } else { - handleNext() } - } catch { + } catch (e) { + console.error('[LanguageScreen] createGuestUser error:', e) enqueueSnackbar( t('Unable to continue as guest. Please try again or sign in.'), { variant: 'error' } 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 c15f022cb71eedaec76e477cb131b91e3534354f Mon Sep 17 00:00:00 2001 From: Siyang Date: Sun, 8 Feb 2026 23:59:18 +0000 Subject: [PATCH 09/13] remove redundant changes --- apis/api-gateway/schema.graphql | 2 +- .../src/app/modules/team/team.resolver.ts | 10 +--- apis/api-users/schema.graphql | 2 +- .../api-users/src/schema/user/objects/user.ts | 57 ++++--------------- apis/api-users/src/schema/user/user.ts | 15 ++--- 5 files changed, 22 insertions(+), 64 deletions(-) diff --git a/apis/api-gateway/schema.graphql b/apis/api-gateway/schema.graphql index 78dab851f21..b18baf32469 100644 --- a/apis/api-gateway/schema.graphql +++ b/apis/api-gateway/schema.graphql @@ -2180,7 +2180,7 @@ type AuthenticatedUser @join__type(graph: API_JOURNEYS, key: "id", extension: tr lastName: String @join__field(graph: API_USERS) email: String! @join__field(graph: API_USERS) imageUrl: String @join__field(graph: API_USERS) - superAdmin: Boolean! @join__field(graph: API_USERS) + superAdmin: Boolean @join__field(graph: API_USERS) emailVerified: Boolean! @join__field(graph: API_USERS) } diff --git a/apis/api-journeys/src/app/modules/team/team.resolver.ts b/apis/api-journeys/src/app/modules/team/team.resolver.ts index bb1bf3f346a..f1a9ab397cf 100644 --- a/apis/api-journeys/src/app/modules/team/team.resolver.ts +++ b/apis/api-journeys/src/app/modules/team/team.resolver.ts @@ -1,5 +1,5 @@ import { subject } from '@casl/ability' -import { Logger, UseGuards } from '@nestjs/common' +import { UseGuards } from '@nestjs/common' import { Args, Mutation, @@ -32,8 +32,6 @@ import { PrismaService } from '../../lib/prisma.service' @Resolver('Team') export class TeamResolver { - private readonly logger = new Logger(TeamResolver.name) - constructor(private readonly prismaService: PrismaService) {} @Query() @@ -74,13 +72,9 @@ export class TeamResolver { @CurrentUserId() userId: string, @Args('input') data ): Promise { - // Debug: trace how far teamCreate gets (guards run before this) - this.logger.log(`[teamCreate] 1. resolver entered userId=${userId}`) - const team = await this.prismaService.team.create({ + return await this.prismaService.team.create({ data: { ...data, userTeams: { create: { userId, role: 'manager' } } } }) - this.logger.log(`[teamCreate] 2. prisma create done teamId=${team.id}`) - return team } @Mutation() diff --git a/apis/api-users/schema.graphql b/apis/api-users/schema.graphql index 5e3b66f469f..2ecc09c1355 100644 --- a/apis/api-users/schema.graphql +++ b/apis/api-users/schema.graphql @@ -15,7 +15,7 @@ type AuthenticatedUser lastName: String email: String! imageUrl: String - superAdmin: Boolean! + superAdmin: Boolean emailVerified: Boolean! } diff --git a/apis/api-users/src/schema/user/objects/user.ts b/apis/api-users/src/schema/user/objects/user.ts index 64f4606af63..30c81a17715 100644 --- a/apis/api-users/src/schema/user/objects/user.ts +++ b/apis/api-users/src/schema/user/objects/user.ts @@ -13,63 +13,30 @@ export type UserShape = PrismaUser | AnonymousUserShape export const AuthenticatedUser = builder.prismaObject('User', { name: 'AuthenticatedUser', fields: (t) => ({ - id: t.field({ - type: 'ID', - nullable: false, - resolve: (user) => { - if (user == null) return '' - if ('userId' in user && user.userId != null) return user.userId - if ('id' in user && user.id != null) return String(user.id) - return '' - } - }), + id: t.exposeID('id', { nullable: false }), firstName: t.field({ type: 'String', nullable: false, resolve: (user) => { - const name = - user != null && 'firstName' in user ? user.firstName : undefined - if (name == null || (typeof name === 'string' && name.trim() === '')) { - if (user != null && 'userId' in user) { - console.warn( - `User ${(user as { userId: string }).userId} has invalid firstName: "${name}", using fallback` - ) - } + // Additional safeguard for firstName field + if (!user.firstName || user.firstName.trim() === '') { + console.warn( + `User ${user.userId} has invalid firstName: "${user.firstName}", using fallback` + ) return 'Unknown User' } - return name + return user.firstName } }), - lastName: t.field({ - type: 'String', - nullable: true, - resolve: (user) => - user != null && 'lastName' in user ? user.lastName ?? null : null - }), + lastName: t.exposeString('lastName', { nullable: true }), email: t.field({ type: 'String', nullable: false, - resolve: (user) => - user != null && 'email' in user ? (user.email ?? '') : '' + resolve: (user) => user.email ?? '' }), - imageUrl: t.field({ - type: 'String', - nullable: true, - resolve: (user) => - user != null && 'imageUrl' in user ? user.imageUrl ?? null : null - }), - superAdmin: t.field({ - type: 'Boolean', - nullable: false, - resolve: (user) => - user != null && 'superAdmin' in user ? user.superAdmin === true : false - }), - emailVerified: t.field({ - type: 'Boolean', - nullable: false, - resolve: (user) => - user != null && 'emailVerified' in user ? user.emailVerified === true : false - }) + imageUrl: t.exposeString('imageUrl'), + superAdmin: t.exposeBoolean('superAdmin'), + emailVerified: t.exposeBoolean('emailVerified', { nullable: false }) }) }) diff --git a/apis/api-users/src/schema/user/user.ts b/apis/api-users/src/schema/user/user.ts index 649fd986000..b89cae260be 100644 --- a/apis/api-users/src/schema/user/user.ts +++ b/apis/api-users/src/schema/user/user.ts @@ -18,24 +18,21 @@ builder.asEntity(AuthenticatedUser, { const user = await findOrFetchUser({}, id, undefined) if (user == null) { - user = await findOrFetchUser({}, id, undefined) - if (user == null) { - console.warn(`Federation: User not found for userId: ${id}`) - return null - } + console.warn(`Federation: User not found for userId: ${id}`) + return null } - // Use userId (Firebase uid) as entity id so gateway re-resolution works - const entity = { ...user, id: user.userId } + // Handle cases where firstName is null or empty (data integrity issue) + // This provides a fallback to prevent GraphQL federation errors if (user.firstName == null || user.firstName.trim() === '') { console.warn( `Federation: User ${id} has null/empty firstName, using fallback` ) - return { ...entity, firstName: 'Unknown User' } + return { ...user, firstName: 'Unknown User' } } - return entity + return user } catch (error) { console.error( `Federation: Error resolving User entity for userId: ${id}`, From ea5ab3ccdae86226064fe757e8ff96886da6f65e Mon Sep 17 00:00:00 2001 From: Siyang Date: Mon, 9 Feb 2026 00:00:49 +0000 Subject: [PATCH 10/13] remove some more changes --- apis/api-users/src/schema/user/objects/user.ts | 2 +- apis/api-users/src/schema/user/user.ts | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/apis/api-users/src/schema/user/objects/user.ts b/apis/api-users/src/schema/user/objects/user.ts index 30c81a17715..3c96f48d62d 100644 --- a/apis/api-users/src/schema/user/objects/user.ts +++ b/apis/api-users/src/schema/user/objects/user.ts @@ -28,7 +28,7 @@ export const AuthenticatedUser = builder.prismaObject('User', { return user.firstName } }), - lastName: t.exposeString('lastName', { nullable: true }), + lastName: t.exposeString('lastName'), email: t.field({ type: 'String', nullable: false, diff --git a/apis/api-users/src/schema/user/user.ts b/apis/api-users/src/schema/user/user.ts index b89cae260be..9923abfa7e3 100644 --- a/apis/api-users/src/schema/user/user.ts +++ b/apis/api-users/src/schema/user/user.ts @@ -24,12 +24,14 @@ builder.asEntity(AuthenticatedUser, { // Handle cases where firstName is null or empty (data integrity issue) // This provides a fallback to prevent GraphQL federation errors - if (user.firstName == null || user.firstName.trim() === '') { console.warn( `Federation: User ${id} has null/empty firstName, using fallback` ) - return { ...user, firstName: 'Unknown User' } + return { + ...user, + firstName: 'Unknown User' + } } return user From 1a496a794c9de5e51dc509717b91ff25fde26bfd Mon Sep 17 00:00:00 2001 From: Siyang Date: Mon, 9 Feb 2026 01:35:37 +0000 Subject: [PATCH 11/13] create journey as draft --- .../Screens/LanguageScreen/LanguageScreen.tsx | 201 +++--------------- .../src/libs/useTeamCreateMutation/index.ts | 5 +- .../useTeamCreateMutation.tsx | 61 ------ .../__generated__/JourneyDuplicate.ts | 3 + .../useJourneyDuplicateMutation.ts | 2 + 5 files changed, 40 insertions(+), 232 deletions(-) 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 1623aa955d7..73925d4e607 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 @@ -9,7 +9,7 @@ import { useRouter } from 'next/router' import { useUser } from 'next-firebase-auth' import { useTranslation } from 'next-i18next' import { useSnackbar } from 'notistack' -import { ReactElement, useEffect, useState } from 'react' +import { ReactElement, useState } from 'react' import { object } from 'yup' import { useJourney } from '@core/journeys/ui/JourneyProvider' @@ -19,36 +19,14 @@ import { useJourneyDuplicateMutation } from '@core/journeys/ui/useJourneyDuplica import { LanguageAutocomplete } from '@core/shared/ui/LanguageAutocomplete' import { JourneyProfileCreate } from '../../../../../../__generated__/JourneyProfileCreate' +import { JourneyStatus } from '../../../../../../__generated__/globalTypes' import { useCurrentUserLazyQuery } from '../../../../../libs/useCurrentUserLazyQuery' import { useGetChildTemplateJourneyLanguages } from '../../../../../libs/useGetChildTemplateJourneyLanguages' import { useGetParentTemplateJourneyLanguages } from '../../../../../libs/useGetParentTemplateJourneyLanguages' -import { - useTeamCreateMutation, - useTeamCreateMutationGuest -} from '../../../../../libs/useTeamCreateMutation' +import { useTeamCreateMutation } from '../../../../../libs/useTeamCreateMutation' import { CustomizationScreen } from '../../../utils/getCustomizeFlowConfig' import { CustomizeFlowNextButton } from '../../CustomizeFlowNextButton' -// const JOURNEY_PROFILE_CREATE = gql` -// mutation JourneyProfileCreate { -// journeyProfileCreate { -// id -// userId -// acceptedTermsAt -// } -// } -// ` - -// const JOURNEY_PROFILE_CREATE = gql` -// mutation JourneyProfileCreate { -// journeyProfileCreate { -// id -// userId -// acceptedTermsAt -// } -// } -// ` - interface LanguageScreenProps { handleNext: () => void handleScreenNavigation: (screen: CustomizationScreen) => void @@ -69,14 +47,6 @@ 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 { @@ -140,123 +110,40 @@ 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 - } + async function createGuestUser(): Promise<{ teamId: string }> { + const teamName = t('My Team') + const isAnonymous = user?.firebaseUser?.isAnonymous ?? false + if (!isAnonymous) { + await signInAnonymously(getAuth(getApp())) + } - // Refetch me after teamCreate (user was created in resolveReference); optional for guest flow - if (meResult?.data?.me == null) { - try { - await loadUser() - } catch { - // ignore + const [, teamResult] = await Promise.all([ + loadUser().catch(() => null), + teamCreate({ + variables: { + input: { title: teamName, publicTitle: teamName } } - } - - // 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 + ]) + + if (teamResult?.data?.teamCreate == null) { + throw new Error('Guest team creation returned no team') } + + return { teamId: teamResult.data.teamCreate.id } } async function duplicateJourneyAndRedirect( journeyId: string, - teamId: string + teamId: string, + status?: JourneyStatus ): Promise { const { data } = await journeyDuplicate({ - variables: { id: journeyId, teamId, forceNonTemplate: true } + variables: { id: journeyId, teamId, forceNonTemplate: true, status } }) if (data?.journeyDuplicate == null) return false @@ -287,6 +174,7 @@ export function LanguageScreen({ } if (isSignedIn) { + // Duplicates journey for a signed in user const teams = query?.data?.teams ?? [] const teamId = query?.data?.getJourneyProfile?.lastActiveTeamId ?? teams[0]?.id @@ -311,46 +199,25 @@ export function LanguageScreen({ setLoading(false) return } else { + // Creates a guest user and duplicates the journey for them 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 - ) + const journeyDuplicateSuccess = await duplicateJourneyAndRedirect( + journeyId, + guestResult.teamId, + JourneyStatus.draft + ) + if (!journeyDuplicateSuccess) { enqueueSnackbar( t( 'Failed to duplicate journey to team, please refresh the page and try again' ), { variant: 'error' } ) + } else { + handleNext() } - } catch (e) { - console.error('[LanguageScreen] createGuestUser error:', e) + } catch { enqueueSnackbar( t('Unable to continue as guest. Please try again or sign in.'), { variant: 'error' } diff --git a/apps/journeys-admin/src/libs/useTeamCreateMutation/index.ts b/apps/journeys-admin/src/libs/useTeamCreateMutation/index.ts index 01b7632c399..38ddb0db05b 100644 --- a/apps/journeys-admin/src/libs/useTeamCreateMutation/index.ts +++ b/apps/journeys-admin/src/libs/useTeamCreateMutation/index.ts @@ -1,4 +1 @@ -export { - useTeamCreateMutation, - useTeamCreateMutationGuest -} from './useTeamCreateMutation' +export { useTeamCreateMutation } from './useTeamCreateMutation' diff --git a/apps/journeys-admin/src/libs/useTeamCreateMutation/useTeamCreateMutation.tsx b/apps/journeys-admin/src/libs/useTeamCreateMutation/useTeamCreateMutation.tsx index 8a27f4257fa..fb2e66796ec 100644 --- a/apps/journeys-admin/src/libs/useTeamCreateMutation/useTeamCreateMutation.tsx +++ b/apps/journeys-admin/src/libs/useTeamCreateMutation/useTeamCreateMutation.tsx @@ -42,33 +42,6 @@ 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 { @@ -103,37 +76,3 @@ 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 - }) -} diff --git a/libs/journeys/ui/src/libs/useJourneyDuplicateMutation/__generated__/JourneyDuplicate.ts b/libs/journeys/ui/src/libs/useJourneyDuplicateMutation/__generated__/JourneyDuplicate.ts index 7724c5a5900..762ef6e2b24 100644 --- a/libs/journeys/ui/src/libs/useJourneyDuplicateMutation/__generated__/JourneyDuplicate.ts +++ b/libs/journeys/ui/src/libs/useJourneyDuplicateMutation/__generated__/JourneyDuplicate.ts @@ -3,6 +3,8 @@ // @generated // This file was automatically generated and should not be edited. +import { JourneyStatus } from '../../../../__generated__/globalTypes'; + // ==================================================== // GraphQL mutation operation: JourneyDuplicate // ==================================================== @@ -21,4 +23,5 @@ export interface JourneyDuplicateVariables { id: string; teamId: string; forceNonTemplate?: boolean | null; + status?: JourneyStatus | null; } diff --git a/libs/journeys/ui/src/libs/useJourneyDuplicateMutation/useJourneyDuplicateMutation.ts b/libs/journeys/ui/src/libs/useJourneyDuplicateMutation/useJourneyDuplicateMutation.ts index de4bc675056..c9d5c5ee5e6 100644 --- a/libs/journeys/ui/src/libs/useJourneyDuplicateMutation/useJourneyDuplicateMutation.ts +++ b/libs/journeys/ui/src/libs/useJourneyDuplicateMutation/useJourneyDuplicateMutation.ts @@ -15,11 +15,13 @@ export const JOURNEY_DUPLICATE = gql` $id: ID! $teamId: ID! $forceNonTemplate: Boolean + $status: JourneyStatus ) { journeyDuplicate( id: $id teamId: $teamId forceNonTemplate: $forceNonTemplate + status: $status ) { id template From 1e28c1f05daac7986ce833ac85ed746916a23674 Mon Sep 17 00:00:00 2001 From: Siyang Date: Mon, 9 Feb 2026 01:36:41 +0000 Subject: [PATCH 12/13] chore: remove guest manual bypass --- .../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 9f2d29d3c23..7b16534ee41 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 1ad922cc88a639aff1b3ba1e3cbe883f4e18acef Mon Sep 17 00:00:00 2001 From: Siyang Date: Mon, 9 Feb 2026 02:18:09 +0000 Subject: [PATCH 13/13] refactor: draft duplicate --- apps/journeys-admin/__generated__/GetAdminJourneys.ts | 6 ------ apps/journeys-admin/__generated__/JourneyDuplicate.ts | 1 + .../Screens/LanguageScreen/LanguageScreen.tsx | 11 ++++++++--- .../__generated__/JourneyDuplicate.ts | 4 +--- .../useJourneyDuplicateMutation.ts | 4 ++-- 5 files changed, 12 insertions(+), 14 deletions(-) diff --git a/apps/journeys-admin/__generated__/GetAdminJourneys.ts b/apps/journeys-admin/__generated__/GetAdminJourneys.ts index 014c09aa5b4..3f904fb27b0 100644 --- a/apps/journeys-admin/__generated__/GetAdminJourneys.ts +++ b/apps/journeys-admin/__generated__/GetAdminJourneys.ts @@ -103,12 +103,6 @@ export interface GetAdminJourneys_journeys { } export interface GetAdminJourneys { - /** - * 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 - */ journeys: GetAdminJourneys_journeys[]; } diff --git a/apps/journeys-admin/__generated__/JourneyDuplicate.ts b/apps/journeys-admin/__generated__/JourneyDuplicate.ts index 7724c5a5900..0f6479f1bec 100644 --- a/apps/journeys-admin/__generated__/JourneyDuplicate.ts +++ b/apps/journeys-admin/__generated__/JourneyDuplicate.ts @@ -21,4 +21,5 @@ export interface JourneyDuplicateVariables { id: string; teamId: string; forceNonTemplate?: boolean | null; + duplicateAsDraft?: boolean | null; } 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 73925d4e607..53bef70f5e9 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 @@ -140,10 +140,15 @@ export function LanguageScreen({ async function duplicateJourneyAndRedirect( journeyId: string, teamId: string, - status?: JourneyStatus + duplicateAsDraft?: boolean ): Promise { const { data } = await journeyDuplicate({ - variables: { id: journeyId, teamId, forceNonTemplate: true, status } + variables: { + id: journeyId, + teamId, + forceNonTemplate: true, + duplicateAsDraft + } }) if (data?.journeyDuplicate == null) return false @@ -205,7 +210,7 @@ export function LanguageScreen({ const journeyDuplicateSuccess = await duplicateJourneyAndRedirect( journeyId, guestResult.teamId, - JourneyStatus.draft + true ) if (!journeyDuplicateSuccess) { enqueueSnackbar( diff --git a/libs/journeys/ui/src/libs/useJourneyDuplicateMutation/__generated__/JourneyDuplicate.ts b/libs/journeys/ui/src/libs/useJourneyDuplicateMutation/__generated__/JourneyDuplicate.ts index 762ef6e2b24..0f6479f1bec 100644 --- a/libs/journeys/ui/src/libs/useJourneyDuplicateMutation/__generated__/JourneyDuplicate.ts +++ b/libs/journeys/ui/src/libs/useJourneyDuplicateMutation/__generated__/JourneyDuplicate.ts @@ -3,8 +3,6 @@ // @generated // This file was automatically generated and should not be edited. -import { JourneyStatus } from '../../../../__generated__/globalTypes'; - // ==================================================== // GraphQL mutation operation: JourneyDuplicate // ==================================================== @@ -23,5 +21,5 @@ export interface JourneyDuplicateVariables { id: string; teamId: string; forceNonTemplate?: boolean | null; - status?: JourneyStatus | null; + duplicateAsDraft?: boolean | null; } diff --git a/libs/journeys/ui/src/libs/useJourneyDuplicateMutation/useJourneyDuplicateMutation.ts b/libs/journeys/ui/src/libs/useJourneyDuplicateMutation/useJourneyDuplicateMutation.ts index c9d5c5ee5e6..40b3bb5b446 100644 --- a/libs/journeys/ui/src/libs/useJourneyDuplicateMutation/useJourneyDuplicateMutation.ts +++ b/libs/journeys/ui/src/libs/useJourneyDuplicateMutation/useJourneyDuplicateMutation.ts @@ -15,13 +15,13 @@ export const JOURNEY_DUPLICATE = gql` $id: ID! $teamId: ID! $forceNonTemplate: Boolean - $status: JourneyStatus + $duplicateAsDraft: Boolean ) { journeyDuplicate( id: $id teamId: $teamId forceNonTemplate: $forceNonTemplate - status: $status + duplicateAsDraft: $duplicateAsDraft ) { id template