diff --git a/packages/twenty-front/src/modules/app/components/AppRouterProviders.tsx b/packages/twenty-front/src/modules/app/components/AppRouterProviders.tsx index 16eb64bde..a63f7f575 100644 --- a/packages/twenty-front/src/modules/app/components/AppRouterProviders.tsx +++ b/packages/twenty-front/src/modules/app/components/AppRouterProviders.tsx @@ -16,8 +16,8 @@ import { PrefetchDataProvider } from '@/prefetch/components/PrefetchDataProvider import { DialogManager } from '@/ui/feedback/dialog-manager/components/DialogManager'; import { DialogManagerScope } from '@/ui/feedback/dialog-manager/scopes/DialogManagerScope'; import { SnackBarProvider } from '@/ui/feedback/snack-bar-manager/components/SnackBarProvider'; -import { UserThemeProviderEffect } from '@/ui/theme/components/AppThemeProvider'; import { BaseThemeProvider } from '@/ui/theme/components/BaseThemeProvider'; +import { UserThemeProviderEffect } from '@/ui/theme/components/UserThemeProviderEffect'; import { PageFavicon } from '@/ui/utilities/page-favicon/components/PageFavicon'; import { PageTitle } from '@/ui/utilities/page-title/components/PageTitle'; import { ServerPreconnect } from '@/ui/utilities/server-preconnect/components/ServerPreconnect'; diff --git a/packages/twenty-front/src/modules/app/effect-components/PageChangeEffect.tsx b/packages/twenty-front/src/modules/app/effect-components/PageChangeEffect.tsx index 7ebbe1ada..f94017524 100644 --- a/packages/twenty-front/src/modules/app/effect-components/PageChangeEffect.tsx +++ b/packages/twenty-front/src/modules/app/effect-components/PageChangeEffect.tsx @@ -4,6 +4,7 @@ import { useLocation, useNavigate, useParams, + useSearchParams, } from 'react-router-dom'; import { useRecoilValue } from 'recoil'; @@ -13,6 +14,7 @@ import { } from '@/analytics/hooks/useEventTracker'; import { useRequestFreshCaptchaToken } from '@/captcha/hooks/useRequestFreshCaptchaToken'; import { isCaptchaScriptLoadedState } from '@/captcha/states/isCaptchaScriptLoadedState'; +import { isCaptchaRequiredForPath } from '@/captcha/utils/isCaptchaRequiredForPath'; import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural'; import { useResetTableRowSelection } from '@/object-record/record-table/hooks/internal/useResetTableRowSelection'; import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope'; @@ -21,10 +23,9 @@ import { AppPath } from '@/types/AppPath'; import { PageHotkeyScope } from '@/types/PageHotkeyScope'; import { SettingsPath } from '@/types/SettingsPath'; import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope'; +import { isDefined } from 'twenty-shared/utils'; import { useIsMatchingLocation } from '~/hooks/useIsMatchingLocation'; import { usePageChangeEffectNavigateLocation } from '~/hooks/usePageChangeEffectNavigateLocation'; -import { isCaptchaRequiredForPath } from '@/captcha/utils/isCaptchaRequiredForPath'; -import { isDefined } from 'twenty-shared/utils'; // TODO: break down into smaller functions and / or hooks // - moved usePageChangeEffectNavigateLocation into dedicated hook @@ -58,11 +59,16 @@ export const PageChangeEffect = () => { } }, [location, previousLocation]); + const [searchParams] = useSearchParams(); + const navigationParams = searchParams.get('animateModal') + ? `?animateModal=${searchParams.get('animateModal')}` + : ''; + useEffect(() => { if (isDefined(pageChangeEffectNavigateLocation)) { - navigate(pageChangeEffectNavigateLocation); + navigate(pageChangeEffectNavigateLocation + navigationParams); } - }, [navigate, pageChangeEffectNavigateLocation]); + }, [navigate, pageChangeEffectNavigateLocation, navigationParams]); useEffect(() => { const isLeavingRecordIndexPage = !!matchPath( diff --git a/packages/twenty-front/src/modules/app/hooks/useCreateAppRouter.tsx b/packages/twenty-front/src/modules/app/hooks/useCreateAppRouter.tsx index f2ef1bec8..d0ff55684 100644 --- a/packages/twenty-front/src/modules/app/hooks/useCreateAppRouter.tsx +++ b/packages/twenty-front/src/modules/app/hooks/useCreateAppRouter.tsx @@ -1,12 +1,13 @@ import { AppRouterProviders } from '@/app/components/AppRouterProviders'; import { SettingsRoutes } from '@/app/components/SettingsRoutes'; +import { Verify } from '@/auth/components/Verify'; -import { VerifyEffect } from '@/auth/components/VerifyEffect'; import { VerifyEmailEffect } from '@/auth/components/VerifyEmailEffect'; import indexAppPath from '@/navigation/utils/indexAppPath'; import { AppPath } from '@/types/AppPath'; import { BlankLayout } from '@/ui/layout/page/components/BlankLayout'; import { DefaultLayout } from '@/ui/layout/page/components/DefaultLayout'; + import { createBrowserRouter, createRoutesFromElements, @@ -38,7 +39,7 @@ export const useCreateAppRouter = ( loader={async () => Promise.resolve(null)} > }> - } /> + } /> } /> } /> } /> diff --git a/packages/twenty-front/src/modules/auth/components/AuthModal.tsx b/packages/twenty-front/src/modules/auth/components/AuthModal.tsx index e50386d83..97734af3a 100644 --- a/packages/twenty-front/src/modules/auth/components/AuthModal.tsx +++ b/packages/twenty-front/src/modules/auth/components/AuthModal.tsx @@ -8,10 +8,20 @@ const StyledContent = styled(Modal.Content)` justify-content: center; `; -type AuthModalProps = { children: React.ReactNode }; +type AuthModalProps = { + children: React.ReactNode; + isOpenAnimated?: boolean; +}; -export const AuthModal = ({ children }: AuthModalProps) => ( - +export const AuthModal = ({ + children, + isOpenAnimated = true, +}: AuthModalProps) => ( + {children} diff --git a/packages/twenty-front/src/modules/auth/components/VerifyEffect.tsx b/packages/twenty-front/src/modules/auth/components/Verify.tsx similarity index 91% rename from packages/twenty-front/src/modules/auth/components/VerifyEffect.tsx rename to packages/twenty-front/src/modules/auth/components/Verify.tsx index f5ecefb0a..89f930b96 100644 --- a/packages/twenty-front/src/modules/auth/components/VerifyEffect.tsx +++ b/packages/twenty-front/src/modules/auth/components/Verify.tsx @@ -6,10 +6,11 @@ import { useVerifyLogin } from '@/auth/hooks/useVerifyLogin'; import { AppPath } from '@/types/AppPath'; import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; -import { useNavigateApp } from '~/hooks/useNavigateApp'; import { isDefined } from 'twenty-shared/utils'; +import { useNavigateApp } from '~/hooks/useNavigateApp'; +import { SignInUpLoading } from '~/pages/auth/SignInUpLoading'; -export const VerifyEffect = () => { +export const Verify = () => { const [searchParams] = useSearchParams(); const loginToken = searchParams.get('loginToken'); const errorMessage = searchParams.get('errorMessage'); @@ -36,5 +37,5 @@ export const VerifyEffect = () => { // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - return <>; + return ; }; diff --git a/packages/twenty-front/src/modules/auth/components/VerifyEmailEffect.tsx b/packages/twenty-front/src/modules/auth/components/VerifyEmailEffect.tsx index 991b552a6..066e25cd1 100644 --- a/packages/twenty-front/src/modules/auth/components/VerifyEmailEffect.tsx +++ b/packages/twenty-front/src/modules/auth/components/VerifyEmailEffect.tsx @@ -3,14 +3,14 @@ import { AppPath } from '@/types/AppPath'; import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; +import { useVerifyLogin } from '@/auth/hooks/useVerifyLogin'; +import { useRedirectToWorkspaceDomain } from '@/domain-manager/hooks/useRedirectToWorkspaceDomain'; import { useLingui } from '@lingui/react/macro'; import { useEffect, useState } from 'react'; import { useSearchParams } from 'react-router-dom'; import { useNavigateApp } from '~/hooks/useNavigateApp'; -import { EmailVerificationSent } from '../sign-in-up/components/EmailVerificationSent'; -import { useRedirectToWorkspaceDomain } from '@/domain-manager/hooks/useRedirectToWorkspaceDomain'; -import { useVerifyLogin } from '@/auth/hooks/useVerifyLogin'; import { getWorkspaceUrl } from '~/utils/getWorkspaceUrl'; +import { EmailVerificationSent } from '../sign-in-up/components/EmailVerificationSent'; export const VerifyEmailEffect = () => { const { getLoginTokenFromEmailVerificationToken } = useAuth(); @@ -50,6 +50,7 @@ export const VerifyEmailEffect = () => { if (workspaceUrl.slice(0, -1) !== window.location.origin) { return redirectToWorkspaceDomain(workspaceUrl, AppPath.Verify, { loginToken: loginToken.token, + animateModal: false, }); } verifyLoginToken(loginToken.token); diff --git a/packages/twenty-front/src/modules/auth/hooks/useAuth.ts b/packages/twenty-front/src/modules/auth/hooks/useAuth.ts index 64bf4eab1..7edee4a62 100644 --- a/packages/twenty-front/src/modules/auth/hooks/useAuth.ts +++ b/packages/twenty-front/src/modules/auth/hooks/useAuth.ts @@ -416,6 +416,7 @@ export const useAuth = () => { { ...(!isEmailVerificationRequired && { loginToken: signUpResult.data.signUp.loginToken.token, + animateModal: false, }), email, }, diff --git a/packages/twenty-front/src/modules/client-config/components/ClientConfigProvider.tsx b/packages/twenty-front/src/modules/client-config/components/ClientConfigProvider.tsx index f88b9652f..4ff304e77 100644 --- a/packages/twenty-front/src/modules/client-config/components/ClientConfigProvider.tsx +++ b/packages/twenty-front/src/modules/client-config/components/ClientConfigProvider.tsx @@ -2,6 +2,8 @@ import { useRecoilValue } from 'recoil'; import { clientConfigApiStatusState } from '@/client-config/states/clientConfigApiStatusState'; import { AppFullScreenErrorFallback } from '@/error-handler/components/AppFullScreenErrorFallback'; +import { AppPath } from '@/types/AppPath'; +import { useIsMatchingLocation } from '~/hooks/useIsMatchingLocation'; export const ClientConfigProvider: React.FC = ({ children, @@ -10,8 +12,15 @@ export const ClientConfigProvider: React.FC = ({ clientConfigApiStatusState, ); + const { isMatchingLocation } = useIsMatchingLocation(); + // TODO: Implement a better loading strategy - if (!isLoaded) return null; + if ( + !isLoaded && + !isMatchingLocation(AppPath.Verify) && + !isMatchingLocation(AppPath.VerifyEmail) + ) + return null; return isErrored && error instanceof Error ? ( { const objectMetadataItems = useRecoilValue(objectMetadataItemsState); - const shouldDisplayChildren = objectMetadataItems.length > 0; return ( diff --git a/packages/twenty-front/src/modules/settings/admin-panel/hooks/useImpersonationRedirect.ts b/packages/twenty-front/src/modules/settings/admin-panel/hooks/useImpersonationRedirect.ts index 437873821..6bcf03f4a 100644 --- a/packages/twenty-front/src/modules/settings/admin-panel/hooks/useImpersonationRedirect.ts +++ b/packages/twenty-front/src/modules/settings/admin-panel/hooks/useImpersonationRedirect.ts @@ -13,7 +13,7 @@ export const useImpersonationRedirect = () => { return redirectToWorkspaceDomain( getWorkspaceUrl(workspaceUrls), AppPath.Verify, - { loginToken }, + { loginToken, animateModal: false }, ); }; diff --git a/packages/twenty-front/src/modules/ui/layout/hooks/__tests__/useShowAuthModal.test.tsx b/packages/twenty-front/src/modules/ui/layout/hooks/__tests__/useShowAuthModal.test.tsx index 613192b41..04ae0d4d8 100644 --- a/packages/twenty-front/src/modules/ui/layout/hooks/__tests__/useShowAuthModal.test.tsx +++ b/packages/twenty-front/src/modules/ui/layout/hooks/__tests__/useShowAuthModal.test.tsx @@ -56,16 +56,16 @@ const getResult = (isDefaultLayoutAuthModalVisible = true) => // prettier-ignore const testCases = [ - { loc: AppPath.Verify, isLogged: true, subscriptionStatus: undefined, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: false }, - { loc: AppPath.Verify, isLogged: true, subscriptionStatus: SubscriptionStatus.Canceled, onboardingStatus: OnboardingStatus.COMPLETED, res: false }, - { loc: AppPath.Verify, isLogged: true, subscriptionStatus: SubscriptionStatus.Unpaid, onboardingStatus: OnboardingStatus.COMPLETED, res: false }, - { loc: AppPath.Verify, isLogged: true, subscriptionStatus: SubscriptionStatus.PastDue, onboardingStatus: OnboardingStatus.COMPLETED, res: false }, - { loc: AppPath.Verify, isLogged: false, subscriptionStatus: undefined, onboardingStatus: undefined, res: false }, - { loc: AppPath.Verify, isLogged: true, subscriptionStatus: SubscriptionStatus.Active, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: false }, - { loc: AppPath.Verify, isLogged: true, subscriptionStatus: SubscriptionStatus.Active, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: false }, - { loc: AppPath.Verify, isLogged: true, subscriptionStatus: SubscriptionStatus.Active, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: false }, - { loc: AppPath.Verify, isLogged: true, subscriptionStatus: SubscriptionStatus.Active, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: false }, - { loc: AppPath.Verify, isLogged: true, subscriptionStatus: SubscriptionStatus.Active, onboardingStatus: OnboardingStatus.COMPLETED, res: false }, + { loc: AppPath.Verify, isLogged: true, subscriptionStatus: undefined, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: true }, + { loc: AppPath.Verify, isLogged: true, subscriptionStatus: SubscriptionStatus.Canceled, onboardingStatus: OnboardingStatus.COMPLETED, res: true }, + { loc: AppPath.Verify, isLogged: true, subscriptionStatus: SubscriptionStatus.Unpaid, onboardingStatus: OnboardingStatus.COMPLETED, res: true }, + { loc: AppPath.Verify, isLogged: true, subscriptionStatus: SubscriptionStatus.PastDue, onboardingStatus: OnboardingStatus.COMPLETED, res: true }, + { loc: AppPath.Verify, isLogged: false, subscriptionStatus: undefined, onboardingStatus: undefined, res: true }, + { loc: AppPath.Verify, isLogged: true, subscriptionStatus: SubscriptionStatus.Active, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: true }, + { loc: AppPath.Verify, isLogged: true, subscriptionStatus: SubscriptionStatus.Active, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: true }, + { loc: AppPath.Verify, isLogged: true, subscriptionStatus: SubscriptionStatus.Active, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: true }, + { loc: AppPath.Verify, isLogged: true, subscriptionStatus: SubscriptionStatus.Active, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: true }, + { loc: AppPath.Verify, isLogged: true, subscriptionStatus: SubscriptionStatus.Active, onboardingStatus: OnboardingStatus.COMPLETED, res: true }, { loc: AppPath.VerifyEmail, isLogged: true, subscriptionStatus: undefined, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: true }, { loc: AppPath.VerifyEmail, isLogged: true, subscriptionStatus: SubscriptionStatus.Canceled, onboardingStatus: OnboardingStatus.COMPLETED, res: true }, diff --git a/packages/twenty-front/src/modules/ui/layout/hooks/useShowAuthModal.ts b/packages/twenty-front/src/modules/ui/layout/hooks/useShowAuthModal.ts index 39e59630f..cfcf3af19 100644 --- a/packages/twenty-front/src/modules/ui/layout/hooks/useShowAuthModal.ts +++ b/packages/twenty-front/src/modules/ui/layout/hooks/useShowAuthModal.ts @@ -6,9 +6,9 @@ import { useOnboardingStatus } from '@/onboarding/hooks/useOnboardingStatus'; import { AppPath } from '@/types/AppPath'; import { isDefaultLayoutAuthModalVisibleState } from '@/ui/layout/states/isDefaultLayoutAuthModalVisibleState'; import { useSubscriptionStatus } from '@/workspace/hooks/useSubscriptionStatus'; +import { isDefined } from 'twenty-shared/utils'; import { OnboardingStatus, SubscriptionStatus } from '~/generated/graphql'; import { useIsMatchingLocation } from '~/hooks/useIsMatchingLocation'; -import { isDefined } from 'twenty-shared/utils'; export const useShowAuthModal = () => { const { isMatchingLocation } = useIsMatchingLocation(); @@ -21,8 +21,11 @@ export const useShowAuthModal = () => { ); return useMemo(() => { - if (isMatchingLocation(AppPath.Verify)) { - return false; + if ( + isMatchingLocation(AppPath.Verify) || + isMatchingLocation(AppPath.VerifyEmail) + ) { + return true; } if ( diff --git a/packages/twenty-front/src/modules/ui/layout/modal/components/Modal.tsx b/packages/twenty-front/src/modules/ui/layout/modal/components/Modal.tsx index ac3279a3d..094548fdb 100644 --- a/packages/twenty-front/src/modules/ui/layout/modal/components/Modal.tsx +++ b/packages/twenty-front/src/modules/ui/layout/modal/components/Modal.tsx @@ -7,6 +7,7 @@ import { useListenClickOutside, } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; +import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import { motion } from 'framer-motion'; import React, { useEffect, useRef } from 'react'; @@ -148,6 +149,7 @@ export type ModalProps = React.PropsWithChildren & { className?: string; hotkeyScope?: ModalHotkeyScope; onEnter?: () => void; + isOpenAnimated?: boolean; modalVariant?: ModalVariants; } & ( | { isClosable: true; onClose: () => void } @@ -170,6 +172,7 @@ export const Modal = ({ isClosable = false, onClose, modalVariant = 'primary', + isOpenAnimated = true, }: ModalProps) => { const isMobile = useIsMobile(); const modalRef = useRef(null); @@ -223,6 +226,8 @@ export const Modal = ({ e.stopPropagation(); }; + const theme = useTheme(); + return ( diff --git a/packages/twenty-front/src/modules/ui/layout/page/components/DefaultLayout.tsx b/packages/twenty-front/src/modules/ui/layout/page/components/DefaultLayout.tsx index 0512c1212..032891614 100644 --- a/packages/twenty-front/src/modules/ui/layout/page/components/DefaultLayout.tsx +++ b/packages/twenty-front/src/modules/ui/layout/page/components/DefaultLayout.tsx @@ -17,7 +17,7 @@ import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; import { Global, css, useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import { AnimatePresence, LayoutGroup, motion } from 'framer-motion'; -import { Outlet } from 'react-router-dom'; +import { Outlet, useSearchParams } from 'react-router-dom'; import { useScreenSize } from 'twenty-ui/utilities'; const StyledLayout = styled.div` @@ -63,6 +63,8 @@ export const DefaultLayout = () => { const windowsWidth = useScreenSize().width; const showAuthModal = useShowAuthModal(); const useShowFullScreen = useShowFullscreen(); + const [searchParams] = useSearchParams(); + const animateModal = searchParams.get('animateModal') !== 'false'; return ( <> @@ -86,7 +88,9 @@ export const DefaultLayout = () => { 2 : 0, }} - transition={{ duration: theme.animation.duration.normal }} + transition={{ + duration: theme.animation.duration.normal, + }} > {!showAuthModal && ( <> @@ -104,7 +108,7 @@ export const DefaultLayout = () => { - + diff --git a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx index f500172e2..e4e1db512 100644 --- a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx +++ b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx @@ -1,30 +1,27 @@ import { DEFAULT_WORKSPACE_LOGO } from '@/ui/navigation/navigation-drawer/constants/DefaultWorkspaceLogo'; -import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; -import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; -import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator'; -import { getWorkspaceUrl } from '~/utils/getWorkspaceUrl'; -import { useRecoilValue, useSetRecoilState } from 'recoil'; -import { Workspaces, workspacesState } from '@/auth/states/workspaces'; -import { useRedirectToWorkspaceDomain } from '@/domain-manager/hooks/useRedirectToWorkspaceDomain'; -import { useLingui } from '@lingui/react/macro'; -import { useBuildWorkspaceUrl } from '@/domain-manager/hooks/useBuildWorkspaceUrl'; -import { multiWorkspaceDropdownState } from '@/ui/navigation/navigation-drawer/states/multiWorkspaceDropdownState'; -import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; -import { SettingsPath } from '@/types/SettingsPath'; -import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; -import { MULTI_WORKSPACE_DROPDOWN_ID } from '@/ui/navigation/navigation-drawer/constants/MultiWorkspaceDropdownId'; import { useAuth } from '@/auth/hooks/useAuth'; +import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; +import { Workspaces, workspacesState } from '@/auth/states/workspaces'; +import { useBuildWorkspaceUrl } from '@/domain-manager/hooks/useBuildWorkspaceUrl'; +import { useRedirectToWorkspaceDomain } from '@/domain-manager/hooks/useRedirectToWorkspaceDomain'; import { AppPath } from '@/types/AppPath'; -import { useSignUpInNewWorkspaceMutation } from '~/generated/graphql'; +import { SettingsPath } from '@/types/SettingsPath'; import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; -import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader'; -import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { SelectHotkeyScope } from '@/ui/input/types/SelectHotkeyScope'; +import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; +import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader'; +import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent'; +import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; +import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator'; +import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; +import { MULTI_WORKSPACE_DROPDOWN_ID } from '@/ui/navigation/navigation-drawer/constants/MultiWorkspaceDropdownId'; +import { multiWorkspaceDropdownState } from '@/ui/navigation/navigation-drawer/states/multiWorkspaceDropdownState'; import { useColorScheme } from '@/ui/theme/hooks/useColorScheme'; import styled from '@emotion/styled'; -import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent'; +import { useLingui } from '@lingui/react/macro'; +import { useRecoilValue, useSetRecoilState } from 'recoil'; import { Avatar, IconDotsVertical, @@ -39,6 +36,9 @@ import { MenuItemSelectAvatar, UndecoratedLink, } from 'twenty-ui/navigation'; +import { useSignUpInNewWorkspaceMutation } from '~/generated/graphql'; +import { getWorkspaceUrl } from '~/utils/getWorkspaceUrl'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; const StyledDescription = styled.div` color: ${({ theme }) => theme.font.color.light}; @@ -79,6 +79,7 @@ export const MultiWorkspaceDropdownDefaultComponents = () => { AppPath.Verify, { loginToken: data.signUpInNewWorkspace.loginToken.token, + animateModal: false, }, '_blank', ); diff --git a/packages/twenty-front/src/modules/ui/theme/components/AppThemeProvider.tsx b/packages/twenty-front/src/modules/ui/theme/components/UserThemeProviderEffect.tsx similarity index 100% rename from packages/twenty-front/src/modules/ui/theme/components/AppThemeProvider.tsx rename to packages/twenty-front/src/modules/ui/theme/components/UserThemeProviderEffect.tsx diff --git a/packages/twenty-front/src/modules/users/components/UserProvider.tsx b/packages/twenty-front/src/modules/users/components/UserProvider.tsx index e3cdede57..a506bf1a4 100644 --- a/packages/twenty-front/src/modules/users/components/UserProvider.tsx +++ b/packages/twenty-front/src/modules/users/components/UserProvider.tsx @@ -15,6 +15,8 @@ export const UserProvider = ({ children }: React.PropsWithChildren) => { const dateTimeFormat = useRecoilValue(dateTimeFormatState); return !isCurrentUserLoaded && + !isMatchingLocation(AppPath.Verify) && + !isMatchingLocation(AppPath.VerifyEmail) && !isMatchingLocation(AppPath.CreateWorkspace) ? ( ) : ( diff --git a/packages/twenty-front/src/modules/users/components/UserProviderEffect.tsx b/packages/twenty-front/src/modules/users/components/UserProviderEffect.tsx index 0f4390c23..a8229c460 100644 --- a/packages/twenty-front/src/modules/users/components/UserProviderEffect.tsx +++ b/packages/twenty-front/src/modules/users/components/UserProviderEffect.tsx @@ -3,8 +3,8 @@ import { useRecoilState, useSetRecoilState } from 'recoil'; import { currentUserState } from '@/auth/states/currentUserState'; import { currentUserWorkspaceState } from '@/auth/states/currentUserWorkspaceState'; -import { currentWorkspaceMembersState } from '@/auth/states/currentWorkspaceMembersStates'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; +import { currentWorkspaceMembersState } from '@/auth/states/currentWorkspaceMembersStates'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { isCurrentUserLoadedState } from '@/auth/states/isCurrentUserLoadingState'; import { workspacesState } from '@/auth/states/workspaces'; @@ -16,15 +16,18 @@ import { detectTimeFormat } from '@/localization/utils/detectTimeFormat'; import { detectTimeZone } from '@/localization/utils/detectTimeZone'; import { getDateFormatFromWorkspaceDateFormat } from '@/localization/utils/getDateFormatFromWorkspaceDateFormat'; import { getTimeFormatFromWorkspaceTimeFormat } from '@/localization/utils/getTimeFormatFromWorkspaceTimeFormat'; +import { AppPath } from '@/types/AppPath'; import { ColorScheme } from '@/workspace-member/types/WorkspaceMember'; -import { WorkspaceMember } from '~/generated-metadata/graphql'; -import { useGetCurrentUserQuery } from '~/generated/graphql'; -import { dynamicActivate } from '~/utils/i18n/dynamicActivate'; import { APP_LOCALES, SOURCE_LOCALE } from 'twenty-shared/translations'; import { isDefined } from 'twenty-shared/utils'; +import { WorkspaceMember } from '~/generated-metadata/graphql'; +import { useGetCurrentUserQuery } from '~/generated/graphql'; +import { useIsMatchingLocation } from '~/hooks/useIsMatchingLocation'; +import { dynamicActivate } from '~/utils/i18n/dynamicActivate'; export const UserProviderEffect = () => { const [isLoading, setIsLoading] = useState(true); + const { isMatchingLocation } = useIsMatchingLocation(); const [isCurrentUserLoaded, setIsCurrentUserLoaded] = useRecoilState( isCurrentUserLoadedState, @@ -44,7 +47,10 @@ export const UserProviderEffect = () => { ); const { loading: queryLoading, data: queryData } = useGetCurrentUserQuery({ - skip: isCurrentUserLoaded, + skip: + isCurrentUserLoaded || + isMatchingLocation(AppPath.Verify) || + isMatchingLocation(AppPath.VerifyEmail), }); useEffect(() => { diff --git a/packages/twenty-front/src/pages/auth/SignInUpLoading.tsx b/packages/twenty-front/src/pages/auth/SignInUpLoading.tsx new file mode 100644 index 000000000..099f5b402 --- /dev/null +++ b/packages/twenty-front/src/pages/auth/SignInUpLoading.tsx @@ -0,0 +1,100 @@ +import { workspacePublicDataState } from '@/auth/states/workspacePublicDataState'; +import { useRecoilValue } from 'recoil'; + +import { Logo } from '@/auth/components/Logo'; +import { Title } from '@/auth/components/Title'; +import { DEFAULT_WORKSPACE_NAME } from '@/ui/navigation/navigation-drawer/constants/DefaultWorkspaceName'; +import { useMemo } from 'react'; + +import { useWorkspaceFromInviteHash } from '@/auth/sign-in-up/hooks/useWorkspaceFromInviteHash'; +import styled from '@emotion/styled'; +import { useLingui } from '@lingui/react/macro'; +import { isNonEmptyString } from '@sniptt/guards'; +import { motion } from 'framer-motion'; +import { isDefined } from 'twenty-shared/utils'; +import { Loader } from 'twenty-ui/feedback'; +import { MainButton } from 'twenty-ui/input'; +import { PublicWorkspaceDataOutput } from '~/generated/graphql'; + +const StyledContentContainer = styled(motion.div)` + margin-bottom: ${({ theme }) => theme.spacing(8)}; + margin-top: ${({ theme }) => theme.spacing(4)}; +`; + +const StyledForm = styled.form` + align-items: center; + display: flex; + flex-direction: column; + width: 100%; + margin-top: ${({ theme }) => theme.spacing(10)}; +`; + +const StandardContent = ({ + workspacePublicData, + signInUpForm, + title, +}: { + workspacePublicData: PublicWorkspaceDataOutput | null; + signInUpForm: JSX.Element | null; + title: string; +}) => { + return ( + <> + + {title} + {signInUpForm} + + ); +}; + +export const SignInUpLoading = () => { + const { t } = useLingui(); + const workspacePublicData = useRecoilValue(workspacePublicDataState); + + const { workspaceInviteHash, workspace: workspaceFromInviteHash } = + useWorkspaceFromInviteHash(); + + const title = useMemo(() => { + if (isDefined(workspaceInviteHash)) { + return `Join ${workspaceFromInviteHash?.displayName ?? ''} team`; + } + const workspaceName = !isDefined(workspacePublicData?.displayName) + ? DEFAULT_WORKSPACE_NAME + : !isNonEmptyString(workspacePublicData?.displayName) + ? t`Your Workspace` + : workspacePublicData?.displayName; + + return t`Welcome to ${workspaceName}`; + }, [ + workspaceFromInviteHash?.displayName, + workspaceInviteHash, + workspacePublicData?.displayName, + t, + ]); + + return ( + +

+ SignInUpLoading +

+ + + } + fullWidth + /> + + + + } + title={title} + /> + ); +}; diff --git a/packages/twenty-front/src/pages/onboarding/ChooseYourPlan.tsx b/packages/twenty-front/src/pages/onboarding/ChooseYourPlan.tsx index 131ac864c..00e198045 100644 --- a/packages/twenty-front/src/pages/onboarding/ChooseYourPlan.tsx +++ b/packages/twenty-front/src/pages/onboarding/ChooseYourPlan.tsx @@ -11,16 +11,16 @@ import { billingState } from '@/client-config/states/billingState'; import styled from '@emotion/styled'; import { Trans, useLingui } from '@lingui/react/macro'; import { useRecoilState, useRecoilValue } from 'recoil'; +import { isDefined } from 'twenty-shared/utils'; +import { Loader } from 'twenty-ui/feedback'; +import { CardPicker, MainButton } from 'twenty-ui/input'; +import { ActionLink, CAL_LINK } from 'twenty-ui/navigation'; import { BillingPlanKey, BillingPriceLicensedDto, SubscriptionInterval, useBillingBaseProductPricesQuery, } from '~/generated/graphql'; -import { isDefined } from 'twenty-shared/utils'; -import { ActionLink, CAL_LINK } from 'twenty-ui/navigation'; -import { CardPicker, MainButton } from 'twenty-ui/input'; -import { Loader } from 'twenty-ui/feedback'; const StyledSubscriptionContainer = styled.div<{ withLongerMarginBottom: boolean; @@ -80,6 +80,10 @@ const StyledLinkGroup = styled.div` } `; +const StyledChooseYourPlanPlaceholder = styled.div` + height: 566px; +`; + export const ChooseYourPlan = () => { const billing = useRecoilValue(billingState); const { t } = useLingui(); @@ -182,82 +186,87 @@ export const ChooseYourPlan = () => { )?.baseProduct.name; return ( - isDefined(baseProductPrice) && - isDefined(billing) && ( - <> - - {hasWithoutCreditCardTrialPeriod - ? t`Choose your Trial` - : t`Get your subscription`} - - {hasWithoutCreditCardTrialPeriod ? ( - - Cancel anytime - - ) : ( - withCreditCardTrialPeriod && ( + <> + {isDefined(baseProductPrice) && isDefined(billing) ? ( + <> + + {hasWithoutCreditCardTrialPeriod + ? t`Choose your Trial` + : t`Get your subscription`} + + {hasWithoutCreditCardTrialPeriod ? ( - {t`Enjoy a ${withCreditCardTrialPeriodDuration}-days free trial`} + Cancel anytime - ) - )} - - - - - - {benefits.map((benefit) => ( - {benefit} - ))} - - - {hasWithoutCreditCardTrialPeriod && ( - - {billing.trialPeriods.map((trialPeriod) => ( - - - - ))} - - )} - isSubmitting && } - disabled={isSubmitting} - /> - - - Log out - - - - Switch to {alternatePlanName} - - - - Book a Call - - - - ) + ) : ( + withCreditCardTrialPeriod && ( + + {t`Enjoy a ${withCreditCardTrialPeriodDuration}-days free trial`} + + ) + )} + + + + + + {benefits.map((benefit) => ( + + {benefit} + + ))} + + + {hasWithoutCreditCardTrialPeriod && ( + + {billing.trialPeriods.map((trialPeriod) => ( + + + + ))} + + )} + isSubmitting && } + disabled={isSubmitting} + /> + + + Log out + + + + Switch to {alternatePlanName} + + + + Book a Call + + + + ) : ( + + )} + ); }; diff --git a/packages/twenty-server/src/engine/core-modules/domain-manager/services/domain-manager.service.ts b/packages/twenty-server/src/engine/core-modules/domain-manager/services/domain-manager.service.ts index 58864662b..22b2103eb 100644 --- a/packages/twenty-server/src/engine/core-modules/domain-manager/services/domain-manager.service.ts +++ b/packages/twenty-server/src/engine/core-modules/domain-manager/services/domain-manager.service.ts @@ -1,8 +1,8 @@ import { Injectable, Logger } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; import { isDefined } from 'twenty-shared/utils'; +import { Repository } from 'typeorm'; import { SEED_APPLE_WORKSPACE_ID } from 'src/database/typeorm-seeds/core/workspaces'; import { WorkspaceSubdomainCustomDomainAndIsCustomDomainEnabledType } from 'src/engine/core-modules/domain-manager/domain-manager.type'; @@ -44,7 +44,7 @@ export class DomainManagerService { private appendSearchParams( url: URL, - searchParams: Record, + searchParams: Record, ) { Object.entries(searchParams).forEach(([key, value]) => { url.searchParams.set(key, value.toString()); @@ -78,7 +78,7 @@ export class DomainManagerService { }: { workspace: WorkspaceSubdomainCustomDomainAndIsCustomDomainEnabledType; pathname?: string; - searchParams?: Record; + searchParams?: Record; }) { const workspaceUrls = this.getWorkspaceUrls(workspace); diff --git a/packages/twenty-server/src/engine/core-modules/email/email.module.ts b/packages/twenty-server/src/engine/core-modules/email/email.module.ts index 0a6b5c3a5..6410facaf 100644 --- a/packages/twenty-server/src/engine/core-modules/email/email.module.ts +++ b/packages/twenty-server/src/engine/core-modules/email/email.module.ts @@ -2,11 +2,11 @@ import { DynamicModule, Global } from '@nestjs/common'; import { EmailModuleAsyncOptions } from 'src/engine/core-modules/email/interfaces/email.interface'; -import { EMAIL_DRIVER } from 'src/engine/core-modules/email/email.constants'; import { LoggerDriver } from 'src/engine/core-modules/email/drivers/logger.driver'; import { SmtpDriver } from 'src/engine/core-modules/email/drivers/smtp.driver'; -import { EmailService } from 'src/engine/core-modules/email/email.service'; import { EmailSenderService } from 'src/engine/core-modules/email/email-sender.service'; +import { EMAIL_DRIVER } from 'src/engine/core-modules/email/email.constants'; +import { EmailService } from 'src/engine/core-modules/email/email.service'; @Global() export class EmailModule {